From cbdf925839ca7b99c121c2fe549bc425d1201c80 Mon Sep 17 00:00:00 2001 From: Scott Gustafson Date: Tue, 15 Aug 2017 10:20:57 -0600 Subject: [PATCH 001/110] moving dig to be the first command to check as nslookup is failing on ubuntu vm Signed-off-by: Scott Gustafson --- getssl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/getssl b/getssl index 7f3713e..b903ab8 100755 --- a/getssl +++ b/getssl @@ -1445,7 +1445,7 @@ get_os requires which requires openssl requires curl -requires nslookup drill dig host DNS_CHECK_FUNC +requires dig nslookup drill host DNS_CHECK_FUNC requires awk requires tr requires date From 69ef4ff80edf807e5a732644b175cee8df8a722e Mon Sep 17 00:00:00 2001 From: Scott Gustafson Date: Tue, 15 Aug 2017 11:04:25 -0600 Subject: [PATCH 002/110] if using dig we need to check for either an SOA or A type record. Signed-off-by: Scott Gustafson --- getssl | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/getssl b/getssl index b903ab8..4b845c0 100755 --- a/getssl +++ b/getssl @@ -389,13 +389,22 @@ check_config() { # check the config files for all obvious errors config_errors=true fi # check domain exist - if [[ "$DNS_CHECK_FUNC" == "drill" ]] || [[ "$DNS_CHECK_FUNC" == "dig" ]]; then + if [[ "$DNS_CHECK_FUNC" == "drill" ]]; then if [[ "$($DNS_CHECK_FUNC "${d}" SOA|grep -c "^${d}")" -ge 1 ]]; then debug "found IP for ${d}" else info "${DOMAIN}: DNS lookup failed for ${d}" config_errors=true fi + elif [[ "$DNS_CHECK_FUNC" == "dig" ]]; then + if [[ "$($DNS_CHECK_FUNC "${d}" -t SOA|grep -c "^${d}")" -ge 1 ]]; then + debug "found SOA IP for ${d}" + elif [[ "$($DNS_CHECK_FUNC "${d}" -t A|grep -c "^${d}")" -ge 1 ]]; then + debug "found A IP for ${d}" + else + info "${DOMAIN}: DNS lookup failed for ${d}" + config_errors=true + fi elif [[ "$DNS_CHECK_FUNC" == "host" ]]; then if [[ "$($DNS_CHECK_FUNC "${d}" |grep -c "^${d}")" -ge 1 ]]; then debug "found IP for ${d}" From 0f9e831b3a5cefdaa54685ee39a5ca10729f9701 Mon Sep 17 00:00:00 2001 From: In/Progress Date: Sun, 2 Dec 2018 19:29:17 +0000 Subject: [PATCH 003/110] Allow file copying via davs (WebDAV over HTTPS) --- getssl | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/getssl b/getssl index 7f3713e..2c7d4a8 100755 --- a/getssl +++ b/getssl @@ -564,6 +564,20 @@ copy_file_to_location() { # copies a file, using scp, sftp or ftp if required. lcd $fromdir put $fromfile _EOF + elif [[ "${to:0:5}" == "davs:" ]] ; then + debug "using davs to copy the file from $from" + davsuser=$(echo "$to"| awk -F: '{print $2}') + davspass=$(echo "$to"| awk -F: '{print $3}') + davshost=$(echo "$to"| awk -F: '{print $4}') + davsport=$(echo "$to"| awk -F: '{print $5}') + davslocn=$(echo "$to"| awk -F: '{print $6}') + davsdirn=$(dirname "$davslocn") + davsfile=$(basename "$davslocn") + fromdir=$(dirname "$from") + fromfile=$(basename "$from") + debug "davs user=$davsuser - pass=$davspass - host=$davshost port=$davsport dir=$davsdirn file=$davsfile" + debug "from dir=$fromdir file=$fromfile" + curl -u "${davsuser}:${davspass}" -T "${fromdir}/${fromfile}" "https://${davshost}:${davsport}${davsdirn}/${davsfile}" else if ! mkdir -p "$(dirname "$to")" ; then error_exit "cannot create ACL directory $(basename "$to")" From f7324f35d711c4479fb286381d97b41585e142e1 Mon Sep 17 00:00:00 2001 From: In/Progress Date: Sun, 2 Dec 2018 20:08:40 +0000 Subject: [PATCH 004/110] Add documentation for ACL transfer via davs --- getssl | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/getssl b/getssl index 2c7d4a8..70878d7 100755 --- a/getssl +++ b/getssl @@ -1313,10 +1313,13 @@ write_domain_template() { # write out a template file for a domain. # If these start with ftp: then the next variables are ftpuserid:ftppassword:servername:ACL_location # These should be of the form "/path/to/your/website/folder/.well-known/acme-challenge" # where "/path/to/your/website/folder/" is the path, on your web server, to the web root for your domain. + # You can also user WebDAV over HTTPS as transport mechanism. To do so, start with davs: followed by username, + # password, host, port (explicitly needed even if using default port 443) and path on the server. #ACL=('/var/www/${DOMAIN}/web/.well-known/acme-challenge' # 'ssh:server5:/var/www/${DOMAIN}/web/.well-known/acme-challenge' # 'ssh:sshuserid@server5:/var/www/${DOMAIN}/web/.well-known/acme-challenge' - # 'ftp:ftpuserid:ftppassword:${DOMAIN}:/web/.well-known/acme-challenge') + # 'ftp:ftpuserid:ftppassword:${DOMAIN}:/web/.well-known/acme-challenge' + # 'davs:davsuserid:davspassword:{DOMAIN}:443:/web/.well-known/acme-challenge') #Set USE_SINGLE_ACL="true" to use a single ACL for all checks #USE_SINGLE_ACL="false" From 3a379ecebb875e37f73318768db6227d068b5cf7 Mon Sep 17 00:00:00 2001 From: In/Progress Date: Sun, 2 Dec 2018 20:19:46 +0000 Subject: [PATCH 005/110] Update Revision history as contribution guidelines suggest --- getssl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/getssl b/getssl index 70878d7..46764ca 100755 --- a/getssl +++ b/getssl @@ -184,10 +184,11 @@ # 2017-01-30 issue #243 compatibility with bash 3.0 (2.08) # 2017-01-30 issue #243 additional compatibility with bash 3.0 (2.09) # 2017-02-18 add OCSP Must-Staple to the domain csr generation (2.10) +# 2018-12-02 allow file transfer using WebDAV over HTTPS (2.11) # ---------------------------------------------------------------------------------------- PROGNAME=${0##*/} -VERSION="2.10" +VERSION="2.11" # defaults ACCOUNT_KEY_LENGTH=4096 From d9669d240f67d4f5023b4d58ffeab86577671450 Mon Sep 17 00:00:00 2001 From: Yannic Haupenthal Date: Thu, 21 Nov 2019 16:05:37 +0100 Subject: [PATCH 006/110] * add IGNORE_PERIOD_CHECK flag --- getssl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/getssl b/getssl index f3e60a4..9d39c9e 100755 --- a/getssl +++ b/getssl @@ -226,6 +226,7 @@ SERVER_TYPE="https" SKIP_HTTP_TOKEN_CHECK="false" SSLCONF="$(openssl version -d 2>/dev/null| cut -d\" -f2)/openssl.cnf" OCSP_MUST_STAPLE="false" +IGNORE_PERIOD_CHECK="false" TEMP_UPGRADE_FILE="" TOKEN_USER_ID="" USE_SINGLE_ACL="false" @@ -1795,7 +1796,7 @@ if [[ ${_CHECK_ALL} -eq 1 ]]; then cmd="$cmd -q" fi # check if $dir looks like a domain name (contains a period) - if [[ $(basename "$dir") == *.* ]]; then + if [[ $(basename "$dir") == *.* || "$IGNORE_PERIOD_CHECK" == "true" ]]; then cmd="$cmd -w $WORKING_DIR $(basename "$dir")" debug "CMD: $cmd" eval "$cmd" From 813276ee80df41d934a462ea6a89340d6215e108 Mon Sep 17 00:00:00 2001 From: Tsaukpaetra Date: Tue, 31 Dec 2019 02:53:02 -0700 Subject: [PATCH 007/110] Create dns_freedns.sh Should probably be fixed up, I've only hacked up @dkerr64's version for acme.sh to work on my local instance. It assumes you're using curl, I didn't bother stealing acme.sh's whole library of functions. DNS_ADD_COMMAND="~/.getssl/dns_freedns.sh add" DNS_DEL_COMMAND="~/.getssl/dns_freedns.sh rm" --- dns_scripts/dns_freedns.sh | 702 +++++++++++++++++++++++++++++++++++++ 1 file changed, 702 insertions(+) create mode 100644 dns_scripts/dns_freedns.sh diff --git a/dns_scripts/dns_freedns.sh b/dns_scripts/dns_freedns.sh new file mode 100644 index 0000000..a6571e1 --- /dev/null +++ b/dns_scripts/dns_freedns.sh @@ -0,0 +1,702 @@ +#!/usr/bin/env sh + +#This file name is "dns_freedns.sh" +#So, here must be a method dns_freedns_add() +#Which will be called by acme.sh to add the txt record to your api system. +#returns 0 means success, otherwise error. +# +#Author: David Kerr +#Report Bugs here: https://github.com/dkerr64/acme.sh +#or here... https://github.com/Neilpang/acme.sh/issues/2305 +# +######## Public functions ##################### + +# Export FreeDNS userid and password in following variables... +# FREEDNS_User=username +# FREEDNS_Password=password +# login cookie is saved in acme account config file so userid / pw +# need to be set only when changed. + +#Usage: dns_freedns_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" +dns_freedns_add() { + fulldomain="_acme-challenge.$1" + txtvalue="$2" + FREEDNS_COOKIE="$(cat $(dirname "$(readlink -f "$0")")/freednscookie.dat)" + + echo "Info: Add TXT record using FreeDNS" + #echo "Debug: fulldomain: $fulldomain" + #echo "Debug: txtvalue: $txtvalue" + + if [ -z "$FREEDNS_User" ] || [ -z "$FREEDNS_Password" ]; then + FREEDNS_User="" + FREEDNS_Password="" + if [ -z "$FREEDNS_COOKIE" ]; then + echo "ERROR: You did not specify the FreeDNS username and password yet." + echo "ERROR: Please export as FREEDNS_User / FREEDNS_Password and try again." + return 1 + fi + using_cached_cookies="true" + else + FREEDNS_COOKIE="$(_freedns_login "$FREEDNS_User" "$FREEDNS_Password")" + if [ -z "$FREEDNS_COOKIE" ]; then + return 1 + fi + using_cached_cookies="false" + fi + + #echo "Debug: FreeDNS login cookies: $FREEDNS_COOKIE (cached = $using_cached_cookies)" + + echo "$FREEDNS_COOKIE">$(dirname "$(readlink -f "$0")")/freednscookie.dat + + # We may have to cycle through the domain name to find the + # TLD that we own... + i=1 + wmax="$(echo "$fulldomain" | tr '.' ' ' | wc -w)" + while [ "$i" -lt "$wmax" ]; do + # split our full domain name into two parts... + sub_domain="$(echo "$fulldomain" | cut -d. -f -"$i")" + i="$(_math "$i" + 1)" + top_domain="$(echo "$fulldomain" | cut -d. -f "$i"-100)" + #echo "Debug: sub_domain: $sub_domain" + #echo "Debug: top_domain: $top_domain" + + DNSdomainid="$(_freedns_domain_id "$top_domain")" + if [ "$?" = "0" ]; then + echo "Info:Domain $top_domain found at FreeDNS, domain_id $DNSdomainid" + break + else + echo "Info:Domain $top_domain not found at FreeDNS, try with next level of TLD" + fi + done + + if [ -z "$DNSdomainid" ]; then + # If domain ID is empty then something went wrong (top level + # domain not found at FreeDNS). + echo "ERROR: Domain $top_domain not found at FreeDNS" + return 1 + fi + + # Add in new TXT record with the value provided + #echo "Debug: Adding TXT record for $fulldomain, $txtvalue" + _freedns_add_txt_record "$FREEDNS_COOKIE" "$DNSdomainid" "$sub_domain" "$txtvalue" + return $? +} + +#Usage: fulldomain txtvalue +#Remove the txt record after validation. +dns_freedns_rm() { + fulldomain="_acme-challenge.$1" + txtvalue="$2" + + echo "Info:Delete TXT record using FreeDNS" + #echo "Debug: fulldomain: $fulldomain" + #echo "Debug: txtvalue: $txtvalue" + + # Need to read cookie from conf file again in case new value set + # during login to FreeDNS when TXT record was created. + FREEDNS_COOKIE="$(cat $(dirname "$(readlink -f "$0")")/freednscookie.dat)" + #echo "Debug: FreeDNS login cookies: $FREEDNS_COOKIE" + + TXTdataid="$(_freedns_data_id "$fulldomain" "TXT")" + if [ "$?" != "0" ]; then + echo "Info:Cannot delete TXT record for $fulldomain, record does not exist at FreeDNS" + return 1 + fi + #echo "Debug: Data ID's found, $TXTdataid" + + # now we have one (or more) TXT record data ID's. Load the page + # for that record and search for the record txt value. If match + # then we can delete it. + lines="$(echo "$TXTdataid" | wc -l)" + #echo "Debug: Found $lines TXT data records for $fulldomain" + i=0 + while [ "$i" -lt "$lines" ]; do + i="$(_math "$i" + 1)" + dataid="$(echo "$TXTdataid" | sed -n "${i}p")" + #echo "Debug: $dataid" + + htmlpage="$(_freedns_retrieve_data_page "$FREEDNS_COOKIE" "$dataid")" + if [ "$?" != "0" ]; then + if [ "$using_cached_cookies" = "true" ]; then + echo "ERROR: Has your FreeDNS username and password changed? If so..." + echo "ERROR: Please export as FREEDNS_User / FREEDNS_Password and try again." + fi + return 1 + fi + + echo "$htmlpage" | grep "value=\""$txtvalue"\"" >/dev/null + if [ "$?" = "0" ]; then + # Found a match... delete the record and return + echo "Info:Deleting TXT record for $fulldomain, $txtvalue" + _freedns_delete_txt_record "$FREEDNS_COOKIE" "$dataid" + return $? + fi + done + + # If we get this far we did not find a match + # Not necessarily an error, but log anyway. + echo "Info:Cannot delete TXT record for $fulldomain, $txtvalue. Does not exist at FreeDNS" + return 0 +} + +#################### Private functions below ################################## + +# usage: _freedns_login username password +# print string "cookie=value" etc. +# returns 0 success +_freedns_login() { + export _H1="Accept-Language:en-US" + username="$1" + password="$2" + url="https://freedns.afraid.org/zc.php?step=2" + + #echo "Debug: Login to FreeDNS as user $username" + data="username=$(printf '%s' "$username" | _url_encode)&password=$(printf '%s' "$password" | _url_encode)&submit=Login&action=auth" + #echo "$data" + + if [ -z "$HTTP_HEADER" ] || ! touch "$HTTP_HEADER"; then + HTTP_HEADER="$(_mktemp)" + fi + htmlpage="$(curl -L --silent --dump-header $HTTP_HEADER -X POST -H "$_H1" -H "$_H2" --data "$data" "$url")" + + if [ "$?" != "0" ]; then + echo "ERROR: FreeDNS login failed for user $username bad RC from _post" + return 1 + fi + + cookies="$(grep -i '^Set-Cookie.*dns_cookie.*$' "$HTTP_HEADER" | _head_n 1 | tr -d "\r\n" | cut -d " " -f 2)" + + # if cookies is not empty then logon successful + if [ -z "$cookies" ]; then + #echo "Debug3: htmlpage: $htmlpage" + echo "ERROR: FreeDNS login failed for user $username. Check $HTTP_HEADER file" + return 1 + fi + + printf "%s" "$cookies" + return 0 +} + +# usage _freedns_retrieve_subdomain_page login_cookies +# echo page retrieved (html) +# returns 0 success +_freedns_retrieve_subdomain_page() { + export _H1="Cookie:$1" + export _H2="Accept-Language:en-US" + url="https://freedns.afraid.org/subdomain/" + + #echo "Debug: Retrieve subdomain page from FreeDNS" + + htmlpage="$(curl -L --silent -H "$_H1" -H "$_H2" "$url")" + + if [ "$?" != "0" ]; then + echo "ERROR: FreeDNS retrieve subdomains failed bad RC from _get" + return 1 + elif [ -z "$htmlpage" ]; then + echo "ERROR: FreeDNS returned empty subdomain page" + return 1 + fi + + #echo "Debug3: htmlpage: $htmlpage" + + printf "%s" "$htmlpage" + return 0 +} + +# usage _freedns_retrieve_data_page login_cookies data_id +# echo page retrieved (html) +# returns 0 success +_freedns_retrieve_data_page() { + export _H1="Cookie:$1" + export _H2="Accept-Language:en-US" + data_id="$2" + url="https://freedns.afraid.org/subdomain/edit.php?data_id=$2" + + #echo "Debug: Retrieve data page for ID $data_id from FreeDNS" + + htmlpage="$(curl -L --silent -H "$_H1" -H "$_H2" "$url")" + + if [ "$?" != "0" ]; then + echo "ERROR: FreeDNS retrieve data page failed bad RC from _get" + return 1 + elif [ -z "$htmlpage" ]; then + echo "ERROR: FreeDNS returned empty data page" + return 1 + fi + + #echo "Debug3: htmlpage: $htmlpage" + + printf "%s" "$htmlpage" + return 0 +} + +# usage _freedns_add_txt_record login_cookies domain_id subdomain value +# returns 0 success +_freedns_add_txt_record() { + export _H1="Cookie:$1" + export _H2="Accept-Language:en-US" + domain_id="$2" + subdomain="$3" + value="$(printf '%s' "$4" | _url_encode)" + url="https://freedns.afraid.org/subdomain/save.php?step=2" + + if [ -z "$HTTP_HEADER" ] || ! touch "$HTTP_HEADER"; then + HTTP_HEADER="$(_mktemp)" + fi + htmlpage="$(curl -L --silent --dump-header $HTTP_HEADER -X POST -H "$_H1" -H "$_H2" --data "type=TXT&domain_id=$domain_id&subdomain=$subdomain&address=%22$value%22&send=Save%21" "$url")" + + if [ "$?" != "0" ]; then + echo "ERROR: FreeDNS failed to add TXT record for $subdomain bad RC from _post" + return 1 + elif ! grep "200 OK" "$HTTP_HEADER" >/dev/null; then + #echo "Debug3: htmlpage: $(cat $HTTP_HEADER)" + echo "ERROR: FreeDNS failed to add TXT record for $subdomain. Check $HTTP_HEADER file" + return 1 + elif _contains "$htmlpage" "security code was incorrect"; then + #echo "Debug3: htmlpage: $htmlpage" + echo "ERROR: FreeDNS failed to add TXT record for $subdomain as FreeDNS requested security code" + echo "ERROR: Note that you cannot use automatic DNS validation for FreeDNS public domains" + return 1 + fi + + #echo "Debug3: htmlpage: $htmlpage" + echo "Info:Added acme challenge TXT record for $fulldomain at FreeDNS" + return 0 +} + +# usage _freedns_delete_txt_record login_cookies data_id +# returns 0 success +_freedns_delete_txt_record() { + export _H1="Cookie:$1" + export _H2="Accept-Language:en-US" + data_id="$2" + url="https://freedns.afraid.org/subdomain/delete2.php" + + htmlheader="$(curl -L --silent -I -H "$_H1" -H "$_H2" "$url?data_id%5B%5D=$data_id&submit=delete+selected")" + + if [ "$?" != "0" ]; then + echo "ERROR: FreeDNS failed to delete TXT record for $data_id bad RC from _get" + return 1 + elif ! _contains "$htmlheader" "200 OK"; then + #echo "Debug2: htmlheader: $htmlheader" + echo "ERROR: FreeDNS failed to delete TXT record $data_id" + return 1 + fi + + echo "Info:Deleted acme challenge TXT record for $fulldomain at FreeDNS" + return 0 +} + +# usage _freedns_domain_id domain_name +# echo the domain_id if found +# return 0 success +_freedns_domain_id() { + # Start by escaping the dots in the domain name + search_domain="$(echo "$1" | sed 's/\./\\./g')" + + # Sometimes FreeDNS does not return the subdomain page but rather + # returns a page regarding becoming a premium member. This usually + # happens after a period of inactivity. Immediately trying again + # returns the correct subdomain page. So, we will try twice to + # load the page and obtain our domain ID + attempts=2 + while [ "$attempts" -gt "0" ]; do + attempts="$(_math "$attempts" - 1)" + + htmlpage="$(_freedns_retrieve_subdomain_page "$FREEDNS_COOKIE")" + if [ "$?" != "0" ]; then + if [ "$using_cached_cookies" = "true" ]; then + echo "ERROR: Has your FreeDNS username and password changed? If so..." + echo "ERROR: Please export as FREEDNS_User / FREEDNS_Password and try again." + fi + return 1 + fi + + domain_id="$(echo "$htmlpage" | tr -d " \t\r\n\v\f" | sed 's//@/g' | tr '@' '\n' \ + | grep "$search_domain\|$search_domain(.*)" \ + | sed -n 's/.*\(edit\.php?edit_domain_id=[0-9a-zA-Z]*\).*/\1/p' \ + | cut -d = -f 2)" + # The above beauty extracts domain ID from the html page... + # strip out all blank space and new lines. Then insert newlines + # before each table row + # search for the domain within each row (which may or may not have + # a text string in brackets (.*) after it. + # And finally extract the domain ID. + if [ -n "$domain_id" ]; then + printf "%s" "$domain_id" + return 0 + fi + #echo "Debug:Domain $search_domain not found. Retry loading subdomain page ($attempts attempts remaining)" + done + #echo "Debug:Domain $search_domain not found after retry" + return 1 +} + +# usage _freedns_data_id domain_name record_type +# echo the data_id(s) if found +# return 0 success +_freedns_data_id() { + # Start by escaping the dots in the domain name + search_domain="$(echo "$1" | sed 's/\./\\./g')" + record_type="$2" + + # Sometimes FreeDNS does not return the subdomain page but rather + # returns a page regarding becoming a premium member. This usually + # happens after a period of inactivity. Immediately trying again + # returns the correct subdomain page. So, we will try twice to + # load the page and obtain our domain ID + attempts=2 + while [ "$attempts" -gt "0" ]; do + attempts="$(_math "$attempts" - 1)" + + htmlpage="$(_freedns_retrieve_subdomain_page "$FREEDNS_COOKIE")" + if [ "$?" != "0" ]; then + if [ "$using_cached_cookies" = "true" ]; then + echo "ERROR: Has your FreeDNS username and password changed? If so..." + echo "ERROR: Please export as FREEDNS_User / FREEDNS_Password and try again." + fi + return 1 + fi + + data_id="$(echo "$htmlpage" | tr -d " \t\r\n\v\f" | sed 's//@/g' | tr '@' '\n' \ + | grep "$record_type" \ + | grep "$search_domain" \ + | sed -n 's/.*\(edit\.php?data_id=[0-9a-zA-Z]*\).*/\1/p' \ + | cut -d = -f 2)" + # The above beauty extracts data ID from the html page... + # strip out all blank space and new lines. Then insert newlines + # before each table row + # search for the record type withing each row (e.g. TXT) + # search for the domain within each row (which is within a + # anchor. And finally extract the domain ID. + if [ -n "$data_id" ]; then + printf "%s" "$data_id" + return 0 + fi + #echo "Debug:Domain $search_domain not found. Retry loading subdomain page ($attempts attempts remaining)" + done + #echo "Debug:Domain $search_domain not found after retry" + return 1 +} + +#### BEGIN things shamefully ripped from https://github.com/Neilpang/acme.sh/blob/master/acme.sh + +#_ascii_hex str +#this can only process ascii chars, should only be used when od command is missing as a backup way. +_ascii_hex() { + _debug2 "Using _ascii_hex" + _str="$1" + _str_len=${#_str} + _h_i=1 + while [ "$_h_i" -le "$_str_len" ]; do + _str_c="$(printf "%s" "$_str" | cut -c "$_h_i")" + printf " %02x" "'$_str_c" + _h_i="$(_math "$_h_i" + 1)" + done +} + +#stdin output hexstr splited by one space +#input:"abc" +#output: " 61 62 63" +_hex_dump() { + if _exists od; then + od -A n -v -t x1 | tr -s " " | sed 's/ $//' | tr -d "\r\t\n" + elif _exists hexdump; then + hexdump -v -e '/1 ""' -e '/1 " %02x" ""' + elif _exists xxd; then + xxd -ps -c 20 -i | sed "s/ 0x/ /g" | tr -d ",\n" | tr -s " " + else + str=$(cat) + _ascii_hex "$str" + fi +} + +#url encode, no-preserved chars +#A B C D E F G H I J K L M N O P Q R S T U V W X Y Z +#41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f 50 51 52 53 54 55 56 57 58 59 5a + +#a b c d e f g h i j k l m n o p q r s t u v w x y z +#61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f 70 71 72 73 74 75 76 77 78 79 7a + +#0 1 2 3 4 5 6 7 8 9 - _ . ~ +#30 31 32 33 34 35 36 37 38 39 2d 5f 2e 7e + +#stdin stdout +_url_encode() { + _hex_str=$(_hex_dump) + for _hex_code in $_hex_str; do + #upper case + case "${_hex_code}" in + "41") + printf "%s" "A" + ;; + "42") + printf "%s" "B" + ;; + "43") + printf "%s" "C" + ;; + "44") + printf "%s" "D" + ;; + "45") + printf "%s" "E" + ;; + "46") + printf "%s" "F" + ;; + "47") + printf "%s" "G" + ;; + "48") + printf "%s" "H" + ;; + "49") + printf "%s" "I" + ;; + "4a") + printf "%s" "J" + ;; + "4b") + printf "%s" "K" + ;; + "4c") + printf "%s" "L" + ;; + "4d") + printf "%s" "M" + ;; + "4e") + printf "%s" "N" + ;; + "4f") + printf "%s" "O" + ;; + "50") + printf "%s" "P" + ;; + "51") + printf "%s" "Q" + ;; + "52") + printf "%s" "R" + ;; + "53") + printf "%s" "S" + ;; + "54") + printf "%s" "T" + ;; + "55") + printf "%s" "U" + ;; + "56") + printf "%s" "V" + ;; + "57") + printf "%s" "W" + ;; + "58") + printf "%s" "X" + ;; + "59") + printf "%s" "Y" + ;; + "5a") + printf "%s" "Z" + ;; + + #lower case + "61") + printf "%s" "a" + ;; + "62") + printf "%s" "b" + ;; + "63") + printf "%s" "c" + ;; + "64") + printf "%s" "d" + ;; + "65") + printf "%s" "e" + ;; + "66") + printf "%s" "f" + ;; + "67") + printf "%s" "g" + ;; + "68") + printf "%s" "h" + ;; + "69") + printf "%s" "i" + ;; + "6a") + printf "%s" "j" + ;; + "6b") + printf "%s" "k" + ;; + "6c") + printf "%s" "l" + ;; + "6d") + printf "%s" "m" + ;; + "6e") + printf "%s" "n" + ;; + "6f") + printf "%s" "o" + ;; + "70") + printf "%s" "p" + ;; + "71") + printf "%s" "q" + ;; + "72") + printf "%s" "r" + ;; + "73") + printf "%s" "s" + ;; + "74") + printf "%s" "t" + ;; + "75") + printf "%s" "u" + ;; + "76") + printf "%s" "v" + ;; + "77") + printf "%s" "w" + ;; + "78") + printf "%s" "x" + ;; + "79") + printf "%s" "y" + ;; + "7a") + printf "%s" "z" + ;; + #numbers + "30") + printf "%s" "0" + ;; + "31") + printf "%s" "1" + ;; + "32") + printf "%s" "2" + ;; + "33") + printf "%s" "3" + ;; + "34") + printf "%s" "4" + ;; + "35") + printf "%s" "5" + ;; + "36") + printf "%s" "6" + ;; + "37") + printf "%s" "7" + ;; + "38") + printf "%s" "8" + ;; + "39") + printf "%s" "9" + ;; + "2d") + printf "%s" "-" + ;; + "5f") + printf "%s" "_" + ;; + "2e") + printf "%s" "." + ;; + "7e") + printf "%s" "~" + ;; + #other hex + *) + printf '%%%s' "$_hex_code" + ;; + esac + done +} + +_exists() { + cmd="$1" + if [ -z "$cmd" ]; then + _usage "Usage: _exists cmd" + return 1 + fi + + if eval type type >/dev/null 2>&1; then + eval type "$cmd" >/dev/null 2>&1 + elif command >/dev/null 2>&1; then + command -v "$cmd" >/dev/null 2>&1 + else + which "$cmd" >/dev/null 2>&1 + fi + ret="$?" + #echo "Debug3: $cmd exists=$ret" + return $ret +} + +_head_n() { + head -n "$1" +} + +_mktemp() { + if _exists mktemp; then + if mktemp 2>/dev/null; then + return 0 + elif _contains "$(mktemp 2>&1)" "-t prefix" && mktemp -t "$PROJECT_NAME" 2>/dev/null; then + #for Mac osx + return 0 + fi + fi + if [ -d "/tmp" ]; then + echo "/tmp/${PROJECT_NAME}wefADf24sf.$(_time).tmp" + return 0 + elif [ "$LE_TEMP_DIR" ] && mkdir -p "$LE_TEMP_DIR"; then + echo "/$LE_TEMP_DIR/wefADf24sf.$(_time).tmp" + return 0 + fi + _err "Can not create temp file." +} + +#a + b +_math() { + _m_opts="$@" + printf "%s" "$(($_m_opts))" +} + +_contains() { + _str="$1" + _sub="$2" + echo "$_str" | grep -- "$_sub" >/dev/null 2>&1 +} + +##Now actually do something with that function +case "$1" in + + add) + dns_freedns_add $2 $3 + ;; + rm) + dns_freedns_rm $2 $3 + ;; +esac From 770277c7ad72b7c78ebcfdec8ba5a7ac4a5089c8 Mon Sep 17 00:00:00 2001 From: Radek SPRTA Date: Thu, 16 Jan 2020 18:27:11 +0100 Subject: [PATCH 008/110] Add support for CloudDNS --- dns_scripts/dns_add_clouddns | 94 ++++++++++++++++++++++++++++++++ dns_scripts/dns_del_clouddns | 101 +++++++++++++++++++++++++++++++++++ 2 files changed, 195 insertions(+) create mode 100755 dns_scripts/dns_add_clouddns create mode 100755 dns_scripts/dns_del_clouddns diff --git a/dns_scripts/dns_add_clouddns b/dns_scripts/dns_add_clouddns new file mode 100755 index 0000000..5236269 --- /dev/null +++ b/dns_scripts/dns_add_clouddns @@ -0,0 +1,94 @@ +#!/usr/bin/env bash +# Need to add your email address and API key to clouddns below or set as env variables +email=${CLOUDDNS_EMAIL:-''} +password=${CLOUDDNS_PASSWORD:-''} +client=${CLOUDDNS_CLIENT:-''} + +# This script adds a token to clouddns DNS for the ACME challenge +# usage dns_add_clouddns "domain name" "token" +# return codes are; +# 0 - success +# 1 - error in input +# 2 - error within internal processing +# 3 - error in result ( domain not found in clouddns etc) + +fulldomain="${1}" +token="${2}" +API='https://admin.vshosting.cloud/clouddns' +LOGIN_API='https://admin.vshosting.cloud/api/public/auth/login' + +# Check initial parameters +if [[ -z "$fulldomain" ]]; then + echo "DNS script requires full domain name as first parameter" + exit 1 +fi +if [[ -z "$token" ]]; then + echo "DNS script requires challenge token as second parameter" + exit 1 +fi +if [[ -z "$email" ]]; then + echo "CLOUDDNS_EMAIL (email) parameter not set" + exit 1 +fi +if [[ -z "$password" ]]; then + echo "CLOUDDNS_PASSWORD (password) parameter not set" + exit 1 +fi +if [[ -z "$client" ]]; then + echo "CLOUDDNS_CLIENT (id) parameter not set" + exit 1 +fi + +# Login to clouddns to get accessToken +resp=$(curl --silent -X POST -H 'Content-Type: application/json' "$LOGIN_API" \ + --data "{\"email\": \"$email\", \"password\": \"$password\"}") +re='"accessToken":"([^,]*)",' # Match access token +if [[ "${resp// }" =~ $re ]]; then + access_token="${BASH_REMATCH[1]}" +fi +if [[ -z "$access_token" ]]; then + echo 'Could not get access token; check your credentials' + exit 3 +fi +curl_params=( -H "Authorization: Bearer $access_token" -H 'Content-Type: application/json' ) + +# Get main domain +domain_root=$(echo "$fulldomain" | awk -F\. '{print $(NF-1) FS $NF}') + +# Get domain id +resp=$(curl --silent "${curl_params[@]}" -X POST "$API/domain/search" \ + --data "{\"search\": [{\"name\": \"clientId\", \"operator\": \"eq\", \"value\": \"$client\"}, {\"name\": \"domainName\", \"operator\": \"eq\", \"value\": \"$domain_root.\"}]}") +re='domainType":"[^"]*","id":"([^,]*)",' # Match domain id +if [[ "${resp//[$'\t\r\n ']}" =~ $re ]]; then + domain_id="${BASH_REMATCH[1]}" +fi + +if [[ -z "$domain_id" ]]; then + echo 'Domain name not found on your CloudDNS account' + exit 3 +fi + +# Add challenge record +txt_record="_acme-challenge.$domain_root." +resp=$(curl --silent "${curl_params[@]}" -X POST "$API/record-txt" \ + --data "{\"type\":\"TXT\",\"name\":\"$txt_record\",\"value\":\"$token\",\"domainId\":\"$domain_id\"}") + +# If adding record failed (error:) then print error message +if [[ "${resp// }" == *'"error"'* ]]; then + if [[ "${resp// }" == *'"code":4136'* ]]; then + echo "DNS challenge token already exists" + exit + fi + re='"message":"([^"]+)"' + if [[ "$resp" =~ $re ]]; then + echo "Error: DNS challenge not added: ${BASH_REMATCH[1]}" + exit 3 + else + echo "Error: DNS challenge not added: unknown error - ${resp}" + exit 3 + fi +fi + +# Publish challenge record +resp=$(curl --silent "${curl_params[@]}" -X PUT "$API/domain/$domain_id/publish" \ + --data "{\"soaTtl\":300}") diff --git a/dns_scripts/dns_del_clouddns b/dns_scripts/dns_del_clouddns new file mode 100755 index 0000000..ddac2b6 --- /dev/null +++ b/dns_scripts/dns_del_clouddns @@ -0,0 +1,101 @@ +#!/usr/bin/env bash +# Need to add your email address and API key to clouddns below or set as env variables +email=${CLOUDDNS_EMAIL:-''} +password=${CLOUDDNS_PASSWORD:-''} +client=${CLOUDDNS_CLIENT:-''} + +# This script adds a token to clouddns DNS for the ACME challenge +# usage dns_add_clouddns "domain name" "token" +# return codes are; +# 0 - success +# 1 - error in input +# 2 - error within internal processing +# 3 - error in result ( domain not found in clouddns etc) + +fulldomain="${1}" +token="${2}" +API='https://admin.vshosting.cloud/clouddns' +LOGIN_API='https://admin.vshosting.cloud/api/public/auth/login' + +# Check initial parameters +if [[ -z "$fulldomain" ]]; then + echo "DNS script requires full domain name as first parameter" + exit 1 +fi +if [[ -z "$token" ]]; then + echo "DNS script requires challenge token as second parameter" + exit 1 +fi +if [[ -z "$email" ]]; then + echo "CLOUDDNS_EMAIL (email) parameter not set" + exit 1 +fi +if [[ -z "$password" ]]; then + echo "CLOUDDNS_PASSWORD (password) parameter not set" + exit 1 +fi +if [[ -z "$client" ]]; then + echo "CLOUDDNS_CLIENT (id) parameter not set" + exit 1 +fi + +# Login to clouddns to get accessToken +resp=$(curl --silent -X POST -H 'Content-Type: application/json' "$LOGIN_API" \ + --data "{\"email\": \"$email\", \"password\": \"$password\"}") +re='"accessToken":"([^,]*)",' # Match access token +if [[ "${resp// }" =~ $re ]]; then + access_token="${BASH_REMATCH[1]}" +fi +if [[ -z "$access_token" ]]; then + echo 'Could not get access token; check your credentials' + exit 3 +fi +curl_params=( -H "Authorization: Bearer $access_token" -H 'Content-Type: application/json' ) + +# Get main domain and challenge record +domain_root=$(echo "$fulldomain" | awk -F\. '{print $(NF-1) FS $NF}') +txt_record="_acme-challenge.$domain_root." + +# Get domain id +curl_domainid_body="{\"search\": [{\"name\": \"clientId\", \"operator\": \"eq\", \"value\": \"$client\"}, {\"name\": \"domainName\", \"operator\": \"eq\", \"value\": \"$domain_root.\"}]}" +resp=$(curl --silent "${curl_params[@]}" -X POST -d "$curl_domainid_body" "$API/domain/search") +re='domainType":"[^"]*","id":"([^,]*)",' # Find result section +if [[ "${resp//[$'\t\r\n ']}" =~ $re ]]; then + domain_id="${BASH_REMATCH[1]}" +fi + +if [[ -z "$domain_id" ]]; then + echo 'Domain name not found on your CloudDNS account' + exit 3 +fi + +# Get challenge record ID +resp=$(curl --silent "${curl_params[@]}" -X GET "$API/domain/$domain_id" ) +re="\"lastDomainRecordList\".*\"id\":\"([^,]*)\"[^}]*\"name\":\"$txt_record\"," # Match domain id +if [[ "${resp//[$'\t\r\n ']}" =~ $re ]]; then + record_id="${BASH_REMATCH[1]}" +fi + +if [[ -z "$record_id" ]]; then + echo 'Challenge record does not exist' + exit 3 +fi + +# Remove challenge record +resp=$(curl --silent "${curl_params[@]}" -X DELETE "$API/record/$record_id") + +# If removing record failed (error:) then print error message +if [[ "${resp// }" == *'"error"'* ]]; then + re='"message":"([^"]+)"' + if [[ "$resp" =~ $re ]]; then + echo "Error: DNS challenge not removed: ${BASH_REMATCH[1]}" + exit 3 + else + echo "Error: DNS challenge not removed: unknown error - ${resp}" + exit 3 + fi +fi + +# Publish challenge record deletion +resp=$(curl --silent "${curl_params[@]}" -X PUT "$API/domain/$domain_id/publish" \ + --data "{\"soaTtl\":300}") From 6c9f0e7655beb760b33a764f60b4ebd5c49ee445 Mon Sep 17 00:00:00 2001 From: Radek SPRTA Date: Thu, 16 Jan 2020 18:30:24 +0100 Subject: [PATCH 009/110] Update changelog --- getssl | 1 + 1 file changed, 1 insertion(+) diff --git a/getssl b/getssl index e52b3d0..f1e2989 100755 --- a/getssl +++ b/getssl @@ -196,6 +196,7 @@ # 2020-01-07 #464 and #486 "json was blank" (change all curl request to use POST-as-GET) # 2020-01-08 Error and exit if rate limited, exit if curl returns nothing # 2020-01-10 Change domain and getssl templates to v2 (2.15) +# 2020-01-16 Add support for CloudDNS # ---------------------------------------------------------------------------------------- PROGNAME=${0##*/} From b8b70f7ceea852bc6194a32cb9959d68c81efeb1 Mon Sep 17 00:00:00 2001 From: Radek SPRTA Date: Thu, 16 Jan 2020 18:49:08 +0100 Subject: [PATCH 010/110] Support certificates with SANs --- dns_scripts/dns_add_clouddns | 2 +- dns_scripts/dns_del_clouddns | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dns_scripts/dns_add_clouddns b/dns_scripts/dns_add_clouddns index 5236269..a4e3f81 100755 --- a/dns_scripts/dns_add_clouddns +++ b/dns_scripts/dns_add_clouddns @@ -69,7 +69,7 @@ if [[ -z "$domain_id" ]]; then fi # Add challenge record -txt_record="_acme-challenge.$domain_root." +txt_record="_acme-challenge.$fulldomain." resp=$(curl --silent "${curl_params[@]}" -X POST "$API/record-txt" \ --data "{\"type\":\"TXT\",\"name\":\"$txt_record\",\"value\":\"$token\",\"domainId\":\"$domain_id\"}") diff --git a/dns_scripts/dns_del_clouddns b/dns_scripts/dns_del_clouddns index ddac2b6..ec22c91 100755 --- a/dns_scripts/dns_del_clouddns +++ b/dns_scripts/dns_del_clouddns @@ -54,7 +54,7 @@ curl_params=( -H "Authorization: Bearer $access_token" -H 'Content-Type: applica # Get main domain and challenge record domain_root=$(echo "$fulldomain" | awk -F\. '{print $(NF-1) FS $NF}') -txt_record="_acme-challenge.$domain_root." +txt_record="_acme-challenge.$fulldomain." # Get domain id curl_domainid_body="{\"search\": [{\"name\": \"clientId\", \"operator\": \"eq\", \"value\": \"$client\"}, {\"name\": \"domainName\", \"operator\": \"eq\", \"value\": \"$domain_root.\"}]}" From 197c5f8faa86d146ec69666a58f9f00e0f7ed149 Mon Sep 17 00:00:00 2001 From: Tim Kimber Date: Fri, 17 Jan 2020 20:34:14 +0000 Subject: [PATCH 011/110] Ignore base64 errors --- getssl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/getssl b/getssl index 5352e11..8269195 100755 --- a/getssl +++ b/getssl @@ -196,10 +196,11 @@ # 2020-01-07 #464 and #486 "json was blank" (change all curl request to use POST-as-GET) # 2020-01-08 Error and exit if rate limited, exit if curl returns nothing # 2020-01-10 Change domain and getssl templates to v2 (2.15) +# 2020-01-17 #473 and #477 Don't use POST-as-GET when sending ready for challenge for ACMEv1 (2.16) # ---------------------------------------------------------------------------------------- PROGNAME=${0##*/} -VERSION="2.15" +VERSION="2.16" # defaults ACCOUNT_KEY_LENGTH=4096 @@ -1444,8 +1445,7 @@ send_signed_request() { # Sends a request to the ACME server, signed with your p responseHeaders=$(cat "$CURL_HEADER") if [[ "$needbase64" && ${response##*()} != "{"* ]]; then # response is in base64 too, decode - #!FIXME need to use openssl base64 decoder if it exists - response=$(echo "$response" | base64 -d) + response=$(echo "$response" | base64 -d 2>&1) fi debug responseHeaders "$responseHeaders" From 6138f4ab1f27908eca837e26f362f9e254a40f9e Mon Sep 17 00:00:00 2001 From: Yannic Haupenthal Date: Tue, 21 Jan 2020 09:42:05 +0100 Subject: [PATCH 012/110] make markdownlint happy --- test/README.md | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/test/README.md b/test/README.md index 9d0aedd..086c58a 100644 --- a/test/README.md +++ b/test/README.md @@ -1,19 +1,31 @@ # Testing -This directory contains a simple test script which tests creating certificates with Pebble (testing version of the LetsEncrypt server) +This directory contains a simple test script which tests creating +certificates with Pebble (testing version of the LetsEncrypt server) Start up pebble, the challdnstest server for DNS challenges -`docker-compose -f "docker-compose.yml" up -d --build` + +```sh +docker-compose -f "docker-compose.yml" up -d --build +``` Run the tests -`docker exec -it getssl /getssl/test/run-test.sh` -Debug (need to set CURL_CA_BUNDLE as pebble uses a local certificate, otherwise you get a "unknown API version" error) -`docker exec -it getssl /bin/bash` -`export CURL_CA_BUNDLE=/root/pebble-ca-bundle.crt` -`/getssl/getssl -d getssl` +```sh +docker exec -it getssl /getssl/test/run-test.sh +``` + +Debug (need to set `CURL_CA_BUNDLE` as pebble uses a local certificate, +otherwise you get a "unknown API version" error) + +```sh +docker exec -it getssl /bin/bash +export CURL_CA_BUNDLE=/root/pebble-ca-bundle.crt +/getssl/getssl -d getssl +``` + +## TODO -# TODO 1. Move to BATS (bash automated testing) instead of run-test.sh 2. Test RHEL6, Debian as well 3. Test SSH, SFTP From 8db7744b3ee20c1b204057b5bc1df3b7742817c3 Mon Sep 17 00:00:00 2001 From: Yannic Haupenthal Date: Tue, 21 Jan 2020 09:48:34 +0100 Subject: [PATCH 013/110] implement the suggested fix in #277 --- getssl | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/getssl b/getssl index 939b351..78e9df3 100755 --- a/getssl +++ b/getssl @@ -234,7 +234,6 @@ SERVER_TYPE="https" SKIP_HTTP_TOKEN_CHECK="false" SSLCONF="$(openssl version -d 2>/dev/null| cut -d\" -f2)/openssl.cnf" OCSP_MUST_STAPLE="false" -IGNORE_PERIOD_CHECK="false" TEMP_UPGRADE_FILE="" TOKEN_USER_ID="" USE_SINGLE_ACL="false" @@ -1831,8 +1830,8 @@ if [[ ${_CHECK_ALL} -eq 1 ]]; then if [[ ${_QUIET} -eq 1 ]]; then cmd="$cmd -q" fi - # check if $dir looks like a domain name (contains a period) - if [[ $(basename "$dir") == *.* || "$IGNORE_PERIOD_CHECK" == "true" ]]; then + # check if $dir is a directory with a getssl.cfg in it + if [[ -f "$dir/getssl.cfg" ]]; then cmd="$cmd -w $WORKING_DIR $(basename "$dir")" debug "CMD: $cmd" eval "$cmd" From 8c7cd703b583e4095c5e41f338388743e8b88c63 Mon Sep 17 00:00:00 2001 From: Yannic Haupenthal Date: Tue, 21 Jan 2020 10:06:47 +0100 Subject: [PATCH 014/110] lint CONTRIBUTING.md --- CONTRIBUTING.md | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 482a7aa..3c8242c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,24 +1,31 @@ # How to contribute -If you are happy writing in bash, please create a PR for any changes you'd like to see included (or bug fixes). +If you are happy writing in bash, please create a PR for any changes +you'd like to see included (or bug fixes). -If you aren't happy writing in bash, please open an issue with as much detail as possible about the issue or what you'd like to see added / improved. +If you aren't happy writing in bash, please open an issue with as much +detail as possible about the issue or what you'd like to see added / +improved. ## Submitting changes -Please update the 'revision history' and version number at the top of the code (without this I can't easily do a merge) +Please update the 'revision history' and version number at the top of +the code (without this I can't easily do a merge) -Please update just one issue per PR. If there are multiple issues, please provide separate PR's one per issue. +Please update just one issue per PR. If there are multiple issues, +please provide separate PR's one per issue. ## Coding conventions -Please see the guidelines at https://github.com/srvrco/getssl/wiki/Bash-Style-guide +Please see the guidelines at ## Testing -Please test with [shellcheck](https://github.com/koalaman/shellcheck), although this will also be tested on github ( via travis) on all PRs. +Please test with [shellcheck](https://github.com/koalaman/shellcheck), +although this will also be tested on github (via travis) on all PRs. -Please remember that the system is used across a wide range of platforms, so if you have access to multiple operating systems, please test on all. +Please remember that the system is used across a wide range of +platforms, so if you have access to multiple operating systems, please +test on all. - -Thanks :) +Thanks :) From 2c1a894224ec57b674d367c5cbd5f0ee3ef70ab7 Mon Sep 17 00:00:00 2001 From: Yannic Haupenthal Date: Tue, 21 Jan 2020 10:07:08 +0100 Subject: [PATCH 015/110] lint bug_report.md --- .github/ISSUE_TEMPLATE/bug_report.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 116d2a5..135ea05 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -12,6 +12,7 @@ A clear and concise description of what the bug is. **To Reproduce** Steps to reproduce the behavior: + 1. Go to '...' 2. Click on '....' 3. Scroll down to '....' @@ -21,8 +22,9 @@ Steps to reproduce the behavior: A clear and concise description of what you expected to happen. **Operating system (please complete the following information):** - - OS: [e.g. Debian 9, Ubuntu 18.04, freeBSD ] - - Bash Version [e.g. GNU bash, version 4.4.12(1)-release (x86_64-pc-linux-gnu)] + +- OS: [e.g. Debian 9, Ubuntu 18.04, freeBSD ] +- Bash Version [e.g. GNU bash, version 4.4.12(1)-release (x86_64-pc-linux-gnu)] **Additional context** Add any other context about the problem here. From 0891d37372bbb86677108aae019c4e454ecf6e7f Mon Sep 17 00:00:00 2001 From: Yannic Haupenthal Date: Tue, 21 Jan 2020 10:07:18 +0100 Subject: [PATCH 016/110] lint README.md (and fix some punctuation) --- README.md | 170 ++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 121 insertions(+), 49 deletions(-) diff --git a/README.md b/README.md index 8549bda..1d0d3fc 100644 --- a/README.md +++ b/README.md @@ -1,44 +1,76 @@ # getssl -Obtain SSL certificates from the letsencrypt.org ACME server. Suitable for automating the process on remote servers. + +Obtain SSL certificates from the letsencrypt.org ACME server. Suitable +for automating the process on remote servers. ## Features -* **Bash** - It runs on virtually all unix machines, including BSD, most Linux distributions, macOS. -* **Get certificates for remote servers** - The tokens used to provide validation of domain ownership, and the certificates themselves can be automatically copied to remote servers (via ssh, sftp or ftp for tokens). The script doesn't need to run on the server itself. This can be useful if you don't have access to run such scripts on the server itself, e.g. if it's a shared server. -* **Runs as a daily cron** - so certificates will be automatically renewed when required. + +* **Bash** - It runs on virtually all unix machines, including BSD, most + Linux distributions, macOS. +* **Get certificates for remote servers** - The tokens used to provide + validation of domain ownership, and the certificates themselves can be + automatically copied to remote servers (via ssh, sftp or ftp for + tokens). The script doesn't need to run on the server itself. This can + be useful if you don't have access to run such scripts on the server + itself, e.g. if it's a shared server. +* **Runs as a daily cron** - so certificates will be automatically + renewed when required. * **Automatic certificate renewals** -* **Checks certificates are correctly loaded**. After installation of a new certificate it will test the port specified ( see [Server-Types](#server-types) for options ) that the certificate is actually being used correctly. -* **Automatically updates** - The script can automatically update itself with bug fixes etc if required. -* **Extensively configurable** - With a simple configuration file for each certificate it is possible to configure it exactly for your needs, whether a simple single domain or multiple domains across multiple servers on the same certificate. +* **Checks certificates are correctly loaded** - After installation of a + new certificate it will test the port specified ( see + [Server-Types](#server-types) for options ) that the certificate is + actually being used correctly. +* **Automatically updates** - The script can automatically update itself + with bug fixes etc if required. +* **Extensively configurable** - With a simple configuration file for + each certificate it is possible to configure it exactly for your + needs, whether a simple single domain or multiple domains across + multiple servers on the same certificate. * **Supports http and dns challenges** - Full ACME implementation * **Simple and easy to use** -* **Detailed debug info** - Whilst it shouldn't be needed, detailed debug information is available. -* **Reload services** - After a new certificate is obtained then the relevant services (e.g. apache/nginx/postfix) can be reloaded. +* **Detailed debug info** - Whilst it shouldn't be needed, detailed + debug information is available. +* **Reload services** - After a new certificate is obtained then the + relevant services (e.g. apache/nginx/postfix) can be reloaded. * **ACME v1 and V2** - Supports both ACME versions 1 and 2 ## Installation -Since the script is only one file, you can use the following command for a quick installation of GetSSL only: -``` + +Since the script is only one file, you can use the following command for +a quick installation of GetSSL only: + +```sh curl --silent https://raw.githubusercontent.com/srvrco/getssl/master/getssl > getssl ; chmod 700 getssl ``` -This will copy the getssl Bash script to the current location and change the permissions to make it executable for you. -For a more comprehensive installation (e.g. install also helper scripts) use the provided Makefile with each release tarball. Use the `install` target. +This will copy the getssl Bash script to the current location and change +the permissions to make it executable for you. + +For a more comprehensive installation (e.g. install also helper scripts) +use the provided Makefile with each release tarball. Use the `install` +target. You'll find the latest version in the git repository: -``` +```sh git clone https://github.com/srvrco/getssl.git ``` -For Arch Linux there are packages in the AUR, see [here](https://aur.archlinux.org/packages/getssl/) and [there](https://aur.archlinux.org/packages/getssl-git/). +For Arch Linux there are packages in the AUR, see +[here](https://aur.archlinux.org/packages/getssl/) and +[there](https://aur.archlinux.org/packages/getssl-git/). -If you use puppet, there is a [GetSSL Puppet module](https://github.com/dthielking/puppet_getssl) by dthielking +If you use puppet, there is a [GetSSL Puppet +module](https://github.com/dthielking/puppet_getssl) by dthielking ## Overview -GetSSL was written in standard bash ( so it can be run on a server, a desktop computer, or even a virtualbox) and add the checks, and certificates to a remote server ( providing you have a ssh with key, sftp or ftp access to the remote server). +GetSSL was written in standard bash ( so it can be run on a server, a +desktop computer, or even a virtualbox) and add the checks, and +certificates to a remote server ( providing you have a ssh with key, +sftp or ftp access to the remote server). -``` +```getssl getssl ver. 2.02 Obtain SSL certificates from the letsencrypt.org ACME server @@ -63,27 +95,36 @@ Options: Once you have obtained the script (see Installation above), the next step is to use -```./getssl -c yourdomain.com``` +```sh +./getssl -c yourdomain.com +``` -where yourdomain.com is the primary domain name that you want to create a certificate for. This will create the following folders and files. +where yourdomain.com is the primary domain name that you want to create +a certificate for. This will create the following folders and files. -``` +```sh ~/.getssl ~/.getssl/getssl.cfg ~/.getssl/yourdomain.com ~/.getssl/yourdomain.com/getssl.cfg ``` -You can then edit ~/.getssl/getssl.cfg to set the values you want as the default for the majority of your certificates. - -Then edit ~/.getssl/yourdomain.com/getssl.cfg to have the values you want for this specific domain (make sure to uncomment and specify correct `ACL` option, since it is required). +You can then edit `~/.getssl/getssl.cfg` to set the values you want as the +default for the majority of your certificates. -You can then just run; +Then edit `~/.getssl/yourdomain.com/getssl.cfg` to have the values you +want for this specific domain (make sure to uncomment and specify +correct `ACL` option, since it is required). -```getssl yourdomain.com ``` +You can then just run: -and it should run, providing output like; +```sh +getssl yourdomain.com ``` + +and it should run, providing output like: + +```sh Registering account Verify each domain Verifying yourdomain.com @@ -98,30 +139,41 @@ copying private key to ssh:server5:/home/yourdomain/ssl/domain.key copying CA certificate to ssh:server5:/home/yourdomain/ssl/chain.crt reloading SSL services ``` -**This will (by default) use the staging server, so should give you a certificate that isn't trusted ( Fake Let's Encrypt).** + +**This will (by default) use the staging server, so should give you a +certificate that isn't trusted ( Fake Let's Encrypt).** Change the server in your config file to get a fully valid certificate. -**Note:** Verification is done via port 80 (http), port 443 (https) or dns. The certificate can be used (and checked with getssl) on alternate ports. +**Note:** Verification is done via port 80 (http), port 443 (https) or +dns. The certificate can be used (and checked with getssl) on alternate +ports. ## Automating updates I use the following cron -``` + +```cron 23 5 * * * /root/scripts/getssl -u -a -q ``` -The cron will automatically update getssl and renew any certificates, only giving output if there are issues / errors. + +The cron will automatically update getssl and renew any certificates, +only giving output if there are issues / errors. * The -u flag updates getssl if there is a more recent version available. * The -a flag automatically renews any certificates that are due for renewal. -* The -q flag is "quiet" so that it only outputs and emails me if there was an error / issue. +* The -q flag is "quiet" so that it only outputs and emails me if there + was an error / issue. ## Structure -The design aim was to provide flexibility in running the code. The default working directory is ~/.getssl ( which can be modified via the command line) +The design aim was to provide flexibility in running the code. The +default working directory is `~/.getssl` (which can be modified via the +command line). -Within the **working directory** is a config file, getssl.cfg which is a simple bash file containing variables, an example of which is +Within the **working directory** is a config file `getssl.cfg` which is a +simple bash file containing variables, an example of which is: -``` +```getssl # Uncomment and modify any variables you need # The staging server is best for testing (hence set as default) CA="https://acme-staging.api.letsencrypt.org" @@ -143,9 +195,11 @@ RENEW_ALLOW="30" SSLCONF="/usr/lib/ssl/openssl.cnf" ``` -then, within the **working directory** there will be a folder for each certificate (based on its domain name). Within that folder will be a config file (again called getssl.cfg). An example of which is; +then, within the **working directory** there will be a folder for each +certificate (based on its domain name). Within that folder will be a +config file (again called `getssl.cfg`). An example of which is: -``` +```getssl # Uncomment and modify any variables you need # see https://github.com/srvrco/getssl/wiki/Config-variables for details # see https://github.com/srvrco/getssl/wiki/Example-config-files for example configs @@ -195,19 +249,27 @@ RELOAD_CMD="service apache2 reload" #CHECK_REMOTE="true" ``` -If a location for a file starts with ssh: it is assumed the next part of the file is the hostname, followed by a colon, and then the path. -Files will be securely copied using scp, and it assumes that you have a key on the server ( for passwordless access). You can set the user, port etc for the server in your .ssh/config file +If a location for a file starts with `ssh:` it is assumed the next part +of the file is the hostname, followed by a colon, and then the path. +Files will be securely copied using scp, and it assumes that you have a +key on the server (for passwordless access). You can set the user, +port etc for the server in your `.ssh/config` file. -If an ACL starts with ftp: or sftp: it as assumed that the line is in the format "ftp:UserID:Password:servername:/path/to/acme-challenge". sftp requires sshpass. -Note: FTP can be used for copying tokens only and can **not** be used for uploading private key or certificates as it's not a secure method of transfer. +If an ACL starts with `ftp:` or `sftp:` it as assumed that the line is +in the format "ftp:UserID:Password:servername:/path/to/acme-challenge". +sftp requires sshpass. +Note: FTP can be used for copying tokens only +and can **not** be used for uploading private key or certificates as +it's not a secure method of transfer. ssh can also be used for the reload command if using on remote servers. Multiple locations can be defined for a file by separating the locations with a semi-colon. +A typical config file for `example.com` and `www.example.com` on the +same server would be: -A typical config file for example.com and www.example.com on the same server would be -``` +```getssl # uncomment and modify any variables you need # The staging server is best for testing CA="https://acme-staging.api.letsencrypt.org" @@ -231,6 +293,7 @@ RELOAD_CMD="service apache2 reload" ``` ## Server-Types + OpenSSL has built-in support for getting the certificate from a number of SSL services these are available in getssl to check if the certificate is installed correctly @@ -252,23 +315,32 @@ these are available in getssl to check if the certificate is installed correctly | ldaps | 636 | | | port number | | | - ## Revoke a certificate In general revoking a certificate is not required. Usage: `getssl -r path/to/cert path/to/key [CA_server]` -You need to specify both the certificate you want to revoke, and the account or private domain key which was used to sign / obtain the original certificate. The CA_server is an optional parameter and defaults to Let's Encrypt ( "https://acme-v01.api.letsencrypt.org" ) as that is currently the only Certificate Authority using the ACME protocol. - +You need to specify both the certificate you want to revoke, and the +account or private domain key which was used to sign / obtain the +original certificate. The `CA_server` is an optional parameter and +defaults to Let's Encrypt ("") as +that is currently the only Certificate Authority using the ACME +protocol. ## Elliptic curve keys -You can use Elliptic curve keys for both the account key and the domain key (different of course, don't use the same key for both). prime256v1 (NIST P-256) and secp384r1 (NIST P-384) are both fully supported. secp521r1 (NIST P-521) is included in the code, but not currently supported by Let's Encrypt). +You can use Elliptic curve keys for both the account key and the domain +key (different of course, don't use the same key for both). prime256v1 +(NIST P-256) and secp384r1 (NIST P-384) are both fully supported. +secp521r1 (NIST P-521) is included in the code, but not currently +supported by Let's Encrypt). ## Issues / problems / help -If you have any issues, please log them at https://github.com/srvrco/getssl/issues + +If you have any issues, please log them at There are additional help pages on the [wiki](https://github.com/srvrco/getssl/wiki) -If you have any suggestions for improvements then pull requests are welcomed, or raise an issue. +If you have any suggestions for improvements then pull requests are +welcomed, or raise an issue. From b1e177f45ea50e84be5f979cad967d823c37a637 Mon Sep 17 00:00:00 2001 From: Radek SPRTA Date: Wed, 22 Jan 2020 05:26:01 +0100 Subject: [PATCH 017/110] Handle domains of .co.uk type --- dns_scripts/dns_add_clouddns | 11 ++++++++++- dns_scripts/dns_del_clouddns | 11 ++++++++++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/dns_scripts/dns_add_clouddns b/dns_scripts/dns_add_clouddns index a4e3f81..f20d1ab 100755 --- a/dns_scripts/dns_add_clouddns +++ b/dns_scripts/dns_add_clouddns @@ -53,7 +53,16 @@ fi curl_params=( -H "Authorization: Bearer $access_token" -H 'Content-Type: application/json' ) # Get main domain -domain_root=$(echo "$fulldomain" | awk -F\. '{print $(NF-1) FS $NF}') +resp=$(curl --silent "${curl_params[@]}" -X POST "$API/domain/search" \ + --data "{\"search\": [{\"name\": \"clientId\", \"operator\": \"eq\", \"value\": \"$client\"}]}") +domain_slice="$fulldomain" +while [[ -z "$domain_root" ]]; do + if [[ "${resp// }" =~ domainName\":\"$domain_slice ]]; then + domain_root="$domain_slice" + _debug domain_root "$domain_root" + fi + domain_slice="${domain_slice#[^\.]*.}" +done # Get domain id resp=$(curl --silent "${curl_params[@]}" -X POST "$API/domain/search" \ diff --git a/dns_scripts/dns_del_clouddns b/dns_scripts/dns_del_clouddns index ec22c91..0b25121 100755 --- a/dns_scripts/dns_del_clouddns +++ b/dns_scripts/dns_del_clouddns @@ -53,7 +53,16 @@ fi curl_params=( -H "Authorization: Bearer $access_token" -H 'Content-Type: application/json' ) # Get main domain and challenge record -domain_root=$(echo "$fulldomain" | awk -F\. '{print $(NF-1) FS $NF}') +resp=$(curl --silent "${curl_params[@]}" -X POST "$API/domain/search" \ + --data "{\"search\": [{\"name\": \"clientId\", \"operator\": \"eq\", \"value\": \"$client\"}]}") +domain_slice="$fulldomain" +while [[ -z "$domain_root" ]]; do + if [[ "${resp// }" =~ domainName\":\"$domain_slice ]]; then + domain_root="$domain_slice" + _debug domain_root "$domain_root" + fi + domain_slice="${domain_slice#[^\.]*.}" +done txt_record="_acme-challenge.$fulldomain." # Get domain id From 25ab41135d550c6e7313838a008dad850057807d Mon Sep 17 00:00:00 2001 From: Tim Kimber Date: Wed, 22 Jan 2020 22:38:45 +0000 Subject: [PATCH 018/110] Fix json_get for 6 parameters when >9 domains --- getssl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/getssl b/getssl index 8269195..0646ce4 100755 --- a/getssl +++ b/getssl @@ -1192,7 +1192,7 @@ json_get() { # get values from json if [[ -n "$6" ]]; then full=$(json_awk "$1") section=$(echo "$full" | grep "\"$2\"" | grep "\"$3\"" | grep "\"$4\"" | awk -F"," '{print $2}') - echo "$full" | grep "^..${5}\",$section" | awk '{print $2}' | tr -d '"' + echo "$full" | grep "^..${5}\",$section\]" | awk '{print $2}' | tr -d '"' elif [[ -n "$5" ]]; then full=$(json_awk "$1") section=$(echo "$full" | grep "\"$2\"" | grep "\"$3\"" | grep "\"$4\"" | awk -F"," '{print $2}') From 839209d5285e1b87093b934128cf9449095a4ec6 Mon Sep 17 00:00:00 2001 From: Tim Kimber Date: Fri, 24 Jan 2020 18:58:20 +0000 Subject: [PATCH 019/110] Use urlbase64_decode instead of base64 -d --- getssl | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/getssl b/getssl index 8269195..48b2655 100755 --- a/getssl +++ b/getssl @@ -1445,7 +1445,7 @@ send_signed_request() { # Sends a request to the ACME server, signed with your p responseHeaders=$(cat "$CURL_HEADER") if [[ "$needbase64" && ${response##*()} != "{"* ]]; then # response is in base64 too, decode - response=$(echo "$response" | base64 -d 2>&1) + response=$(urlbase64_decode "$response") fi debug responseHeaders "$responseHeaders" @@ -1561,6 +1561,18 @@ urlbase64() { # urlbase64: base64 encoded string with '+' replaced with '-' and openssl base64 -e | tr -d '\n\r' | os_esed -e 's:=*$::g' -e 'y:+/:-_:' } +# base64url decode +# From: https://gist.github.com/alvis/89007e96f7958f2686036d4276d28e47 +urlbase64_decode() { + INPUT=$1 # $(if [ -z "$1" ]; then echo -n $(cat -); else echo -n "$1"; fi) + MOD=$(($(echo -n "$INPUT" | wc -c) % 4)) + PADDING=$(if [ $MOD -eq 2 ]; then echo -n '=='; elif [ $MOD -eq 3 ]; then echo -n '=' ; fi) + echo -n "$INPUT$PADDING" | + sed s/-/+/g | + sed s/_/\\//g | + openssl base64 -d -A +} + usage() { # echos out the program usage echo "Usage: $PROGNAME [-h|--help] [-d|--debug] [-c|--create] [-f|--force] [-a|--all] [-q|--quiet]"\ "[-Q|--mute] [-u|--upgrade] [-k|--keep #] [-U|--nocheck] [-r|--revoke cert key] [-w working_dir] domain" From 0040d84d22b4b6832c51855a96a78d6642377729 Mon Sep 17 00:00:00 2001 From: Tim Kimber Date: Fri, 24 Jan 2020 19:05:18 +0000 Subject: [PATCH 020/110] Update revision history --- getssl | 1 + 1 file changed, 1 insertion(+) diff --git a/getssl b/getssl index 0646ce4..32df8d1 100755 --- a/getssl +++ b/getssl @@ -197,6 +197,7 @@ # 2020-01-08 Error and exit if rate limited, exit if curl returns nothing # 2020-01-10 Change domain and getssl templates to v2 (2.15) # 2020-01-17 #473 and #477 Don't use POST-as-GET when sending ready for challenge for ACMEv1 (2.16) +# 2020-01-22 #475 and #483 Fix grep regex for >9 subdomains in json_get # ---------------------------------------------------------------------------------------- PROGNAME=${0##*/} From 52883615229334216d00507b0346fd04efba1310 Mon Sep 17 00:00:00 2001 From: Tim Kimber Date: Fri, 24 Jan 2020 20:17:15 +0000 Subject: [PATCH 021/110] Test using BATS --- docker-compose.yml | 54 ++++++++++++++-- test/1-simple-http01.bats | 28 +++++++++ test/2-simple-dns01.bats | 28 +++++++++ test/4-more-than-10-hosts.bats | 39 ++++++++++++ test/Dockerfile-centos6 | 22 +++++++ test/Dockerfile-rhel6 | 22 ------- ...{Dockerfile-ubuntu => Dockerfile-ubuntu18} | 10 +-- test/README.md | 26 +++++--- test/run-test.sh | 47 +++----------- test/test-config/getssl-dns01.cfg | 2 +- test/test-config/getssl-http01-10-hosts.cfg | 28 +++++++++ test/test-config/getssl-http01.cfg | 6 +- test/test-config/nginx-ubuntu-no-ssl | 63 ------------------- test/test_helper.bash | 44 +++++++++++++ 14 files changed, 274 insertions(+), 145 deletions(-) create mode 100644 test/1-simple-http01.bats create mode 100644 test/2-simple-dns01.bats create mode 100644 test/4-more-than-10-hosts.bats create mode 100644 test/Dockerfile-centos6 delete mode 100644 test/Dockerfile-rhel6 rename test/{Dockerfile-ubuntu => Dockerfile-ubuntu18} (66%) create mode 100644 test/test-config/getssl-http01-10-hosts.cfg create mode 100644 test/test_helper.bash diff --git a/docker-compose.yml b/docker-compose.yml index f4b3567..cbe52e2 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,8 +7,8 @@ services: environment: # with Go 1.13.x which defaults TLS 1.3 to on GODEBUG: "tls13=1" - # don't reuse authorizations (breaks testing force renew) - PEBBLE_AUTHZREUSE: 0 + # Don't re-use authorisations (breaks force renew test scripts) + # PEBBLE_AUTHZREUSE: "0" ports: - 14000:14000 # HTTPS ACME API - 15000:15000 # HTTPS Management API @@ -23,16 +23,60 @@ services: networks: acmenet: ipv4_address: 10.30.50.3 - getssl: + getssl-ubuntu18: build: context: . - dockerfile: test/Dockerfile-ubuntu - container_name: getssl + dockerfile: test/Dockerfile-ubuntu18 + container_name: getssl-ubuntu18 volumes: - .:/getssl + environment: + GETSSL_HOST: ubuntu18.getssl.test + GETSSL_IP: 10.30.50.4 + NGINX_CONFIG: /etc/nginx/sites-enabled/default networks: acmenet: ipv4_address: 10.30.50.4 + aliases: + - ubuntu18.getssl.test + - a.ubuntu18.getssl.test + - b.ubuntu18.getssl.test + - c.ubuntu18.getssl.test + - d.ubuntu18.getssl.test + - e.ubuntu18.getssl.test + - f.ubuntu18.getssl.test + - g.ubuntu18.getssl.test + - h.ubuntu18.getssl.test + - i.ubuntu18.getssl.test + - j.ubuntu18.getssl.test + - k.ubuntu18.getssl.test + getssl-centos6: + build: + context: . + dockerfile: test/Dockerfile-centos6 + container_name: getssl-centos6 + volumes: + - .:/getssl + environment: + GETSSL_HOST: centos6.getssl.test + GETSSL_IP: 10.30.50.5 + NGINX_CONFIG: /etc/nginx/conf.d/default.conf + networks: + acmenet: + ipv4_address: 10.30.50.5 + aliases: + - centos6.getssl.test + - a.centos6.getssl.test + - b.centos6.getssl.test + - c.centos6.getssl.test + - d.centos6.getssl.test + - e.centos6.getssl.test + - f.centos6.getssl.test + - g.centos6.getssl.test + - h.centos6.getssl.test + - i.centos6.getssl.test + - j.centos6.getssl.test + - k.centos6.getssl.test networks: acmenet: diff --git a/test/1-simple-http01.bats b/test/1-simple-http01.bats new file mode 100644 index 0000000..40416b2 --- /dev/null +++ b/test/1-simple-http01.bats @@ -0,0 +1,28 @@ +#! /usr/bin/env bats + +load '/bats-support/load.bash' +load '/bats-assert/load.bash' +load '/getssl/test/test_helper.bash' + + +# This is run for every test +setup() { + export CURL_CA_BUNDLE=/root/pebble-ca-bundle.crt +} + + +@test "Create new certificate using HTTP-01 verification" { + CONFIG_FILE="getssl-http01.cfg" + setup_environment + init_getssl + create_certificate + assert_success +} + + +@test "Force renewal of certificate using HTTP-01" { + #!FIXME test certificate has been updated + run ${CODE_DIR}/getssl -f $GETSSL_HOST + assert_success + cleanup_environment +} diff --git a/test/2-simple-dns01.bats b/test/2-simple-dns01.bats new file mode 100644 index 0000000..e1a37ec --- /dev/null +++ b/test/2-simple-dns01.bats @@ -0,0 +1,28 @@ +#! /usr/bin/env bats + +load '/bats-support/load.bash' +load '/bats-assert/load.bash' +load '/getssl/test/test_helper.bash' + + +# This is run for every test +setup() { + export CURL_CA_BUNDLE=/root/pebble-ca-bundle.crt +} + + +@test "Create new certificate using DNS-01 verification" { + CONFIG_FILE="getssl-dns01.cfg" + setup_environment + init_getssl + create_certificate + assert_success +} + + +@test "Force renewal of certificate using DNS-01" { + #!FIXME test certificate has been updated + run ${CODE_DIR}/getssl -f $GETSSL_HOST + assert_success + cleanup_environment +} diff --git a/test/4-more-than-10-hosts.bats b/test/4-more-than-10-hosts.bats new file mode 100644 index 0000000..0493197 --- /dev/null +++ b/test/4-more-than-10-hosts.bats @@ -0,0 +1,39 @@ +#! /usr/bin/env bats + +load '/bats-support/load.bash' +load '/bats-assert/load.bash' +load '/getssl/test/test_helper.bash' + + +# This is run for every test +setup() { + export CURL_CA_BUNDLE=/root/pebble-ca-bundle.crt +} + + +@test "Create certificates for more than 10 hosts using HTTP-01 verification" { + CONFIG_FILE="getssl-http01-10-hosts.cfg" + setup_environment + + # Add 11 hosts to DNS (also need to be added as aliases in docker-compose.yml) + for prefix in a b c d e f g h i j k; do + curl -X POST -d '{"host":"'$prefix.$GETSSL_HOST'", "addresses":["'$GETSSL_IP'"]}' http://10.30.50.3:8055/add-a + done + + init_getssl + create_certificate + assert_success +} + + +@test "Force renewal of more than 10 certificates using HTTP-01" { + #!FIXME test certificate has been updated + run ${CODE_DIR}/getssl -f $GETSSL_HOST + assert_success + + # Remove all the dns aliases + cleanup_environment + for prefix in a b c d e f g h i j k; do + curl -X POST -d '{"host":"'$prefix.$GETSSL_HOST'", "addresses":["'$GETSSL_IP'"]}' http://10.30.50.3:8055/del-a + done +} diff --git a/test/Dockerfile-centos6 b/test/Dockerfile-centos6 new file mode 100644 index 0000000..f370a0b --- /dev/null +++ b/test/Dockerfile-centos6 @@ -0,0 +1,22 @@ +FROM centos:centos6 + +# Update and install required software +RUN yum -y update +RUN yum -y install epel-release +RUN yum -y install git curl dnsutils wget nginx + +WORKDIR /root +RUN mkdir /etc/nginx/pki +RUN mkdir /etc/nginx/pki/private +COPY ./test/test-config/nginx-ubuntu-no-ssl /etc/nginx/conf.d/default.conf + +# BATS (Bash Automated Testings) +RUN git clone https://github.com/bats-core/bats-core.git /bats-core +RUN git clone https://github.com/jasonkarns/bats-support /bats-support +RUN git clone https://github.com/jasonkarns/bats-assert-1 /bats-assert +RUN /bats-core/install.sh /usr/local + +EXPOSE 80 443 + +# Run eternal loop - for testing +CMD ["/bin/bash", "-c", "while :; do sleep 10; done"] diff --git a/test/Dockerfile-rhel6 b/test/Dockerfile-rhel6 deleted file mode 100644 index 019da84..0000000 --- a/test/Dockerfile-rhel6 +++ /dev/null @@ -1,22 +0,0 @@ -FROM roboxes/rhel6 -# FROM centos:centos6 -# bionic = latest 18 version - -# Update and install required software -RUN yum -y update -RUN yum -y install epel-release -RUN yum -y install git curl dnsutils wget # nginx-light - -WORKDIR /root -#RUN mkdir /etc/nginx/pki -#RUN mkdir /etc/nginx/pki/private -#COPY ./test/test-config/nginx-ubuntu-sites-enabled-default /etc/nginx/sites-enabled/default - -# BATS (Bash Automated Testings) -# RUN git clone https://github.com/bats-core/bats-core.git -# RUN bats-core/install.sh /usr/local - -EXPOSE 80 443 - -# Run eternal loop - for testing -CMD ["/bin/bash", "-c", "while :; do sleep 10; done"] diff --git a/test/Dockerfile-ubuntu b/test/Dockerfile-ubuntu18 similarity index 66% rename from test/Dockerfile-ubuntu rename to test/Dockerfile-ubuntu18 index b0f09f8..6dd92c6 100644 --- a/test/Dockerfile-ubuntu +++ b/test/Dockerfile-ubuntu18 @@ -1,8 +1,8 @@ -FROM ubuntu:xenial +FROM ubuntu:bionic # bionic = latest 18 version # Update and install required software -RUN apt-get update +RUN apt-get update --fix-missing # TODO work out why default version of awk fails RUN apt-get install -y git curl dnsutils wget gawk nginx-light # linux-libc-dev make gcc binutils RUN apt-get install -y vim dos2unix # for debugging @@ -14,8 +14,10 @@ RUN mkdir /etc/nginx/pki/private COPY ./test/test-config/nginx-ubuntu-no-ssl /etc/nginx/sites-enabled/default # BATS (Bash Automated Testings) -# RUN git clone https://github.com/bats-core/bats-core.git -# RUN bats-core/install.sh /usr/local +RUN git clone https://github.com/bats-core/bats-core.git /bats-core +RUN git clone https://github.com/jasonkarns/bats-support /bats-support +RUN git clone https://github.com/jasonkarns/bats-assert-1 /bats-assert +RUN /bats-core/install.sh /usr/local EXPOSE 80 443 diff --git a/test/README.md b/test/README.md index 086c58a..9157aa8 100644 --- a/test/README.md +++ b/test/README.md @@ -12,21 +12,29 @@ docker-compose -f "docker-compose.yml" up -d --build Run the tests ```sh -docker exec -it getssl /getssl/test/run-test.sh +docker exec -it getssl bats /getssl/test ``` -Debug (need to set `CURL_CA_BUNDLE` as pebble uses a local certificate, +Run individual test + +```sh +docker exec -it getssl bats /getssl/test/ +``` + +Debug (uses helper script to set `CURL_CA_BUNDLE` as pebble uses a local certificate, otherwise you get a "unknown API version" error) ```sh -docker exec -it getssl /bin/bash -export CURL_CA_BUNDLE=/root/pebble-ca-bundle.crt -/getssl/getssl -d getssl +docker exec -it getssl- /getssl/test/run-test.sh ` + +eg. + +```sh +docker exec -it getssl-ubuntu18 /getssl/test/run-test.sh getssl-http01.cfg ``` ## TODO -1. Move to BATS (bash automated testing) instead of run-test.sh -2. Test RHEL6, Debian as well -3. Test SSH, SFTP -4. Test wildcards +1. Test RHEL6, Debian as well +2. Test SSH, SFTP +3. Test wildcards diff --git a/test/run-test.sh b/test/run-test.sh index 8051922..b6f7c72 100644 --- a/test/run-test.sh +++ b/test/run-test.sh @@ -1,43 +1,14 @@ -#! /bin/bash +#!/usr/bin/env bash -set -e +# This runs getssl outside of the BATS framework for debugging, etc, against pebble +# Usage: /getssl/test/run-test.sh getssl-http01.cfg -# Test setup -if [[ -d /root/.getssl ]]; then - rm -r /root/.getssl -fi +CONFIG_FILE=$1 +source /getssl/test/test_helper.bash -wget --no-clobber https://raw.githubusercontent.com/letsencrypt/pebble/master/test/certs/pebble.minica.pem -# cat /etc/pki/tls/certs/ca-bundle.crt /root/pebble.minica.pem > /root/pebble-ca-bundle.crt -cat /etc/ssl/certs/ca-certificates.crt /root/pebble.minica.pem > /root/pebble-ca-bundle.crt +setup_environment 3>&1 export CURL_CA_BUNDLE=/root/pebble-ca-bundle.crt -curl -X POST -d '{"host":"getssl", "addresses":["10.30.50.4"]}' http://10.30.50.3:8055/add-a - -# Test #1 - http-01 verification -echo Test \#1 - http-01 verification - -cp /getssl/test/test-config/nginx-ubuntu-no-ssl /etc/nginx/sites-enabled/default -service nginx restart -/getssl/getssl -c getssl -cp /getssl/test/test-config/getssl-http01.cfg /root/.getssl/getssl/getssl.cfg -/getssl/getssl -f getssl - -# Test #2 - http-01 forced renewal -echo Test \#2 - http-01 forced renewal -/getssl/getssl getssl -f - -# Test cleanup -rm -r /root/.getssl - -# Test #3 - dns-01 verification -echo Test \#3 - dns-01 verification -cp /getssl/test/test-config/nginx-ubuntu-no-ssl /etc/nginx/sites-enabled/default -service nginx restart -/getssl/getssl -c getssl -cp /getssl/test/test-config/getssl-dns01.cfg /root/.getssl/getssl/getssl.cfg -/getssl/getssl getssl - -# Test #4 - dns-01 forced renewal -echo Test \#4 - dns-01 forced renewal -/getssl/getssl getssl -f +"${CODE_DIR}/getssl" -c "$GETSSL_HOST" 3>&1 +cp "${CODE_DIR}/test/test-config/${CONFIG_FILE}" "${INSTALL_DIR}/.getssl/${GETSSL_HOST}/getssl.cfg" +"${CODE_DIR}/getssl" -f -d "$GETSSL_HOST" 3>&1 diff --git a/test/test-config/getssl-dns01.cfg b/test/test-config/getssl-dns01.cfg index 49c58b5..790dee2 100644 --- a/test/test-config/getssl-dns01.cfg +++ b/test/test-config/getssl-dns01.cfg @@ -44,7 +44,7 @@ DOMAIN_CHAIN_LOCATION="" # this is the domain cert and CA cert DOMAIN_PEM_LOCATION="" # this is the domain_key, domain cert and CA cert # The command needed to reload apache / nginx or whatever you use -RELOAD_CMD="cp /getssl/test/test-config/nginx-ubuntu-ssl /etc/nginx/sites-enabled/default && service nginx restart" +RELOAD_CMD="cp /getssl/test/test-config/nginx-ubuntu-ssl ${NGINX_CONFIG} && service nginx restart >&3-" # Define the server type. This can be https, ftp, ftpi, imap, imaps, pop3, pop3s, smtp, # smtps_deprecated, smtps, smtp_submission, xmpp, xmpps, ldaps or a port number which diff --git a/test/test-config/getssl-http01-10-hosts.cfg b/test/test-config/getssl-http01-10-hosts.cfg new file mode 100644 index 0000000..f521d52 --- /dev/null +++ b/test/test-config/getssl-http01-10-hosts.cfg @@ -0,0 +1,28 @@ +# Uncomment and modify any variables you need +# see https://github.com/srvrco/getssl/wiki/Config-variables for details +# see https://github.com/srvrco/getssl/wiki/Example-config-files for example configs + +CA="https://pebble:14000/dir" + +# Additional domains - this could be multiple domains / subdomains in a comma separated list +# Note: this is Additional domains - so should not include the primary domain. +SANS="a.${GETSSL_HOST},b.${GETSSL_HOST},c.${GETSSL_HOST},d.${GETSSL_HOST},e.${GETSSL_HOST},f.${GETSSL_HOST},g.${GETSSL_HOST},h.${GETSSL_HOST},i.${GETSSL_HOST},j.${GETSSL_HOST},k.${GETSSL_HOST}" + +# Acme Challenge Location. +ACL=('/var/www/html/.well-known/acme-challenge') + +# Use a single ACL for all checks +USE_SINGLE_ACL="true" + +# Location for all your certs, these can either be on the server (full path name) +DOMAIN_CERT_LOCATION="/etc/nginx/pki/server.crt" +DOMAIN_KEY_LOCATION="/etc/nginx/pki/private/server.key" +CA_CERT_LOCATION="/etc/nginx/pki/chain.crt" +DOMAIN_CHAIN_LOCATION="" # this is the domain cert and CA cert +DOMAIN_PEM_LOCATION="" # this is the domain_key, domain cert and CA cert + +# The command needed to reload apache / nginx or whatever you use +RELOAD_CMD="cp /getssl/test/test-config/nginx-ubuntu-ssl ${NGINX_CONFIG} && service nginx restart >&3-" + +#SERVER_TYPE="https" +#CHECK_REMOTE="true" diff --git a/test/test-config/getssl-http01.cfg b/test/test-config/getssl-http01.cfg index f3dc5ad..305dd49 100644 --- a/test/test-config/getssl-http01.cfg +++ b/test/test-config/getssl-http01.cfg @@ -43,11 +43,11 @@ DOMAIN_CHAIN_LOCATION="" # this is the domain cert and CA cert DOMAIN_PEM_LOCATION="" # this is the domain_key, domain cert and CA cert # The command needed to reload apache / nginx or whatever you use -RELOAD_CMD="cp /getssl/test/test-config/nginx-ubuntu-ssl /etc/nginx/sites-enabled/default && service nginx restart" +RELOAD_CMD="cp /getssl/test/test-config/nginx-ubuntu-ssl ${NGINX_CONFIG} && service nginx restart >&3-" # Define the server type. This can be https, ftp, ftpi, imap, imaps, pop3, pop3s, smtp, # smtps_deprecated, smtps, smtp_submission, xmpp, xmpps, ldaps or a port number which # will be checked for certificate expiry and also will be checked after # an update to confirm correct certificate is running (if CHECK_REMOTE) is set to true -#SERVER_TYPE="https" -#CHECK_REMOTE="true" +SERVER_TYPE="https" +CHECK_REMOTE="true" diff --git a/test/test-config/nginx-ubuntu-no-ssl b/test/test-config/nginx-ubuntu-no-ssl index c78d646..e7b046e 100644 --- a/test/test-config/nginx-ubuntu-no-ssl +++ b/test/test-config/nginx-ubuntu-no-ssl @@ -1,16 +1,3 @@ -## -# You should look at the following URL's in order to grasp a solid understanding -# of Nginx configuration files in order to fully unleash the power of Nginx. -# http://wiki.nginx.org/Pitfalls -# http://wiki.nginx.org/QuickStart -# http://wiki.nginx.org/Configuration -# -# Generally, you will want to move this file somewhere, and start with a clean -# file but keep this around for reference. Or just disable in sites-enabled. -# -# Please see /usr/share/doc/nginx-doc/examples/ for more detailed examples. -## - # Default server configuration # server { @@ -26,18 +13,6 @@ server { listen 5001 default_server; listen [::]:5001 default_server; - # - # Note: You should disable gzip for SSL traffic. - # See: https://bugs.debian.org/773332 - # - # Read up on ssl_ciphers to ensure a secure configuration. - # See: https://bugs.debian.org/765782 - # - # Self signed certs generated by the ssl-cert package - # Don't use them in a production server! - # - # include snippets/snakeoil.conf; - root /var/www/html; # Add index.php to the list if you are using PHP @@ -52,42 +27,4 @@ server { # as directory, then fall back to displaying a 404. try_files $uri $uri/ =404; } - - # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000 - # - #location ~ \.php$ { - # include snippets/fastcgi-php.conf; - # - # # With php7.0-cgi alone: - # fastcgi_pass 127.0.0.1:9000; - # # With php7.0-fpm: - # fastcgi_pass unix:/run/php/php7.0-fpm.sock; - #} - - # deny access to .htaccess files, if Apache's document root - # concurs with nginx's one - # - #location ~ /\.ht { - # deny all; - #} } - - -# Virtual Host configuration for example.com -# -# You can move that to a different file under sites-available/ and symlink that -# to sites-enabled/ to enable it. -# -#server { -# listen 80; -# listen [::]:80; -# -# server_name example.com; -# -# root /var/www/example.com; -# index index.html; -# -# location / { -# try_files $uri $uri/ =404; -# } -#} diff --git a/test/test_helper.bash b/test/test_helper.bash new file mode 100644 index 0000000..6f506fd --- /dev/null +++ b/test/test_helper.bash @@ -0,0 +1,44 @@ +INSTALL_DIR=/root +CODE_DIR=/getssl + + +setup_environment() { + # One-off test setup + if [[ -d ${INSTALL_DIR}/.getssl ]]; then + rm -r ${INSTALL_DIR}/.getssl + fi + + if [ ! -f ${INSTALL_DIR}/pebble.minica.pem ]; then + wget --no-clobber https://raw.githubusercontent.com/letsencrypt/pebble/master/test/certs/pebble.minica.pem 2>&1 + CERT_FILE=/etc/ssl/certs/ca-certificates.crt + if [ ! -f $CERT_FILE ]; then + CERT_FILE=/etc/pki/tls/certs/ca-bundle.crt + fi + cat $CERT_FILE ${INSTALL_DIR}/pebble.minica.pem > ${INSTALL_DIR}/pebble-ca-bundle.crt + fi + + curl -X POST -d '{"host":"'"$GETSSL_HOST"'", "addresses":["'"$GETSSL_IP"'"]}' http://10.30.50.3:8055/add-a + cp ${CODE_DIR}/test/test-config/nginx-ubuntu-no-ssl ${NGINX_CONFIG} + service nginx restart >&3- +} + + +cleanup_environment() { + curl -X POST -d '{"host":"'"$GETSSL_HOST"'", "addresses":["'"$GETSSL_IP"'"]}' http://10.30.50.3:8055/del-a +} + + +init_getssl() { + # Run initialisation (create account key, etc) + run ${CODE_DIR}/getssl -c "$GETSSL_HOST" + assert_success + [ -d "$INSTALL_DIR/.getssl" ] +} + + +create_certificate() { + # Create certificate + cp "${CODE_DIR}/test/test-config/${CONFIG_FILE}" "${INSTALL_DIR}/.getssl/${GETSSL_HOST}/getssl.cfg" + run ${CODE_DIR}/getssl "$GETSSL_HOST" + #!FIXME test certificate has been placed in the expected location +} From b3480db63b498798de9295c8d1153eb145c7dbd9 Mon Sep 17 00:00:00 2001 From: Tim Kimber Date: Fri, 24 Jan 2020 20:17:52 +0000 Subject: [PATCH 022/110] Fix for "already verified" problem seen with ACMEv2 during testing --- getssl | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/getssl b/getssl index 622fcb1..a542c0b 100755 --- a/getssl +++ b/getssl @@ -1608,13 +1608,13 @@ write_domain_template() { # write out a template file for a domain. # If these start with ftp: then the next variables are ftpuserid:ftppassword:servername:ACL_location # These should be of the form "/path/to/your/website/folder/.well-known/acme-challenge" # where "/path/to/your/website/folder/" is the path, on your web server, to the web root for your domain. - # You can also user WebDAV over HTTPS as transport mechanism. To do so, start with davs: followed by username, - # password, host, port (explicitly needed even if using default port 443) and path on the server. + # You can also user WebDAV over HTTPS as transport mechanism. To do so, start with davs: followed by username, + # password, host, port (explicitly needed even if using default port 443) and path on the server. #ACL=('/var/www/${DOMAIN}/web/.well-known/acme-challenge' # 'ssh:server5:/var/www/${DOMAIN}/web/.well-known/acme-challenge' # 'ssh:sshuserid@server5:/var/www/${DOMAIN}/web/.well-known/acme-challenge' # 'ftp:ftpuserid:ftppassword:${DOMAIN}:/web/.well-known/acme-challenge' - # 'davs:davsuserid:davspassword:{DOMAIN}:443:/web/.well-known/acme-challenge') + # 'davs:davsuserid:davspassword:{DOMAIN}:443:/web/.well-known/acme-challenge') #Set USE_SINGLE_ACL="true" to use a single ACL for all checks #USE_SINGLE_ACL="false" @@ -2212,7 +2212,7 @@ for d in $alldomains; do error_exit "new-authz error: $response" fi else - response_status="" + send_signed_request "${AuthLink[$dn]}" "" fi if [[ $response_status == "valid" ]]; then @@ -2237,7 +2237,6 @@ for d in $alldomains; do uri=$(json_get "$response" "uri" "dns-01") debug uri "$uri" else # APIv2 - send_signed_request "${AuthLink[$dn]}" "" debug "authlink response = $response" # get the token from the http-01 component token=$(json_get "$response" "challenges" "type" "dns-01" "token") From 08a6485911c22d71adc561d9331f7008a98ee1a7 Mon Sep 17 00:00:00 2001 From: Tim Kimber Date: Sun, 26 Jan 2020 14:09:39 +0000 Subject: [PATCH 023/110] Update revision history --- getssl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/getssl b/getssl index 6c3870f..763677f 100755 --- a/getssl +++ b/getssl @@ -200,6 +200,8 @@ # 2020-01-22 #475 and #483 Fix grep regex for >9 subdomains in json_get # 2020-01-24 Add support for CloudDNS # 2020-01-24 allow file transfer using WebDAV over HTTPS +# 2020-01-26 Use urlbase64_decode() instead of base64 -d +# 2020-01-26 Fix "already verified" error for ACMEv2 # ---------------------------------------------------------------------------------------- PROGNAME=${0##*/} From d8a0e4ca237545b6515d29beba193c9edf4d07c4 Mon Sep 17 00:00:00 2001 From: Tim Kimber Date: Tue, 28 Jan 2020 19:47:15 +0100 Subject: [PATCH 024/110] Check output for "Error", "Failed" and "Warning" --- test/1-simple-http01.bats | 6 ++++++ test/2-simple-dns01.bats | 6 ++++++ test/4-more-than-10-hosts.bats | 7 ++++++- test/Dockerfile-ubuntu18 | 3 +++ test/README.md | 6 +++--- test/{run-test.sh => debug-test.sh} | 4 ++-- test/run-all-tests.sh | 4 ++++ 7 files changed, 30 insertions(+), 6 deletions(-) rename test/{run-test.sh => debug-test.sh} (79%) create mode 100644 test/run-all-tests.sh diff --git a/test/1-simple-http01.bats b/test/1-simple-http01.bats index 40416b2..4c55304 100644 --- a/test/1-simple-http01.bats +++ b/test/1-simple-http01.bats @@ -17,6 +17,9 @@ setup() { init_getssl create_certificate assert_success + refute_output --regexp '[Ff][Aa][Ii][Ll][Ee][Dd]' + refute_output --regexp '[Ee][Rr][Rr][Oo][Rr]' + refute_output --regexp '[Ww][Aa][Rr][Nn][Ii][Nn][Gg]' } @@ -24,5 +27,8 @@ setup() { #!FIXME test certificate has been updated run ${CODE_DIR}/getssl -f $GETSSL_HOST assert_success + refute_output --regexp '[Ff][Aa][Ii][Ll][Ee][Dd]' + refute_output --regexp '[Ee][Rr][Rr][Oo][Rr]' + refute_output --regexp '[Ww][Aa][Rr][Nn][Ii][Nn][Gg]' cleanup_environment } diff --git a/test/2-simple-dns01.bats b/test/2-simple-dns01.bats index e1a37ec..9d9f44b 100644 --- a/test/2-simple-dns01.bats +++ b/test/2-simple-dns01.bats @@ -17,6 +17,9 @@ setup() { init_getssl create_certificate assert_success + refute_output --regexp '[Ff][Aa][Ii][Ll][Ee][Dd]' + refute_output --regexp '[Ee][Rr][Rr][Oo][Rr]' + refute_output --regexp '[Ww][Aa][Rr][Nn][Ii][Nn][Gg]' } @@ -24,5 +27,8 @@ setup() { #!FIXME test certificate has been updated run ${CODE_DIR}/getssl -f $GETSSL_HOST assert_success + refute_output --regexp '[Ff][Aa][Ii][Ll][Ee][Dd]' + refute_output --regexp '[Ee][Rr][Rr][Oo][Rr]' + refute_output --regexp '[Ww][Aa][Rr][Nn][Ii][Nn][Gg]' cleanup_environment } diff --git a/test/4-more-than-10-hosts.bats b/test/4-more-than-10-hosts.bats index 0493197..01e364d 100644 --- a/test/4-more-than-10-hosts.bats +++ b/test/4-more-than-10-hosts.bats @@ -23,6 +23,9 @@ setup() { init_getssl create_certificate assert_success + refute_output --regexp '[Ff][Aa][Ii][Ll][Ee][Dd]' + refute_output --regexp '[Ee][Rr][Rr][Oo][Rr]' + refute_output --regexp '[Ww][Aa][Rr][Nn][Ii][Nn][Gg]' } @@ -30,7 +33,9 @@ setup() { #!FIXME test certificate has been updated run ${CODE_DIR}/getssl -f $GETSSL_HOST assert_success - + refute_output --regexp '[Ff][Aa][Ii][Ll][Ee][Dd]' + refute_output --regexp '[Ee][Rr][Rr][Oo][Rr]' + refute_output --regexp '[Ww][Aa][Rr][Nn][Ii][Nn][Gg]' # Remove all the dns aliases cleanup_environment for prefix in a b c d e f g h i j k; do diff --git a/test/Dockerfile-ubuntu18 b/test/Dockerfile-ubuntu18 index 6dd92c6..1b3765c 100644 --- a/test/Dockerfile-ubuntu18 +++ b/test/Dockerfile-ubuntu18 @@ -13,6 +13,9 @@ RUN mkdir /etc/nginx/pki RUN mkdir /etc/nginx/pki/private COPY ./test/test-config/nginx-ubuntu-no-ssl /etc/nginx/sites-enabled/default +# Prevent "Can't load /root/.rnd into RNG" error from openssl +RUN touch /root/.rnd + # BATS (Bash Automated Testings) RUN git clone https://github.com/bats-core/bats-core.git /bats-core RUN git clone https://github.com/jasonkarns/bats-support /bats-support diff --git a/test/README.md b/test/README.md index 9157aa8..98ff929 100644 --- a/test/README.md +++ b/test/README.md @@ -12,7 +12,7 @@ docker-compose -f "docker-compose.yml" up -d --build Run the tests ```sh -docker exec -it getssl bats /getssl/test +test/run-all-tests.sh ``` Run individual test @@ -25,12 +25,12 @@ Debug (uses helper script to set `CURL_CA_BUNDLE` as pebble uses a local certifi otherwise you get a "unknown API version" error) ```sh -docker exec -it getssl- /getssl/test/run-test.sh ` +docker exec -it getssl- /getssl/test/debug-test.sh ` eg. ```sh -docker exec -it getssl-ubuntu18 /getssl/test/run-test.sh getssl-http01.cfg +docker exec -it getssl-ubuntu18 /getssl/test/debug-test.sh getssl-http01.cfg ``` ## TODO diff --git a/test/run-test.sh b/test/debug-test.sh similarity index 79% rename from test/run-test.sh rename to test/debug-test.sh index b6f7c72..23d1983 100644 --- a/test/run-test.sh +++ b/test/debug-test.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash # This runs getssl outside of the BATS framework for debugging, etc, against pebble -# Usage: /getssl/test/run-test.sh getssl-http01.cfg +# Usage: /getssl/test/debug-test.sh getssl-http01.cfg CONFIG_FILE=$1 source /getssl/test/test_helper.bash @@ -11,4 +11,4 @@ export CURL_CA_BUNDLE=/root/pebble-ca-bundle.crt "${CODE_DIR}/getssl" -c "$GETSSL_HOST" 3>&1 cp "${CODE_DIR}/test/test-config/${CONFIG_FILE}" "${INSTALL_DIR}/.getssl/${GETSSL_HOST}/getssl.cfg" -"${CODE_DIR}/getssl" -f -d "$GETSSL_HOST" 3>&1 +"${CODE_DIR}/getssl" -f "$GETSSL_HOST" 3>&1 diff --git a/test/run-all-tests.sh b/test/run-all-tests.sh new file mode 100644 index 0000000..d0749e1 --- /dev/null +++ b/test/run-all-tests.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash + +docker exec -it getssl-centos6 bats /getssl/test +docker exec -it getssl-ubuntu18 bats /getssl/test From 2a5824afeff449e35d513a457f0f1ea8560bd78c Mon Sep 17 00:00:00 2001 From: Tim Kimber Date: Wed, 29 Jan 2020 15:27:57 +0100 Subject: [PATCH 025/110] Show error message if awk doesn't support json_awk --- docker-compose.yml | 16 ++++++++++++++++ getssl | 11 +++++++++++ test/5-old-awk-error.bats | 25 +++++++++++++++++++++++++ test/Dockerfile-ubuntu18-no-gawk | 17 +++++++++++++++++ test/run-all-tests.sh | 1 + 5 files changed, 70 insertions(+) create mode 100644 test/5-old-awk-error.bats create mode 100644 test/Dockerfile-ubuntu18-no-gawk diff --git a/docker-compose.yml b/docker-compose.yml index cbe52e2..d031d30 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -77,6 +77,22 @@ services: - i.centos6.getssl.test - j.centos6.getssl.test - k.centos6.getssl.test + getssl-ubuntu18-no-gawk: + build: + context: . + dockerfile: test/Dockerfile-ubuntu18-no-gawk + container_name: getssl-ubuntu18-no-gawk + volumes: + - .:/getssl + environment: + GETSSL_HOST: ubuntu18-no-gawk.getssl.test + GETSSL_IP: 10.30.50.6 + NGINX_CONFIG: /etc/nginx/sites-enabled/default + networks: + acmenet: + ipv4_address: 10.30.50.6 + aliases: + - ubuntu18-no-gawk.getssl.test networks: acmenet: diff --git a/getssl b/getssl index 763677f..810a30a 100755 --- a/getssl +++ b/getssl @@ -15,6 +15,8 @@ # For usage, run "getssl -h" or see https://github.com/srvrco/getssl +# ACMEv2 process is documented at https://tools.ietf.org/html/rfc8555#section-7.4 + # Revision history: # 2016-01-08 Created (v0.1) # 2016-01-11 type correction and upload to github (v0.2) @@ -202,6 +204,7 @@ # 2020-01-24 allow file transfer using WebDAV over HTTPS # 2020-01-26 Use urlbase64_decode() instead of base64 -d # 2020-01-26 Fix "already verified" error for ACMEv2 +# 2020-01-29 Check awk new enough to support json_awk # ---------------------------------------------------------------------------------------- PROGNAME=${0##*/} @@ -1982,6 +1985,14 @@ else fi debug "Using API v$API" +# Check if awk supports json_awk (required for ACMEv2) +if [[ $API -eq 2 ]]; then + json_awk_test=$(json_awk '{ "test": "1" }' 2>/dev/null) + if [[ "${json_awk_test}" == "" ]]; then + error_exit "Your version of awk does not work with json_awk (see http://github.com/step-/JSON.awk/issues/6), please install a newer version of mawk or gawk" + fi +fi + # if check_remote is true then connect and obtain the current certificate (if not forcing renewal) if [[ "${CHECK_REMOTE}" == "true" ]] && [[ $_FORCE_RENEW -eq 0 ]]; then debug "getting certificate for $DOMAIN from remote server" diff --git a/test/5-old-awk-error.bats b/test/5-old-awk-error.bats new file mode 100644 index 0000000..4a00b31 --- /dev/null +++ b/test/5-old-awk-error.bats @@ -0,0 +1,25 @@ +#! /usr/bin/env bats + +load '/bats-support/load.bash' +load '/bats-assert/load.bash' +load '/getssl/test/test_helper.bash' + + +# This is run for every test +setup() { + export CURL_CA_BUNDLE=/root/pebble-ca-bundle.crt +} + + +@test "Check getssl fails if an old version of awk is installed" { + CONFIG_FILE="getssl-http01.cfg" + # Make sure this test only runs on an image running an old version of awk + awk_version=$(awk -V 2>/dev/null) || true + if [[ "$awk_version" == "" ]]; then + setup_environment + init_getssl + create_certificate + assert_failure + assert_output "getssl: Your version of awk does not work with json_awk (see http://github.com/step-/JSON.awk/issues/6), please install a newer version of mawk or gawk" + fi +} diff --git a/test/Dockerfile-ubuntu18-no-gawk b/test/Dockerfile-ubuntu18-no-gawk new file mode 100644 index 0000000..809708a --- /dev/null +++ b/test/Dockerfile-ubuntu18-no-gawk @@ -0,0 +1,17 @@ +FROM ubuntu:bionic +# bionic = latest 18 version + +# Update and install required software +RUN apt-get update --fix-missing +RUN apt-get install -y git curl dnsutils wget nginx-light + +WORKDIR /root + +# BATS (Bash Automated Testings) +RUN git clone https://github.com/bats-core/bats-core.git /bats-core +RUN git clone https://github.com/jasonkarns/bats-support /bats-support +RUN git clone https://github.com/jasonkarns/bats-assert-1 /bats-assert +RUN /bats-core/install.sh /usr/local + +# Run eternal loop - for testing +CMD ["/bin/bash", "-c", "while :; do sleep 10; done"] diff --git a/test/run-all-tests.sh b/test/run-all-tests.sh index d0749e1..7372e5b 100644 --- a/test/run-all-tests.sh +++ b/test/run-all-tests.sh @@ -2,3 +2,4 @@ docker exec -it getssl-centos6 bats /getssl/test docker exec -it getssl-ubuntu18 bats /getssl/test +docker exec -it getssl-ubuntu18-no-gawk bats /getssl/test/5-old-awk-error.bats From b785c42bab857d1f5f041dbe969cd443433b096f Mon Sep 17 00:00:00 2001 From: Tim Kimber Date: Wed, 29 Jan 2020 18:04:51 +0100 Subject: [PATCH 026/110] Less fragile way of deciding whether to run old awk test --- docker-compose.yml | 1 + test/5-old-awk-error.bats | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index d031d30..97bb1e5 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -88,6 +88,7 @@ services: GETSSL_HOST: ubuntu18-no-gawk.getssl.test GETSSL_IP: 10.30.50.6 NGINX_CONFIG: /etc/nginx/sites-enabled/default + TEST_AWK: "yes" networks: acmenet: ipv4_address: 10.30.50.6 diff --git a/test/5-old-awk-error.bats b/test/5-old-awk-error.bats index 4a00b31..0f234a2 100644 --- a/test/5-old-awk-error.bats +++ b/test/5-old-awk-error.bats @@ -14,8 +14,7 @@ setup() { @test "Check getssl fails if an old version of awk is installed" { CONFIG_FILE="getssl-http01.cfg" # Make sure this test only runs on an image running an old version of awk - awk_version=$(awk -V 2>/dev/null) || true - if [[ "$awk_version" == "" ]]; then + if [[ "$TEST_AWK" != "" ]]; then setup_environment init_getssl create_certificate From 26f224bd3c458f7b33bf311c50f3477cd7eec93c Mon Sep 17 00:00:00 2001 From: Tim Kimber Date: Fri, 31 Jan 2020 19:09:51 +0000 Subject: [PATCH 027/110] Create stale.yml --- .github/workflows/stale.yml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 .github/workflows/stale.yml diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml new file mode 100644 index 0000000..8699ac5 --- /dev/null +++ b/.github/workflows/stale.yml @@ -0,0 +1,17 @@ +name: "Close stale issues" +on: + schedule: + - cron: "0 0 * * *" + +jobs: + stale: + runs-on: ubuntu-latest + steps: + - uses: actions/stale@v1 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + stale-issue-message: 'This issue will be closed as no response' + stale-issue-label: 'needs more information' + exempt-issue-label: 'enhancement' + days-before-stale: 60 + days-before-close: 30 From 410a3c9087b0c2ca01cf55a23c5875e66195c90f Mon Sep 17 00:00:00 2001 From: Tim Kimber Date: Wed, 5 Feb 2020 12:51:43 +0000 Subject: [PATCH 028/110] Fix epoch_date for busybox and json_awk for gawk v5 --- getssl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/getssl b/getssl index 810a30a..c1bb890 100755 --- a/getssl +++ b/getssl @@ -716,7 +716,7 @@ date_epoc() { # convert the date into epoch time elif [[ "$os" == "mac" ]]; then date -j -f "%b %d %T %Y %Z" "$1" +%s elif [[ "$os" == "busybox" ]]; then - de_ld=$(echo "$1" | awk '{print $1 $2 $3 $4}') + de_ld=$(echo "$1" | awk '{print $1 " " $2 " " $3 " " $4}') date -D "%b %d %T %Y" -d "$de_ld" +%s else date -d "$1" +%s @@ -1174,7 +1174,7 @@ function scream(msg) { function tokenize(a1,pq,pb,ESCAPE,CHAR,STRING,NUMBER,KEYWORD,SPACE) { SPACE="[[:space:]]+" - gsub(/\"[^[:cntrl:]\"\\]*((\\[^u[:cntrl:]]|\\u[0-9a-fA-F]{4})[^[:cntrl:]\"\\]*)*\"|-?(0|[1-9][0-9]*)([.][0-9]*)?([eE][+-]?[0-9]*)?|null|false|true|[[:space:]]+|./, "\n&", a1) + gsub(/"[^[:cntrl:]"\\]*((\\[^u[:cntrl:]]|\\u[0-9a-fA-F]{4})[^[:cntrl:]"\\]*)*"|-?(0|[1-9][0-9]*)([.][0-9]*)?([eE][+-]?[0-9]*)?|null|false|true|[[:space:]]+|./, "\n&", a1) gsub("\n" SPACE, "\n", a1) sub(/^\n/, "", a1) ITOKENS=0 # get_token() helper From 886b456ae338a7bfa800ea27d697e68ce39ed6f7 Mon Sep 17 00:00:00 2001 From: Tim Kimber Date: Wed, 5 Feb 2020 12:52:25 +0000 Subject: [PATCH 029/110] Test improvements and add Alpine Linux --- docker-compose.yml | 33 ++++++++++++++++++--- test/Dockerfile-alpine | 20 +++++++++++++ test/Dockerfile-centos6 | 2 +- test/Dockerfile-ubuntu18 | 2 +- test/Dockerfile-ubuntu18-no-gawk | 2 +- test/alpine-supervisord.conf | 14 +++++++++ test/restart-nginx | 8 +++++ test/test-config/getssl-dns01.cfg | 29 +++--------------- test/test-config/getssl-http01-10-hosts.cfg | 8 ++--- test/test-config/getssl-http01.cfg | 30 ++----------------- test/test_helper.bash | 3 +- 11 files changed, 86 insertions(+), 65 deletions(-) create mode 100644 test/Dockerfile-alpine create mode 100644 test/alpine-supervisord.conf create mode 100644 test/restart-nginx diff --git a/docker-compose.yml b/docker-compose.yml index 97bb1e5..ebd5369 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,8 +7,6 @@ services: environment: # with Go 1.13.x which defaults TLS 1.3 to on GODEBUG: "tls13=1" - # Don't re-use authorisations (breaks force renew test scripts) - # PEBBLE_AUTHZREUSE: "0" ports: - 14000:14000 # HTTPS ACME API - 15000:15000 # HTTPS Management API @@ -77,6 +75,33 @@ services: - i.centos6.getssl.test - j.centos6.getssl.test - k.centos6.getssl.test + getssl-alpine: + build: + context: . + dockerfile: test/Dockerfile-alpine + container_name: getssl-alpine + volumes: + - .:/getssl + environment: + GETSSL_HOST: alpine.getssl.test + GETSSL_IP: 10.30.50.6 + NGINX_CONFIG: /etc/nginx/conf.d/default.conf + networks: + acmenet: + ipv4_address: 10.30.50.6 + aliases: + - alpine.getssl.test + - a.alpine.getssl.test + - b.alpine.getssl.test + - c.alpine.getssl.test + - d.alpine.getssl.test + - e.alpine.getssl.test + - f.alpine.getssl.test + - g.alpine.getssl.test + - h.alpine.getssl.test + - i.alpine.getssl.test + - j.alpine.getssl.test + - k.alpine.getssl.test getssl-ubuntu18-no-gawk: build: context: . @@ -86,12 +111,12 @@ services: - .:/getssl environment: GETSSL_HOST: ubuntu18-no-gawk.getssl.test - GETSSL_IP: 10.30.50.6 + GETSSL_IP: 10.30.50.7 NGINX_CONFIG: /etc/nginx/sites-enabled/default TEST_AWK: "yes" networks: acmenet: - ipv4_address: 10.30.50.6 + ipv4_address: 10.30.50.7 aliases: - ubuntu18-no-gawk.getssl.test diff --git a/test/Dockerfile-alpine b/test/Dockerfile-alpine new file mode 100644 index 0000000..e9ee6c3 --- /dev/null +++ b/test/Dockerfile-alpine @@ -0,0 +1,20 @@ +FROM alpine:latest + +RUN apk --no-cache add supervisor openssl git curl bind-tools wget gawk nginx bash +# RUN apk --no-cache add vim dos2unix # for debugging + +WORKDIR /root +RUN mkdir /run/nginx +RUN mkdir /etc/nginx/pki +RUN mkdir /etc/nginx/pki/private +COPY ./test/test-config/nginx-ubuntu-no-ssl /etc/nginx/sites-enabled/default +COPY ./test/alpine-supervisord.conf /etc/supervisord.conf + +# BATS (Bash Automated Testings) +RUN git clone https://github.com/bats-core/bats-core.git /bats-core +RUN git clone https://github.com/jasonkarns/bats-support /bats-support +RUN git clone https://github.com/jasonkarns/bats-assert-1 /bats-assert +RUN /bats-core/install.sh /usr/local + +# Use supervisord to run nginx in the background +ENTRYPOINT /usr/bin/supervisord -c /etc/supervisord.conf diff --git a/test/Dockerfile-centos6 b/test/Dockerfile-centos6 index f370a0b..a82bf1f 100644 --- a/test/Dockerfile-centos6 +++ b/test/Dockerfile-centos6 @@ -19,4 +19,4 @@ RUN /bats-core/install.sh /usr/local EXPOSE 80 443 # Run eternal loop - for testing -CMD ["/bin/bash", "-c", "while :; do sleep 10; done"] +CMD tail -f /dev/null diff --git a/test/Dockerfile-ubuntu18 b/test/Dockerfile-ubuntu18 index 1b3765c..31554cd 100644 --- a/test/Dockerfile-ubuntu18 +++ b/test/Dockerfile-ubuntu18 @@ -25,4 +25,4 @@ RUN /bats-core/install.sh /usr/local EXPOSE 80 443 # Run eternal loop - for testing -CMD ["/bin/bash", "-c", "while :; do sleep 10; done"] +CMD tail -f /dev/null diff --git a/test/Dockerfile-ubuntu18-no-gawk b/test/Dockerfile-ubuntu18-no-gawk index 809708a..3eb9732 100644 --- a/test/Dockerfile-ubuntu18-no-gawk +++ b/test/Dockerfile-ubuntu18-no-gawk @@ -14,4 +14,4 @@ RUN git clone https://github.com/jasonkarns/bats-assert-1 /bats-assert RUN /bats-core/install.sh /usr/local # Run eternal loop - for testing -CMD ["/bin/bash", "-c", "while :; do sleep 10; done"] +CMD tail -f /dev/null diff --git a/test/alpine-supervisord.conf b/test/alpine-supervisord.conf new file mode 100644 index 0000000..8eec585 --- /dev/null +++ b/test/alpine-supervisord.conf @@ -0,0 +1,14 @@ +[supervisord] +nodaemon=true +logfile=/tmp/supervisord.log +childlogdir=/tmp +pidfile = /tmp/supervisord.pid + +[program:nginx] +command=nginx -g 'daemon off;' +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 +autorestart=false +startretries=0 diff --git a/test/restart-nginx b/test/restart-nginx new file mode 100644 index 0000000..d35f60f --- /dev/null +++ b/test/restart-nginx @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +if [ "$GETSSL_HOST" = "alpine.getssl.test" ]; then + killall -HUP nginx >&3- + sleep 5 +else + service nginx restart >&3- +fi diff --git a/test/test-config/getssl-dns01.cfg b/test/test-config/getssl-dns01.cfg index 790dee2..98637b0 100644 --- a/test/test-config/getssl-dns01.cfg +++ b/test/test-config/getssl-dns01.cfg @@ -2,35 +2,17 @@ # see https://github.com/srvrco/getssl/wiki/Config-variables for details # see https://github.com/srvrco/getssl/wiki/Example-config-files for example configs # -# The staging server is best for testing -#CA="https://acme-staging.api.letsencrypt.org" -# This server issues full certificates, however has rate limits -#CA="https://acme-v01.api.letsencrypt.org" CA="https://pebble:14000/dir" VALIDATE_VIA_DNS=true DNS_ADD_COMMAND="/getssl/dns_scripts/dns_add_challtestsrv" DNS_DEL_COMMAND="/getssl/dns_scripts/dns_del_challtestsrv" -# AUTH_DNS_SERVER=10.30.50.3 - -#PRIVATE_KEY_ALG="rsa" # Additional domains - this could be multiple domains / subdomains in a comma separated list -# Note: this is Additional domains - so should not include the primary domain. SANS="" # Acme Challenge Location. The first line for the domain, the following ones for each additional domain. -# If these start with ssh: then the next variable is assumed to be the hostname and the rest the location. -# An ssh key will be needed to provide you with access to the remote server. -# Optionally, you can specify a different userid for ssh/scp to use on the remote server before the @ sign. -# If left blank, the username on the local server will be used to authenticate against the remote server. -# If these start with ftp: then the next variables are ftpuserid:ftppassword:servername:ACL_location -# These should be of the form "/path/to/your/website/folder/.well-known/acme-challenge" -# where "/path/to/your/website/folder/" is the path, on your web server, to the web root for your domain. ACL=('/var/www/html/.well-known/acme-challenge') -# 'ssh:server5:/var/www/getssltest.hopto.org/web/.well-known/acme-challenge' -# 'ssh:sshuserid@server5:/var/www/getssltest.hopto.org/web/.well-known/acme-challenge' -# 'ftp:ftpuserid:ftppassword:getssltest.hopto.org:/web/.well-known/acme-challenge') #Set USE_SINGLE_ACL="true" to use a single ACL for all checks USE_SINGLE_ACL="false" @@ -44,11 +26,8 @@ DOMAIN_CHAIN_LOCATION="" # this is the domain cert and CA cert DOMAIN_PEM_LOCATION="" # this is the domain_key, domain cert and CA cert # The command needed to reload apache / nginx or whatever you use -RELOAD_CMD="cp /getssl/test/test-config/nginx-ubuntu-ssl ${NGINX_CONFIG} && service nginx restart >&3-" +RELOAD_CMD="cp /getssl/test/test-config/nginx-ubuntu-ssl ${NGINX_CONFIG} && /getssl/test/restart-nginx" -# Define the server type. This can be https, ftp, ftpi, imap, imaps, pop3, pop3s, smtp, -# smtps_deprecated, smtps, smtp_submission, xmpp, xmpps, ldaps or a port number which -# will be checked for certificate expiry and also will be checked after -# an update to confirm correct certificate is running (if CHECK_REMOTE) is set to true -#SERVER_TYPE="https" -#CHECK_REMOTE="true" +# Define the server type and confirm correct certificate is installed +SERVER_TYPE="https" +CHECK_REMOTE="true" diff --git a/test/test-config/getssl-http01-10-hosts.cfg b/test/test-config/getssl-http01-10-hosts.cfg index f521d52..d5c364e 100644 --- a/test/test-config/getssl-http01-10-hosts.cfg +++ b/test/test-config/getssl-http01-10-hosts.cfg @@ -5,7 +5,6 @@ CA="https://pebble:14000/dir" # Additional domains - this could be multiple domains / subdomains in a comma separated list -# Note: this is Additional domains - so should not include the primary domain. SANS="a.${GETSSL_HOST},b.${GETSSL_HOST},c.${GETSSL_HOST},d.${GETSSL_HOST},e.${GETSSL_HOST},f.${GETSSL_HOST},g.${GETSSL_HOST},h.${GETSSL_HOST},i.${GETSSL_HOST},j.${GETSSL_HOST},k.${GETSSL_HOST}" # Acme Challenge Location. @@ -22,7 +21,8 @@ DOMAIN_CHAIN_LOCATION="" # this is the domain cert and CA cert DOMAIN_PEM_LOCATION="" # this is the domain_key, domain cert and CA cert # The command needed to reload apache / nginx or whatever you use -RELOAD_CMD="cp /getssl/test/test-config/nginx-ubuntu-ssl ${NGINX_CONFIG} && service nginx restart >&3-" +RELOAD_CMD="cp /getssl/test/test-config/nginx-ubuntu-ssl ${NGINX_CONFIG} && /getssl/test/restart-nginx" -#SERVER_TYPE="https" -#CHECK_REMOTE="true" +# Define the server type and confirm correct certificate is installed +SERVER_TYPE="https" +CHECK_REMOTE="true" diff --git a/test/test-config/getssl-http01.cfg b/test/test-config/getssl-http01.cfg index 305dd49..f7d75ea 100644 --- a/test/test-config/getssl-http01.cfg +++ b/test/test-config/getssl-http01.cfg @@ -2,34 +2,13 @@ # see https://github.com/srvrco/getssl/wiki/Config-variables for details # see https://github.com/srvrco/getssl/wiki/Example-config-files for example configs # -# The staging server is best for testing -#CA="https://acme-staging.api.letsencrypt.org" -# This server issues full certificates, however has rate limits -#CA="https://acme-v01.api.letsencrypt.org" CA="https://pebble:14000/dir" -#VALIDATE_VIA_DNS=true -#DNS_ADD_COMMAND="/getssl/dns_scripts/dns_add_challtestsrv" -#DNS_DEL_COMMAND="/getssl/dns_scripts/dns_del_challtestsrv" - -#PRIVATE_KEY_ALG="rsa" - # Additional domains - this could be multiple domains / subdomains in a comma separated list -# Note: this is Additional domains - so should not include the primary domain. SANS="" -# Acme Challenge Location. The first line for the domain, the following ones for each additional domain. -# If these start with ssh: then the next variable is assumed to be the hostname and the rest the location. -# An ssh key will be needed to provide you with access to the remote server. -# Optionally, you can specify a different userid for ssh/scp to use on the remote server before the @ sign. -# If left blank, the username on the local server will be used to authenticate against the remote server. -# If these start with ftp: then the next variables are ftpuserid:ftppassword:servername:ACL_location -# These should be of the form "/path/to/your/website/folder/.well-known/acme-challenge" -# where "/path/to/your/website/folder/" is the path, on your web server, to the web root for your domain. +# Acme Challenge Location. ACL=('/var/www/html/.well-known/acme-challenge') -# 'ssh:server5:/var/www/getssltest.hopto.org/web/.well-known/acme-challenge' -# 'ssh:sshuserid@server5:/var/www/getssltest.hopto.org/web/.well-known/acme-challenge' -# 'ftp:ftpuserid:ftppassword:getssltest.hopto.org:/web/.well-known/acme-challenge') #Set USE_SINGLE_ACL="true" to use a single ACL for all checks USE_SINGLE_ACL="false" @@ -43,11 +22,8 @@ DOMAIN_CHAIN_LOCATION="" # this is the domain cert and CA cert DOMAIN_PEM_LOCATION="" # this is the domain_key, domain cert and CA cert # The command needed to reload apache / nginx or whatever you use -RELOAD_CMD="cp /getssl/test/test-config/nginx-ubuntu-ssl ${NGINX_CONFIG} && service nginx restart >&3-" +RELOAD_CMD="cp /getssl/test/test-config/nginx-ubuntu-ssl ${NGINX_CONFIG} && /getssl/test/restart-nginx" -# Define the server type. This can be https, ftp, ftpi, imap, imaps, pop3, pop3s, smtp, -# smtps_deprecated, smtps, smtp_submission, xmpp, xmpps, ldaps or a port number which -# will be checked for certificate expiry and also will be checked after -# an update to confirm correct certificate is running (if CHECK_REMOTE) is set to true +# Define the server type and confirm correct certificate is installed SERVER_TYPE="https" CHECK_REMOTE="true" diff --git a/test/test_helper.bash b/test/test_helper.bash index 6f506fd..0ac9a43 100644 --- a/test/test_helper.bash +++ b/test/test_helper.bash @@ -19,7 +19,7 @@ setup_environment() { curl -X POST -d '{"host":"'"$GETSSL_HOST"'", "addresses":["'"$GETSSL_IP"'"]}' http://10.30.50.3:8055/add-a cp ${CODE_DIR}/test/test-config/nginx-ubuntu-no-ssl ${NGINX_CONFIG} - service nginx restart >&3- + /getssl/test/restart-nginx } @@ -40,5 +40,4 @@ create_certificate() { # Create certificate cp "${CODE_DIR}/test/test-config/${CONFIG_FILE}" "${INSTALL_DIR}/.getssl/${GETSSL_HOST}/getssl.cfg" run ${CODE_DIR}/getssl "$GETSSL_HOST" - #!FIXME test certificate has been placed in the expected location } From 978de4bda0a088478a447d80f802e3f48565d66e Mon Sep 17 00:00:00 2001 From: Robert de Bath Date: Thu, 6 Feb 2020 06:30:34 +0000 Subject: [PATCH 030/110] Change -debug=1 to -debug for nslookup Debian dnsutils 1:9.11.5.P4+dfsg-5.1 errors on -debug=1 Dnsutils version 1:9.10.3.dfsg.P4-12.3+deb9u4 and version 1:9.2.1-2.woody.2 (from 2003) accept both -debug=1 and -debug so I've changed this globally. --- getssl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/getssl b/getssl index c1bb890..2f1a26b 100755 --- a/getssl +++ b/getssl @@ -813,7 +813,7 @@ get_auth_dns() { # get the authoritative dns server for a domain (sets primary_n return fi - res=$(nslookup -debug=1 -type=soa -type=ns "$gad_d" ${gad_s}) + res=$(nslookup -debug -type=soa -type=ns "$gad_d" ${gad_s}) if [[ "$(echo "$res" | grep -c "Non-authoritative")" -gt 0 ]]; then # this is a Non-authoritative server, need to check for an authoritative one. @@ -826,9 +826,9 @@ get_auth_dns() { # get the authoritative dns server for a domain (sets primary_n fi if [[ -z "$gad_s" ]]; then - res=$(nslookup -debug=1 -type=soa -type=ns "$gad_d") + res=$(nslookup -debug -type=soa -type=ns "$gad_d") else - res=$(nslookup -debug=1 -type=soa -type=ns "$gad_d" "${gad_s}") + res=$(nslookup -debug -type=soa -type=ns "$gad_d" "${gad_s}") fi if [[ "$(echo "$res" | grep -c "canonical name")" -gt 0 ]]; then From 86e27920189e42ee41dcba1038541b4cb23e0c70 Mon Sep 17 00:00:00 2001 From: Robert de Bath Date: Thu, 6 Feb 2020 14:13:10 +0000 Subject: [PATCH 031/110] Fix regex for non-gnu versions of awk. Recent versions of gawk have upgraded the regex processing with l10n based character classes. Replace use of these so other versions of awk can be used. --- getssl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/getssl b/getssl index 2f1a26b..b7b9094 100755 --- a/getssl +++ b/getssl @@ -1027,7 +1027,7 @@ info() { # write out info as long as the quiet flag has not been set. json_awk() { # AWK json converter used for API2 - needs tidying up ;) # shellcheck disable=SC2086 -echo $1 | awk ' +echo "$1" | tr -d '\n' | awk ' { tokenize($0) # while(get_token()) {print TOKEN} if (0 == parse()) { @@ -1173,8 +1173,8 @@ function scream(msg) { } function tokenize(a1,pq,pb,ESCAPE,CHAR,STRING,NUMBER,KEYWORD,SPACE) { - SPACE="[[:space:]]+" - gsub(/"[^[:cntrl:]"\\]*((\\[^u[:cntrl:]]|\\u[0-9a-fA-F]{4})[^[:cntrl:]"\\]*)*"|-?(0|[1-9][0-9]*)([.][0-9]*)?([eE][+-]?[0-9]*)?|null|false|true|[[:space:]]+|./, "\n&", a1) + SPACE="[ \t\n]+" + gsub(/"[^\001-\037"\\]*((\\[^u\001-\037]|\\u[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F])[^\001-\037"\\]*)*"|-?(0|[1-9][0-9]*)([.][0-9]*)?([eE][+-]?[0-9]*)?|null|false|true|[ \t\n]+|./, "\n&", a1) gsub("\n" SPACE, "\n", a1) sub(/^\n/, "", a1) ITOKENS=0 # get_token() helper From 6b17701cdce2de30b8408b08d7d121271904943f Mon Sep 17 00:00:00 2001 From: Tim Kimber Date: Thu, 6 Feb 2020 17:25:30 +0000 Subject: [PATCH 032/110] Use default awk on most images, add debian:latest and ubuntu:latest --- docker-compose.yml | 135 +++++++++++------- test/5-old-awk-error.bats | 24 ---- test/Dockerfile-alpine | 10 +- test/Dockerfile-centos6 | 2 + ...ile-ubuntu18-no-gawk => Dockerfile-debian} | 10 +- test/Dockerfile-ubuntu | 23 +++ test/Dockerfile-ubuntu18 | 9 +- test/run-all-tests.sh | 4 +- test/test_helper.bash | 2 +- 9 files changed, 134 insertions(+), 85 deletions(-) delete mode 100644 test/5-old-awk-error.bats rename test/{Dockerfile-ubuntu18-no-gawk => Dockerfile-debian} (71%) create mode 100644 test/Dockerfile-ubuntu diff --git a/docker-compose.yml b/docker-compose.yml index ebd5369..09a4264 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -21,33 +21,33 @@ services: networks: acmenet: ipv4_address: 10.30.50.3 - getssl-ubuntu18: + getssl-alpine: build: context: . - dockerfile: test/Dockerfile-ubuntu18 - container_name: getssl-ubuntu18 + dockerfile: test/Dockerfile-alpine + container_name: getssl-alpine volumes: - .:/getssl environment: - GETSSL_HOST: ubuntu18.getssl.test - GETSSL_IP: 10.30.50.4 - NGINX_CONFIG: /etc/nginx/sites-enabled/default + GETSSL_HOST: alpine.getssl.test + GETSSL_IP: 10.30.50.10 + NGINX_CONFIG: /etc/nginx/conf.d/default.conf networks: acmenet: - ipv4_address: 10.30.50.4 + ipv4_address: 10.30.50.10 aliases: - - ubuntu18.getssl.test - - a.ubuntu18.getssl.test - - b.ubuntu18.getssl.test - - c.ubuntu18.getssl.test - - d.ubuntu18.getssl.test - - e.ubuntu18.getssl.test - - f.ubuntu18.getssl.test - - g.ubuntu18.getssl.test - - h.ubuntu18.getssl.test - - i.ubuntu18.getssl.test - - j.ubuntu18.getssl.test - - k.ubuntu18.getssl.test + - alpine.getssl.test + - a.alpine.getssl.test + - b.alpine.getssl.test + - c.alpine.getssl.test + - d.alpine.getssl.test + - e.alpine.getssl.test + - f.alpine.getssl.test + - g.alpine.getssl.test + - h.alpine.getssl.test + - i.alpine.getssl.test + - j.alpine.getssl.test + - k.alpine.getssl.test getssl-centos6: build: context: . @@ -57,11 +57,11 @@ services: - .:/getssl environment: GETSSL_HOST: centos6.getssl.test - GETSSL_IP: 10.30.50.5 + GETSSL_IP: 10.30.50.11 NGINX_CONFIG: /etc/nginx/conf.d/default.conf networks: acmenet: - ipv4_address: 10.30.50.5 + ipv4_address: 10.30.50.11 aliases: - centos6.getssl.test - a.centos6.getssl.test @@ -75,50 +75,89 @@ services: - i.centos6.getssl.test - j.centos6.getssl.test - k.centos6.getssl.test - getssl-alpine: + getssl-debian: build: context: . - dockerfile: test/Dockerfile-alpine - container_name: getssl-alpine + dockerfile: test/Dockerfile-debian + container_name: getssl-debian volumes: - .:/getssl environment: - GETSSL_HOST: alpine.getssl.test - GETSSL_IP: 10.30.50.6 - NGINX_CONFIG: /etc/nginx/conf.d/default.conf + GETSSL_HOST: debian.getssl.test + GETSSL_IP: 10.30.50.12 + NGINX_CONFIG: /etc/nginx/sites-enabled/default networks: acmenet: - ipv4_address: 10.30.50.6 + ipv4_address: 10.30.50.12 aliases: - - alpine.getssl.test - - a.alpine.getssl.test - - b.alpine.getssl.test - - c.alpine.getssl.test - - d.alpine.getssl.test - - e.alpine.getssl.test - - f.alpine.getssl.test - - g.alpine.getssl.test - - h.alpine.getssl.test - - i.alpine.getssl.test - - j.alpine.getssl.test - - k.alpine.getssl.test - getssl-ubuntu18-no-gawk: + - debian.getssl.test + - a.debian.getssl.test + - b.debian.getssl.test + - c.debian.getssl.test + - d.debian.getssl.test + - e.debian.getssl.test + - f.debian.getssl.test + - g.debian.getssl.test + - h.debian.getssl.test + - i.debian.getssl.test + - j.debian.getssl.test + - k.debian.getssl.test + getssl-ubuntu: build: context: . - dockerfile: test/Dockerfile-ubuntu18-no-gawk - container_name: getssl-ubuntu18-no-gawk + dockerfile: test/Dockerfile-ubuntu + container_name: getssl-ubuntu volumes: - .:/getssl environment: - GETSSL_HOST: ubuntu18-no-gawk.getssl.test - GETSSL_IP: 10.30.50.7 + GETSSL_HOST: ubuntu.getssl.test + GETSSL_IP: 10.30.50.13 NGINX_CONFIG: /etc/nginx/sites-enabled/default - TEST_AWK: "yes" networks: acmenet: - ipv4_address: 10.30.50.7 + ipv4_address: 10.30.50.13 aliases: - - ubuntu18-no-gawk.getssl.test + - ubuntu.getssl.test + - a.ubuntu.getssl.test + - b.ubuntu.getssl.test + - c.ubuntu.getssl.test + - d.ubuntu.getssl.test + - e.ubuntu.getssl.test + - f.ubuntu.getssl.test + - g.ubuntu.getssl.test + - h.ubuntu.getssl.test + - i.ubuntu.getssl.test + - j.ubuntu.getssl.test + - k.ubuntu.getssl.test + getssl-ubuntu18: + build: + context: . + dockerfile: test/Dockerfile-ubuntu18 + container_name: getssl-ubuntu18 + volumes: + - .:/getssl + environment: + GETSSL_HOST: ubuntu18.getssl.test + GETSSL_IP: 10.30.50.14 + NGINX_CONFIG: /etc/nginx/sites-enabled/default + networks: + acmenet: + ipv4_address: 10.30.50.14 + aliases: + - ubuntu18.getssl.test + - a.ubuntu18.getssl.test + - b.ubuntu18.getssl.test + - c.ubuntu18.getssl.test + - d.ubuntu18.getssl.test + - e.ubuntu18.getssl.test + - f.ubuntu18.getssl.test + - g.ubuntu18.getssl.test + - h.ubuntu18.getssl.test + - i.ubuntu18.getssl.test + - j.ubuntu18.getssl.test + - k.ubuntu18.getssl.test + + networks: acmenet: diff --git a/test/5-old-awk-error.bats b/test/5-old-awk-error.bats deleted file mode 100644 index 0f234a2..0000000 --- a/test/5-old-awk-error.bats +++ /dev/null @@ -1,24 +0,0 @@ -#! /usr/bin/env bats - -load '/bats-support/load.bash' -load '/bats-assert/load.bash' -load '/getssl/test/test_helper.bash' - - -# This is run for every test -setup() { - export CURL_CA_BUNDLE=/root/pebble-ca-bundle.crt -} - - -@test "Check getssl fails if an old version of awk is installed" { - CONFIG_FILE="getssl-http01.cfg" - # Make sure this test only runs on an image running an old version of awk - if [[ "$TEST_AWK" != "" ]]; then - setup_environment - init_getssl - create_certificate - assert_failure - assert_output "getssl: Your version of awk does not work with json_awk (see http://github.com/step-/JSON.awk/issues/6), please install a newer version of mawk or gawk" - fi -} diff --git a/test/Dockerfile-alpine b/test/Dockerfile-alpine index e9ee6c3..ff69490 100644 --- a/test/Dockerfile-alpine +++ b/test/Dockerfile-alpine @@ -1,14 +1,15 @@ FROM alpine:latest -RUN apk --no-cache add supervisor openssl git curl bind-tools wget gawk nginx bash -# RUN apk --no-cache add vim dos2unix # for debugging +# Note this image uses busybox awk instead of gawk + +RUN apk --no-cache add supervisor openssl git curl bind-tools wget nginx bash WORKDIR /root + +# Create nginx directories in standard places RUN mkdir /run/nginx RUN mkdir /etc/nginx/pki RUN mkdir /etc/nginx/pki/private -COPY ./test/test-config/nginx-ubuntu-no-ssl /etc/nginx/sites-enabled/default -COPY ./test/alpine-supervisord.conf /etc/supervisord.conf # BATS (Bash Automated Testings) RUN git clone https://github.com/bats-core/bats-core.git /bats-core @@ -17,4 +18,5 @@ RUN git clone https://github.com/jasonkarns/bats-assert-1 /bats-assert RUN /bats-core/install.sh /usr/local # Use supervisord to run nginx in the background +COPY ./test/alpine-supervisord.conf /etc/supervisord.conf ENTRYPOINT /usr/bin/supervisord -c /etc/supervisord.conf diff --git a/test/Dockerfile-centos6 b/test/Dockerfile-centos6 index a82bf1f..9149dad 100644 --- a/test/Dockerfile-centos6 +++ b/test/Dockerfile-centos6 @@ -1,5 +1,7 @@ FROM centos:centos6 +# Note this image uses gawk + # Update and install required software RUN yum -y update RUN yum -y install epel-release diff --git a/test/Dockerfile-ubuntu18-no-gawk b/test/Dockerfile-debian similarity index 71% rename from test/Dockerfile-ubuntu18-no-gawk rename to test/Dockerfile-debian index 3eb9732..c4c88a1 100644 --- a/test/Dockerfile-ubuntu18-no-gawk +++ b/test/Dockerfile-debian @@ -1,11 +1,17 @@ -FROM ubuntu:bionic -# bionic = latest 18 version +FROM debian:latest + +# Note this image uses mawk 1.3 # Update and install required software RUN apt-get update --fix-missing RUN apt-get install -y git curl dnsutils wget nginx-light WORKDIR /root +RUN mkdir /etc/nginx/pki +RUN mkdir /etc/nginx/pki/private + +# Prevent "Can't load /root/.rnd into RNG" error from openssl +# RUN touch /root/.rnd # BATS (Bash Automated Testings) RUN git clone https://github.com/bats-core/bats-core.git /bats-core diff --git a/test/Dockerfile-ubuntu b/test/Dockerfile-ubuntu new file mode 100644 index 0000000..290100d --- /dev/null +++ b/test/Dockerfile-ubuntu @@ -0,0 +1,23 @@ +FROM ubuntu:latest + +# Note this image uses mawk1.3 + +# Update and install required software +RUN apt-get update --fix-missing +RUN apt-get install -y git curl dnsutils wget nginx-light +RUN apt-get install -y vim dos2unix # for debugging +# TODO test with drill, dig, host + +WORKDIR /root + +# Prevent "Can't load /root/.rnd into RNG" error from openssl +RUN touch /root/.rnd + +# BATS (Bash Automated Testings) +RUN git clone https://github.com/bats-core/bats-core.git /bats-core +RUN git clone https://github.com/jasonkarns/bats-support /bats-support +RUN git clone https://github.com/jasonkarns/bats-assert-1 /bats-assert +RUN /bats-core/install.sh /usr/local + +# Run eternal loop - for testing +CMD tail -f /dev/null diff --git a/test/Dockerfile-ubuntu18 b/test/Dockerfile-ubuntu18 index 31554cd..ebe7607 100644 --- a/test/Dockerfile-ubuntu18 +++ b/test/Dockerfile-ubuntu18 @@ -1,12 +1,11 @@ FROM ubuntu:bionic -# bionic = latest 18 version +# bionic = 18 LTS (long term support) + +# Note this image uses gawk # Update and install required software RUN apt-get update --fix-missing -# TODO work out why default version of awk fails -RUN apt-get install -y git curl dnsutils wget gawk nginx-light # linux-libc-dev make gcc binutils -RUN apt-get install -y vim dos2unix # for debugging -# TODO test with drill, dig, host +RUN apt-get install -y git curl dnsutils wget gawk nginx-light WORKDIR /root RUN mkdir /etc/nginx/pki diff --git a/test/run-all-tests.sh b/test/run-all-tests.sh index 7372e5b..b526c63 100644 --- a/test/run-all-tests.sh +++ b/test/run-all-tests.sh @@ -1,5 +1,7 @@ #!/usr/bin/env bash +docker exec -it getssl-alpine bats /getssl/test docker exec -it getssl-centos6 bats /getssl/test +docker exec -it getssl-debian bats /getssl/test +docker exec -it getssl-ubuntu bats /getssl/test docker exec -it getssl-ubuntu18 bats /getssl/test -docker exec -it getssl-ubuntu18-no-gawk bats /getssl/test/5-old-awk-error.bats diff --git a/test/test_helper.bash b/test/test_helper.bash index 0ac9a43..b33ee0b 100644 --- a/test/test_helper.bash +++ b/test/test_helper.bash @@ -18,7 +18,7 @@ setup_environment() { fi curl -X POST -d '{"host":"'"$GETSSL_HOST"'", "addresses":["'"$GETSSL_IP"'"]}' http://10.30.50.3:8055/add-a - cp ${CODE_DIR}/test/test-config/nginx-ubuntu-no-ssl ${NGINX_CONFIG} + cp ${CODE_DIR}/test/test-config/nginx-ubuntu-no-ssl "${NGINX_CONFIG}" /getssl/test/restart-nginx } From d42f08a97497ca7d1da834bc7949239a0b1f548e Mon Sep 17 00:00:00 2001 From: Tim Kimber Date: Sun, 9 Feb 2020 17:32:40 +0000 Subject: [PATCH 033/110] Update release notes and version --- getssl | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/getssl b/getssl index b7b9094..de52ba3 100755 --- a/getssl +++ b/getssl @@ -205,10 +205,12 @@ # 2020-01-26 Use urlbase64_decode() instead of base64 -d # 2020-01-26 Fix "already verified" error for ACMEv2 # 2020-01-29 Check awk new enough to support json_awk +# 2020-02-05 Fix epoch_date for busybox +# 2020-02-06 Bugfixes for json_awk and nslookup to support old awk versions (2.17) # ---------------------------------------------------------------------------------------- PROGNAME=${0##*/} -VERSION="2.16" +VERSION="2.17" # defaults ACCOUNT_KEY_LENGTH=4096 From 19a9445811bbbdeba74afe393264545d73a28e48 Mon Sep 17 00:00:00 2001 From: Tim Kimber Date: Tue, 11 Feb 2020 07:14:50 +0000 Subject: [PATCH 034/110] Add SCP_OPTS and SFTP_OPTS --- getssl | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/getssl b/getssl index de52ba3..f048e6c 100755 --- a/getssl +++ b/getssl @@ -207,6 +207,7 @@ # 2020-01-29 Check awk new enough to support json_awk # 2020-02-05 Fix epoch_date for busybox # 2020-02-06 Bugfixes for json_awk and nslookup to support old awk versions (2.17) +# 2020-02-11 Add SCP_OPTS and SFTP_OPTS # ---------------------------------------------------------------------------------------- PROGNAME=${0##*/} @@ -549,8 +550,9 @@ copy_file_to_location() { # copies a file, using scp, sftp or ftp if required. info "copying $cert to $to" debug "copying from $from to $to" if [[ "${to:0:4}" == "ssh:" ]] ; then - debug "using scp scp -q $from ${to:4}" - if ! scp -q "$from" "${to:4}" >/dev/null 2>&1 ; then + debug "using scp -q $SCP_OPTS $from ${to:4}" + # shellcheck disable=SC2086 + if ! scp -q $SCP_OPTS "$from" "${to:4}" >/dev/null 2>&1 ; then error_exit "problem copying file to the server using scp. scp $from ${to:4}" fi @@ -596,9 +598,10 @@ copy_file_to_location() { # copies a file, using scp, sftp or ftp if required. ftpfile=$(basename "$ftplocn") fromdir=$(dirname "$from") fromfile=$(basename "$from") - debug "sftp user=$ftpuser - pass=$ftppass - host=$ftphost dir=$ftpdirn file=$ftpfile" + debug "sftp $SFTP_OPTS user=$ftpuser - pass=$ftppass - host=$ftphost dir=$ftpdirn file=$ftpfile" debug "from dir=$fromdir file=$fromfile" - sshpass -p "$ftppass" sftp "$ftpuser@$ftphost" <<- _EOF + # shellcheck disable=SC2086 + sshpass -p "$ftppass" sftp $SFTP_OPTS "$ftpuser@$ftphost" <<- _EOF cd $ftpdirn lcd $fromdir put $fromfile From dcb107ed7d599bd69420c6acf54daa852d4ae411 Mon Sep 17 00:00:00 2001 From: Tim Kimber Date: Tue, 11 Feb 2020 07:42:11 +0000 Subject: [PATCH 035/110] Add SSH_OPTS to template --- getssl | 32 ++++++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/getssl b/getssl index f048e6c..2c15739 100755 --- a/getssl +++ b/getssl @@ -1607,15 +1607,19 @@ usage() { # echos out the program usage write_domain_template() { # write out a template file for a domain. cat > "$1" <<- _EOF_domain_ + # This file is read second (and per domain if running with the -a option) + # and overwrites any settings from the first file + # # Uncomment and modify any variables you need # see https://github.com/srvrco/getssl/wiki/Config-variables for details # see https://github.com/srvrco/getssl/wiki/Example-config-files for example configs # # The staging server is best for testing - #CA="https://acme-staging-v02.api.letsencrypt.org/directory" + #CA="https://acme-staging-v02.api.letsencrypt.org/" # This server issues full certificates, however has rate limits #CA="https://acme-v02.api.letsencrypt.org" + # Private key types - can be rsa, prime256v1, secp384r1 or secp521r1 #PRIVATE_KEY_ALG="rsa" # Additional domains - this could be multiple domains / subdomains in a comma separated list @@ -1630,15 +1634,19 @@ write_domain_template() { # write out a template file for a domain. # If these start with ftp: then the next variables are ftpuserid:ftppassword:servername:ACL_location # These should be of the form "/path/to/your/website/folder/.well-known/acme-challenge" # where "/path/to/your/website/folder/" is the path, on your web server, to the web root for your domain. - # You can also user WebDAV over HTTPS as transport mechanism. To do so, start with davs: followed by username, - # password, host, port (explicitly needed even if using default port 443) and path on the server. + # You can also user WebDAV over HTTPS as transport mechanism. To do so, start with davs: followed by username, + # password, host, port (explicitly needed even if using default port 443) and path on the server. #ACL=('/var/www/${DOMAIN}/web/.well-known/acme-challenge' # 'ssh:server5:/var/www/${DOMAIN}/web/.well-known/acme-challenge' # 'ssh:sshuserid@server5:/var/www/${DOMAIN}/web/.well-known/acme-challenge' # 'ftp:ftpuserid:ftppassword:${DOMAIN}:/web/.well-known/acme-challenge' - # 'davs:davsuserid:davspassword:{DOMAIN}:443:/web/.well-known/acme-challenge') + # 'davs:davsuserid:davspassword:{DOMAIN}:443:/web/.well-known/acme-challenge') + + # Specify SSH options, e.g. non standard port in SSH_OPTS + # (Can also use SCP_OPTS and SFTP_OPTS) + # SSH_OPTS=-p 12345 - #Set USE_SINGLE_ACL="true" to use a single ACL for all checks + # Set USE_SINGLE_ACL="true" to use a single ACL for all checks #USE_SINGLE_ACL="false" # Location for all your certs, these can either be on the server (full path name) @@ -1652,6 +1660,9 @@ write_domain_template() { # write out a template file for a domain. # The command needed to reload apache / nginx or whatever you use #RELOAD_CMD="" + # Uncomment the following line to prevent non-interactive renewals of certificates + #PREVENT_NON_INTERACTIVE_RENEWAL="true" + # Define the server type. This can be https, ftp, ftpi, imap, imaps, pop3, pop3s, smtp, # smtps_deprecated, smtps, smtp_submission, xmpp, xmpps, ldaps or a port number which # will be checked for certificate expiry and also will be checked after @@ -1664,27 +1675,36 @@ write_domain_template() { # write out a template file for a domain. write_getssl_template() { # write out the main template file cat > "$1" <<- _EOF_getssl_ + # This file is read first and is common to all domains + # # Uncomment and modify any variables you need # see https://github.com/srvrco/getssl/wiki/Config-variables for details # # The staging server is best for testing (hence set as default) - CA="https://acme-staging-v02.api.letsencrypt.org/directory" + CA="https://acme-staging-v02.api.letsencrypt.org" # This server issues full certificates, however has rate limits #CA="https://acme-v02.api.letsencrypt.org" + # The agreement that must be signed with the CA, if not defined the default agreement will be used #AGREEMENT="$AGREEMENT" # Set an email address associated with your account - generally set at account level rather than domain. #ACCOUNT_EMAIL="me@example.com" ACCOUNT_KEY_LENGTH=4096 ACCOUNT_KEY="$WORKING_DIR/account.key" + + # Account key and private key types - can be rsa, prime256v1, secp384r1 or secp521r1 + #ACCOUNT_KEY_TYPE="rsa" PRIVATE_KEY_ALG="rsa" #REUSE_PRIVATE_KEY="true" # The command needed to reload apache / nginx or whatever you use #RELOAD_CMD="" + # The time period within which you want to allow renewal of a certificate # this prevents hitting some of the rate limits. + # Creating a file called FORCE_RENEWAL in the domain directory allows one-off overrides + # of this setting RENEW_ALLOW="30" # Define the server type. This can be https, ftp, ftpi, imap, imaps, pop3, pop3s, smtp, From 83de0e3910b7b708b377517a49bcff9582f77dc4 Mon Sep 17 00:00:00 2001 From: Tim Kimber Date: Wed, 12 Feb 2020 14:21:04 +0000 Subject: [PATCH 036/110] Fix for DUAL_RSA_ECDSA not working with ACMEv2 (#334, #474, #502) --- getssl | 3123 +++++++++-------- test/3-dual-rsa-ecdsa.bats | 43 + test/Dockerfile-debian | 3 - test/README.md | 6 +- .../getssl-dns01-dual-rsa-ecdsa.cfg | 37 + .../getssl-http01-dual-rsa-ecdsa.cfg | 33 + 6 files changed, 1681 insertions(+), 1564 deletions(-) create mode 100644 test/3-dual-rsa-ecdsa.bats create mode 100644 test/test-config/getssl-dns01-dual-rsa-ecdsa.cfg create mode 100644 test/test-config/getssl-http01-dual-rsa-ecdsa.cfg diff --git a/getssl b/getssl index 2c15739..0d593d7 100755 --- a/getssl +++ b/getssl @@ -297,11 +297,12 @@ check_challenge_completion() { # checks with the ACME server if our challenge is domain=$2 keyauthorization=$3 - debug "sending request to ACME server saying we're ready for challenge" + info "sending request to ACME server saying we're ready for challenge" # check response from our request to perform challenge if [[ $API -eq 1 ]]; then send_signed_request "$uri" "{\"resource\": \"challenge\", \"keyAuthorization\": \"$keyauthorization\"}" + if [[ -n "$code" ]] && [[ ! "$code" == '202' ]] ; then error_exit "$domain:Challenge error: $code" fi @@ -315,7 +316,7 @@ check_challenge_completion() { # checks with the ACME server if our challenge is # loop "forever" to keep checking for a response from the ACME server. while true ; do - debug "checking if challenge is complete" + info "checking if challenge is complete" if [[ $API -eq 1 ]]; then if ! get_cr "$uri" ; then error_exit "$domain:Verify error:$code" @@ -548,7 +549,6 @@ copy_file_to_location() { # copies a file, using scp, sftp or ftp if required. IFS=\; read -r -a copy_locations <<<"$3" for to in "${copy_locations[@]}"; do info "copying $cert to $to" - debug "copying from $from to $to" if [[ "${to:0:4}" == "ssh:" ]] ; then debug "using scp -q $SCP_OPTS $from ${to:4}" # shellcheck disable=SC2086 @@ -715,6 +715,29 @@ create_key() { # create a domain key (if it doesn't already exist) fi } +create_order() { + dstring="[" + for d in $alldomains; do + dstring="${dstring}{\"type\":\"dns\",\"value\":\"$d\"}," + done + dstring="${dstring::${#dstring}-1}]" + # request NewOrder currently seems to ignore the dates .... + # dstring="${dstring},\"notBefore\": \"$(date -d "-1 hour" --utc +%FT%TZ)\"" + # dstring="${dstring},\"notAfter\": \"$(date -d "2 days" --utc +%FT%TZ)\"" + request="{\"identifiers\": $dstring}" + send_signed_request "$URL_newOrder" "$request" + OrderLink=$(echo "$responseHeaders" | grep -i location | awk '{print $2}'| tr -d '\r\n ') + debug "Order link $OrderLink" + FinalizeLink=$(json_get "$response" "finalize") + dn=0 + for d in $alldomains; do + # get authorizations link + AuthLink[$dn]=$(json_get "$response" "identifiers" "value" "$d" "authorizations" "x") + debug "authorizations link for $d - ${AuthLink[$dn]}" + ((dn++)) + done +} + date_epoc() { # convert the date into epoch time if [[ "$os" == "bsd" ]]; then date -j -f "%b %d %T %Y %Z" "$1" +%s @@ -757,1746 +780,1724 @@ error_exit() { # give error message on error exit exit 1 } -get_auth_dns() { # get the authoritative dns server for a domain (sets primary_ns ) - gad_d="$1" # domain name - gad_s="$PUBLIC_DNS_SERVER" # start with PUBLIC_DNS_SERVER - - if [[ "$os" == "cygwin" ]]; then - all_auth_dns_servers=$(nslookup -type=soa "${d}" ${PUBLIC_DNS_SERVER} 2>/dev/null \ - | grep "primary name server" \ - | awk '{print $NF}') - if [[ -z "$all_auth_dns_servers" ]]; then - error_exit "couldn't find primary DNS server - please set AUTH_DNS_SERVER in config" - fi - primary_ns="$all_auth_dns_servers" - return +fulfill_challenges() { +dn=0 +for d in $alldomains; do + # $d is domain in current loop, which is number $dn for ACL + info "Verifying $d" + if [[ "$USE_SINGLE_ACL" == "true" ]]; then + DOMAIN_ACL="${ACL[0]}" + else + DOMAIN_ACL="${ACL[$dn]}" fi - if [[ "$DNS_CHECK_FUNC" == "drill" ]] || [[ "$DNS_CHECK_FUNC" == "dig" ]]; then - if [[ -z "$gad_s" ]]; then #checking for CNAMEs - res=$($DNS_CHECK_FUNC CNAME "$gad_d"| grep "^$gad_d") - else - res=$($DNS_CHECK_FUNC CNAME "$gad_d" "@$gad_s"| grep "^$gad_d") - fi - if [[ -n "$res" ]]; then # domain is a CNAME so get main domain - gad_d=$(echo "$res"| awk '{print $5}' |sed 's/\.$//g') - fi - if [[ -z "$gad_s" ]]; then #checking for CNAMEs - res=$($DNS_CHECK_FUNC NS "$gad_d"| grep "^$gad_d") - else - res=$($DNS_CHECK_FUNC NS "$gad_d" "@$gad_s"| grep "^$gad_d") - fi - if [[ -z "$res" ]]; then - error_exit "couldn't find primary DNS server - please set AUTH_DNS_SERVER in config" - else - all_auth_dns_servers=$(echo "$res" | awk '$4 ~ "NS" {print $5}' | sed 's/\.$//g'|tr '\n' ' ') - fi - if [[ $CHECK_ALL_AUTH_DNS == "true" ]]; then - primary_ns="$all_auth_dns_servers" - else - primary_ns=$(echo "$all_auth_dns_servers" | awk '{print $1}') - fi - return - fi + # request a challenge token from ACME server + if [[ $API -eq 1 ]]; then + request="{\"resource\":\"new-authz\",\"identifier\":{\"type\":\"dns\",\"value\":\"$d\"}}" + send_signed_request "$URL_new_authz" "$request" + debug "completed send_signed_request" - if [[ "$DNS_CHECK_FUNC" == "host" ]]; then - if [[ -z "$gad_s" ]]; then - res=$($DNS_CHECK_FUNC -t NS "$gad_d"| grep "name server") - else - res=$($DNS_CHECK_FUNC -t NS "$gad_d" "$gad_s"| grep "name server") - fi - if [[ -z "$res" ]]; then - error_exit "couldn't find primary DNS server - please set AUTH_DNS_SERVER in config" - else - all_auth_dns_servers=$(echo "$res" | awk '{print $4}' | sed 's/\.$//g'|tr '\n' ' ') - fi - if [[ $CHECK_ALL_AUTH_DNS == "true" ]]; then - primary_ns="$all_auth_dns_servers" - else - primary_ns=$(echo "$all_auth_dns_servers" | awk '{print $1}') + # check if we got a valid response and token, if not then error exit + if [[ -n "$code" ]] && [[ ! "$code" == '201' ]] ; then + error_exit "new-authz error: $response" fi - return + else + send_signed_request "${AuthLink[$dn]}" "" fi - res=$(nslookup -debug -type=soa -type=ns "$gad_d" ${gad_s}) - - if [[ "$(echo "$res" | grep -c "Non-authoritative")" -gt 0 ]]; then - # this is a Non-authoritative server, need to check for an authoritative one. - gad_s=$(echo "$res" | awk '$2 ~ "nameserver" {print $4; exit }' |sed 's/\.$//g') - if [[ "$(echo "$res" | grep -c "an't find")" -gt 0 ]]; then - # if domain name doesn't exist, then find auth servers for next level up - gad_s=$(echo "$res" | awk '$1 ~ "origin" {print $3; exit }') - gad_d=$(echo "$res" | awk '$1 ~ "->" {print $2; exit}') + if [[ $response_status == "valid" ]]; then + info "$d is already validated" + if [[ "$DEACTIVATE_AUTH" == "true" ]]; then + deactivate_url="$(echo "$responseHeaders" | awk ' $1 ~ "^Location" {print $2}' | tr -d "\r")" + deactivate_url_list+=" $deactivate_url " + debug "url added to deactivate list ${deactivate_url}" + debug "deactivate list is now $deactivate_url_list" fi - fi - - if [[ -z "$gad_s" ]]; then - res=$(nslookup -debug -type=soa -type=ns "$gad_d") + # increment domain-counter + ((dn++)) else - res=$(nslookup -debug -type=soa -type=ns "$gad_d" "${gad_s}") - fi + PREVIOUSLY_VALIDATED="false" + if [[ $VALIDATE_VIA_DNS == "true" ]]; then # set up the correct DNS token for verification + if [[ $API -eq 1 ]]; then + # get the dns component of the ACME response + # get the token from the dns component + token=$(json_get "$response" "token" "dns-01") + # get the uri from the dns component + uri=$(json_get "$response" "uri" "dns-01") + debug uri "$uri" + else # APIv2 + debug "authlink response = $response" + # get the token from the http-01 component + token=$(json_get "$response" "challenges" "type" "dns-01" "token") + # get the uri from the http component + uri=$(json_get "$response" "challenges" "type" "dns-01" "url") + debug uri "$uri" + fi - if [[ "$(echo "$res" | grep -c "canonical name")" -gt 0 ]]; then - gad_d=$(echo "$res" | awk ' $2 ~ "canonical" {print $5; exit }' |sed 's/\.$//g') - elif [[ "$(echo "$res" | grep -c "an't find")" -gt 0 ]]; then - gad_s=$(echo "$res" | awk ' $1 ~ "origin" {print $3; exit }') - gad_d=$(echo "$res"| awk '$1 ~ "->" {print $2; exit}') - fi + keyauthorization="$token.$thumbprint" + debug keyauthorization "$keyauthorization" - all_auth_dns_servers=$(nslookup -type=soa -type=ns "$gad_d" "$gad_s" \ - | awk ' $2 ~ "nameserver" {print $4}' \ - | sed 's/\.$//g'| tr '\n' ' ') - if [[ $CHECK_ALL_AUTH_DNS == "true" ]]; then - primary_ns="$all_auth_dns_servers" - else - primary_ns=$(echo "$all_auth_dns_servers" | awk '{print $1}') - fi -} + #create signed authorization key from token. + auth_key=$(printf '%s' "$keyauthorization" | openssl dgst -sha256 -binary \ + | openssl base64 -e \ + | tr -d '\n\r' \ + | sed -e 's:=*$::g' -e 'y:+/:-_:') + debug auth_key "$auth_key" -get_certificate() { # get certificate for csr, if all domains validated. - gc_csr=$1 # the csr file - gc_certfile=$2 # The filename for the certificate - gc_cafile=$3 # The filename for the CA certificate + debug "adding dns via command: $DNS_ADD_COMMAND $d $auth_key" + if ! eval "$DNS_ADD_COMMAND" "$d" "$auth_key" ; then + error_exit "DNS_ADD_COMMAND failed for domain $d" + fi - der=$(openssl req -in "$gc_csr" -outform DER | urlbase64) - debug "der $der" - if [[ $API -eq 1 ]]; then - send_signed_request "$URL_new_cert" "{\"resource\": \"new-cert\", \"csr\": \"$der\"}" "needbase64" - # convert certificate information into correct format and save to file. - CertData=$(awk ' $1 ~ "^Location" {print $2}' "$CURL_HEADER" |tr -d '\r') - debug "certdata location = $CertData" - if [[ "$CertData" ]] ; then - echo -----BEGIN CERTIFICATE----- > "$gc_certfile" - curl --user-agent "$CURL_USERAGENT" --silent "$CertData" | openssl base64 -e >> "$gc_certfile" - echo -----END CERTIFICATE----- >> "$gc_certfile" - info "Certificate saved in $CERT_FILE" - fi + # find a primary / authoritative DNS server for the domain + if [[ -z "$AUTH_DNS_SERVER" ]]; then + get_auth_dns "$d" + else + primary_ns="$AUTH_DNS_SERVER" + fi + debug primary_ns "$primary_ns" - # If certificate wasn't a valid certificate, error exit. - if [[ -z "$CertData" ]] ; then - response2=$(echo "$response" | fold -w64 |openssl base64 -d) - debug "response was $response" - error_exit "Sign failed: $(echo "$response2" | grep "detail")" - fi + # make a directory to hold pending dns-challenges + if [[ ! -d "$TEMP_DIR/dns_verify" ]]; then + mkdir "$TEMP_DIR/dns_verify" + fi - # get a copy of the CA certificate. - IssuerData=$(grep -i '^Link' "$CURL_HEADER" \ - | cut -d " " -f 2\ - | cut -d ';' -f 1 \ - | sed 's///g') - if [[ "$IssuerData" ]] ; then - echo -----BEGIN CERTIFICATE----- > "$gc_cafile" - curl --user-agent "$CURL_USERAGENT" --silent "$IssuerData" | openssl base64 -e >> "$gc_cafile" - echo -----END CERTIFICATE----- >> "$gc_cafile" - info "The intermediate CA cert is in $gc_cafile" - fi - else # APIv2 - send_signed_request "$FinalizeLink" "{\"csr\": \"$der\"}" "needbase64" - debug "order link was $OrderLink" - send_signed_request "$OrderLink" "" - CertData=$(json_get "$response" "certificate") - debug "CertData is at $CertData" - send_signed_request "$CertData" "" "" "$FULL_CHAIN" - info "Full certificate saved in $FULL_CHAIN" - awk -v CERT_FILE="$CERT_FILE" -v CA_CERT="$CA_CERT" 'BEGIN {outfile=CERT_FILE} split_after==1 {outfile=CA_CERT;split_after=0} /-----END CERTIFICATE-----/ {split_after=1} {print > outfile}' "$FULL_CHAIN" - info "Certificate saved in $CERT_FILE" - fi -} + # generate a file with the current variables for the dns-challenge + cat > "$TEMP_DIR/dns_verify/$d" <<- _EOF_ + token="${token}" + uri="${uri}" + keyauthorization="${keyauthorization}" + d="${d}" + primary_ns="${primary_ns}" + auth_key="${auth_key}" + _EOF_ -get_cr() { # get curl response - url="$1" - debug url "$url" - response=$(curl --user-agent "$CURL_USERAGENT" --silent "$url") - ret=$? - debug response "$response" - code=$(json_get "$response" status) - debug code "$code" - debug "get_cr return code $ret" - return $ret -} + else # set up the correct http token for verification + if [[ $API -eq 1 ]]; then + # get the token from the http component + token=$(json_get "$response" "token" "http-01") + # get the uri from the http component + uri=$(json_get "$response" "uri" "http-01") + debug uri "$uri" + else # APIv2 + send_signed_request "${AuthLink[$dn]}" "" + debug "authlink response = $response" + # get the token from the http-01 component + token=$(json_get "$response" "challenges" "type" "http-01" "token") + # get the uri from the http component + uri=$(json_get "$response" "challenges" "type" "http-01" "url" | head -n1) + debug uri "$uri" + fi -get_os() { # function to get the current Operating System - uname_res=$(uname -s) - if [[ $(date -h 2>&1 | grep -ic busybox) -gt 0 ]]; then - os="busybox" - elif [[ ${uname_res} == "Linux" ]]; then - os="linux" - elif [[ ${uname_res} == "FreeBSD" ]]; then - os="bsd" - elif [[ ${uname_res} == "Darwin" ]]; then - os="mac" - elif [[ ${uname_res:0:6} == "CYGWIN" ]]; then - os="cygwin" - elif [[ ${uname_res:0:5} == "MINGW" ]]; then - os="mingw" - else - os="unknown" - fi - debug "detected os type = $os" -} + #create signed authorization key from token. + keyauthorization="$token.$thumbprint" -get_signing_params() { # get signing parameters from key - skey=$1 - if openssl rsa -in "${skey}" -noout 2>/dev/null ; then # RSA key - pub_exp64=$(openssl rsa -in "${skey}" -noout -text \ - | grep publicExponent \ - | grep -oE "0x[a-f0-9]+" \ - | cut -d'x' -f2 \ - | hex2bin \ - | urlbase64) - pub_mod64=$(openssl rsa -in "${skey}" -noout -modulus \ - | cut -d'=' -f2 \ - | hex2bin \ - | urlbase64) + # save variable into temporary file + echo -n "$keyauthorization" > "$TEMP_DIR/$token" + chmod 644 "$TEMP_DIR/$token" - jwk='{"e":"'"${pub_exp64}"'","kty":"RSA","n":"'"${pub_mod64}"'"}' - jwkalg="RS256" - signalg="sha256" - elif openssl ec -in "${skey}" -noout 2>/dev/null ; then # Elliptic curve key. - crv="$(openssl ec -in "$skey" -noout -text 2>/dev/null | awk '$2 ~ "CURVE:" {print $3}')" - if [[ -z "$crv" ]]; then - gsp_keytype="$(openssl ec -in "$skey" -noout -text 2>/dev/null \ - | grep "^ASN1 OID:" \ - | awk '{print $3}')" - case "$gsp_keytype" in - prime256v1) crv="P-256" ;; - secp384r1) crv="P-384" ;; - secp521r1) crv="P-521" ;; - *) error_exit "invalid curve algorithm type $gsp_keytype";; - esac - fi - case "$crv" in - P-256) jwkalg="ES256" ; signalg="sha256" ;; - P-384) jwkalg="ES384" ; signalg="sha384" ;; - P-521) jwkalg="ES512" ; signalg="sha512" ;; - *) error_exit "invalid curve algorithm type $crv";; - esac - pubtext="$(openssl ec -in "$skey" -noout -text 2>/dev/null \ - | awk '/^pub:/{p=1;next}/^ASN1 OID:/{p=0}p' \ - | tr -d ": \n\r")" - mid=$(( (${#pubtext} -2) / 2 + 2 )) - debug "pubtext = $pubtext" - x64=$(echo "$pubtext" | cut -b 3-$mid | hex2bin | urlbase64) - y64=$(echo "$pubtext" | cut -b $((mid+1))-${#pubtext} | hex2bin | urlbase64) - jwk='{"crv":"'"$crv"'","kty":"EC","x":"'"$x64"'","y":"'"$y64"'"}' - debug "jwk $jwk" - else - error_exit "Invalid key file" - fi - thumbprint="$(printf "%s" "$jwk" | openssl dgst -sha256 -binary | urlbase64)" - debug "jwk alg = $jwkalg" - debug "jwk = $jwk" - debug "thumbprint $thumbprint" -} + # copy to token to acme challenge location + umask 0022 + IFS=\; read -r -a token_locations <<<"$DOMAIN_ACL" + for t_loc in "${token_locations[@]}"; do + debug "copying file from $TEMP_DIR/$token to ${t_loc}" + copy_file_to_location "challenge token" \ + "$TEMP_DIR/$token" \ + "${t_loc}/$token" + done + umask "$ORIG_UMASK" -graceful_exit() { # normal exit function. - clean_up - exit -} + wellknown_url="${CHALLENGE_CHECK_TYPE}://${d}/.well-known/acme-challenge/$token" + debug wellknown_url "$wellknown_url" -help_message() { # print out the help message - cat <<- _EOF_ - $PROGNAME ver. $VERSION - Obtain SSL certificates from the letsencrypt.org ACME server + if [[ "$SKIP_HTTP_TOKEN_CHECK" == "true" ]]; then + info "SKIP_HTTP_TOKEN_CHECK=true so not checking that token is working correctly" + else + sleep "$HTTP_TOKEN_CHECK_WAIT" + # check that we can reach the challenge ourselves, if not, then error + if [[ ! "$(curl --user-agent "$CURL_USERAGENT" -k --silent --location "$wellknown_url")" == "$keyauthorization" ]]; then + error_exit "for some reason could not reach $wellknown_url - please check it manually" + fi + fi - $(usage) + check_challenge_completion "$uri" "$d" "$keyauthorization" - Options: - -a, --all Check all certificates - -d, --debug Output debug information - -c, --create Create default config files - -f, --force Force renewal of cert (overrides expiry checks) - -h, --help Display this help message and exit - -q, --quiet Quiet mode (only outputs on error, success of new cert, or getssl was upgraded) - -Q, --mute Like -q, but also mute notification about successful upgrade - -r, --revoke "cert" "key" [CA_server] Revoke a certificate (the cert and key are required) - -u, --upgrade Upgrade getssl if a more recent version is available - -k, --keep "#" Maximum number of old getssl versions to keep when upgrading - -U, --nocheck Do not check if a more recent version is available - -w working_dir "Working directory" + debug "remove token from ${DOMAIN_ACL}" + IFS=\; read -r -a token_locations <<<"$DOMAIN_ACL" + for t_loc in "${token_locations[@]}"; do + if [[ "${t_loc:0:4}" == "ssh:" ]] ; then + sshhost=$(echo "${t_loc}"| awk -F: '{print $2}') + command="rm -f ${t_loc:(( ${#sshhost} + 5))}/${token:?}" + debug "running following command to remove token" + debug "ssh $SSH_OPTS $sshhost ${command}" + # shellcheck disable=SC2029 + # shellcheck disable=SC2086 + ssh $SSH_OPTS "$sshhost" "${command}" 1>/dev/null 2>&1 + rm -f "${TEMP_DIR:?}/${token:?}" + elif [[ "${t_loc:0:4}" == "ftp:" ]] ; then + debug "using ftp to remove token file" + ftpuser=$(echo "${t_loc}"| awk -F: '{print $2}') + ftppass=$(echo "${t_loc}"| awk -F: '{print $3}') + ftphost=$(echo "${t_loc}"| awk -F: '{print $4}') + ftplocn=$(echo "${t_loc}"| awk -F: '{print $5}') + debug "ftp user=$ftpuser - pass=$ftppass - host=$ftphost location=$ftplocn" + ftp -n <<- EOF + open $ftphost + user $ftpuser $ftppass + cd $ftplocn + delete ${token:?} + EOF + else + rm -f "${t_loc:?}/${token:?}" + fi + done + fi + # increment domain-counter + ((dn++)) + fi +done # end of ... loop through domains for cert ( from SANS list) - _EOF_ -} +# perform validation if via DNS challenge +if [[ $VALIDATE_VIA_DNS == "true" ]]; then + # loop through dns-variable files to check if dns has been changed + for dnsfile in "$TEMP_DIR"/dns_verify/*; do + if [[ -e "$dnsfile" ]]; then + debug "loading DNSfile: $dnsfile" + # shellcheck source=/dev/null + . "$dnsfile" -hex2bin() { # Remove spaces, add leading zero, escape as hex string ensuring no trailing new line char -# printf -- "$(cat | os_esed -e 's/[[:space:]]//g' -e 's/^(.(.{2})*)$/0\1/' -e 's/(.{2})/\\x\1/g')" - echo -e -n "$(cat | os_esed -e 's/[[:space:]]//g' -e 's/^(.(.{2})*)$/0\1/' -e 's/(.{2})/\\x\1/g')" -} + # check for token at public dns server, waiting for a valid response. + for ns in $primary_ns; do + debug "checking dns at $ns" + ntries=0 + check_dns="fail" + while [[ "$check_dns" == "fail" ]]; do + if [[ "$os" == "cygwin" ]]; then + check_result=$(nslookup -type=txt "_acme-challenge.${d}" "${ns}" \ + | grep ^_acme -A2\ + | grep '"'|awk -F'"' '{ print $2}') + elif [[ "$DNS_CHECK_FUNC" == "drill" ]] || [[ "$DNS_CHECK_FUNC" == "dig" ]]; then + check_result=$($DNS_CHECK_FUNC TXT "_acme-challenge.${d}" "@${ns}" \ + | grep '300 IN TXT'|awk -F'"' '{ print $2}') + elif [[ "$DNS_CHECK_FUNC" == "host" ]]; then + check_result=$($DNS_CHECK_FUNC -t TXT "_acme-challenge.${d}" "${ns}" \ + | grep 'descriptive text'|awk -F'"' '{ print $2}') + else + check_result=$(nslookup -type=txt "_acme-challenge.${d}" "${ns}" \ + | grep 'text ='|awk -F'"' '{ print $2}') + fi + debug "expecting $auth_key" + debug "${ns} gave ... $check_result" -info() { # write out info as long as the quiet flag has not been set. - if [[ ${_QUIET} -eq 0 ]]; then - echo "$@" + if [[ "$check_result" == *"$auth_key"* ]]; then + check_dns="success" + else + if [[ $ntries -lt 100 ]]; then + ntries=$(( ntries + 1 )) + info "checking DNS at ${ns} for ${d}. Attempt $ntries/100 gave wrong result, "\ + "waiting $DNS_WAIT secs before checking again" + sleep $DNS_WAIT + else + debug "dns check failed - removing existing value" + error_exit "checking _acme-challenge.${d} gave $check_result not $auth_key" + fi + fi + done + done + fi + done + + if [[ "$DNS_EXTRA_WAIT" -gt 0 && "$PREVIOUSLY_VALIDATED" != "true" ]]; then + info "sleeping $DNS_EXTRA_WAIT seconds before asking the ACME-server to check the dns" + sleep "$DNS_EXTRA_WAIT" fi -} -json_awk() { # AWK json converter used for API2 - needs tidying up ;) -# shellcheck disable=SC2086 -echo "$1" | tr -d '\n' | awk ' -{ - tokenize($0) # while(get_token()) {print TOKEN} - if (0 == parse()) { - apply(JPATHS, NJPATHS) - } -} + # loop through dns-variable files to let the ACME server check the challenges + for dnsfile in "$TEMP_DIR"/dns_verify/*; do + if [[ -e "$dnsfile" ]]; then + debug "loading DNSfile: $dnsfile" + # shellcheck source=/dev/null + . "$dnsfile" -function apply (ary,size,i) { - for (i=1; i NTOKENS) to = NTOKENS - for (i = from; i < ITOKENS; i++) - context = context sprintf("%s ", TOKENS[i]) - context = context "<<" got ">> " - for (i = ITOKENS + 1; i <= to; i++) - context = context sprintf("%s ", TOKENS[i]) - scream("json_awk expected <" expected "> but got <" got "> at input token " ITOKENS "\n" context) -} - -function reset() { - TOKEN=""; delete TOKENS; NTOKENS=ITOKENS=0 - delete JPATHS; NJPATHS=0 - VALUE="" -} - -function scream(msg) { - FAILS[FILENAME] = FAILS[FILENAME] (FAILS[FILENAME]!="" ? "\n" : "") msg - msg = FILENAME ": " msg - print msg >"/dev/stderr" -} - -function tokenize(a1,pq,pb,ESCAPE,CHAR,STRING,NUMBER,KEYWORD,SPACE) { - SPACE="[ \t\n]+" - gsub(/"[^\001-\037"\\]*((\\[^u\001-\037]|\\u[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F])[^\001-\037"\\]*)*"|-?(0|[1-9][0-9]*)([.][0-9]*)?([eE][+-]?[0-9]*)?|null|false|true|[ \t\n]+|./, "\n&", a1) - gsub("\n" SPACE, "\n", a1) - sub(/^\n/, "", a1) - ITOKENS=0 # get_token() helper - return NTOKENS = split(a1, TOKENS, /\n/) -}' -} +get_auth_dns() { # get the authoritative dns server for a domain (sets primary_ns ) + gad_d="$1" # domain name + gad_s="$PUBLIC_DNS_SERVER" # start with PUBLIC_DNS_SERVER -json_get() { # get values from json - if [[ -z "$1" ]] || [[ "$1" == "null" ]]; then - echo "json was blank" + if [[ "$os" == "cygwin" ]]; then + all_auth_dns_servers=$(nslookup -type=soa "${d}" ${PUBLIC_DNS_SERVER} 2>/dev/null \ + | grep "primary name server" \ + | awk '{print $NF}') + if [[ -z "$all_auth_dns_servers" ]]; then + error_exit "couldn't find primary DNS server - please set AUTH_DNS_SERVER in config" + fi + primary_ns="$all_auth_dns_servers" return fi - if [[ $API = 1 ]]; then - # remove newlines, so it's a single chunk of JSON - json_data=$( echo "$1" | tr '\n' ' ') - # if $3 is defined, this is the section which the item is in. - if [[ -n "$3" ]]; then - jg_section=$(echo "$json_data" | awk -F"[}]" '{for(i=1;i<=NF;i++){if($i~/\"'"${3}"'\"/){print $i}}}') - if [[ "$2" == "uri" ]]; then - jg_subsect=$(echo "$jg_section" | awk -F"[,]" '{for(i=1;i<=NF;i++){if($i~/\"'"${2}"'\"/){print $(i)}}}') - jg_result=$(echo "$jg_subsect" | awk -F'"' '{print $4}') - else - jg_result=$(echo "$jg_section" | awk -F"[,:}]" '{for(i=1;i<=NF;i++){if($i~/\"'"${2}"'\"/){print $(i+1)}}}') - fi + + if [[ "$DNS_CHECK_FUNC" == "drill" ]] || [[ "$DNS_CHECK_FUNC" == "dig" ]]; then + if [[ -z "$gad_s" ]]; then #checking for CNAMEs + res=$($DNS_CHECK_FUNC CNAME "$gad_d"| grep "^$gad_d") else - jg_result=$(echo "$json_data" |awk -F"[,:}]" '{for(i=1;i<=NF;i++){if($i~/\"'"${2}"'\"/){print $(i+1)}}}') + res=$($DNS_CHECK_FUNC CNAME "$gad_d" "@$gad_s"| grep "^$gad_d") fi - # check number of quotes - jg_q=${jg_result//[^\"]/} - # if 2 quotes, assume it's a quoted variable and just return the data within the quotes. - if [[ ${#jg_q} -eq 2 ]]; then - echo "$jg_result" | awk -F'"' '{print $2}' + if [[ -n "$res" ]]; then # domain is a CNAME so get main domain + gad_d=$(echo "$res"| awk '{print $5}' |sed 's/\.$//g') + fi + if [[ -z "$gad_s" ]]; then #checking for CNAMEs + res=$($DNS_CHECK_FUNC NS "$gad_d"| grep "^$gad_d") else - echo "$jg_result" + res=$($DNS_CHECK_FUNC NS "$gad_d" "@$gad_s"| grep "^$gad_d") fi - else - if [[ -n "$6" ]]; then - full=$(json_awk "$1") - section=$(echo "$full" | grep "\"$2\"" | grep "\"$3\"" | grep "\"$4\"" | awk -F"," '{print $2}') - echo "$full" | grep "^..${5}\",$section\]" | awk '{print $2}' | tr -d '"' - elif [[ -n "$5" ]]; then - full=$(json_awk "$1") - section=$(echo "$full" | grep "\"$2\"" | grep "\"$3\"" | grep "\"$4\"" | awk -F"," '{print $2}') - echo "$full" | grep "^..${2}\",$section" | grep "$5" | awk '{print $2}' | tr -d '"' - elif [[ -n "$3" ]]; then - json_awk "$1" | grep "^..${2}...${3}" | awk '{print $2}' | tr -d '"' - elif [[ -n "$2" ]]; then - json_awk "$1" | grep "^..${2}" | awk '{print $2}' | tr -d '"' + if [[ -z "$res" ]]; then + error_exit "couldn't find primary DNS server - please set AUTH_DNS_SERVER in config" else - json_awk "$1" + all_auth_dns_servers=$(echo "$res" | awk '$4 ~ "NS" {print $5}' | sed 's/\.$//g'|tr '\n' ' ') fi + if [[ $CHECK_ALL_AUTH_DNS == "true" ]]; then + primary_ns="$all_auth_dns_servers" + else + primary_ns=$(echo "$all_auth_dns_servers" | awk '{print $1}') + fi + return fi -} - -os_esed() { # Use different sed version for different os types (extended regex) - if [[ "$os" == "bsd" ]]; then # BSD requires -E flag for extended regex - sed -E "${@}" - elif [[ "$os" == "mac" ]]; then # MAC uses older BSD style sed. - sed -E "${@}" - else - sed -r "${@}" - fi -} -purge_archive() { # purge archive of old, invalid, certificates - arcdir="$1/archive" - debug "purging archives in ${arcdir}/" - for padir in "$arcdir"/????_??_??_??_??; do - # check each directory - if [[ -d "$padir" ]]; then - tstamp=$(basename "$padir"| awk -F"_" '{print $1"-"$2"-"$3" "$4":"$5}') - if [[ "$os" == "bsd" ]]; then - direpoc=$(date -j -f "%F %H:%M" "$tstamp" +%s) - elif [[ "$os" == "mac" ]]; then - direpoc=$(date -j -f "%F %H:%M" "$tstamp" +%s) - else - direpoc=$(date -d "$tstamp" +%s) - fi - current_epoc=$(date "+%s") - # as certs currently valid for 90 days, purge anything older than 100 - purgedate=$((current_epoc - 60*60*24*100)) - if [[ "$direpoc" -lt "$purgedate" ]]; then - echo "purge $padir" - rm -rf "${padir:?}" - fi + if [[ "$DNS_CHECK_FUNC" == "host" ]]; then + if [[ -z "$gad_s" ]]; then + res=$($DNS_CHECK_FUNC -t NS "$gad_d"| grep "name server") + else + res=$($DNS_CHECK_FUNC -t NS "$gad_d" "$gad_s"| grep "name server") fi - done -} - -reload_service() { # Runs a command to reload services ( via ssh if needed) - if [[ -n "$RELOAD_CMD" ]]; then - info "reloading SSL services" - if [[ "${RELOAD_CMD:0:4}" == "ssh:" ]] ; then - sshhost=$(echo "$RELOAD_CMD"| awk -F: '{print $2}') - command=${RELOAD_CMD:(( ${#sshhost} + 5))} - debug "running following command to reload cert" - debug "ssh $SSH_OPTS $sshhost ${command}" - # shellcheck disable=SC2029 - # shellcheck disable=SC2086 - ssh $SSH_OPTS "$sshhost" "${command}" 1>/dev/null 2>&1 - # allow 2 seconds for services to restart - sleep 2 + if [[ -z "$res" ]]; then + error_exit "couldn't find primary DNS server - please set AUTH_DNS_SERVER in config" else - debug "running reload command $RELOAD_CMD" - if ! eval "$RELOAD_CMD" ; then - error_exit "error running $RELOAD_CMD" - fi + all_auth_dns_servers=$(echo "$res" | awk '{print $4}' | sed 's/\.$//g'|tr '\n' ' ') + fi + if [[ $CHECK_ALL_AUTH_DNS == "true" ]]; then + primary_ns="$all_auth_dns_servers" + else + primary_ns=$(echo "$all_auth_dns_servers" | awk '{print $1}') fi + return fi -} -revoke_certificate() { # revoke a certificate - debug "revoking cert $REVOKE_CERT" - debug "using key $REVOKE_KEY" - ACCOUNT_KEY="$REVOKE_KEY" - # need to set the revoke key as "account_key" since it's used in send_signed_request. - get_signing_params "$REVOKE_KEY" - TEMP_DIR=$(mktemp -d 2>/dev/null || mktemp -d -t getssl) - debug "revoking from $CA" - rcertdata=$(openssl x509 -in "$REVOKE_CERT" -inform PEM -outform DER | urlbase64) - send_signed_request "$URL_revoke" "{\"resource\": \"revoke-cert\", \"certificate\": \"$rcertdata\"}" - if [[ $code -eq "200" ]]; then - info "certificate revoked" + res=$(nslookup -debug -type=soa -type=ns "$gad_d" ${gad_s}) + + if [[ "$(echo "$res" | grep -c "Non-authoritative")" -gt 0 ]]; then + # this is a Non-authoritative server, need to check for an authoritative one. + gad_s=$(echo "$res" | awk '$2 ~ "nameserver" {print $4; exit }' |sed 's/\.$//g') + if [[ "$(echo "$res" | grep -c "an't find")" -gt 0 ]]; then + # if domain name doesn't exist, then find auth servers for next level up + gad_s=$(echo "$res" | awk '$1 ~ "origin" {print $3; exit }') + gad_d=$(echo "$res" | awk '$1 ~ "->" {print $2; exit}') + fi + fi + + if [[ -z "$gad_s" ]]; then + res=$(nslookup -debug -type=soa -type=ns "$gad_d") else - error_exit "Revocation failed: $(echo "$response" | grep "detail")" + res=$(nslookup -debug -type=soa -type=ns "$gad_d" "${gad_s}") fi -} -requires() { # check if required function is available - if [[ "$#" -gt 1 ]]; then # if more than 1 value, check list - for i in "$@"; do - if [[ "$i" == "${!#}" ]]; then # if on last variable then exit as not found - error_exit "this script requires one of: ${*:1:$(($#-1))}" - fi - res=$(command -v "$i" 2>/dev/null) - debug "checking for $i ... $res" - if [[ -n "$res" ]]; then # if function found, then set variable to function and return - debug "function $i found at $res - setting ${!#} to $i" - eval "${!#}=\$i" - return - fi - done - else # only one value, so check it. - result=$(command -v "$1" 2>/dev/null) - debug "checking for required $1 ... $result" - if [[ -z "$result" ]]; then - error_exit "This script requires $1 installed" - fi + if [[ "$(echo "$res" | grep -c "canonical name")" -gt 0 ]]; then + gad_d=$(echo "$res" | awk ' $2 ~ "canonical" {print $5; exit }' |sed 's/\.$//g') + elif [[ "$(echo "$res" | grep -c "an't find")" -gt 0 ]]; then + gad_s=$(echo "$res" | awk ' $1 ~ "origin" {print $3; exit }') + gad_d=$(echo "$res"| awk '$1 ~ "->" {print $2; exit}') fi -} -set_server_type() { # uses SERVER_TYPE to set REMOTE_PORT and REMOTE_EXTRA - if [[ ${SERVER_TYPE} == "https" ]] || [[ ${SERVER_TYPE} == "webserver" ]]; then - REMOTE_PORT=443 - elif [[ ${SERVER_TYPE} == "ftp" ]]; then - REMOTE_PORT=21 - REMOTE_EXTRA="-starttls ftp" - elif [[ ${SERVER_TYPE} == "ftpi" ]]; then - REMOTE_PORT=990 - elif [[ ${SERVER_TYPE} == "imap" ]]; then - REMOTE_PORT=143 - REMOTE_EXTRA="-starttls imap" - elif [[ ${SERVER_TYPE} == "imaps" ]]; then - REMOTE_PORT=993 - elif [[ ${SERVER_TYPE} == "pop3" ]]; then - REMOTE_PORT=110 - REMOTE_EXTRA="-starttls pop3" - elif [[ ${SERVER_TYPE} == "pop3s" ]]; then - REMOTE_PORT=995 - elif [[ ${SERVER_TYPE} == "smtp" ]]; then - REMOTE_PORT=25 - REMOTE_EXTRA="-starttls smtp" - elif [[ ${SERVER_TYPE} == "smtps_deprecated" ]]; then - REMOTE_PORT=465 - elif [[ ${SERVER_TYPE} == "smtps" ]] || [[ ${SERVER_TYPE} == "smtp_submission" ]]; then - REMOTE_PORT=587 - REMOTE_EXTRA="-starttls smtp" - elif [[ ${SERVER_TYPE} == "xmpp" ]]; then - REMOTE_PORT=5222 - REMOTE_EXTRA="-starttls xmpp" - elif [[ ${SERVER_TYPE} == "xmpps" ]]; then - REMOTE_PORT=5269 - elif [[ ${SERVER_TYPE} == "ldaps" ]]; then - REMOTE_PORT=636 - elif [[ ${SERVER_TYPE} =~ ^[0-9]+$ ]]; then - REMOTE_PORT=${SERVER_TYPE} + all_auth_dns_servers=$(nslookup -type=soa -type=ns "$gad_d" "$gad_s" \ + | awk ' $2 ~ "nameserver" {print $4}' \ + | sed 's/\.$//g'| tr '\n' ' ') + if [[ $CHECK_ALL_AUTH_DNS == "true" ]]; then + primary_ns="$all_auth_dns_servers" else - info "${DOMAIN}: unknown server type \"$SERVER_TYPE\" in SERVER_TYPE" - config_errors=true + primary_ns=$(echo "$all_auth_dns_servers" | awk '{print $1}') fi } -send_signed_request() { # Sends a request to the ACME server, signed with your private key. - url=$1 - payload=$2 - needbase64=$3 - outfile=$4 # save response into this file (certificate data) +get_certificate() { # get certificate for csr, if all domains validated. + gc_csr=$1 # the csr file + gc_certfile=$2 # The filename for the certificate + gc_cafile=$3 # The filename for the CA certificate - debug url "$url" + der=$(openssl req -in "$gc_csr" -outform DER | urlbase64) + if [[ $API -eq 1 ]]; then + send_signed_request "$URL_new_cert" "{\"resource\": \"new-cert\", \"csr\": \"$der\"}" "needbase64" + # convert certificate information into correct format and save to file. + CertData=$(awk ' $1 ~ "^Location" {print $2}' "$CURL_HEADER" |tr -d '\r') + if [[ "$CertData" ]] ; then + echo -----BEGIN CERTIFICATE----- > "$gc_certfile" + curl --user-agent "$CURL_USERAGENT" --silent "$CertData" | openssl base64 -e >> "$gc_certfile" + echo -----END CERTIFICATE----- >> "$gc_certfile" + info "Certificate saved in $CERT_FILE" + fi - CURL_HEADER="$TEMP_DIR/curl.header" - dp="$TEMP_DIR/curl.dump" + # If certificate wasn't a valid certificate, error exit. + if [[ -z "$CertData" ]] ; then + response2=$(echo "$response" | fold -w64 |openssl base64 -d) + debug "response was $response" + error_exit "Sign failed: $(echo "$response2" | grep "detail")" + fi - CURL="curl " - # shellcheck disable=SC2072 - if [[ "$($CURL -V | head -1 | cut -d' ' -f2 )" > "7.33" ]]; then - CURL="$CURL --http1.1 " + # get a copy of the CA certificate. + IssuerData=$(grep -i '^Link' "$CURL_HEADER" \ + | cut -d " " -f 2\ + | cut -d ';' -f 1 \ + | sed 's///g') + if [[ "$IssuerData" ]] ; then + echo -----BEGIN CERTIFICATE----- > "$gc_cafile" + curl --user-agent "$CURL_USERAGENT" --silent "$IssuerData" | openssl base64 -e >> "$gc_cafile" + echo -----END CERTIFICATE----- >> "$gc_cafile" + info "The intermediate CA cert is in $gc_cafile" + fi + else # APIv2 + info "Requesting Finalize Link" + send_signed_request "$FinalizeLink" "{\"csr\": \"$der\"}" "needbase64" + info Requesting Order Link + debug "order link was $OrderLink" + send_signed_request "$OrderLink" "" + # if ACME response is processing (still creating certificates) then wait and try again. + while [[ "$response_status" == "processing" ]]; do + info "ACME server still Processing certificates" + sleep 5 + send_signed_request "$OrderLink" "" + done + info "Requesting certificate" + CertData=$(json_get "$response" "certificate") + send_signed_request "$CertData" "" "" "$FULL_CHAIN" + info "Full certificate saved in $FULL_CHAIN" + awk -v CERT_FILE="$gc_certfile" -v CA_CERT="$gc_cafile" 'BEGIN {outfile=CERT_FILE} split_after==1 {outfile=CA_CERT;split_after=0} /-----END CERTIFICATE-----/ {split_after=1} {print > outfile}' "$FULL_CHAIN" + info "Certificate saved in $gc_certfile" fi +} - CURL="$CURL --user-agent $CURL_USERAGENT --silent --dump-header $CURL_HEADER " +get_cr() { # get curl response + url="$1" + debug url "$url" + response=$(curl --user-agent "$CURL_USERAGENT" --silent "$url") + ret=$? + debug response "$response" + code=$(json_get "$response" status) + debug code "$code" + debug "get_cr return code $ret" + return $ret +} - if [[ ${_USE_DEBUG} -eq 1 ]]; then - CURL="$CURL --trace-ascii $dp " +get_os() { # function to get the current Operating System + uname_res=$(uname -s) + if [[ $(date -h 2>&1 | grep -ic busybox) -gt 0 ]]; then + os="busybox" + elif [[ ${uname_res} == "Linux" ]]; then + os="linux" + elif [[ ${uname_res} == "FreeBSD" ]]; then + os="bsd" + elif [[ ${uname_res} == "Darwin" ]]; then + os="mac" + elif [[ ${uname_res:0:6} == "CYGWIN" ]]; then + os="cygwin" + elif [[ ${uname_res:0:5} == "MINGW" ]]; then + os="mingw" + else + os="unknown" fi + debug "detected os type = $os" + if [[ -f /etc/issue ]]; then + debug "Running $(cat /etc/issue)" + fi +} - # convert payload to url base 64 - payload64="$(printf '%s' "${payload}" | urlbase64)" +get_signing_params() { # get signing parameters from key + skey=$1 + if openssl rsa -in "${skey}" -noout 2>/dev/null ; then # RSA key + pub_exp64=$(openssl rsa -in "${skey}" -noout -text \ + | grep publicExponent \ + | grep -oE "0x[a-f0-9]+" \ + | cut -d'x' -f2 \ + | hex2bin \ + | urlbase64) + pub_mod64=$(openssl rsa -in "${skey}" -noout -modulus \ + | cut -d'=' -f2 \ + | hex2bin \ + | urlbase64) - # get nonce from ACME server - if [[ $API -eq 1 ]]; then - nonceurl="$CA/directory" - nonce=$($CURL -I $nonceurl | grep "^Replay-Nonce:" | awk '{print $2}' | tr -d '\r\n ') - else # APIv2 - nonce=$($CURL -I "$URL_newNonce" | grep "^Replay-Nonce:" | awk '{print $2}' | tr -d '\r\n ') + jwk='{"e":"'"${pub_exp64}"'","kty":"RSA","n":"'"${pub_mod64}"'"}' + jwkalg="RS256" + signalg="sha256" + elif openssl ec -in "${skey}" -noout 2>/dev/null ; then # Elliptic curve key. + crv="$(openssl ec -in "$skey" -noout -text 2>/dev/null | awk '$2 ~ "CURVE:" {print $3}')" + if [[ -z "$crv" ]]; then + gsp_keytype="$(openssl ec -in "$skey" -noout -text 2>/dev/null \ + | grep "^ASN1 OID:" \ + | awk '{print $3}')" + case "$gsp_keytype" in + prime256v1) crv="P-256" ;; + secp384r1) crv="P-384" ;; + secp521r1) crv="P-521" ;; + *) error_exit "invalid curve algorithm type $gsp_keytype";; + esac + fi + case "$crv" in + P-256) jwkalg="ES256" ; signalg="sha256" ;; + P-384) jwkalg="ES384" ; signalg="sha384" ;; + P-521) jwkalg="ES512" ; signalg="sha512" ;; + *) error_exit "invalid curve algorithm type $crv";; + esac + pubtext="$(openssl ec -in "$skey" -noout -text 2>/dev/null \ + | awk '/^pub:/{p=1;next}/^ASN1 OID:/{p=0}p' \ + | tr -d ": \n\r")" + mid=$(( (${#pubtext} -2) / 2 + 2 )) + x64=$(echo "$pubtext" | cut -b 3-$mid | hex2bin | urlbase64) + y64=$(echo "$pubtext" | cut -b $((mid+1))-${#pubtext} | hex2bin | urlbase64) + jwk='{"crv":"'"$crv"'","kty":"EC","x":"'"$x64"'","y":"'"$y64"'"}' + else + error_exit "Invalid key file" fi + thumbprint="$(printf "%s" "$jwk" | openssl dgst -sha256 -binary | urlbase64)" + debug "jwk alg = $jwkalg" +} - nonceproblem="true" - while [[ "$nonceproblem" == "true" ]]; do +graceful_exit() { # normal exit function. + clean_up + exit +} - debug nonce "$nonce" +help_message() { # print out the help message + cat <<- _EOF_ + $PROGNAME ver. $VERSION + Obtain SSL certificates from the letsencrypt.org ACME server - # Build header with just our public key and algorithm information - header='{"alg": "'"$jwkalg"'", "jwk": '"$jwk"'}' + $(usage) - # Build another header which also contains the previously received nonce and encode it as urlbase64 - if [[ $API -eq 1 ]]; then - protected='{"alg": "'"$jwkalg"'", "jwk": '"$jwk"', "nonce": "'"${nonce}"'", "url": "'"${url}"'"}' - protected64="$(printf '%s' "${protected}" | urlbase64)" - else # APIv2 - if [[ -z "$KID" ]]; then - debug "KID is blank, so using jwk" - protected='{"alg": "'"$jwkalg"'", "jwk": '"$jwk"', "nonce": "'"${nonce}"'", "url": "'"${url}"'"}' - protected64="$(printf '%s' "${protected}" | urlbase64)" - else - debug "using KID=${KID}" - protected="{\"alg\": \"$jwkalg\", \"kid\": \"$KID\",\"nonce\": \"${nonce}\", \"url\": \"${url}\"}" - protected64="$(printf '%s' "${protected}" | urlbase64)" - fi - fi - - # Sign header with nonce and our payload with our private key and encode signature as urlbase64 - sign_string "$(printf '%s' "${protected64}.${payload64}")" "${ACCOUNT_KEY}" "$signalg" - - # Send header + extended header + payload + signature to the acme-server - if [[ $API -eq 1 ]]; then - debug "header = $header" - debug "protected = $protected" - debug "payload = $payload" - body="{\"header\": ${header}," - body="${body}\"protected\": \"${protected64}\"," - body="${body}\"payload\": \"${payload64}\"," - body="${body}\"signature\": \"${signed64}\"}" - debug "header, payload and signature = $body" - else - debug "protected = $protected" - debug "payload = $payload" - body="{" - body="${body}\"protected\": \"${protected64}\"," - body="${body}\"payload\": \"${payload64}\"," - body="${body}\"signature\": \"${signed64}\"}" - debug "header, payload and signature = $body" - fi - - code="500" - loop_limit=5 - while [[ "$code" -eq 500 ]]; do - if [[ "$outfile" ]] ; then - $CURL -X POST -H "Content-Type: application/jose+json" --data "$body" "$url" > "$outfile" - response=$(cat "$outfile") - elif [[ "$needbase64" ]] ; then - response=$($CURL -X POST -H "Content-Type: application/jose+json" --data "$body" "$url" | urlbase64) - else - response=$($CURL -X POST -H "Content-Type: application/jose+json" --data "$body" "$url") - fi - - if [[ "$response" == "" ]]; then - error_exit "ERROR curl \"$url\" returned nothing" - fi - - responseHeaders=$(cat "$CURL_HEADER") - if [[ "$needbase64" && ${response##*()} != "{"* ]]; then - # response is in base64 too, decode - response=$(urlbase64_decode "$response") - fi + Options: + -a, --all Check all certificates + -d, --debug Output debug information + -c, --create Create default config files + -f, --force Force renewal of cert (overrides expiry checks) + -h, --help Display this help message and exit + -q, --quiet Quiet mode (only outputs on error, success of new cert, or getssl was upgraded) + -Q, --mute Like -q, but also mute notification about successful upgrade + -r, --revoke "cert" "key" [CA_server] Revoke a certificate (the cert and key are required) + -u, --upgrade Upgrade getssl if a more recent version is available + -k, --keep "#" Maximum number of old getssl versions to keep when upgrading + -U, --nocheck Do not check if a more recent version is available + -w working_dir "Working directory" - debug responseHeaders "$responseHeaders" - debug response "$response" - code=$(awk ' $1 ~ "^HTTP" {print $2}' "$CURL_HEADER" | tail -1) - debug code "$code" - if [[ $API -eq 1 ]]; then - response_status=$(json_get "$response" status \ - | head -1| awk -F'"' '{print $2}') - else # APIv2 - if [[ "$outfile" && "$response" ]]; then - debug "response written to $outfile" - elif [[ ${response##*()} == "{"* ]]; then - response_status=$(json_get "$response" status) - else - debug "response not in json format" - debug "$response" - fi - fi - debug "response status = $response_status" - if [[ "$code" -eq 500 ]]; then - info "error on acme server - trying again ...." - debug "loop_limit = $loop_limit" - sleep 5 - loop_limit=$((loop_limit - 1)) - if [[ $loop_limit -lt 1 ]]; then - error_exit "500 error from ACME server: $response" - fi - fi - if [[ "$code" -eq 429 ]]; then - error_exit "429 rate limited error from ACME server" - fi - done - if [[ $response == *"error:badNonce"* ]]; then - debug "bad nonce" - nonce=$(echo "$responseHeaders" | grep -i "^replay-nonce:" | awk '{print $2}' | tr -d '\r\n ') - debug "trying new nonce $nonce" - else - nonceproblem="false" - fi - done + _EOF_ } -sign_string() { # sign a string with a given key and algorithm and return urlbase64 - # sets the result in variable signed64 - str=$1 - key=$2 - signalg=$3 - - if openssl rsa -in "${skey}" -noout 2>/dev/null ; then # RSA key - signed64="$(printf '%s' "${str}" | openssl dgst -"$signalg" -sign "$key" | urlbase64)" - elif openssl ec -in "${skey}" -noout 2>/dev/null ; then # Elliptic curve key. - signed=$(printf '%s' "${str}" | openssl dgst -"$signalg" -sign "$key" -hex | awk '{print $2}') - debug "EC signature $signed" - if [[ "${signed:4:4}" == "0220" ]]; then #sha256 - R=$(echo "$signed" | cut -c 9-72) - part2=$(echo "$signed" | cut -c 73-) - elif [[ "${signed:4:4}" == "0221" ]]; then #sha256 - R=$(echo "$signed" | cut -c 11-74) - part2=$(echo "$signed" | cut -c 75-) - elif [[ "${signed:4:4}" == "0230" ]]; then #sha384 - R=$(echo "$signed" | cut -c 9-104) - part2=$(echo "$signed" | cut -c 105-) - elif [[ "${signed:4:4}" == "0231" ]]; then #sha384 - R=$(echo "$signed" | cut -c 11-106) - part2=$(echo "$signed" | cut -c 107-) - elif [[ "${signed:6:4}" == "0241" ]]; then #sha512 - R=$(echo "$signed" | cut -c 11-140) - part2=$(echo "$signed" | cut -c 141-) - elif [[ "${signed:6:4}" == "0242" ]]; then #sha512 - R=$(echo "$signed" | cut -c 11-142) - part2=$(echo "$signed" | cut -c 143-) - else - error_exit "error in EC signing couldn't get R from $signed" - fi - debug "R $R" - - if [[ "${part2:0:4}" == "0220" ]]; then #sha256 - S=$(echo "$part2" | cut -c 5-68) - elif [[ "${part2:0:4}" == "0221" ]]; then #sha256 - S=$(echo "$part2" | cut -c 7-70) - elif [[ "${part2:0:4}" == "0230" ]]; then #sha384 - S=$(echo "$part2" | cut -c 5-100) - elif [[ "${part2:0:4}" == "0231" ]]; then #sha384 - S=$(echo "$part2" | cut -c 7-102) - elif [[ "${part2:0:4}" == "0241" ]]; then #sha512 - S=$(echo "$part2" | cut -c 5-136) - elif [[ "${part2:0:4}" == "0242" ]]; then #sha512 - S=$(echo "$part2" | cut -c 5-136) - else - error_exit "error in EC signing couldn't get S from $signed" - fi +hex2bin() { # Remove spaces, add leading zero, escape as hex string ensuring no trailing new line char +# printf -- "$(cat | os_esed -e 's/[[:space:]]//g' -e 's/^(.(.{2})*)$/0\1/' -e 's/(.{2})/\\x\1/g')" + echo -e -n "$(cat | os_esed -e 's/[[:space:]]//g' -e 's/^(.(.{2})*)$/0\1/' -e 's/(.{2})/\\x\1/g')" +} - debug "S $S" - signed64=$(printf '%s' "${R}${S}" | hex2bin | urlbase64 ) - debug "encoded RS $signed64" +info() { # write out info as long as the quiet flag has not been set. + if [[ ${_QUIET} -eq 0 ]]; then + echo "$@" fi } -signal_exit() { # Handle trapped signals - case $1 in - INT) - error_exit "Program interrupted by user" ;; - TERM) - echo -e "\n$PROGNAME: Program terminated" >&2 - graceful_exit ;; - *) - error_exit "$PROGNAME: Terminating on unknown signal" ;; - esac +json_awk() { # AWK json converter used for API2 - needs tidying up ;) +# shellcheck disable=SC2086 +echo "$1" | tr -d '\n' | awk ' +{ + tokenize($0) # while(get_token()) {print TOKEN} + if (0 == parse()) { + apply(JPATHS, NJPATHS) + } } -urlbase64() { # urlbase64: base64 encoded string with '+' replaced with '-' and '/' replaced with '_' - openssl base64 -e | tr -d '\n\r' | os_esed -e 's:=*$::g' -e 'y:+/:-_:' +function apply (ary,size,i) { + for (i=1; i "$1" <<- _EOF_getssl_ - # This file is read first and is common to all domains - # - # Uncomment and modify any variables you need - # see https://github.com/srvrco/getssl/wiki/Config-variables for details - # - # The staging server is best for testing (hence set as default) - CA="https://acme-staging-v02.api.letsencrypt.org" - # This server issues full certificates, however has rate limits - #CA="https://acme-v02.api.letsencrypt.org" - - # The agreement that must be signed with the CA, if not defined the default agreement will be used - #AGREEMENT="$AGREEMENT" +function parse( ret) { + get_token() + if (ret = parse_value()) { + return ret + } + if (get_token()) { + report("EOF", TOKEN) + return 11 + } + return 0 +} - # Set an email address associated with your account - generally set at account level rather than domain. - #ACCOUNT_EMAIL="me@example.com" - ACCOUNT_KEY_LENGTH=4096 - ACCOUNT_KEY="$WORKING_DIR/account.key" +function report(expected, got, i,from,to,context) { + from = ITOKENS - 10; if (from < 1) from = 1 + to = ITOKENS + 10; if (to > NTOKENS) to = NTOKENS + for (i = from; i < ITOKENS; i++) + context = context sprintf("%s ", TOKENS[i]) + context = context "<<" got ">> " + for (i = ITOKENS + 1; i <= to; i++) + context = context sprintf("%s ", TOKENS[i]) + scream("json_awk expected <" expected "> but got <" got "> at input token " ITOKENS "\n" context) +} - # Account key and private key types - can be rsa, prime256v1, secp384r1 or secp521r1 - #ACCOUNT_KEY_TYPE="rsa" - PRIVATE_KEY_ALG="rsa" - #REUSE_PRIVATE_KEY="true" +function reset() { + TOKEN=""; delete TOKENS; NTOKENS=ITOKENS=0 + delete JPATHS; NJPATHS=0 + VALUE="" +} - # The command needed to reload apache / nginx or whatever you use - #RELOAD_CMD="" +function scream(msg) { + FAILS[FILENAME] = FAILS[FILENAME] (FAILS[FILENAME]!="" ? "\n" : "") msg + msg = FILENAME ": " msg + print msg >"/dev/stderr" +} - # The time period within which you want to allow renewal of a certificate - # this prevents hitting some of the rate limits. - # Creating a file called FORCE_RENEWAL in the domain directory allows one-off overrides - # of this setting - RENEW_ALLOW="30" +function tokenize(a1,pq,pb,ESCAPE,CHAR,STRING,NUMBER,KEYWORD,SPACE) { + SPACE="[ \t\n]+" + gsub(/"[^\001-\037"\\]*((\\[^u\001-\037]|\\u[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F])[^\001-\037"\\]*)*"|-?(0|[1-9][0-9]*)([.][0-9]*)?([eE][+-]?[0-9]*)?|null|false|true|[ \t\n]+|./, "\n&", a1) + gsub("\n" SPACE, "\n", a1) + sub(/^\n/, "", a1) + ITOKENS=0 # get_token() helper + return NTOKENS = split(a1, TOKENS, /\n/) +}' +} - # Define the server type. This can be https, ftp, ftpi, imap, imaps, pop3, pop3s, smtp, - # smtps_deprecated, smtps, smtp_submission, xmpp, xmpps, ldaps or a port number which - # will be checked for certificate expiry and also will be checked after - # an update to confirm correct certificate is running (if CHECK_REMOTE) is set to true - SERVER_TYPE="https" - CHECK_REMOTE="true" +json_get() { # get values from json + if [[ -z "$1" ]] || [[ "$1" == "null" ]]; then + echo "json was blank" + return + fi + if [[ $API = 1 ]]; then + # remove newlines, so it's a single chunk of JSON + json_data=$( echo "$1" | tr '\n' ' ') + # if $3 is defined, this is the section which the item is in. + if [[ -n "$3" ]]; then + jg_section=$(echo "$json_data" | awk -F"[}]" '{for(i=1;i<=NF;i++){if($i~/\"'"${3}"'\"/){print $i}}}') + if [[ "$2" == "uri" ]]; then + jg_subsect=$(echo "$jg_section" | awk -F"[,]" '{for(i=1;i<=NF;i++){if($i~/\"'"${2}"'\"/){print $(i)}}}') + jg_result=$(echo "$jg_subsect" | awk -F'"' '{print $4}') + else + jg_result=$(echo "$jg_section" | awk -F"[,:}]" '{for(i=1;i<=NF;i++){if($i~/\"'"${2}"'\"/){print $(i+1)}}}') + fi + else + jg_result=$(echo "$json_data" |awk -F"[,:}]" '{for(i=1;i<=NF;i++){if($i~/\"'"${2}"'\"/){print $(i+1)}}}') + fi + # check number of quotes + jg_q=${jg_result//[^\"]/} + # if 2 quotes, assume it's a quoted variable and just return the data within the quotes. + if [[ ${#jg_q} -eq 2 ]]; then + echo "$jg_result" | awk -F'"' '{print $2}' + else + echo "$jg_result" + fi + else + if [[ -n "$6" ]]; then + full=$(json_awk "$1") + section=$(echo "$full" | grep "\"$2\"" | grep "\"$3\"" | grep "\"$4\"" | awk -F"," '{print $2}') + echo "$full" | grep "^..${5}\",$section\]" | awk '{print $2}' | tr -d '"' + elif [[ -n "$5" ]]; then + full=$(json_awk "$1") + section=$(echo "$full" | grep "\"$2\"" | grep "\"$3\"" | grep "\"$4\"" | awk -F"," '{print $2}') + echo "$full" | grep "^..${2}\",$section" | grep "$5" | awk '{print $2}' | tr -d '"' + elif [[ -n "$3" ]]; then + json_awk "$1" | grep "^..${2}...${3}" | awk '{print $2}' | tr -d '"' + elif [[ -n "$2" ]]; then + json_awk "$1" | grep "^..${2}" | awk '{print $2}' | tr -d '"' + else + json_awk "$1" + fi + fi +} - # Use the following 3 variables if you want to validate via DNS - #VALIDATE_VIA_DNS="true" - #DNS_ADD_COMMAND= - #DNS_DEL_COMMAND= - _EOF_getssl_ +os_esed() { # Use different sed version for different os types (extended regex) + if [[ "$os" == "bsd" ]]; then # BSD requires -E flag for extended regex + sed -E "${@}" + elif [[ "$os" == "mac" ]]; then # MAC uses older BSD style sed. + sed -E "${@}" + else + sed -r "${@}" + fi } -write_openssl_conf() { # write out a minimal openssl conf - cat > "$1" <<- _EOF_openssl_conf_ - # minimal openssl.cnf file - distinguished_name = req_distinguished_name - [ req_distinguished_name ] - [v3_req] - [v3_ca] - _EOF_openssl_conf_ +purge_archive() { # purge archive of old, invalid, certificates + arcdir="$1/archive" + debug "purging archives in ${arcdir}/" + for padir in "$arcdir"/????_??_??_??_??; do + # check each directory + if [[ -d "$padir" ]]; then + tstamp=$(basename "$padir"| awk -F"_" '{print $1"-"$2"-"$3" "$4":"$5}') + if [[ "$os" == "bsd" ]]; then + direpoc=$(date -j -f "%F %H:%M" "$tstamp" +%s) + elif [[ "$os" == "mac" ]]; then + direpoc=$(date -j -f "%F %H:%M" "$tstamp" +%s) + else + direpoc=$(date -d "$tstamp" +%s) + fi + current_epoc=$(date "+%s") + # as certs currently valid for 90 days, purge anything older than 100 + purgedate=$((current_epoc - 60*60*24*100)) + if [[ "$direpoc" -lt "$purgedate" ]]; then + echo "purge $padir" + rm -rf "${padir:?}" + fi + fi + done } -# Trap signals -trap "signal_exit TERM" TERM HUP -trap "signal_exit INT" INT - -# Parse command-line -while [[ -n ${1+defined} ]]; do - case $1 in - -h | --help) - help_message; graceful_exit ;; - -d | --debug) - _USE_DEBUG=1 ;; - -c | --create) - _CREATE_CONFIG=1 ;; - -f | --force) - _FORCE_RENEW=1 ;; - -a | --all) - _CHECK_ALL=1 ;; - -k | --keep) - shift; _KEEP_VERSIONS="$1";; - -q | --quiet) - _QUIET=1 ;; - -Q | --mute) - _QUIET=1 - _MUTE=1 ;; - -r | --revoke) - _REVOKE=1 - shift - REVOKE_CERT="$1" - shift - REVOKE_KEY="$1" - shift - REVOKE_CA="$1" ;; - -u | --upgrade) - _UPGRADE=1 ;; - -U | --nocheck) - _UPGRADE_CHECK=0 ;; - -w) - shift; WORKING_DIR="$1" ;; - -*) - usage - error_exit "Unknown option $1" ;; - *) - if [[ -n $DOMAIN ]]; then - error_exit "invalid command line $DOMAIN - it appears to contain more than one domain" +reload_service() { # Runs a command to reload services ( via ssh if needed) + if [[ -n "$RELOAD_CMD" ]]; then + info "reloading SSL services" + if [[ "${RELOAD_CMD:0:4}" == "ssh:" ]] ; then + sshhost=$(echo "$RELOAD_CMD"| awk -F: '{print $2}') + command=${RELOAD_CMD:(( ${#sshhost} + 5))} + debug "running following command to reload cert" + debug "ssh $SSH_OPTS $sshhost ${command}" + # shellcheck disable=SC2029 + # shellcheck disable=SC2086 + ssh $SSH_OPTS "$sshhost" "${command}" 1>/dev/null 2>&1 + # allow 2 seconds for services to restart + sleep 2 + else + debug "running reload command $RELOAD_CMD" + if ! eval "$RELOAD_CMD" ; then + error_exit "error running $RELOAD_CMD" fi - DOMAIN="$1" - if [[ -z $DOMAIN ]]; then - error_exit "invalid command line - it appears to contain a null variable" - fi ;; - esac - shift -done - -# Main logic -############ - -# Get the current OS, so the correct functions can be used for that OS. (sets the variable os) -get_os - -# check if "recent" version of bash. -#if [[ "${BASH_VERSINFO[0]}${BASH_VERSINFO[1]}" -lt 42 ]]; then -# info "this script is designed for bash v4.2 or later - earlier version may give errors" -#fi - -#check if required applications are included - -requires which -requires openssl -requires curl -requires nslookup drill dig host DNS_CHECK_FUNC -requires awk -requires tr -requires date -requires grep -requires sed -requires sort -requires mktemp - -# Check if upgrades are available (unless they have specified -U to ignore Upgrade checks) -if [[ $_UPGRADE_CHECK -eq 1 ]]; then - check_getssl_upgrade -fi + fi + fi +} -# Revoke a certificate if requested -if [[ $_REVOKE -eq 1 ]]; then - if [[ -z $REVOKE_CA ]]; then - CA=$DEFAULT_REVOKE_CA - elif [[ "$REVOKE_CA" == "-d" ]]; then - _USE_DEBUG=1 - CA=$DEFAULT_REVOKE_CA +revoke_certificate() { # revoke a certificate + debug "revoking cert $REVOKE_CERT" + debug "using key $REVOKE_KEY" + ACCOUNT_KEY="$REVOKE_KEY" + # need to set the revoke key as "account_key" since it's used in send_signed_request. + get_signing_params "$REVOKE_KEY" + TEMP_DIR=$(mktemp -d 2>/dev/null || mktemp -d -t getssl) + debug "revoking from $CA" + rcertdata=$(openssl x509 -in "$REVOKE_CERT" -inform PEM -outform DER | urlbase64) + send_signed_request "$URL_revoke" "{\"resource\": \"revoke-cert\", \"certificate\": \"$rcertdata\"}" + if [[ $code -eq "200" ]]; then + info "certificate revoked" else - CA=$REVOKE_CA + error_exit "Revocation failed: $(echo "$response" | grep "detail")" fi - URL_revoke=$(curl --user-agent "$CURL_USERAGENT" "${CA}/directory" 2>/dev/null | grep "revoke-cert" | awk -F'"' '{print $4}') - revoke_certificate - graceful_exit -fi +} -# get latest agreement from CA (as default) -AGREEMENT=$(curl --user-agent "$CURL_USERAGENT" -I "${CA}/terms" 2>/dev/null | awk 'tolower($1) ~ "location:" {print $2}'|tr -d '\r') +requires() { # check if required function is available + if [[ "$#" -gt 1 ]]; then # if more than 1 value, check list + for i in "$@"; do + if [[ "$i" == "${!#}" ]]; then # if on last variable then exit as not found + error_exit "this script requires one of: ${*:1:$(($#-1))}" + fi + res=$(command -v "$i" 2>/dev/null) + debug "checking for $i ... $res" + if [[ -n "$res" ]]; then # if function found, then set variable to function and return + debug "function $i found at $res - setting ${!#} to $i" + eval "${!#}=\$i" + return + fi + done + else # only one value, so check it. + result=$(command -v "$1" 2>/dev/null) + debug "checking for required $1 ... $result" + if [[ -z "$result" ]]; then + error_exit "This script requires $1 installed" + fi + fi +} -# if nothing in command line, print help and exit. -if [[ -z "$DOMAIN" ]] && [[ ${_CHECK_ALL} -ne 1 ]]; then - help_message - graceful_exit -fi +set_server_type() { # uses SERVER_TYPE to set REMOTE_PORT and REMOTE_EXTRA + if [[ ${SERVER_TYPE} == "https" ]] || [[ ${SERVER_TYPE} == "webserver" ]]; then + REMOTE_PORT=443 + elif [[ ${SERVER_TYPE} == "ftp" ]]; then + REMOTE_PORT=21 + REMOTE_EXTRA="-starttls ftp" + elif [[ ${SERVER_TYPE} == "ftpi" ]]; then + REMOTE_PORT=990 + elif [[ ${SERVER_TYPE} == "imap" ]]; then + REMOTE_PORT=143 + REMOTE_EXTRA="-starttls imap" + elif [[ ${SERVER_TYPE} == "imaps" ]]; then + REMOTE_PORT=993 + elif [[ ${SERVER_TYPE} == "pop3" ]]; then + REMOTE_PORT=110 + REMOTE_EXTRA="-starttls pop3" + elif [[ ${SERVER_TYPE} == "pop3s" ]]; then + REMOTE_PORT=995 + elif [[ ${SERVER_TYPE} == "smtp" ]]; then + REMOTE_PORT=25 + REMOTE_EXTRA="-starttls smtp" + elif [[ ${SERVER_TYPE} == "smtps_deprecated" ]]; then + REMOTE_PORT=465 + elif [[ ${SERVER_TYPE} == "smtps" ]] || [[ ${SERVER_TYPE} == "smtp_submission" ]]; then + REMOTE_PORT=587 + REMOTE_EXTRA="-starttls smtp" + elif [[ ${SERVER_TYPE} == "xmpp" ]]; then + REMOTE_PORT=5222 + REMOTE_EXTRA="-starttls xmpp" + elif [[ ${SERVER_TYPE} == "xmpps" ]]; then + REMOTE_PORT=5269 + elif [[ ${SERVER_TYPE} == "ldaps" ]]; then + REMOTE_PORT=636 + elif [[ ${SERVER_TYPE} =~ ^[0-9]+$ ]]; then + REMOTE_PORT=${SERVER_TYPE} + else + info "${DOMAIN}: unknown server type \"$SERVER_TYPE\" in SERVER_TYPE" + config_errors=true + fi +} -# if the "working directory" doesn't exist, then create it. -if [[ ! -d "$WORKING_DIR" ]]; then - debug "Making working directory - $WORKING_DIR" - mkdir -p "$WORKING_DIR" -fi +send_signed_request() { # Sends a request to the ACME server, signed with your private key. + url=$1 + payload=$2 + needbase64=$3 + outfile=$4 # save response into this file (certificate data) -# read any variables from config in working directory -if [[ -s "$WORKING_DIR/getssl.cfg" ]]; then - debug "reading config from $WORKING_DIR/getssl.cfg" - # shellcheck source=/dev/null - . "$WORKING_DIR/getssl.cfg" -fi + debug url "$url" -# Define defaults for variables not set in the main config. -ACCOUNT_KEY="${ACCOUNT_KEY:=$WORKING_DIR/account.key}" -DOMAIN_STORAGE="${DOMAIN_STORAGE:=$WORKING_DIR}" -DOMAIN_DIR="$DOMAIN_STORAGE/$DOMAIN" -CERT_FILE="$DOMAIN_DIR/${DOMAIN}.crt" -FULL_CHAIN="$DOMAIN_DIR/fullchain.crt" -CA_CERT="$DOMAIN_DIR/chain.crt" -TEMP_DIR="$DOMAIN_DIR/tmp" -if [[ "$os" == "mingw" ]]; then - CSR_SUBJECT="//" -fi + CURL_HEADER="$TEMP_DIR/curl.header" + dp="$TEMP_DIR/curl.dump" -# Set the OPENSSL_CONF environment variable so openssl knows which config to use -export OPENSSL_CONF=$SSLCONF + CURL="curl " + # shellcheck disable=SC2072 + if [[ "$($CURL -V | head -1 | cut -d' ' -f2 )" > "7.33" ]]; then + CURL="$CURL --http1.1 " + fi -# if "-a" option then check other parameters and create run for each domain. -if [[ ${_CHECK_ALL} -eq 1 ]]; then - info "Check all certificates" + CURL="$CURL --user-agent $CURL_USERAGENT --silent --dump-header $CURL_HEADER " - if [[ ${_CREATE_CONFIG} -eq 1 ]]; then - error_exit "cannot combine -c|--create with -a|--all" + if [[ ${_USE_DEBUG} -eq 1 ]]; then + CURL="$CURL --trace-ascii $dp " fi - if [[ ${_FORCE_RENEW} -eq 1 ]]; then - error_exit "cannot combine -f|--force with -a|--all because of rate limits" - fi + # convert payload to url base 64 + payload64="$(printf '%s' "${payload}" | urlbase64)" - if [[ ! -d "$DOMAIN_STORAGE" ]]; then - error_exit "DOMAIN_STORAGE not found - $DOMAIN_STORAGE" + # get nonce from ACME server + if [[ $API -eq 1 ]]; then + nonceurl="$CA/directory" + nonce=$($CURL -I $nonceurl | grep "^Replay-Nonce:" | awk '{print $2}' | tr -d '\r\n ') + else # APIv2 + nonce=$($CURL -I "$URL_newNonce" | grep "^Replay-Nonce:" | awk '{print $2}' | tr -d '\r\n ') fi - for dir in "${DOMAIN_STORAGE}"/*; do - if [[ -d "$dir" ]]; then - debug "Checking $dir" - cmd="$0 -U" # No update checks when calling recursively - if [[ ${_USE_DEBUG} -eq 1 ]]; then - cmd="$cmd -d" + nonceproblem="true" + while [[ "$nonceproblem" == "true" ]]; do + + # Build header with just our public key and algorithm information + header='{"alg": "'"$jwkalg"'", "jwk": '"$jwk"'}' + + # Build another header which also contains the previously received nonce and encode it as urlbase64 + if [[ $API -eq 1 ]]; then + protected='{"alg": "'"$jwkalg"'", "jwk": '"$jwk"', "nonce": "'"${nonce}"'", "url": "'"${url}"'"}' + protected64="$(printf '%s' "${protected}" | urlbase64)" + else # APIv2 + if [[ -z "$KID" ]]; then + debug "KID is blank, so using jwk" + protected='{"alg": "'"$jwkalg"'", "jwk": '"$jwk"', "nonce": "'"${nonce}"'", "url": "'"${url}"'"}' + protected64="$(printf '%s' "${protected}" | urlbase64)" + else + debug "using KID=${KID}" + protected="{\"alg\": \"$jwkalg\", \"kid\": \"$KID\",\"nonce\": \"${nonce}\", \"url\": \"${url}\"}" + protected64="$(printf '%s' "${protected}" | urlbase64)" fi - if [[ ${_QUIET} -eq 1 ]]; then - cmd="$cmd -q" + fi + + # Sign header with nonce and our payload with our private key and encode signature as urlbase64 + sign_string "$(printf '%s' "${protected64}.${payload64}")" "${ACCOUNT_KEY}" "$signalg" + + # Send header + extended header + payload + signature to the acme-server + debug "payload = $payload" + if [[ $API -eq 1 ]]; then + body="{\"header\": ${header}," + body="${body}\"protected\": \"${protected64}\"," + body="${body}\"payload\": \"${payload64}\"," + body="${body}\"signature\": \"${signed64}\"}" + else + body="{" + body="${body}\"protected\": \"${protected64}\"," + body="${body}\"payload\": \"${payload64}\"," + body="${body}\"signature\": \"${signed64}\"}" + fi + + code="500" + loop_limit=5 + while [[ "$code" -eq 500 ]]; do + if [[ "$outfile" ]] ; then + $CURL -X POST -H "Content-Type: application/jose+json" --data "$body" "$url" > "$outfile" + response=$(cat "$outfile") + elif [[ "$needbase64" ]] ; then + response=$($CURL -X POST -H "Content-Type: application/jose+json" --data "$body" "$url" | urlbase64) + else + response=$($CURL -X POST -H "Content-Type: application/jose+json" --data "$body" "$url") fi - # check if $dir is a directory with a getssl.cfg in it - if [[ -f "$dir/getssl.cfg" ]]; then - cmd="$cmd -w $WORKING_DIR $(basename "$dir")" - debug "CMD: $cmd" - eval "$cmd" + + if [[ "$response" == "" ]]; then + error_exit "ERROR curl \"$url\" returned nothing" fi + + responseHeaders=$(cat "$CURL_HEADER") + if [[ "$needbase64" && ${response##*()} != "{"* ]]; then + # response is in base64 too, decode + response=$(urlbase64_decode "$response") + fi + + debug responseHeaders "$responseHeaders" + debug response "$response" + code=$(awk ' $1 ~ "^HTTP" {print $2}' "$CURL_HEADER" | tail -1) + debug code "$code" + if [[ "$code" == 4* && $response != *"error:badNonce"* ]]; then + detail=$(echo "$response" | grep "detail") + error_exit "ACME server returned error: ${code}: ${detail}" + fi + + if [[ $API -eq 1 ]]; then + response_status=$(json_get "$response" status \ + | head -1| awk -F'"' '{print $2}') + else # APIv2 + if [[ "$outfile" && "$response" ]]; then + debug "response written to $outfile" + elif [[ ${response##*()} == "{"* ]]; then + response_status=$(json_get "$response" status) + else + debug "response not in json format" + debug "$response" + fi + fi + debug "response status = $response_status" + if [[ "$code" -eq 500 ]]; then + info "error on acme server - trying again ...." + debug "loop_limit = $loop_limit" + sleep 5 + loop_limit=$((loop_limit - 1)) + if [[ $loop_limit -lt 1 ]]; then + error_exit "500 error from ACME server: $response" + fi + fi + done + if [[ $response == *"error:badNonce"* ]]; then + debug "bad nonce" + nonce=$(echo "$responseHeaders" | grep -i "^replay-nonce:" | awk '{print $2}' | tr -d '\r\n ') + debug "trying new nonce $nonce" + else + nonceproblem="false" fi done +} - graceful_exit -fi -# end of "-a" option (looping through all domains) +sign_string() { # sign a string with a given key and algorithm and return urlbase64 + # sets the result in variable signed64 + str=$1 + key=$2 + signalg=$3 -# if "-c|--create" option used, then create config files. -if [[ ${_CREATE_CONFIG} -eq 1 ]]; then - # If main config file does not exists then create it. - if [[ ! -s "$WORKING_DIR/getssl.cfg" ]]; then - info "creating main config file $WORKING_DIR/getssl.cfg" - if [[ ! -s "$SSLCONF" ]]; then - SSLCONF="$WORKING_DIR/openssl.cnf" - write_openssl_conf "$SSLCONF" + if openssl rsa -in "${skey}" -noout 2>/dev/null ; then # RSA key + signed64="$(printf '%s' "${str}" | openssl dgst -"$signalg" -sign "$key" | urlbase64)" + elif openssl ec -in "${skey}" -noout 2>/dev/null ; then # Elliptic curve key. + signed=$(printf '%s' "${str}" | openssl dgst -"$signalg" -sign "$key" -hex | awk '{print $2}') + debug "EC signature $signed" + if [[ "${signed:4:4}" == "0220" ]]; then #sha256 + R=$(echo "$signed" | cut -c 9-72) + part2=$(echo "$signed" | cut -c 73-) + elif [[ "${signed:4:4}" == "0221" ]]; then #sha256 + R=$(echo "$signed" | cut -c 11-74) + part2=$(echo "$signed" | cut -c 75-) + elif [[ "${signed:4:4}" == "0230" ]]; then #sha384 + R=$(echo "$signed" | cut -c 9-104) + part2=$(echo "$signed" | cut -c 105-) + elif [[ "${signed:4:4}" == "0231" ]]; then #sha384 + R=$(echo "$signed" | cut -c 11-106) + part2=$(echo "$signed" | cut -c 107-) + elif [[ "${signed:6:4}" == "0241" ]]; then #sha512 + R=$(echo "$signed" | cut -c 11-140) + part2=$(echo "$signed" | cut -c 141-) + elif [[ "${signed:6:4}" == "0242" ]]; then #sha512 + R=$(echo "$signed" | cut -c 11-142) + part2=$(echo "$signed" | cut -c 143-) + else + error_exit "error in EC signing couldn't get R from $signed" fi - write_getssl_template "$WORKING_DIR/getssl.cfg" - fi - # If domain and domain config don't exist then create them. - if [[ ! -d "$DOMAIN_DIR" ]]; then - info "Making domain directory - $DOMAIN_DIR" - mkdir -p "$DOMAIN_DIR" - fi - if [[ -s "$DOMAIN_DIR/getssl.cfg" ]]; then - info "domain config already exists $DOMAIN_DIR/getssl.cfg" - else - info "creating domain config file in $DOMAIN_DIR/getssl.cfg" - # if domain has an existing cert, copy from domain and use to create defaults. - EX_CERT=$(echo \ - | openssl s_client -servername "${DOMAIN}" -connect "${DOMAIN}:443" 2>/dev/null \ - | openssl x509 2>/dev/null) - EX_SANS="www.${DOMAIN}" - if [[ -n "${EX_CERT}" ]]; then - EX_SANS=$(echo "$EX_CERT" \ - | openssl x509 -noout -text 2>/dev/null| grep "Subject Alternative Name" -A2 \ - | grep -Eo "DNS:[a-zA-Z 0-9.-]*" | sed "s@DNS:$DOMAIN@@g" | grep -v '^$' | cut -c 5-) - EX_SANS=${EX_SANS//$'\n'/','} + debug "R $R" + + if [[ "${part2:0:4}" == "0220" ]]; then #sha256 + S=$(echo "$part2" | cut -c 5-68) + elif [[ "${part2:0:4}" == "0221" ]]; then #sha256 + S=$(echo "$part2" | cut -c 7-70) + elif [[ "${part2:0:4}" == "0230" ]]; then #sha384 + S=$(echo "$part2" | cut -c 5-100) + elif [[ "${part2:0:4}" == "0231" ]]; then #sha384 + S=$(echo "$part2" | cut -c 7-102) + elif [[ "${part2:0:4}" == "0241" ]]; then #sha512 + S=$(echo "$part2" | cut -c 5-136) + elif [[ "${part2:0:4}" == "0242" ]]; then #sha512 + S=$(echo "$part2" | cut -c 5-136) + else + error_exit "error in EC signing couldn't get S from $signed" fi - write_domain_template "$DOMAIN_DIR/getssl.cfg" + + debug "S $S" + signed64=$(printf '%s' "${R}${S}" | hex2bin | urlbase64 ) + debug "encoded RS $signed64" fi - TEMP_DIR="$DOMAIN_DIR/tmp" - # end of "-c|--create" option, so exit - graceful_exit -fi -# end of "-c|--create" option to create config file. +} -# if domain directory doesn't exist, then create it. -if [[ ! -d "$DOMAIN_DIR" ]]; then - debug "Making working directory - $DOMAIN_DIR" - mkdir -p "$DOMAIN_DIR" -fi +signal_exit() { # Handle trapped signals + case $1 in + INT) + error_exit "Program interrupted by user" ;; + TERM) + echo -e "\n$PROGNAME: Program terminated" >&2 + graceful_exit ;; + *) + error_exit "$PROGNAME: Terminating on unknown signal" ;; + esac +} -# define a temporary directory, and if it doesn't exist, create it. -TEMP_DIR="$DOMAIN_DIR/tmp" -if [[ ! -d "${TEMP_DIR}" ]]; then - debug "Making temp directory - ${TEMP_DIR}" - mkdir -p "${TEMP_DIR}" -fi +urlbase64() { # urlbase64: base64 encoded string with '+' replaced with '-' and '/' replaced with '_' + openssl base64 -e | tr -d '\n\r' | os_esed -e 's:=*$::g' -e 'y:+/:-_:' +} -# read any variables from config in domain directory -if [[ -s "$DOMAIN_DIR/getssl.cfg" ]]; then - debug "reading config from $DOMAIN_DIR/getssl.cfg" - # shellcheck source=/dev/null - . "$DOMAIN_DIR/getssl.cfg" -fi +# base64url decode +# From: https://gist.github.com/alvis/89007e96f7958f2686036d4276d28e47 +urlbase64_decode() { + INPUT=$1 # $(if [ -z "$1" ]; then echo -n $(cat -); else echo -n "$1"; fi) + MOD=$(($(echo -n "$INPUT" | wc -c) % 4)) + PADDING=$(if [ $MOD -eq 2 ]; then echo -n '=='; elif [ $MOD -eq 3 ]; then echo -n '=' ; fi) + echo -n "$INPUT$PADDING" | + sed s/-/+/g | + sed s/_/\\//g | + openssl base64 -d -A +} -# from SERVER_TYPE set REMOTE_PORT and REMOTE_EXTRA -set_server_type +usage() { # echos out the program usage + echo "Usage: $PROGNAME [-h|--help] [-d|--debug] [-c|--create] [-f|--force] [-a|--all] [-q|--quiet]"\ + "[-Q|--mute] [-u|--upgrade] [-k|--keep #] [-U|--nocheck] [-r|--revoke cert key] [-w working_dir] domain" +} -# check config for typical errors. -check_config +write_domain_template() { # write out a template file for a domain. + cat > "$1" <<- _EOF_domain_ + # This file is read second (and per domain if running with the -a option) + # and overwrites any settings from the first file + # + # Uncomment and modify any variables you need + # see https://github.com/srvrco/getssl/wiki/Config-variables for details + # see https://github.com/srvrco/getssl/wiki/Example-config-files for example configs + # + # The staging server is best for testing + #CA="https://acme-staging-v02.api.letsencrypt.org/" + # This server issues full certificates, however has rate limits + #CA="https://acme-v02.api.letsencrypt.org" -if [[ -e "$DOMAIN_DIR/FORCE_RENEWAL" ]]; then - rm -f "$DOMAIN_DIR/FORCE_RENEWAL" || error_exit "problem deleting file $DOMAIN_DIR/FORCE_RENEWAL" - _FORCE_RENEW=1 - info "${DOMAIN}: forcing renewal (due to FORCE_RENEWAL file)" -fi + # Private key types - can be rsa, prime256v1, secp384r1 or secp521r1 + #PRIVATE_KEY_ALG="rsa" -# Obtain CA resource locations -ca_all_loc=$(curl --user-agent "$CURL_USERAGENT" "${CA}" 2>/dev/null) -debug "ca_all_loc from ${CA} gives $ca_all_loc" -# APIv1 -URL_new_reg=$(echo "$ca_all_loc" | grep "new-reg" | awk -F'"' '{print $4}') -URL_new_authz=$(echo "$ca_all_loc" | grep "new-authz" | awk -F'"' '{print $4}') -URL_new_cert=$(echo "$ca_all_loc" | grep "new-cert" | awk -F'"' '{print $4}') -#API v2 -URL_newAccount=$(echo "$ca_all_loc" | grep "newAccount" | awk -F'"' '{print $4}') -URL_newNonce=$(echo "$ca_all_loc" | grep "newNonce" | awk -F'"' '{print $4}') -URL_newOrder=$(echo "$ca_all_loc" | grep "newOrder" | awk -F'"' '{print $4}') -if [[ -z "$URL_new_reg" ]] && [[ -z "$URL_newAccount" ]]; then - ca_all_loc=$(curl --user-agent "$CURL_USERAGENT" "${CA}/directory" 2>/dev/null) - debug "ca_all_loc from ${CA}/directory gives $ca_all_loc" - # APIv1 - URL_new_reg=$(echo "$ca_all_loc" | grep "new-reg" | awk -F'"' '{print $4}') - URL_new_authz=$(echo "$ca_all_loc" | grep "new-authz" | awk -F'"' '{print $4}') - URL_new_cert=$(echo "$ca_all_loc" | grep "new-cert" | awk -F'"' '{print $4}') - #API v2 - URL_newAccount=$(echo "$ca_all_loc" | grep "newAccount" | awk -F'"' '{print $4}') - URL_newNonce=$(echo "$ca_all_loc" | grep "newNonce" | awk -F'"' '{print $4}') - URL_newOrder=$(echo "$ca_all_loc" | grep "newOrder" | awk -F'"' '{print $4}') -fi + # Additional domains - this could be multiple domains / subdomains in a comma separated list + # Note: this is Additional domains - so should not include the primary domain. + SANS="${EX_SANS}" -if [[ -n "$URL_new_reg" ]]; then - API=1 -elif [[ -n "$URL_newAccount" ]]; then - API=2 -else - info "unknown API version" - graceful_exit -fi -debug "Using API v$API" + # Acme Challenge Location. The first line for the domain, the following ones for each additional domain. + # If these start with ssh: then the next variable is assumed to be the hostname and the rest the location. + # An ssh key will be needed to provide you with access to the remote server. + # Optionally, you can specify a different userid for ssh/scp to use on the remote server before the @ sign. + # If left blank, the username on the local server will be used to authenticate against the remote server. + # If these start with ftp: then the next variables are ftpuserid:ftppassword:servername:ACL_location + # These should be of the form "/path/to/your/website/folder/.well-known/acme-challenge" + # where "/path/to/your/website/folder/" is the path, on your web server, to the web root for your domain. + # You can also user WebDAV over HTTPS as transport mechanism. To do so, start with davs: followed by username, + # password, host, port (explicitly needed even if using default port 443) and path on the server. + #ACL=('/var/www/${DOMAIN}/web/.well-known/acme-challenge' + # 'ssh:server5:/var/www/${DOMAIN}/web/.well-known/acme-challenge' + # 'ssh:sshuserid@server5:/var/www/${DOMAIN}/web/.well-known/acme-challenge' + # 'ftp:ftpuserid:ftppassword:${DOMAIN}:/web/.well-known/acme-challenge' + # 'davs:davsuserid:davspassword:{DOMAIN}:443:/web/.well-known/acme-challenge') -# Check if awk supports json_awk (required for ACMEv2) -if [[ $API -eq 2 ]]; then - json_awk_test=$(json_awk '{ "test": "1" }' 2>/dev/null) - if [[ "${json_awk_test}" == "" ]]; then - error_exit "Your version of awk does not work with json_awk (see http://github.com/step-/JSON.awk/issues/6), please install a newer version of mawk or gawk" - fi -fi + # Specify SSH options, e.g. non standard port in SSH_OPTS + # (Can also use SCP_OPTS and SFTP_OPTS) + # SSH_OPTS=-p 12345 -# if check_remote is true then connect and obtain the current certificate (if not forcing renewal) -if [[ "${CHECK_REMOTE}" == "true" ]] && [[ $_FORCE_RENEW -eq 0 ]]; then - debug "getting certificate for $DOMAIN from remote server" - # shellcheck disable=SC2086 - EX_CERT=$(echo \ - | openssl s_client -servername "${DOMAIN}" -connect "${DOMAIN}:${REMOTE_PORT}" ${REMOTE_EXTRA} 2>/dev/null \ - | openssl x509 2>/dev/null) - if [[ -n "$EX_CERT" ]]; then # if obtained a cert - if [[ -s "$CERT_FILE" ]]; then # if local exists - CERT_LOCAL=$(openssl x509 -noout -fingerprint < "$CERT_FILE" 2>/dev/null) - else # since local doesn't exist leave empty so that the domain validation will happen - CERT_LOCAL="" - fi - CERT_REMOTE=$(echo "$EX_CERT" | openssl x509 -noout -fingerprint 2>/dev/null) - if [[ "$CERT_LOCAL" == "$CERT_REMOTE" ]]; then - debug "certificate on server is same as the local cert" - else - # check if the certificate is for the right domain - EX_CERT_DOMAIN=$(echo "$EX_CERT" | openssl x509 -text \ - | sed -n -e 's/^ *Subject: .* CN=\([A-Za-z0-9.-]*\).*$/\1/p; /^ *DNS:.../ { s/ *DNS://g; y/,/\n/; p; }' \ - | sort -u | grep "^$DOMAIN\$") - if [[ "$EX_CERT_DOMAIN" == "$DOMAIN" ]]; then - # check renew-date on ex_cert and compare to local ( if local exists) - enddate_ex=$(echo "$EX_CERT" | openssl x509 -noout -enddate 2>/dev/null| cut -d= -f 2-) - enddate_ex_s=$(date_epoc "$enddate_ex") - debug "external cert has enddate $enddate_ex ( $enddate_ex_s ) " - if [[ -s "$CERT_FILE" ]]; then # if local exists - enddate_lc=$(openssl x509 -noout -enddate < "$CERT_FILE" 2>/dev/null| cut -d= -f 2-) - enddate_lc_s=$(date_epoc "$enddate_lc") - debug "local cert has enddate $enddate_lc ( $enddate_lc_s ) " - else - enddate_lc_s=0 - debug "local cert doesn't exist" - fi - if [[ "$enddate_ex_s" -eq "$enddate_lc_s" ]]; then - debug "certificates expire at the same time" - elif [[ "$enddate_ex_s" -gt "$enddate_lc_s" ]]; then - # remote has longer to expiry date than local copy. - debug "remote cert has longer to run than local cert - ignoring" - else - info "${DOMAIN}: remote cert expires sooner than local, attempting to upload from local" - copy_file_to_location "domain certificate" \ - "$CERT_FILE" \ - "$DOMAIN_CERT_LOCATION" - copy_file_to_location "private key" \ - "$DOMAIN_DIR/${DOMAIN}.key" \ - "$DOMAIN_KEY_LOCATION" - copy_file_to_location "CA certificate" "$CA_CERT" "$CA_CERT_LOCATION" - cat "$CERT_FILE" "$CA_CERT" > "$TEMP_DIR/${DOMAIN}_chain.pem" - copy_file_to_location "full pem" \ - "$TEMP_DIR/${DOMAIN}_chain.pem" \ - "$DOMAIN_CHAIN_LOCATION" - cat "$DOMAIN_DIR/${DOMAIN}.key" "$CERT_FILE" > "$TEMP_DIR/${DOMAIN}_K_C.pem" - copy_file_to_location "private key and domain cert pem" \ - "$TEMP_DIR/${DOMAIN}_K_C.pem" \ - "$DOMAIN_KEY_CERT_LOCATION" - cat "$DOMAIN_DIR/${DOMAIN}.key" "$CERT_FILE" "$CA_CERT" > "$TEMP_DIR/${DOMAIN}.pem" - copy_file_to_location "full pem" \ - "$TEMP_DIR/${DOMAIN}.pem" \ - "$DOMAIN_PEM_LOCATION" - reload_service - fi - else - info "${DOMAIN}: Certificate on remote domain does not match, ignoring remote certificate" + # Set USE_SINGLE_ACL="true" to use a single ACL for all checks + #USE_SINGLE_ACL="false" + + # Location for all your certs, these can either be on the server (full path name) + # or using ssh /sftp as for the ACL + #DOMAIN_CERT_LOCATION="/etc/ssl/${DOMAIN}.crt" # this is domain cert + #DOMAIN_KEY_LOCATION="/etc/ssl/${DOMAIN}.key" # this is domain key + #CA_CERT_LOCATION="/etc/ssl/chain.crt" # this is CA cert + #DOMAIN_CHAIN_LOCATION="" # this is the domain cert and CA cert + #DOMAIN_PEM_LOCATION="" # this is the domain key, domain cert and CA cert + + # The command needed to reload apache / nginx or whatever you use + #RELOAD_CMD="" + + # Uncomment the following line to prevent non-interactive renewals of certificates + #PREVENT_NON_INTERACTIVE_RENEWAL="true" + + # Define the server type. This can be https, ftp, ftpi, imap, imaps, pop3, pop3s, smtp, + # smtps_deprecated, smtps, smtp_submission, xmpp, xmpps, ldaps or a port number which + # will be checked for certificate expiry and also will be checked after + # an update to confirm correct certificate is running (if CHECK_REMOTE) is set to true + #SERVER_TYPE="https" + #CHECK_REMOTE="true" + #CHECK_REMOTE_WAIT="2" # wait 2 seconds before checking the remote server + _EOF_domain_ +} + +write_getssl_template() { # write out the main template file + cat > "$1" <<- _EOF_getssl_ + # This file is read first and is common to all domains + # + # Uncomment and modify any variables you need + # see https://github.com/srvrco/getssl/wiki/Config-variables for details + # + # The staging server is best for testing (hence set as default) + CA="https://acme-staging-v02.api.letsencrypt.org" + # This server issues full certificates, however has rate limits + #CA="https://acme-v02.api.letsencrypt.org" + + # The agreement that must be signed with the CA, if not defined the default agreement will be used + #AGREEMENT="$AGREEMENT" + + # Set an email address associated with your account - generally set at account level rather than domain. + #ACCOUNT_EMAIL="me@example.com" + ACCOUNT_KEY_LENGTH=4096 + ACCOUNT_KEY="$WORKING_DIR/account.key" + + # Account key and private key types - can be rsa, prime256v1, secp384r1 or secp521r1 + #ACCOUNT_KEY_TYPE="rsa" + PRIVATE_KEY_ALG="rsa" + #REUSE_PRIVATE_KEY="true" + + # The command needed to reload apache / nginx or whatever you use + #RELOAD_CMD="" + + # The time period within which you want to allow renewal of a certificate + # this prevents hitting some of the rate limits. + # Creating a file called FORCE_RENEWAL in the domain directory allows one-off overrides + # of this setting + RENEW_ALLOW="30" + + # Define the server type. This can be https, ftp, ftpi, imap, imaps, pop3, pop3s, smtp, + # smtps_deprecated, smtps, smtp_submission, xmpp, xmpps, ldaps or a port number which + # will be checked for certificate expiry and also will be checked after + # an update to confirm correct certificate is running (if CHECK_REMOTE) is set to true + SERVER_TYPE="https" + CHECK_REMOTE="true" + + # Use the following 3 variables if you want to validate via DNS + #VALIDATE_VIA_DNS="true" + #DNS_ADD_COMMAND= + #DNS_DEL_COMMAND= + _EOF_getssl_ +} + +write_openssl_conf() { # write out a minimal openssl conf + cat > "$1" <<- _EOF_openssl_conf_ + # minimal openssl.cnf file + distinguished_name = req_distinguished_name + [ req_distinguished_name ] + [v3_req] + [v3_ca] + _EOF_openssl_conf_ +} + +# Trap signals +trap "signal_exit TERM" TERM HUP +trap "signal_exit INT" INT + +# Parse command-line +while [[ -n ${1+defined} ]]; do + case $1 in + -h | --help) + help_message; graceful_exit ;; + -d | --debug) + _USE_DEBUG=1 ;; + -c | --create) + _CREATE_CONFIG=1 ;; + -f | --force) + _FORCE_RENEW=1 ;; + -a | --all) + _CHECK_ALL=1 ;; + -k | --keep) + shift; _KEEP_VERSIONS="$1";; + -q | --quiet) + _QUIET=1 ;; + -Q | --mute) + _QUIET=1 + _MUTE=1 ;; + -r | --revoke) + _REVOKE=1 + shift + REVOKE_CERT="$1" + shift + REVOKE_KEY="$1" + shift + REVOKE_CA="$1" ;; + -u | --upgrade) + _UPGRADE=1 ;; + -U | --nocheck) + _UPGRADE_CHECK=0 ;; + -w) + shift; WORKING_DIR="$1" ;; + -*) + usage + error_exit "Unknown option $1" ;; + *) + if [[ -n $DOMAIN ]]; then + error_exit "invalid command line $DOMAIN - it appears to contain more than one domain" fi - fi - else - info "${DOMAIN}: no certificate obtained from host" - fi - # end of .... if obtained a cert + DOMAIN="$1" + if [[ -z $DOMAIN ]]; then + error_exit "invalid command line - it appears to contain a null variable" + fi ;; + esac + shift +done + +# Main logic +############ + +# Get the current OS, so the correct functions can be used for that OS. (sets the variable os) +get_os + +# check if "recent" version of bash. +#if [[ "${BASH_VERSINFO[0]}${BASH_VERSINFO[1]}" -lt 42 ]]; then +# info "this script is designed for bash v4.2 or later - earlier version may give errors" +#fi + +#check if required applications are included + +requires which +requires openssl +requires curl +requires nslookup drill dig host DNS_CHECK_FUNC +requires awk +requires tr +requires date +requires grep +requires sed +requires sort +requires mktemp + +# Check if upgrades are available (unless they have specified -U to ignore Upgrade checks) +if [[ $_UPGRADE_CHECK -eq 1 ]]; then + check_getssl_upgrade fi -# end of .... check_remote is true then connect and obtain the current certificate -# if there is an existing certificate file, check details. -if [[ -s "$CERT_FILE" ]]; then - debug "certificate $CERT_FILE exists" - enddate=$(openssl x509 -in "$CERT_FILE" -noout -enddate 2>/dev/null| cut -d= -f 2-) - debug "local cert is valid until $enddate" - if [[ "$enddate" != "-" ]]; then - enddate_s=$(date_epoc "$enddate") - if [[ $(date_renew) -lt "$enddate_s" ]] && [[ $_FORCE_RENEW -ne 1 ]]; then - issuer=$(openssl x509 -in "$CERT_FILE" -noout -issuer 2>/dev/null) - if [[ "$issuer" == *"Fake LE Intermediate"* ]] && [[ "$CA" == "https://acme-v02.api.letsencrypt.org" ]]; then - debug "upgrading from fake cert to real" - else - info "${DOMAIN}: certificate is valid for more than $RENEW_ALLOW days (until $enddate)" - # everything is OK, so exit. - graceful_exit - fi - else - debug "${DOMAIN}: certificate needs renewal" - fi +# Revoke a certificate if requested +if [[ $_REVOKE -eq 1 ]]; then + if [[ -z $REVOKE_CA ]]; then + CA=$DEFAULT_REVOKE_CA + elif [[ "$REVOKE_CA" == "-d" ]]; then + _USE_DEBUG=1 + CA=$DEFAULT_REVOKE_CA + else + CA=$REVOKE_CA fi + URL_revoke=$(curl --user-agent "$CURL_USERAGENT" "${CA}/directory" 2>/dev/null | grep "revoke-cert" | awk -F'"' '{print $4}') + revoke_certificate + graceful_exit fi -# end of .... if there is an existing certificate file, check details. -if [[ ! -t 0 ]] && [[ "$PREVENT_NON_INTERACTIVE_RENEWAL" = "true" ]]; then - errmsg="$DOMAIN due for renewal," - errmsg="${errmsg} but not completed due to PREVENT_NON_INTERACTIVE_RENEWAL=true in config" - error_exit "$errmsg" -fi +# get latest agreement from CA (as default) +AGREEMENT=$(curl --user-agent "$CURL_USERAGENT" -I "${CA}/terms" 2>/dev/null | awk 'tolower($1) ~ "location:" {print $2}'|tr -d '\r') -# create account key if it doesn't exist. -if [[ -s "$ACCOUNT_KEY" ]]; then - debug "Account key exists at $ACCOUNT_KEY skipping generation" -else - info "creating account key $ACCOUNT_KEY" - create_key "$ACCOUNT_KEY_TYPE" "$ACCOUNT_KEY" "$ACCOUNT_KEY_LENGTH" +# if nothing in command line, print help and exit. +if [[ -z "$DOMAIN" ]] && [[ ${_CHECK_ALL} -ne 1 ]]; then + help_message + graceful_exit fi -# if not reusing private key, then remove the old keys -if [[ "$REUSE_PRIVATE_KEY" != "true" ]]; then - if [[ -s "$DOMAIN_DIR/${DOMAIN}.key" ]]; then - rm -f "$DOMAIN_DIR/${DOMAIN}.key" - fi - if [[ -s "$DOMAIN_DIR/${DOMAIN}.ec.key" ]]; then - rm -f "$DOMAIN_DIR/${DOMAIN}.ecs.key" - fi -fi -# create new domain keys if they don't already exist -if [[ "$DUAL_RSA_ECDSA" == "false" ]]; then - create_key "${PRIVATE_KEY_ALG}" "$DOMAIN_DIR/${DOMAIN}.key" "$DOMAIN_KEY_LENGTH" -else - create_key "rsa" "$DOMAIN_DIR/${DOMAIN}.key" "$DOMAIN_KEY_LENGTH" - create_key "${PRIVATE_KEY_ALG}" "$DOMAIN_DIR/${DOMAIN}.ec.key" "$DOMAIN_KEY_LENGTH" +# if the "working directory" doesn't exist, then create it. +if [[ ! -d "$WORKING_DIR" ]]; then + debug "Making working directory - $WORKING_DIR" + mkdir -p "$WORKING_DIR" fi -# End of creating domain keys. -#create SAN -if [[ -z "$SANS" ]]; then - SANLIST="subjectAltName=DNS:${DOMAIN}" -elif [[ "$IGNORE_DIRECTORY_DOMAIN" == "true" ]]; then - SANLIST="subjectAltName=DNS:${SANS//,/,DNS:}" -else - SANLIST="subjectAltName=DNS:${DOMAIN},DNS:${SANS//,/,DNS:}" +# read any variables from config in working directory +if [[ -s "$WORKING_DIR/getssl.cfg" ]]; then + debug "reading config from $WORKING_DIR/getssl.cfg" + # shellcheck source=/dev/null + . "$WORKING_DIR/getssl.cfg" fi -debug "created SAN list = $SANLIST" -#create CSR's -if [[ "$DUAL_RSA_ECDSA" == "false" ]]; then - create_csr "$DOMAIN_DIR/${DOMAIN}.csr" "$DOMAIN_DIR/${DOMAIN}.key" -else - create_csr "$DOMAIN_DIR/${DOMAIN}.csr" "$DOMAIN_DIR/${DOMAIN}.key" - create_csr "$DOMAIN_DIR/${DOMAIN}.ec.csr" "$DOMAIN_DIR/${DOMAIN}.ec.key" +# Define defaults for variables not set in the main config. +ACCOUNT_KEY="${ACCOUNT_KEY:=$WORKING_DIR/account.key}" +DOMAIN_STORAGE="${DOMAIN_STORAGE:=$WORKING_DIR}" +DOMAIN_DIR="$DOMAIN_STORAGE/$DOMAIN" +CERT_FILE="$DOMAIN_DIR/${DOMAIN}.crt" +FULL_CHAIN="$DOMAIN_DIR/fullchain.crt" +CA_CERT="$DOMAIN_DIR/chain.crt" +TEMP_DIR="$DOMAIN_DIR/tmp" +if [[ "$os" == "mingw" ]]; then + CSR_SUBJECT="//" fi -# use account key to register with CA -# currently the code registers every time, and gets an "already registered" back if it has been. -get_signing_params "$ACCOUNT_KEY" +# Set the OPENSSL_CONF environment variable so openssl knows which config to use +export OPENSSL_CONF=$SSLCONF -info "Registering account" -# send the request to the ACME server. -if [[ $API -eq 1 ]]; then - if [[ "$ACCOUNT_EMAIL" ]] ; then - regjson='{"resource": "new-reg", "contact": ["mailto: '$ACCOUNT_EMAIL'"], "agreement": "'$AGREEMENT'"}' - else - regjson='{"resource": "new-reg", "agreement": "'$AGREEMENT'"}' - fi - send_signed_request "$URL_new_reg" "$regjson" -elif [[ $API -eq 2 ]]; then - if [[ "$ACCOUNT_EMAIL" ]] ; then - regjson='{"termsOfServiceAgreed": true, "contact": ["mailto: '$ACCOUNT_EMAIL'"]}' - else - regjson='{"termsOfServiceAgreed": true}' - fi - send_signed_request "$URL_newAccount" "$regjson" -else - debug "cant determine account API" - graceful_exit -fi +# if "-a" option then check other parameters and create run for each domain. +if [[ ${_CHECK_ALL} -eq 1 ]]; then + info "Check all certificates" -if [[ "$code" == "" ]] || [[ "$code" == '201' ]] ; then - info "Registered" - KID=$(echo "$responseHeaders" | grep -i "^location" | awk '{print $2}'| tr -d '\r\n ') - debug "KID=_$KID}_" - echo "$response" > "$TEMP_DIR/account.json" -elif [[ "$code" == '409' ]] ; then - KID=$(echo "$responseHeaders" | grep -i "^location" | awk '{print $2}'| tr -d '\r\n ') - debug responseHeaders "$responseHeaders" - debug "Already registered KID=$KID" -elif [[ "$code" == '200' ]] ; then - KID=$(echo "$responseHeaders" | grep -i "^location" | awk '{print $2}'| tr -d '\r\n ') - debug responseHeaders "$responseHeaders" - debug "Already registered account, KID=${KID}" -else - error_exit "Error registering account ...$responseHeaders ... $(json_get "$response" detail)" -fi -# end of registering account with CA + if [[ ${_CREATE_CONFIG} -eq 1 ]]; then + error_exit "cannot combine -c|--create with -a|--all" + fi -# verify each domain -info "Verify each domain" + if [[ ${_FORCE_RENEW} -eq 1 ]]; then + error_exit "cannot combine -f|--force with -a|--all because of rate limits" + fi -# loop through domains for cert ( from SANS list) -if [[ "$IGNORE_DIRECTORY_DOMAIN" == "true" ]]; then - alldomains=${SANS//,/ } -else - alldomains=$(echo "$DOMAIN,$SANS" | sed "s/,/ /g") -fi + if [[ ! -d "$DOMAIN_STORAGE" ]]; then + error_exit "DOMAIN_STORAGE not found - $DOMAIN_STORAGE" + fi -if [[ $API -eq 2 ]]; then - dstring="[" - for d in $alldomains; do - dstring="${dstring}{\"type\":\"dns\",\"value\":\"$d\"}," - done - dstring="${dstring::${#dstring}-1}]" - # request NewOrder currently seems to ignore the dates .... - # dstring="${dstring},\"notBefore\": \"$(date -d "-1 hour" --utc +%FT%TZ)\"" - # dstring="${dstring},\"notAfter\": \"$(date -d "2 days" --utc +%FT%TZ)\"" - request="{\"identifiers\": $dstring}" - send_signed_request "$URL_newOrder" "$request" - OrderLink=$(echo "$responseHeaders" | grep -i location | awk '{print $2}'| tr -d '\r\n ') - debug "Order link $OrderLink" - FinalizeLink=$(json_get "$response" "finalize") - debug "finalise link $FinalizeLink" - dn=0 - for d in $alldomains; do - # get authorizations link - AuthLink[$dn]=$(json_get "$response" "identifiers" "value" "$d" "authorizations" "x") - debug "authorizations link for $d - ${AuthLink[$dn]}" - ((dn++)) + for dir in "${DOMAIN_STORAGE}"/*; do + if [[ -d "$dir" ]]; then + debug "Checking $dir" + cmd="$0 -U" # No update checks when calling recursively + if [[ ${_USE_DEBUG} -eq 1 ]]; then + cmd="$cmd -d" + fi + if [[ ${_QUIET} -eq 1 ]]; then + cmd="$cmd -q" + fi + # check if $dir is a directory with a getssl.cfg in it + if [[ -f "$dir/getssl.cfg" ]]; then + cmd="$cmd -w $WORKING_DIR $(basename "$dir")" + debug "CMD: $cmd" + eval "$cmd" + fi + fi done + + graceful_exit fi +# end of "-a" option (looping through all domains) -dn=0 -for d in $alldomains; do - # $d is domain in current loop, which is number $dn for ACL - info "Verifying $d" - if [[ "$USE_SINGLE_ACL" == "true" ]]; then - DOMAIN_ACL="${ACL[0]}" +# if "-c|--create" option used, then create config files. +if [[ ${_CREATE_CONFIG} -eq 1 ]]; then + # If main config file does not exists then create it. + if [[ ! -s "$WORKING_DIR/getssl.cfg" ]]; then + info "creating main config file $WORKING_DIR/getssl.cfg" + if [[ ! -s "$SSLCONF" ]]; then + SSLCONF="$WORKING_DIR/openssl.cnf" + write_openssl_conf "$SSLCONF" + fi + write_getssl_template "$WORKING_DIR/getssl.cfg" + fi + # If domain and domain config don't exist then create them. + if [[ ! -d "$DOMAIN_DIR" ]]; then + info "Making domain directory - $DOMAIN_DIR" + mkdir -p "$DOMAIN_DIR" + fi + if [[ -s "$DOMAIN_DIR/getssl.cfg" ]]; then + info "domain config already exists $DOMAIN_DIR/getssl.cfg" else - DOMAIN_ACL="${ACL[$dn]}" + info "creating domain config file in $DOMAIN_DIR/getssl.cfg" + # if domain has an existing cert, copy from domain and use to create defaults. + EX_CERT=$(echo \ + | openssl s_client -servername "${DOMAIN}" -connect "${DOMAIN}:443" 2>/dev/null \ + | openssl x509 2>/dev/null) + EX_SANS="www.${DOMAIN}" + if [[ -n "${EX_CERT}" ]]; then + EX_SANS=$(echo "$EX_CERT" \ + | openssl x509 -noout -text 2>/dev/null| grep "Subject Alternative Name" -A2 \ + | grep -Eo "DNS:[a-zA-Z 0-9.-]*" | sed "s@DNS:$DOMAIN@@g" | grep -v '^$' | cut -c 5-) + EX_SANS=${EX_SANS//$'\n'/','} + fi + write_domain_template "$DOMAIN_DIR/getssl.cfg" fi + TEMP_DIR="$DOMAIN_DIR/tmp" + # end of "-c|--create" option, so exit + graceful_exit +fi +# end of "-c|--create" option to create config file. - # request a challenge token from ACME server - if [[ $API -eq 1 ]]; then - request="{\"resource\":\"new-authz\",\"identifier\":{\"type\":\"dns\",\"value\":\"$d\"}}" - send_signed_request "$URL_new_authz" "$request" - debug "completed send_signed_request" +# if domain directory doesn't exist, then create it. +if [[ ! -d "$DOMAIN_DIR" ]]; then + debug "Making working directory - $DOMAIN_DIR" + mkdir -p "$DOMAIN_DIR" +fi - # check if we got a valid response and token, if not then error exit - if [[ -n "$code" ]] && [[ ! "$code" == '201' ]] ; then - error_exit "new-authz error: $response" - fi - else - send_signed_request "${AuthLink[$dn]}" "" - fi +# define a temporary directory, and if it doesn't exist, create it. +TEMP_DIR="$DOMAIN_DIR/tmp" +if [[ ! -d "${TEMP_DIR}" ]]; then + debug "Making temp directory - ${TEMP_DIR}" + mkdir -p "${TEMP_DIR}" +fi - if [[ $response_status == "valid" ]]; then - info "$d is already validated" - if [[ "$DEACTIVATE_AUTH" == "true" ]]; then - deactivate_url="$(echo "$responseHeaders" | awk ' $1 ~ "^Location" {print $2}' | tr -d "\r")" - deactivate_url_list+=" $deactivate_url " - debug "url added to deactivate list ${deactivate_url}" - debug "deactivate list is now $deactivate_url_list" - fi - # increment domain-counter - ((dn++)) - else - PREVIOUSLY_VALIDATED="false" - if [[ $VALIDATE_VIA_DNS == "true" ]]; then # set up the correct DNS token for verification - if [[ $API -eq 1 ]]; then - # get the dns component of the ACME response - # get the token from the dns component - token=$(json_get "$response" "token" "dns-01") - debug token "$token" - # get the uri from the dns component - uri=$(json_get "$response" "uri" "dns-01") - debug uri "$uri" - else # APIv2 - debug "authlink response = $response" - # get the token from the http-01 component - token=$(json_get "$response" "challenges" "type" "dns-01" "token") - debug token "$token" - # get the uri from the http component - uri=$(json_get "$response" "challenges" "type" "dns-01" "url") - debug uri "$uri" - fi +# read any variables from config in domain directory +if [[ -s "$DOMAIN_DIR/getssl.cfg" ]]; then + debug "reading config from $DOMAIN_DIR/getssl.cfg" + # shellcheck source=/dev/null + . "$DOMAIN_DIR/getssl.cfg" +fi - keyauthorization="$token.$thumbprint" - debug keyauthorization "$keyauthorization" +# from SERVER_TYPE set REMOTE_PORT and REMOTE_EXTRA +set_server_type - #create signed authorization key from token. - auth_key=$(printf '%s' "$keyauthorization" | openssl dgst -sha256 -binary \ - | openssl base64 -e \ - | tr -d '\n\r' \ - | sed -e 's:=*$::g' -e 'y:+/:-_:') - debug auth_key "$auth_key" +# check config for typical errors. +check_config - debug "adding dns via command: $DNS_ADD_COMMAND $d $auth_key" - if ! eval "$DNS_ADD_COMMAND" "$d" "$auth_key" ; then - error_exit "DNS_ADD_COMMAND failed for domain $d" - fi +if [[ -e "$DOMAIN_DIR/FORCE_RENEWAL" ]]; then + rm -f "$DOMAIN_DIR/FORCE_RENEWAL" || error_exit "problem deleting file $DOMAIN_DIR/FORCE_RENEWAL" + _FORCE_RENEW=1 + info "${DOMAIN}: forcing renewal (due to FORCE_RENEWAL file)" +fi - # find a primary / authoritative DNS server for the domain - if [[ -z "$AUTH_DNS_SERVER" ]]; then - get_auth_dns "$d" - else - primary_ns="$AUTH_DNS_SERVER" - fi - debug primary_ns "$primary_ns" +# Obtain CA resource locations +ca_all_loc=$(curl --user-agent "$CURL_USERAGENT" "${CA}" 2>/dev/null) +debug "ca_all_loc from ${CA} gives $ca_all_loc" +# APIv1 +URL_new_reg=$(echo "$ca_all_loc" | grep "new-reg" | awk -F'"' '{print $4}') +URL_new_authz=$(echo "$ca_all_loc" | grep "new-authz" | awk -F'"' '{print $4}') +URL_new_cert=$(echo "$ca_all_loc" | grep "new-cert" | awk -F'"' '{print $4}') +#API v2 +URL_newAccount=$(echo "$ca_all_loc" | grep "newAccount" | awk -F'"' '{print $4}') +URL_newNonce=$(echo "$ca_all_loc" | grep "newNonce" | awk -F'"' '{print $4}') +URL_newOrder=$(echo "$ca_all_loc" | grep "newOrder" | awk -F'"' '{print $4}') +if [[ -z "$URL_new_reg" ]] && [[ -z "$URL_newAccount" ]]; then + ca_all_loc=$(curl --user-agent "$CURL_USERAGENT" "${CA}/directory" 2>/dev/null) + debug "ca_all_loc from ${CA}/directory gives $ca_all_loc" + # APIv1 + URL_new_reg=$(echo "$ca_all_loc" | grep "new-reg" | awk -F'"' '{print $4}') + URL_new_authz=$(echo "$ca_all_loc" | grep "new-authz" | awk -F'"' '{print $4}') + URL_new_cert=$(echo "$ca_all_loc" | grep "new-cert" | awk -F'"' '{print $4}') + #API v2 + URL_newAccount=$(echo "$ca_all_loc" | grep "newAccount" | awk -F'"' '{print $4}') + URL_newNonce=$(echo "$ca_all_loc" | grep "newNonce" | awk -F'"' '{print $4}') + URL_newOrder=$(echo "$ca_all_loc" | grep "newOrder" | awk -F'"' '{print $4}') +fi - # make a directory to hold pending dns-challenges - if [[ ! -d "$TEMP_DIR/dns_verify" ]]; then - mkdir "$TEMP_DIR/dns_verify" - fi +if [[ -n "$URL_new_reg" ]]; then + API=1 +elif [[ -n "$URL_newAccount" ]]; then + API=2 +else + info "unknown API version" + graceful_exit +fi +debug "Using API v$API" - # generate a file with the current variables for the dns-challenge - cat > "$TEMP_DIR/dns_verify/$d" <<- _EOF_ - token="${token}" - uri="${uri}" - keyauthorization="${keyauthorization}" - d="${d}" - primary_ns="${primary_ns}" - auth_key="${auth_key}" - _EOF_ +# Check if awk supports json_awk (required for ACMEv2) +if [[ $API -eq 2 ]]; then + json_awk_test=$(json_awk '{ "test": "1" }' 2>/dev/null) + if [[ "${json_awk_test}" == "" ]]; then + error_exit "Your version of awk does not work with json_awk (see http://github.com/step-/JSON.awk/issues/6), please install a newer version of mawk or gawk" + fi +fi - else # set up the correct http token for verification - if [[ $API -eq 1 ]]; then - # get the token from the http component - token=$(json_get "$response" "token" "http-01") - debug token "$token" - # get the uri from the http component - uri=$(json_get "$response" "uri" "http-01") - debug uri "$uri" - else # APIv2 - send_signed_request "${AuthLink[$dn]}" "" - debug "authlink response = $response" - # get the token from the http-01 component - token=$(json_get "$response" "challenges" "type" "http-01" "token") - debug token "$token" - # get the uri from the http component - uri=$(json_get "$response" "challenges" "type" "http-01" "url" | head -n1) - debug uri "$uri" +# if check_remote is true then connect and obtain the current certificate (if not forcing renewal) +if [[ "${CHECK_REMOTE}" == "true" ]] && [[ $_FORCE_RENEW -eq 0 ]]; then + debug "getting certificate for $DOMAIN from remote server" + # shellcheck disable=SC2086 + EX_CERT=$(echo \ + | openssl s_client -servername "${DOMAIN}" -connect "${DOMAIN}:${REMOTE_PORT}" ${REMOTE_EXTRA} 2>/dev/null \ + | openssl x509 2>/dev/null) + if [[ -n "$EX_CERT" ]]; then # if obtained a cert + if [[ -s "$CERT_FILE" ]]; then # if local exists + CERT_LOCAL=$(openssl x509 -noout -fingerprint < "$CERT_FILE" 2>/dev/null) + else # since local doesn't exist leave empty so that the domain validation will happen + CERT_LOCAL="" + fi + CERT_REMOTE=$(echo "$EX_CERT" | openssl x509 -noout -fingerprint 2>/dev/null) + if [[ "$CERT_LOCAL" == "$CERT_REMOTE" ]]; then + debug "certificate on server is same as the local cert" + else + # check if the certificate is for the right domain + EX_CERT_DOMAIN=$(echo "$EX_CERT" | openssl x509 -text \ + | sed -n -e 's/^ *Subject: .* CN=\([A-Za-z0-9.-]*\).*$/\1/p; /^ *DNS:.../ { s/ *DNS://g; y/,/\n/; p; }' \ + | sort -u | grep "^$DOMAIN\$") + if [[ "$EX_CERT_DOMAIN" == "$DOMAIN" ]]; then + # check renew-date on ex_cert and compare to local ( if local exists) + enddate_ex=$(echo "$EX_CERT" | openssl x509 -noout -enddate 2>/dev/null| cut -d= -f 2-) + enddate_ex_s=$(date_epoc "$enddate_ex") + debug "external cert has enddate $enddate_ex ( $enddate_ex_s ) " + if [[ -s "$CERT_FILE" ]]; then # if local exists + enddate_lc=$(openssl x509 -noout -enddate < "$CERT_FILE" 2>/dev/null| cut -d= -f 2-) + enddate_lc_s=$(date_epoc "$enddate_lc") + debug "local cert has enddate $enddate_lc ( $enddate_lc_s ) " + else + enddate_lc_s=0 + debug "local cert doesn't exist" + fi + if [[ "$enddate_ex_s" -eq "$enddate_lc_s" ]]; then + debug "certificates expire at the same time" + elif [[ "$enddate_ex_s" -gt "$enddate_lc_s" ]]; then + # remote has longer to expiry date than local copy. + debug "remote cert has longer to run than local cert - ignoring" + else + info "${DOMAIN}: remote cert expires sooner than local, attempting to upload from local" + copy_file_to_location "domain certificate" \ + "$CERT_FILE" \ + "$DOMAIN_CERT_LOCATION" + copy_file_to_location "private key" \ + "$DOMAIN_DIR/${DOMAIN}.key" \ + "$DOMAIN_KEY_LOCATION" + copy_file_to_location "CA certificate" "$CA_CERT" "$CA_CERT_LOCATION" + cat "$CERT_FILE" "$CA_CERT" > "$TEMP_DIR/${DOMAIN}_chain.pem" + copy_file_to_location "full pem" \ + "$TEMP_DIR/${DOMAIN}_chain.pem" \ + "$DOMAIN_CHAIN_LOCATION" + cat "$DOMAIN_DIR/${DOMAIN}.key" "$CERT_FILE" > "$TEMP_DIR/${DOMAIN}_K_C.pem" + copy_file_to_location "private key and domain cert pem" \ + "$TEMP_DIR/${DOMAIN}_K_C.pem" \ + "$DOMAIN_KEY_CERT_LOCATION" + cat "$DOMAIN_DIR/${DOMAIN}.key" "$CERT_FILE" "$CA_CERT" > "$TEMP_DIR/${DOMAIN}.pem" + copy_file_to_location "full pem" \ + "$TEMP_DIR/${DOMAIN}.pem" \ + "$DOMAIN_PEM_LOCATION" + reload_service + fi + else + info "${DOMAIN}: Certificate on remote domain does not match, ignoring remote certificate" fi + fi + else + info "${DOMAIN}: no certificate obtained from host" + fi + # end of .... if obtained a cert +fi +# end of .... check_remote is true then connect and obtain the current certificate - #create signed authorization key from token. - keyauthorization="$token.$thumbprint" - debug keyauthorization "$keyauthorization" - - # save variable into temporary file - echo -n "$keyauthorization" > "$TEMP_DIR/$token" - chmod 644 "$TEMP_DIR/$token" - - # copy to token to acme challenge location - umask 0022 - IFS=\; read -r -a token_locations <<<"$DOMAIN_ACL" - for t_loc in "${token_locations[@]}"; do - debug "copying file from $TEMP_DIR/$token to ${t_loc}" - copy_file_to_location "challenge token" \ - "$TEMP_DIR/$token" \ - "${t_loc}/$token" - done - umask "$ORIG_UMASK" - - wellknown_url="${CHALLENGE_CHECK_TYPE}://${d}/.well-known/acme-challenge/$token" - debug wellknown_url "$wellknown_url" - - if [[ "$SKIP_HTTP_TOKEN_CHECK" == "true" ]]; then - info "SKIP_HTTP_TOKEN_CHECK=true so not checking that token is working correctly" +# if there is an existing certificate file, check details. +if [[ -s "$CERT_FILE" ]]; then + debug "certificate $CERT_FILE exists" + enddate=$(openssl x509 -in "$CERT_FILE" -noout -enddate 2>/dev/null| cut -d= -f 2-) + debug "local cert is valid until $enddate" + if [[ "$enddate" != "-" ]]; then + enddate_s=$(date_epoc "$enddate") + if [[ $(date_renew) -lt "$enddate_s" ]] && [[ $_FORCE_RENEW -ne 1 ]]; then + issuer=$(openssl x509 -in "$CERT_FILE" -noout -issuer 2>/dev/null) + if [[ "$issuer" == *"Fake LE Intermediate"* ]] && [[ "$CA" == "https://acme-v02.api.letsencrypt.org" ]]; then + debug "upgrading from fake cert to real" else - sleep "$HTTP_TOKEN_CHECK_WAIT" - # check that we can reach the challenge ourselves, if not, then error - if [[ ! "$(curl --user-agent "$CURL_USERAGENT" -k --silent --location "$wellknown_url")" == "$keyauthorization" ]]; then - error_exit "for some reason could not reach $wellknown_url - please check it manually" - fi + info "${DOMAIN}: certificate is valid for more than $RENEW_ALLOW days (until $enddate)" + # everything is OK, so exit. + graceful_exit fi + else + debug "${DOMAIN}: certificate needs renewal" + fi + fi +fi +# end of .... if there is an existing certificate file, check details. - check_challenge_completion "$uri" "$d" "$keyauthorization" +if [[ ! -t 0 ]] && [[ "$PREVENT_NON_INTERACTIVE_RENEWAL" = "true" ]]; then + errmsg="$DOMAIN due for renewal," + errmsg="${errmsg} but not completed due to PREVENT_NON_INTERACTIVE_RENEWAL=true in config" + error_exit "$errmsg" +fi - debug "remove token from ${DOMAIN_ACL}" - IFS=\; read -r -a token_locations <<<"$DOMAIN_ACL" - for t_loc in "${token_locations[@]}"; do - if [[ "${t_loc:0:4}" == "ssh:" ]] ; then - sshhost=$(echo "${t_loc}"| awk -F: '{print $2}') - command="rm -f ${t_loc:(( ${#sshhost} + 5))}/${token:?}" - debug "running following command to remove token" - debug "ssh $SSH_OPTS $sshhost ${command}" - # shellcheck disable=SC2029 - # shellcheck disable=SC2086 - ssh $SSH_OPTS "$sshhost" "${command}" 1>/dev/null 2>&1 - rm -f "${TEMP_DIR:?}/${token:?}" - elif [[ "${t_loc:0:4}" == "ftp:" ]] ; then - debug "using ftp to remove token file" - ftpuser=$(echo "${t_loc}"| awk -F: '{print $2}') - ftppass=$(echo "${t_loc}"| awk -F: '{print $3}') - ftphost=$(echo "${t_loc}"| awk -F: '{print $4}') - ftplocn=$(echo "${t_loc}"| awk -F: '{print $5}') - debug "ftp user=$ftpuser - pass=$ftppass - host=$ftphost location=$ftplocn" - ftp -n <<- EOF - open $ftphost - user $ftpuser $ftppass - cd $ftplocn - delete ${token:?} - EOF - else - rm -f "${t_loc:?}/${token:?}" - fi - done - fi - # increment domain-counter - ((dn++)) +# create account key if it doesn't exist. +if [[ -s "$ACCOUNT_KEY" ]]; then + debug "Account key exists at $ACCOUNT_KEY skipping generation" +else + info "creating account key $ACCOUNT_KEY" + create_key "$ACCOUNT_KEY_TYPE" "$ACCOUNT_KEY" "$ACCOUNT_KEY_LENGTH" +fi + +# if not reusing private key, then remove the old keys +if [[ "$REUSE_PRIVATE_KEY" != "true" ]]; then + if [[ -s "$DOMAIN_DIR/${DOMAIN}.key" ]]; then + rm -f "$DOMAIN_DIR/${DOMAIN}.key" fi -done # end of ... loop through domains for cert ( from SANS list) + if [[ -s "$DOMAIN_DIR/${DOMAIN}.ec.key" ]]; then + rm -f "$DOMAIN_DIR/${DOMAIN}.ec.key" + fi +fi +# create new domain keys if they don't already exist +if [[ "$DUAL_RSA_ECDSA" == "false" ]]; then + create_key "${PRIVATE_KEY_ALG}" "$DOMAIN_DIR/${DOMAIN}.key" "$DOMAIN_KEY_LENGTH" +else + create_key "rsa" "$DOMAIN_DIR/${DOMAIN}.key" "$DOMAIN_KEY_LENGTH" + create_key "${PRIVATE_KEY_ALG}" "$DOMAIN_DIR/${DOMAIN}.ec.key" "$DOMAIN_KEY_LENGTH" +fi +# End of creating domain keys. -# perform validation if via DNS challenge -if [[ $VALIDATE_VIA_DNS == "true" ]]; then - # loop through dns-variable files to check if dns has been changed - for dnsfile in "$TEMP_DIR"/dns_verify/*; do - if [[ -e "$dnsfile" ]]; then - debug "loading DNSfile: $dnsfile" - # shellcheck source=/dev/null - . "$dnsfile" +#create SAN +if [[ -z "$SANS" ]]; then + SANLIST="subjectAltName=DNS:${DOMAIN}" +elif [[ "$IGNORE_DIRECTORY_DOMAIN" == "true" ]]; then + SANLIST="subjectAltName=DNS:${SANS//,/,DNS:}" +else + SANLIST="subjectAltName=DNS:${DOMAIN},DNS:${SANS//,/,DNS:}" +fi +debug "created SAN list = $SANLIST" - # check for token at public dns server, waiting for a valid response. - for ns in $primary_ns; do - debug "checking dns at $ns" - ntries=0 - check_dns="fail" - while [[ "$check_dns" == "fail" ]]; do - if [[ "$os" == "cygwin" ]]; then - check_result=$(nslookup -type=txt "_acme-challenge.${d}" "${ns}" \ - | grep ^_acme -A2\ - | grep '"'|awk -F'"' '{ print $2}') - elif [[ "$DNS_CHECK_FUNC" == "drill" ]] || [[ "$DNS_CHECK_FUNC" == "dig" ]]; then - check_result=$($DNS_CHECK_FUNC TXT "_acme-challenge.${d}" "@${ns}" \ - | grep '300 IN TXT'|awk -F'"' '{ print $2}') - elif [[ "$DNS_CHECK_FUNC" == "host" ]]; then - check_result=$($DNS_CHECK_FUNC -t TXT "_acme-challenge.${d}" "${ns}" \ - | grep 'descriptive text'|awk -F'"' '{ print $2}') - else - check_result=$(nslookup -type=txt "_acme-challenge.${d}" "${ns}" \ - | grep 'text ='|awk -F'"' '{ print $2}') - fi - debug "expecting $auth_key" - debug "${ns} gave ... $check_result" +#create CSR's +if [[ "$DUAL_RSA_ECDSA" == "false" ]]; then + create_csr "$DOMAIN_DIR/${DOMAIN}.csr" "$DOMAIN_DIR/${DOMAIN}.key" +else + create_csr "$DOMAIN_DIR/${DOMAIN}.csr" "$DOMAIN_DIR/${DOMAIN}.key" + create_csr "$DOMAIN_DIR/${DOMAIN}.ec.csr" "$DOMAIN_DIR/${DOMAIN}.ec.key" +fi - if [[ "$check_result" == *"$auth_key"* ]]; then - check_dns="success" - else - if [[ $ntries -lt 100 ]]; then - ntries=$(( ntries + 1 )) - info "checking DNS at ${ns} for ${d}. Attempt $ntries/100 gave wrong result, "\ - "waiting $DNS_WAIT secs before checking again" - sleep $DNS_WAIT - else - debug "dns check failed - removing existing value" - error_exit "checking _acme-challenge.${d} gave $check_result not $auth_key" - fi - fi - done - done - fi - done +# use account key to register with CA +# currently the code registers every time, and gets an "already registered" back if it has been. +get_signing_params "$ACCOUNT_KEY" - if [[ "$DNS_EXTRA_WAIT" -gt 0 && "$PREVIOUSLY_VALIDATED" != "true" ]]; then - info "sleeping $DNS_EXTRA_WAIT seconds before asking the ACME-server to check the dns" - sleep "$DNS_EXTRA_WAIT" +info "Registering account" +# send the request to the ACME server. +if [[ $API -eq 1 ]]; then + if [[ "$ACCOUNT_EMAIL" ]] ; then + regjson='{"resource": "new-reg", "contact": ["mailto: '$ACCOUNT_EMAIL'"], "agreement": "'$AGREEMENT'"}' + else + regjson='{"resource": "new-reg", "agreement": "'$AGREEMENT'"}' fi + send_signed_request "$URL_new_reg" "$regjson" +elif [[ $API -eq 2 ]]; then + if [[ "$ACCOUNT_EMAIL" ]] ; then + regjson='{"termsOfServiceAgreed": true, "contact": ["mailto: '$ACCOUNT_EMAIL'"]}' + else + regjson='{"termsOfServiceAgreed": true}' + fi + send_signed_request "$URL_newAccount" "$regjson" +else + debug "cant determine account API" + graceful_exit +fi - # loop through dns-variable files to let the ACME server check the challenges - for dnsfile in "$TEMP_DIR"/dns_verify/*; do - if [[ -e "$dnsfile" ]]; then - debug "loading DNSfile: $dnsfile" - # shellcheck source=/dev/null - . "$dnsfile" +if [[ "$code" == "" ]] || [[ "$code" == '201' ]] ; then + info "Registered" + KID=$(echo "$responseHeaders" | grep -i "^location" | awk '{print $2}'| tr -d '\r\n ') + debug "KID=_$KID}_" + echo "$response" > "$TEMP_DIR/account.json" +elif [[ "$code" == '409' ]] ; then + KID=$(echo "$responseHeaders" | grep -i "^location" | awk '{print $2}'| tr -d '\r\n ') + debug responseHeaders "$responseHeaders" + debug "Already registered KID=$KID" +elif [[ "$code" == '200' ]] ; then + KID=$(echo "$responseHeaders" | grep -i "^location" | awk '{print $2}'| tr -d '\r\n ') + debug responseHeaders "$responseHeaders" + debug "Already registered account, KID=${KID}" +else + error_exit "Error registering account ...$responseHeaders ... $(json_get "$response" detail)" +fi +# end of registering account with CA - check_challenge_completion "$uri" "$d" "$keyauthorization" +# verify each domain +info "Verify each domain" - debug "remove DNS entry" - eval "$DNS_DEL_COMMAND" "$d" "$auth_key" - # remove $dnsfile after each loop. - rm -f "$dnsfile" - fi - done +# loop through domains for cert ( from SANS list) +if [[ "$IGNORE_DIRECTORY_DOMAIN" == "true" ]]; then + alldomains=${SANS//,/ } +else + alldomains=$(echo "$DOMAIN,$SANS" | sed "s/,/ /g") +fi + +if [[ $API -eq 2 ]]; then + create_order fi -# end of ... perform validation if via DNS challenge -#end of varify each domain. -# Verification has been completed for all SANS, so request certificate. +fulfill_challenges + +# Verification has been completed for all SANS, so request certificate. info "Verification completed, obtaining certificate." #obtain the certificate. @@ -2504,6 +2505,12 @@ get_certificate "$DOMAIN_DIR/${DOMAIN}.csr" \ "$CERT_FILE" \ "$CA_CERT" if [[ "$DUAL_RSA_ECDSA" == "true" ]]; then + info "Creating order for EC certificate" + if [[ $API -eq 2 ]]; then + create_order + fulfill_challenges + fi + info "obtaining EC certificate." get_certificate "$DOMAIN_DIR/${DOMAIN}.ec.csr" \ "${CERT_FILE%.*}.ec.crt" \ "${CA_CERT%.*}.ec.crt" @@ -2549,7 +2556,7 @@ if [[ -n "$DOMAIN_CHAIN_LOCATION" ]]; then copy_file_to_location "full chain" "$TEMP_DIR/${DOMAIN}_chain.pem" "$to_location" if [[ "$DUAL_RSA_ECDSA" == "true" ]]; then cat "${CERT_FILE%.*}.ec.crt" "${CA_CERT%.*}.ec.crt" > "$TEMP_DIR/${DOMAIN}_chain.pem.ec" - copy_file_to_location "full chain" "$TEMP_DIR/${DOMAIN}_chain.pem.ec" "${to_location}.ec" + copy_file_to_location "full chain" "$TEMP_DIR/${DOMAIN}_chain.pem.ec" "${to_location%.*}.ec.${to_location##*.}" fi fi # if DOMAIN_KEY_CERT_LOCATION is not blank, then create and copy file. @@ -2563,7 +2570,7 @@ if [[ -n "$DOMAIN_KEY_CERT_LOCATION" ]]; then copy_file_to_location "private key and domain cert pem" "$TEMP_DIR/${DOMAIN}_K_C.pem" "$to_location" if [[ "$DUAL_RSA_ECDSA" == "true" ]]; then cat "$DOMAIN_DIR/${DOMAIN}.ec.key" "${CERT_FILE%.*}.ec.crt" > "$TEMP_DIR/${DOMAIN}_K_C.pem.ec" - copy_file_to_location "private ec key and domain cert pem" "$TEMP_DIR/${DOMAIN}_K_C.pem.ec" "${to_location}.ec" + copy_file_to_location "private ec key and domain cert pem" "$TEMP_DIR/${DOMAIN}_K_C.pem.ec" "${to_location%.*}.ec.${to_location##*.}" fi fi # if DOMAIN_PEM_LOCATION is not blank, then create and copy file. @@ -2577,7 +2584,7 @@ if [[ -n "$DOMAIN_PEM_LOCATION" ]]; then copy_file_to_location "full key, cert and chain pem" "$TEMP_DIR/${DOMAIN}.pem" "$to_location" if [[ "$DUAL_RSA_ECDSA" == "true" ]]; then cat "$DOMAIN_DIR/${DOMAIN}.ec.key" "${CERT_FILE%.*}.ec.crt" "${CA_CERT%.*}.ec.crt" > "$TEMP_DIR/${DOMAIN}.pem.ec" - copy_file_to_location "full ec key, cert and chain pem" "$TEMP_DIR/${DOMAIN}.pem.ec" "${to_location}.ec" + copy_file_to_location "full ec key, cert and chain pem" "$TEMP_DIR/${DOMAIN}.pem.ec" "${to_location%.*}.ec.${to_location##*.}" fi fi # end of copying certs. diff --git a/test/3-dual-rsa-ecdsa.bats b/test/3-dual-rsa-ecdsa.bats new file mode 100644 index 0000000..2dc9257 --- /dev/null +++ b/test/3-dual-rsa-ecdsa.bats @@ -0,0 +1,43 @@ +#! /usr/bin/env bats + +load '/bats-support/load.bash' +load '/bats-assert/load.bash' +load '/getssl/test/test_helper.bash' + + +# This is run for every test +setup() { + export CURL_CA_BUNDLE=/root/pebble-ca-bundle.crt +} + + +@test "Create dual certificates using HTTP-01 verification" { + CONFIG_FILE="getssl-http01-dual-rsa-ecdsa.cfg" + setup_environment + init_getssl + create_certificate + assert_success +} + + +@test "Force renewal of dual certificates using HTTP-01" { + #!FIXME test certificate has been updated + run ${CODE_DIR}/getssl -f $GETSSL_HOST + assert_success +} + +@test "Create dual certificates using DNS-01 verification" { + CONFIG_FILE="getssl-dns01-dual-rsa-ecdsa.cfg" + setup_environment + init_getssl + create_certificate + assert_success +} + + +@test "Force renewal of dual certificates using DNS-01" { + #!FIXME test certificate has been updated + run ${CODE_DIR}/getssl -f $GETSSL_HOST + assert_success + cleanup_environment +} diff --git a/test/Dockerfile-debian b/test/Dockerfile-debian index c4c88a1..95ebbac 100644 --- a/test/Dockerfile-debian +++ b/test/Dockerfile-debian @@ -10,9 +10,6 @@ WORKDIR /root RUN mkdir /etc/nginx/pki RUN mkdir /etc/nginx/pki/private -# Prevent "Can't load /root/.rnd into RNG" error from openssl -# RUN touch /root/.rnd - # BATS (Bash Automated Testings) RUN git clone https://github.com/bats-core/bats-core.git /bats-core RUN git clone https://github.com/jasonkarns/bats-support /bats-support diff --git a/test/README.md b/test/README.md index 98ff929..7648f17 100644 --- a/test/README.md +++ b/test/README.md @@ -35,6 +35,6 @@ docker exec -it getssl-ubuntu18 /getssl/test/debug-test.sh getssl-http01.cfg ## TODO -1. Test RHEL6, Debian as well -2. Test SSH, SFTP -3. Test wildcards +1. Test wildcards +2. Test SSH, SFTP, SCP +3. Test change of key algorithm diff --git a/test/test-config/getssl-dns01-dual-rsa-ecdsa.cfg b/test/test-config/getssl-dns01-dual-rsa-ecdsa.cfg new file mode 100644 index 0000000..042ed15 --- /dev/null +++ b/test/test-config/getssl-dns01-dual-rsa-ecdsa.cfg @@ -0,0 +1,37 @@ +# Uncomment and modify any variables you need +# see https://github.com/srvrco/getssl/wiki/Config-variables for details +# see https://github.com/srvrco/getssl/wiki/Example-config-files for example configs +# +CA="https://pebble:14000/dir" + +VALIDATE_VIA_DNS=true +DNS_ADD_COMMAND="/getssl/dns_scripts/dns_add_challtestsrv" +DNS_DEL_COMMAND="/getssl/dns_scripts/dns_del_challtestsrv" + +DUAL_RSA_ECDSA="true" +ACCOUNT_KEY_TYPE="prime256v1" +PRIVATE_KEY_ALG="prime256v1" + +# Additional domains - this could be multiple domains / subdomains in a comma separated list +SANS="" + +# Acme Challenge Location. The first line for the domain, the following ones for each additional domain. +ACL=('/var/www/html/.well-known/acme-challenge') + +#Set USE_SINGLE_ACL="true" to use a single ACL for all checks +USE_SINGLE_ACL="false" + +# Location for all your certs, these can either be on the server (full path name) +# or using ssh /sftp as for the ACL +DOMAIN_CERT_LOCATION="/etc/nginx/pki/server.crt" +DOMAIN_KEY_LOCATION="/etc/nginx/pki/private/server.key" +CA_CERT_LOCATION="/etc/nginx/pki/chain.crt" +DOMAIN_CHAIN_LOCATION="" # this is the domain cert and CA cert +DOMAIN_PEM_LOCATION="" # this is the domain_key, domain cert and CA cert + +# The command needed to reload apache / nginx or whatever you use +RELOAD_CMD="cp /getssl/test/test-config/nginx-ubuntu-ssl ${NGINX_CONFIG} && /getssl/test/restart-nginx" + +# Define the server type and confirm correct certificate is installed +SERVER_TYPE="https" +CHECK_REMOTE="true" diff --git a/test/test-config/getssl-http01-dual-rsa-ecdsa.cfg b/test/test-config/getssl-http01-dual-rsa-ecdsa.cfg new file mode 100644 index 0000000..f6cfcb7 --- /dev/null +++ b/test/test-config/getssl-http01-dual-rsa-ecdsa.cfg @@ -0,0 +1,33 @@ +# Uncomment and modify any variables you need +# see https://github.com/srvrco/getssl/wiki/Config-variables for details +# see https://github.com/srvrco/getssl/wiki/Example-config-files for example configs +# +CA="https://pebble:14000/dir" + +DUAL_RSA_ECDSA="true" +ACCOUNT_KEY_TYPE="prime256v1" +PRIVATE_KEY_ALG="prime256v1" + +# Additional domains - this could be multiple domains / subdomains in a comma separated list +SANS="" + +# Acme Challenge Location. +ACL=('/var/www/html/.well-known/acme-challenge') + +#Set USE_SINGLE_ACL="true" to use a single ACL for all checks +USE_SINGLE_ACL="false" + +# Location for all your certs, these can either be on the server (full path name) +# or using ssh /sftp as for the ACL +DOMAIN_CERT_LOCATION="/etc/nginx/pki/server.crt" +DOMAIN_KEY_LOCATION="/etc/nginx/pki/private/server.key" +CA_CERT_LOCATION="/etc/nginx/pki/chain.crt" +DOMAIN_CHAIN_LOCATION="" # this is the domain cert and CA cert +DOMAIN_PEM_LOCATION="" # this is the domain_key, domain cert and CA cert + +# The command needed to reload apache / nginx or whatever you use +RELOAD_CMD="cp /getssl/test/test-config/nginx-ubuntu-ssl ${NGINX_CONFIG} && /getssl/test/restart-nginx" + +# Define the server type and confirm correct certificate is installed +SERVER_TYPE="https" +CHECK_REMOTE="true" From b09e1807ba0a8d5c1bfc90f8af456842decb39bb Mon Sep 17 00:00:00 2001 From: Tim Kimber Date: Wed, 12 Feb 2020 14:22:30 +0000 Subject: [PATCH 037/110] Fix #424 - Sporadic "error in EC signing couldn't get R from ..." --- getssl | 29 ++++++++++----- test/5-secp384-http01.bats | 41 ++++++++++++++++++++++ test/test-config/getssl-http01-secp384.cfg | 32 +++++++++++++++++ test/test-config/getssl-http01-secp521.cfg | 32 +++++++++++++++++ 4 files changed, 125 insertions(+), 9 deletions(-) create mode 100644 test/5-secp384-http01.bats create mode 100644 test/test-config/getssl-http01-secp384.cfg create mode 100644 test/test-config/getssl-http01-secp521.cfg diff --git a/getssl b/getssl index 0d593d7..5af09e1 100755 --- a/getssl +++ b/getssl @@ -1801,20 +1801,27 @@ sign_string() { # sign a string with a given key and algorithm and return urlbas elif openssl ec -in "${skey}" -noout 2>/dev/null ; then # Elliptic curve key. signed=$(printf '%s' "${str}" | openssl dgst -"$signalg" -sign "$key" -hex | awk '{print $2}') debug "EC signature $signed" - if [[ "${signed:4:4}" == "0220" ]]; then #sha256 + if [[ "${signed:4:4}" == "021f" ]]; then #sha256 which needs padding + R=$(echo -n 00;echo "$signed" | cut -c 9-70) + part2=$(echo "$signed" | cut -c 71-) + elif [[ "${signed:4:4}" == "0220" ]]; then #sha256 R=$(echo "$signed" | cut -c 9-72) part2=$(echo "$signed" | cut -c 73-) - elif [[ "${signed:4:4}" == "0221" ]]; then #sha256 + elif [[ "${signed:4:4}" == "0221" ]]; then #sha256 which needs trimming R=$(echo "$signed" | cut -c 11-74) part2=$(echo "$signed" | cut -c 75-) + elif [[ "${signed:4:4}" == "022f" ]]; then #sha384 which needs padding + info "Padding sha384" + R=$(echo -n 00;echo "$signed" | cut -c 9-102) + part2=$(echo "$signed" | cut -c 103-) elif [[ "${signed:4:4}" == "0230" ]]; then #sha384 R=$(echo "$signed" | cut -c 9-104) part2=$(echo "$signed" | cut -c 105-) - elif [[ "${signed:4:4}" == "0231" ]]; then #sha384 + elif [[ "${signed:4:4}" == "0231" ]]; then #sha384 which needs trimming R=$(echo "$signed" | cut -c 11-106) part2=$(echo "$signed" | cut -c 107-) - elif [[ "${signed:6:4}" == "0241" ]]; then #sha512 - R=$(echo "$signed" | cut -c 11-140) + elif [[ "${signed:6:4}" == "0241" ]]; then #sha512 which needs padding + R=$(echo -n 00;echo "$signed" | cut -c 11-140) part2=$(echo "$signed" | cut -c 141-) elif [[ "${signed:6:4}" == "0242" ]]; then #sha512 R=$(echo "$signed" | cut -c 11-142) @@ -1824,18 +1831,22 @@ sign_string() { # sign a string with a given key and algorithm and return urlbas fi debug "R $R" - if [[ "${part2:0:4}" == "0220" ]]; then #sha256 + if [[ "${part2:0:4}" == "021f" ]]; then #sha256 with padding + S=$(echo -n 00;echo "$part2" | cut -c 5-) + elif [[ "${part2:0:4}" == "0220" ]]; then #sha256 S=$(echo "$part2" | cut -c 5-68) elif [[ "${part2:0:4}" == "0221" ]]; then #sha256 S=$(echo "$part2" | cut -c 7-70) + elif [[ "${part2:0:4}" == "022f" ]]; then #sha384 with padding + S=$(echo -n 00;echo "$part2" | cut -c 5-) elif [[ "${part2:0:4}" == "0230" ]]; then #sha384 S=$(echo "$part2" | cut -c 5-100) elif [[ "${part2:0:4}" == "0231" ]]; then #sha384 S=$(echo "$part2" | cut -c 7-102) - elif [[ "${part2:0:4}" == "0241" ]]; then #sha512 - S=$(echo "$part2" | cut -c 5-136) + elif [[ "${part2:0:4}" == "0241" ]]; then #sha512 with padding + S=$(echo -n 00;echo "$part2" | cut -c 5-) elif [[ "${part2:0:4}" == "0242" ]]; then #sha512 - S=$(echo "$part2" | cut -c 5-136) + S=$(echo "$part2" | cut -c 5-) else error_exit "error in EC signing couldn't get S from $signed" fi diff --git a/test/5-secp384-http01.bats b/test/5-secp384-http01.bats new file mode 100644 index 0000000..9010d58 --- /dev/null +++ b/test/5-secp384-http01.bats @@ -0,0 +1,41 @@ +#! /usr/bin/env bats + +load '/bats-support/load.bash' +load '/bats-assert/load.bash' +load '/getssl/test/test_helper.bash' + + +# This is run for every test +setup() { + export CURL_CA_BUNDLE=/root/pebble-ca-bundle.crt +} + + +@test "Create new secp384r1 certificate using HTTP-01 verification" { + CONFIG_FILE="getssl-http01-secp384.cfg" + setup_environment + init_getssl + create_certificate + assert_success +} + + +@test "Force renewal of secp384r1 certificate using HTTP-01" { + run ${CODE_DIR}/getssl -f $GETSSL_HOST + assert_success +} + + +@test "Create new secp521r1 certificate using HTTP-01 verification" { + CONFIG_FILE="getssl-http01-secp521.cfg" + setup_environment + init_getssl + create_certificate + assert_success +} + + +@test "Force renewal of secp521r1 certificate using HTTP-01" { + run ${CODE_DIR}/getssl -f $GETSSL_HOST + assert_success +} diff --git a/test/test-config/getssl-http01-secp384.cfg b/test/test-config/getssl-http01-secp384.cfg new file mode 100644 index 0000000..4fa3e82 --- /dev/null +++ b/test/test-config/getssl-http01-secp384.cfg @@ -0,0 +1,32 @@ +# Uncomment and modify any variables you need +# see https://github.com/srvrco/getssl/wiki/Config-variables for details +# see https://github.com/srvrco/getssl/wiki/Example-config-files for example configs +# +CA="https://pebble:14000/dir" + +ACCOUNT_KEY_TYPE="secp384r1" +PRIVATE_KEY_ALG="secp384r1" + +# Additional domains - this could be multiple domains / subdomains in a comma separated list +SANS="" + +# Acme Challenge Location. +ACL=('/var/www/html/.well-known/acme-challenge') + +#Set USE_SINGLE_ACL="true" to use a single ACL for all checks +USE_SINGLE_ACL="false" + +# Location for all your certs, these can either be on the server (full path name) +# or using ssh /sftp as for the ACL +DOMAIN_CERT_LOCATION="/etc/nginx/pki/server.crt" +DOMAIN_KEY_LOCATION="/etc/nginx/pki/private/server.key" +CA_CERT_LOCATION="/etc/nginx/pki/chain.crt" +DOMAIN_CHAIN_LOCATION="" # this is the domain cert and CA cert +DOMAIN_PEM_LOCATION="" # this is the domain_key, domain cert and CA cert + +# The command needed to reload apache / nginx or whatever you use +RELOAD_CMD="cp /getssl/test/test-config/nginx-ubuntu-ssl ${NGINX_CONFIG} && /getssl/test/restart-nginx" + +# Define the server type and confirm correct certificate is installed +SERVER_TYPE="https" +CHECK_REMOTE="true" diff --git a/test/test-config/getssl-http01-secp521.cfg b/test/test-config/getssl-http01-secp521.cfg new file mode 100644 index 0000000..6068fbf --- /dev/null +++ b/test/test-config/getssl-http01-secp521.cfg @@ -0,0 +1,32 @@ +# Uncomment and modify any variables you need +# see https://github.com/srvrco/getssl/wiki/Config-variables for details +# see https://github.com/srvrco/getssl/wiki/Example-config-files for example configs +# +CA="https://pebble:14000/dir" + +ACCOUNT_KEY_TYPE="secp521r1" +PRIVATE_KEY_ALG="secp521r1" + +# Additional domains - this could be multiple domains / subdomains in a comma separated list +SANS="" + +# Acme Challenge Location. +ACL=('/var/www/html/.well-known/acme-challenge') + +#Set USE_SINGLE_ACL="true" to use a single ACL for all checks +USE_SINGLE_ACL="false" + +# Location for all your certs, these can either be on the server (full path name) +# or using ssh /sftp as for the ACL +DOMAIN_CERT_LOCATION="/etc/nginx/pki/server.crt" +DOMAIN_KEY_LOCATION="/etc/nginx/pki/private/server.key" +CA_CERT_LOCATION="/etc/nginx/pki/chain.crt" +DOMAIN_CHAIN_LOCATION="" # this is the domain cert and CA cert +DOMAIN_PEM_LOCATION="" # this is the domain_key, domain cert and CA cert + +# The command needed to reload apache / nginx or whatever you use +RELOAD_CMD="cp /getssl/test/test-config/nginx-ubuntu-ssl ${NGINX_CONFIG} && /getssl/test/restart-nginx" + +# Define the server type and confirm correct certificate is installed +SERVER_TYPE="https" +CHECK_REMOTE="true" From e0626f37c07fb80cc6f0416ce08a2922269cc7e3 Mon Sep 17 00:00:00 2001 From: Tim Kimber Date: Wed, 12 Feb 2020 16:01:35 +0000 Subject: [PATCH 038/110] Update version and revision history --- getssl | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/getssl b/getssl index 5af09e1..7de661c 100755 --- a/getssl +++ b/getssl @@ -208,10 +208,12 @@ # 2020-02-05 Fix epoch_date for busybox # 2020-02-06 Bugfixes for json_awk and nslookup to support old awk versions (2.17) # 2020-02-11 Add SCP_OPTS and SFTP_OPTS +# 2020-02-12 Fix for DUAL_RSA_ECDSA not working with ACMEv2 (#334, #474, #502) +# 2020-02-12 Fix #424 - Sporadic "error in EC signing couldn't get R from ..." (2.18) # ---------------------------------------------------------------------------------------- PROGNAME=${0##*/} -VERSION="2.17" +VERSION="2.18" # defaults ACCOUNT_KEY_LENGTH=4096 From a9d0419c75fbfb57031a4a4410097d4256079b07 Mon Sep 17 00:00:00 2001 From: Tim Kimber Date: Wed, 12 Feb 2020 20:50:38 +0000 Subject: [PATCH 039/110] Fix "Registration key already in use" error --- getssl | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/getssl b/getssl index 7de661c..2de9612 100755 --- a/getssl +++ b/getssl @@ -210,10 +210,11 @@ # 2020-02-11 Add SCP_OPTS and SFTP_OPTS # 2020-02-12 Fix for DUAL_RSA_ECDSA not working with ACMEv2 (#334, #474, #502) # 2020-02-12 Fix #424 - Sporadic "error in EC signing couldn't get R from ..." (2.18) +# 2020-02-12 Fix "Registration key already in use" (2.19) # ---------------------------------------------------------------------------------------- PROGNAME=${0##*/} -VERSION="2.18" +VERSION="2.19" # defaults ACCOUNT_KEY_LENGTH=4096 @@ -1753,7 +1754,7 @@ send_signed_request() { # Sends a request to the ACME server, signed with your p debug response "$response" code=$(awk ' $1 ~ "^HTTP" {print $2}' "$CURL_HEADER" | tail -1) debug code "$code" - if [[ "$code" == 4* && $response != *"error:badNonce"* ]]; then + if [[ "$code" == 4* && $response != *"error:badNonce"* && "$code" != 409 ]]; then detail=$(echo "$response" | grep "detail") error_exit "ACME server returned error: ${code}: ${detail}" fi From 74f4bbeac9ebe819f8f90f539e7944c3994ae10f Mon Sep 17 00:00:00 2001 From: Tim Kimber Date: Fri, 14 Feb 2020 07:18:26 +0000 Subject: [PATCH 040/110] Fix bug #505 with DUAL_RSA_ECDSA and multiple locations --- getssl | 37 +++++++++++------ test/4-more-than-10-hosts.bats | 4 +- test/6-dual-rsa-ecdsa-copy-2-locations.bats | 40 +++++++++++++++++++ test/debug-test.sh | 14 ++++++- test/run-all-tests.cmd | 5 +++ ...tssl-http01-dual-rsa-ecdsa-2-locations.cfg | 32 +++++++++++++++ test/test_helper.bash | 6 +-- 7 files changed, 119 insertions(+), 19 deletions(-) create mode 100644 test/6-dual-rsa-ecdsa-copy-2-locations.bats create mode 100644 test/run-all-tests.cmd create mode 100644 test/test-config/getssl-http01-dual-rsa-ecdsa-2-locations.cfg diff --git a/getssl b/getssl index 2de9612..dbcd867 100755 --- a/getssl +++ b/getssl @@ -211,10 +211,11 @@ # 2020-02-12 Fix for DUAL_RSA_ECDSA not working with ACMEv2 (#334, #474, #502) # 2020-02-12 Fix #424 - Sporadic "error in EC signing couldn't get R from ..." (2.18) # 2020-02-12 Fix "Registration key already in use" (2.19) +# 2020-02-13 Fix bug with copying to all locations when creating RSA and ECDSA certs (2.20) # ---------------------------------------------------------------------------------------- PROGNAME=${0##*/} -VERSION="2.19" +VERSION="2.20" # defaults ACCOUNT_KEY_LENGTH=4096 @@ -549,8 +550,12 @@ copy_file_to_location() { # copies a file, using scp, sftp or ftp if required. cert=$1 # descriptive name, just used for display from=$2 # current file location to=$3 # location to move file to. + suffix=$4 # (optional) optional suffix for DUAL_RSA_ECDSA, i.e. save to private.key becomes save to private.ec.key IFS=\; read -r -a copy_locations <<<"$3" for to in "${copy_locations[@]}"; do + if [[ -n "$suffix" ]]; then + to="${to%.*}.${suffix}.${to##*.}" + fi info "copying $cert to $to" if [[ "${to:0:4}" == "ssh:" ]] ; then debug "using scp -q $SCP_OPTS $from ${to:4}" @@ -1823,6 +1828,9 @@ sign_string() { # sign a string with a given key and algorithm and return urlbas elif [[ "${signed:4:4}" == "0231" ]]; then #sha384 which needs trimming R=$(echo "$signed" | cut -c 11-106) part2=$(echo "$signed" | cut -c 107-) + elif [[ "${signed:6:4}" == "0240" ]]; then #sha512 which needs padding + R=$(echo -n 00;echo "$signed" | cut -c 9-138) + part2=$(echo "$signed" | cut -c 141-) elif [[ "${signed:6:4}" == "0241" ]]; then #sha512 which needs padding R=$(echo -n 00;echo "$signed" | cut -c 11-140) part2=$(echo "$signed" | cut -c 141-) @@ -1846,6 +1854,8 @@ sign_string() { # sign a string with a given key and algorithm and return urlbas S=$(echo "$part2" | cut -c 5-100) elif [[ "${part2:0:4}" == "0231" ]]; then #sha384 S=$(echo "$part2" | cut -c 7-102) + elif [[ "${part2:0:4}" == "0240" ]]; then #sha512 with padding + S=$(echo -n 00;echo "$part2" | cut -c 5-) elif [[ "${part2:0:4}" == "0241" ]]; then #sha512 with padding S=$(echo -n 00;echo "$part2" | cut -c 5-) elif [[ "${part2:0:4}" == "0242" ]]; then #sha512 @@ -2545,17 +2555,20 @@ if [[ "$DUAL_RSA_ECDSA" == "true" ]]; then if [[ -n "$DOMAIN_CERT_LOCATION" ]]; then copy_file_to_location "ec domain certificate" \ "${CERT_FILE%.*}.ec.crt" \ - "${DOMAIN_CERT_LOCATION%.*}.ec.crt" + "${DOMAIN_CERT_LOCATION}" \ + "ec" fi if [[ -n "$DOMAIN_KEY_LOCATION" ]]; then - copy_file_to_location "ec private key" \ - "$DOMAIN_DIR/${DOMAIN}.ec.key" \ - "${DOMAIN_KEY_LOCATION%.*}.ec.key" + copy_file_to_location "ec private key" \ + "$DOMAIN_DIR/${DOMAIN}.ec.key" \ + "${DOMAIN_KEY_LOCATION}" \ + "ec" fi if [[ -n "$CA_CERT_LOCATION" ]]; then - copy_file_to_location "ec CA certificate" \ - "${CA_CERT%.*}.ec.crt" \ - "${CA_CERT_LOCATION%.*}.ec.crt" + copy_file_to_location "ec CA certificate" \ + "${CA_CERT%.*}.ec.crt" \ + "${CA_CERT_LOCATION%.*}.crt" \ + "ec" fi fi @@ -2570,7 +2583,7 @@ if [[ -n "$DOMAIN_CHAIN_LOCATION" ]]; then copy_file_to_location "full chain" "$TEMP_DIR/${DOMAIN}_chain.pem" "$to_location" if [[ "$DUAL_RSA_ECDSA" == "true" ]]; then cat "${CERT_FILE%.*}.ec.crt" "${CA_CERT%.*}.ec.crt" > "$TEMP_DIR/${DOMAIN}_chain.pem.ec" - copy_file_to_location "full chain" "$TEMP_DIR/${DOMAIN}_chain.pem.ec" "${to_location%.*}.ec.${to_location##*.}" + copy_file_to_location "full chain" "$TEMP_DIR/${DOMAIN}_chain.pem.ec" "${to_location}" "ec" fi fi # if DOMAIN_KEY_CERT_LOCATION is not blank, then create and copy file. @@ -2583,8 +2596,8 @@ if [[ -n "$DOMAIN_KEY_CERT_LOCATION" ]]; then cat "$DOMAIN_DIR/${DOMAIN}.key" "$CERT_FILE" > "$TEMP_DIR/${DOMAIN}_K_C.pem" copy_file_to_location "private key and domain cert pem" "$TEMP_DIR/${DOMAIN}_K_C.pem" "$to_location" if [[ "$DUAL_RSA_ECDSA" == "true" ]]; then - cat "$DOMAIN_DIR/${DOMAIN}.ec.key" "${CERT_FILE%.*}.ec.crt" > "$TEMP_DIR/${DOMAIN}_K_C.pem.ec" - copy_file_to_location "private ec key and domain cert pem" "$TEMP_DIR/${DOMAIN}_K_C.pem.ec" "${to_location%.*}.ec.${to_location##*.}" + cat "$DOMAIN_DIR/${DOMAIN}.ec.key" "${CERT_FILE%.*}.ec.crt" > "$TEMP_DIR/${DOMAIN}_K_C.pem.ec" + copy_file_to_location "private ec key and domain cert pem" "$TEMP_DIR/${DOMAIN}_K_C.pem.ec" "${to_location}" "ec" fi fi # if DOMAIN_PEM_LOCATION is not blank, then create and copy file. @@ -2598,7 +2611,7 @@ if [[ -n "$DOMAIN_PEM_LOCATION" ]]; then copy_file_to_location "full key, cert and chain pem" "$TEMP_DIR/${DOMAIN}.pem" "$to_location" if [[ "$DUAL_RSA_ECDSA" == "true" ]]; then cat "$DOMAIN_DIR/${DOMAIN}.ec.key" "${CERT_FILE%.*}.ec.crt" "${CA_CERT%.*}.ec.crt" > "$TEMP_DIR/${DOMAIN}.pem.ec" - copy_file_to_location "full ec key, cert and chain pem" "$TEMP_DIR/${DOMAIN}.pem.ec" "${to_location%.*}.ec.${to_location##*.}" + copy_file_to_location "full ec key, cert and chain pem" "$TEMP_DIR/${DOMAIN}.pem.ec" "${to_location}" "ec" fi fi # end of copying certs. diff --git a/test/4-more-than-10-hosts.bats b/test/4-more-than-10-hosts.bats index 01e364d..ff61d52 100644 --- a/test/4-more-than-10-hosts.bats +++ b/test/4-more-than-10-hosts.bats @@ -17,7 +17,7 @@ setup() { # Add 11 hosts to DNS (also need to be added as aliases in docker-compose.yml) for prefix in a b c d e f g h i j k; do - curl -X POST -d '{"host":"'$prefix.$GETSSL_HOST'", "addresses":["'$GETSSL_IP'"]}' http://10.30.50.3:8055/add-a + curl --silent -X POST -d '{"host":"'$prefix.$GETSSL_HOST'", "addresses":["'$GETSSL_IP'"]}' http://10.30.50.3:8055/add-a done init_getssl @@ -39,6 +39,6 @@ setup() { # Remove all the dns aliases cleanup_environment for prefix in a b c d e f g h i j k; do - curl -X POST -d '{"host":"'$prefix.$GETSSL_HOST'", "addresses":["'$GETSSL_IP'"]}' http://10.30.50.3:8055/del-a + curl --silent -X POST -d '{"host":"'$prefix.$GETSSL_HOST'", "addresses":["'$GETSSL_IP'"]}' http://10.30.50.3:8055/del-a done } diff --git a/test/6-dual-rsa-ecdsa-copy-2-locations.bats b/test/6-dual-rsa-ecdsa-copy-2-locations.bats new file mode 100644 index 0000000..4e64043 --- /dev/null +++ b/test/6-dual-rsa-ecdsa-copy-2-locations.bats @@ -0,0 +1,40 @@ +#! /usr/bin/env bats + +load '/bats-support/load.bash' +load '/bats-assert/load.bash' +load '/getssl/test/test_helper.bash' + + +# These are run for every test, not once per file +setup() { + export CURL_CA_BUNDLE=/root/pebble-ca-bundle.crt + curl --silent -X POST -d '{"host":"'a.$GETSSL_HOST'", "addresses":["'$GETSSL_IP'"]}' http://10.30.50.3:8055/add-a +} + + +teardown() { + curl --silent -X POST -d '{"host":"'a.$GETSSL_HOST'", "addresses":["'$GETSSL_IP'"]}' http://10.30.50.3:8055/del-a +} + + +@test "Create dual certificates and copy RSA and ECDSA chain and key to two locations" { + CONFIG_FILE="getssl-http01-dual-rsa-ecdsa-2-locations.cfg" + setup_environment + mkdir -p /root/a.${GETSSL_HOST} + + init_getssl + create_certificate + assert_success + + # Check that the RSA chain and key have been copied to both locations + assert [ -e "/etc/nginx/pki/domain-chain.crt" ] + assert [ -e "/root/a.${GETSSL_HOST}/domain-chain.crt" ] + assert [ -e "/etc/nginx/pki/private/server.key" ] + assert [ -e "/root/a.${GETSSL_HOST}/server.key" ] + + # Check that the ECDSA chain and key have been copied to both locations + assert [ -e "/etc/nginx/pki/domain-chain.ec.crt" ] + assert [ -e "/root/a.${GETSSL_HOST}/domain-chain.ec.crt" ] + assert [ -e "/etc/nginx/pki/private/server.ec.key" ] + assert [ -e "/root/a.${GETSSL_HOST}/server.ec.key" ] +} diff --git a/test/debug-test.sh b/test/debug-test.sh index 23d1983..ab00666 100644 --- a/test/debug-test.sh +++ b/test/debug-test.sh @@ -3,12 +3,22 @@ # This runs getssl outside of the BATS framework for debugging, etc, against pebble # Usage: /getssl/test/debug-test.sh getssl-http01.cfg +DEBUG="" +if [ $# -eq 2 ]; then + DEBUG=$1 + shift +fi + CONFIG_FILE=$1 +if [ ! -e "$CONFIG_FILE" ]; then + CONFIG_FILE=${CODE_DIR}/test/test-config/${CONFIG_FILE} +fi source /getssl/test/test_helper.bash setup_environment 3>&1 export CURL_CA_BUNDLE=/root/pebble-ca-bundle.crt "${CODE_DIR}/getssl" -c "$GETSSL_HOST" 3>&1 -cp "${CODE_DIR}/test/test-config/${CONFIG_FILE}" "${INSTALL_DIR}/.getssl/${GETSSL_HOST}/getssl.cfg" -"${CODE_DIR}/getssl" -f "$GETSSL_HOST" 3>&1 +cp "${CONFIG_FILE}" "${INSTALL_DIR}/.getssl/${GETSSL_HOST}/getssl.cfg" +# shellcheck disable=SC2086 +"${CODE_DIR}/getssl" ${DEBUG} -f "$GETSSL_HOST" 3>&1 diff --git a/test/run-all-tests.cmd b/test/run-all-tests.cmd new file mode 100644 index 0000000..16c6fd5 --- /dev/null +++ b/test/run-all-tests.cmd @@ -0,0 +1,5 @@ +docker exec -it getssl-alpine bats /getssl/test +docker exec -it getssl-centos6 bats /getssl/test +docker exec -it getssl-debian bats /getssl/test +docker exec -it getssl-ubuntu bats /getssl/test +docker exec -it getssl-ubuntu18 bats /getssl/test diff --git a/test/test-config/getssl-http01-dual-rsa-ecdsa-2-locations.cfg b/test/test-config/getssl-http01-dual-rsa-ecdsa-2-locations.cfg new file mode 100644 index 0000000..80533ce --- /dev/null +++ b/test/test-config/getssl-http01-dual-rsa-ecdsa-2-locations.cfg @@ -0,0 +1,32 @@ +# Test that more than one location can be specified for CERT and KEY locations and that the +# files are copied to both locations when both RSA and ECDSA certificates are created +# +CA="https://pebble:14000/dir" + +DUAL_RSA_ECDSA="true" +ACCOUNT_KEY_TYPE="prime256v1" +PRIVATE_KEY_ALG="prime256v1" + +# Additional domains - this could be multiple domains / subdomains in a comma separated list +SANS="a.${GETSSL_HOST}" + +# Acme Challenge Location. +ACL=('/var/www/html/.well-known/acme-challenge') + +#Set USE_SINGLE_ACL="true" to use a single ACL for all checks +USE_SINGLE_ACL="true" + +# Location for all your certs, these can either be on the server (full path name) +# or using ssh /sftp as for the ACL +DOMAIN_CERT_LOCATION="/etc/nginx/pki/server.crt" +DOMAIN_KEY_LOCATION="/etc/nginx/pki/private/server.key;/root/a.${GETSSL_HOST}/server.key" +CA_CERT_LOCATION="/etc/nginx/pki/chain.crt" +DOMAIN_CHAIN_LOCATION="/etc/nginx/pki/domain-chain.crt;/root/a.${GETSSL_HOST}/domain-chain.crt" # this is the domain cert and CA cert +DOMAIN_PEM_LOCATION="" # this is the domain_key, domain cert and CA cert + +# The command needed to reload apache / nginx or whatever you use +RELOAD_CMD="cp /getssl/test/test-config/nginx-ubuntu-ssl ${NGINX_CONFIG} && /getssl/test/restart-nginx" + +# Define the server type and confirm correct certificate is installed +SERVER_TYPE="https" +CHECK_REMOTE="true" diff --git a/test/test_helper.bash b/test/test_helper.bash index b33ee0b..554d60a 100644 --- a/test/test_helper.bash +++ b/test/test_helper.bash @@ -9,7 +9,7 @@ setup_environment() { fi if [ ! -f ${INSTALL_DIR}/pebble.minica.pem ]; then - wget --no-clobber https://raw.githubusercontent.com/letsencrypt/pebble/master/test/certs/pebble.minica.pem 2>&1 + wget --quiet --no-clobber https://raw.githubusercontent.com/letsencrypt/pebble/master/test/certs/pebble.minica.pem 2>&1 CERT_FILE=/etc/ssl/certs/ca-certificates.crt if [ ! -f $CERT_FILE ]; then CERT_FILE=/etc/pki/tls/certs/ca-bundle.crt @@ -17,14 +17,14 @@ setup_environment() { cat $CERT_FILE ${INSTALL_DIR}/pebble.minica.pem > ${INSTALL_DIR}/pebble-ca-bundle.crt fi - curl -X POST -d '{"host":"'"$GETSSL_HOST"'", "addresses":["'"$GETSSL_IP"'"]}' http://10.30.50.3:8055/add-a + curl --silent -X POST -d '{"host":"'"$GETSSL_HOST"'", "addresses":["'"$GETSSL_IP"'"]}' http://10.30.50.3:8055/add-a cp ${CODE_DIR}/test/test-config/nginx-ubuntu-no-ssl "${NGINX_CONFIG}" /getssl/test/restart-nginx } cleanup_environment() { - curl -X POST -d '{"host":"'"$GETSSL_HOST"'", "addresses":["'"$GETSSL_IP"'"]}' http://10.30.50.3:8055/del-a + curl --silent -X POST -d '{"host":"'"$GETSSL_HOST"'", "addresses":["'"$GETSSL_IP"'"]}' http://10.30.50.3:8055/del-a } From 535909bd3b54944d6292ed29a2c1e245c081c1a8 Mon Sep 17 00:00:00 2001 From: sergio Date: Fri, 21 Feb 2020 01:06:23 +0300 Subject: [PATCH 041/110] ${options} must be without quotes --- dns_scripts/dns_add_nsupdate | 2 +- dns_scripts/dns_del_nsupdate | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dns_scripts/dns_add_nsupdate b/dns_scripts/dns_add_nsupdate index 13b0fc9..7a2f300 100755 --- a/dns_scripts/dns_add_nsupdate +++ b/dns_scripts/dns_add_nsupdate @@ -23,7 +23,7 @@ fi # Note that blank line is a "send" command to nsupdate -nsupdate "${options}" -v < Date: Fri, 21 Feb 2020 01:30:55 +0300 Subject: [PATCH 042/110] indent correction --- dns_scripts/dns_add_nsupdate | 14 +++++++------- dns_scripts/dns_del_nsupdate | 14 +++++++------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/dns_scripts/dns_add_nsupdate b/dns_scripts/dns_add_nsupdate index 7a2f300..7e0a722 100755 --- a/dns_scripts/dns_add_nsupdate +++ b/dns_scripts/dns_add_nsupdate @@ -14,11 +14,11 @@ token="$2" if [ -n "${DNS_NSUPDATE_KEYFILE}" ]; then - if [ -n "${DNS_NSUPDATE_KEY_HOOK}" ] && ! ${DNS_NSUPDATE_KEY_HOOK} 'add' 'open' "${fulldomain}" ; then - exit $(( $? + 128 )) - fi + if [ -n "${DNS_NSUPDATE_KEY_HOOK}" ] && ! ${DNS_NSUPDATE_KEY_HOOK} 'add' 'open' "${fulldomain}" ; then + exit $(( $? + 128 )) + fi - options="-k ${DNS_NSUPDATE_KEYFILE}" + options="-k ${DNS_NSUPDATE_KEYFILE}" fi # Note that blank line is a "send" command to nsupdate @@ -31,9 +31,9 @@ EOF sts=$? if [ -n "${DNS_NSUPDATE_KEYFILE}" ]; then - if [ -n "${DNS_NSUPDATE_KEY_HOOK}" ] && ! ${DNS_NSUPDATE_KEY_HOOK} 'add' 'close' "${fulldomain}"; then - exit $(( sts + ( $? * 10 ) )) - fi + if [ -n "${DNS_NSUPDATE_KEY_HOOK}" ] && ! ${DNS_NSUPDATE_KEY_HOOK} 'add' 'close' "${fulldomain}"; then + exit $(( sts + ( $? * 10 ) )) + fi fi exit ${sts} diff --git a/dns_scripts/dns_del_nsupdate b/dns_scripts/dns_del_nsupdate index d2ff048..8e0f253 100755 --- a/dns_scripts/dns_del_nsupdate +++ b/dns_scripts/dns_del_nsupdate @@ -14,11 +14,11 @@ token="$2" # 'open" / 'close' if [ -n "${DNS_NSUPDATE_KEYFILE}" ]; then - if [ -n "${DNS_NSUPDATE_KEY_HOOK}" ] && ! "${DNS_NSUPDATE_KEY_HOOK}" 'del' 'open' "${fulldomain}" ; then - exit $(( $? + 128 )) - fi + if [ -n "${DNS_NSUPDATE_KEY_HOOK}" ] && ! "${DNS_NSUPDATE_KEY_HOOK}" 'del' 'open' "${fulldomain}" ; then + exit $(( $? + 128 )) + fi - options="-k ${DNS_NSUPDATE_KEYFILE}" + options="-k ${DNS_NSUPDATE_KEYFILE}" fi # Note that blank line is a "send" command to nsupdate @@ -31,9 +31,9 @@ EOF sts=$? if [ -n "${DNS_NSUPDATE_KEYFILE}" ]; then - if [ -n "${DNS_NSUPDATE_KEY_HOOK}" ] && ! "${DNS_NSUPDATE_KEY_HOOK}" 'del' 'close' "${fulldomain}" ; then - exit $(( sts + ( $? * 10 ) )) - fi + if [ -n "${DNS_NSUPDATE_KEY_HOOK}" ] && ! "${DNS_NSUPDATE_KEY_HOOK}" 'del' 'close' "${fulldomain}" ; then + exit $(( sts + ( $? * 10 ) )) + fi fi exit ${sts} From b869824b49cc9e24e4592f75452a8b3b3527bf6d Mon Sep 17 00:00:00 2001 From: sergio Date: Fri, 21 Feb 2020 02:28:25 +0300 Subject: [PATCH 043/110] nsupdate scripts: add support for custom nameserver and zone --- dns_scripts/dns_add_nsupdate | 10 ++++++---- dns_scripts/dns_del_nsupdate | 10 ++++++---- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/dns_scripts/dns_add_nsupdate b/dns_scripts/dns_add_nsupdate index 7e0a722..98f5e7f 100755 --- a/dns_scripts/dns_add_nsupdate +++ b/dns_scripts/dns_add_nsupdate @@ -21,12 +21,14 @@ if [ -n "${DNS_NSUPDATE_KEYFILE}" ]; then options="-k ${DNS_NSUPDATE_KEYFILE}" fi -# Note that blank line is a "send" command to nsupdate +if [ -n "${DNS_SERVER}" ]; then + cmd+="server ${DNS_SERVER}\n" +fi -nsupdate ${options} -v < Date: Sat, 22 Feb 2020 18:02:21 +0000 Subject: [PATCH 044/110] Use openssl asn1parse in sign_string --- getssl | 80 ++++++++++++++++------------------------------------------ 1 file changed, 22 insertions(+), 58 deletions(-) diff --git a/getssl b/getssl index dbcd867..d64b276 100755 --- a/getssl +++ b/getssl @@ -339,7 +339,7 @@ check_challenge_completion() { # checks with the ACME server if our challenge is # if ACME response is that their check gave an invalid response, error exit if [[ "$status" == "invalid" ]] ; then - err_detail=$(json_get "$response" detail) + err_detail=$(echo "$response" | grep "detail") error_exit "$domain:Verify error:$err_detail" fi @@ -347,7 +347,8 @@ check_challenge_completion() { # checks with the ACME server if our challenge is if [[ "$status" == "pending" ]] ; then info "Pending" else - error_exit "$domain:Verify error:$response" + err_detail=$(echo "$response" | grep "detail") + error_exit "$domain:Verify error:$status:$err_detail" fi debug "sleep 5 secs before testing verify again" sleep 5 @@ -431,7 +432,7 @@ check_config() { # check the config files for all obvious errors info "${DOMAIN}: ACL location not specified for domain $d in $DOMAIN_DIR/getssl.cfg" config_errors=true fi - # check domain exist + # check domain exists if [[ "$DNS_CHECK_FUNC" == "drill" ]] || [[ "$DNS_CHECK_FUNC" == "dig" ]]; then if [[ "$($DNS_CHECK_FUNC "${d}" |grep -c "${d}")" -ge 1 ]]; then debug "found IP for ${d}" @@ -1807,65 +1808,28 @@ sign_string() { # sign a string with a given key and algorithm and return urlbas if openssl rsa -in "${skey}" -noout 2>/dev/null ; then # RSA key signed64="$(printf '%s' "${str}" | openssl dgst -"$signalg" -sign "$key" | urlbase64)" elif openssl ec -in "${skey}" -noout 2>/dev/null ; then # Elliptic curve key. - signed=$(printf '%s' "${str}" | openssl dgst -"$signalg" -sign "$key" -hex | awk '{print $2}') - debug "EC signature $signed" - if [[ "${signed:4:4}" == "021f" ]]; then #sha256 which needs padding - R=$(echo -n 00;echo "$signed" | cut -c 9-70) - part2=$(echo "$signed" | cut -c 71-) - elif [[ "${signed:4:4}" == "0220" ]]; then #sha256 - R=$(echo "$signed" | cut -c 9-72) - part2=$(echo "$signed" | cut -c 73-) - elif [[ "${signed:4:4}" == "0221" ]]; then #sha256 which needs trimming - R=$(echo "$signed" | cut -c 11-74) - part2=$(echo "$signed" | cut -c 75-) - elif [[ "${signed:4:4}" == "022f" ]]; then #sha384 which needs padding - info "Padding sha384" - R=$(echo -n 00;echo "$signed" | cut -c 9-102) - part2=$(echo "$signed" | cut -c 103-) - elif [[ "${signed:4:4}" == "0230" ]]; then #sha384 - R=$(echo "$signed" | cut -c 9-104) - part2=$(echo "$signed" | cut -c 105-) - elif [[ "${signed:4:4}" == "0231" ]]; then #sha384 which needs trimming - R=$(echo "$signed" | cut -c 11-106) - part2=$(echo "$signed" | cut -c 107-) - elif [[ "${signed:6:4}" == "0240" ]]; then #sha512 which needs padding - R=$(echo -n 00;echo "$signed" | cut -c 9-138) - part2=$(echo "$signed" | cut -c 141-) - elif [[ "${signed:6:4}" == "0241" ]]; then #sha512 which needs padding - R=$(echo -n 00;echo "$signed" | cut -c 11-140) - part2=$(echo "$signed" | cut -c 141-) - elif [[ "${signed:6:4}" == "0242" ]]; then #sha512 - R=$(echo "$signed" | cut -c 11-142) - part2=$(echo "$signed" | cut -c 143-) + # ECDSA signature width + # e.g. 521 bits requires 66 bytes to express, a signature consists of 2 integers so 132 bytes + # https://crypto.stackexchange.com/questions/12299/ecc-key-size-and-signature-size/ + if [ "$signalg" = "sha256" ]; then + w=64 + elif [ "$signalg" = "sha384" ]; then + w=96 + elif [ "$signalg" = "sha512" ]; then + w=132 else - error_exit "error in EC signing couldn't get R from $signed" + error_exit "Unknown signing algorithm $signalg" fi + asn1parse=$(printf '%s' "${str}" | openssl dgst -"$signalg" -sign "$key" | openssl asn1parse -inform DER) + #shellcheck disable=SC2086 + R=$(echo $asn1parse | awk '{ print $13 }' | cut -c2-) debug "R $R" - - if [[ "${part2:0:4}" == "021f" ]]; then #sha256 with padding - S=$(echo -n 00;echo "$part2" | cut -c 5-) - elif [[ "${part2:0:4}" == "0220" ]]; then #sha256 - S=$(echo "$part2" | cut -c 5-68) - elif [[ "${part2:0:4}" == "0221" ]]; then #sha256 - S=$(echo "$part2" | cut -c 7-70) - elif [[ "${part2:0:4}" == "022f" ]]; then #sha384 with padding - S=$(echo -n 00;echo "$part2" | cut -c 5-) - elif [[ "${part2:0:4}" == "0230" ]]; then #sha384 - S=$(echo "$part2" | cut -c 5-100) - elif [[ "${part2:0:4}" == "0231" ]]; then #sha384 - S=$(echo "$part2" | cut -c 7-102) - elif [[ "${part2:0:4}" == "0240" ]]; then #sha512 with padding - S=$(echo -n 00;echo "$part2" | cut -c 5-) - elif [[ "${part2:0:4}" == "0241" ]]; then #sha512 with padding - S=$(echo -n 00;echo "$part2" | cut -c 5-) - elif [[ "${part2:0:4}" == "0242" ]]; then #sha512 - S=$(echo "$part2" | cut -c 5-) - else - error_exit "error in EC signing couldn't get S from $signed" - fi - + #shellcheck disable=SC2086 + S=$(echo $asn1parse | awk '{ print $20 }' | cut -c2-) debug "S $S" - signed64=$(printf '%s' "${R}${S}" | hex2bin | urlbase64 ) + + # pad R and S to the correct length for the signing algorithm + signed64=$(printf "%${w}s%${w}s" "${R}" "${S}" | tr ' ' '0' | hex2bin | urlbase64 ) debug "encoded RS $signed64" fi } From aa5eb90a6b8a54bf178c0b620b8c5ae8ebed4b91 Mon Sep 17 00:00:00 2001 From: Tim Kimber Date: Sat, 22 Feb 2020 18:04:45 +0000 Subject: [PATCH 045/110] Add tests using staging server --- dns_scripts/dns_add_duckdns | 19 ++++ dns_scripts/dns_del_duckdns | 12 +++ docker-compose.yml | 19 +++- test/1-simple-http01.bats | 7 +- test/2-simple-dns01.bats | 8 +- test/3-dual-rsa-ecdsa.bats | 14 ++- test/4-more-than-10-hosts.bats | 7 +- test/5-secp384-http01.bats | 12 +++ test/6-dual-rsa-ecdsa-copy-2-locations.bats | 13 ++- test/7-duckdns-dns01.bats | 42 ++++++++ test/8-duckdns-ecdsa.bats | 103 ++++++++++++++++++++ test/debug-test.sh | 8 +- test/run-all-tests.cmd | 1 + test/run-all-tests.sh | 1 + test/test-config/getssl-duckdns01.cfg | 37 +++++++ 15 files changed, 293 insertions(+), 10 deletions(-) create mode 100644 dns_scripts/dns_add_duckdns create mode 100644 dns_scripts/dns_del_duckdns create mode 100644 test/7-duckdns-dns01.bats create mode 100644 test/8-duckdns-ecdsa.bats create mode 100644 test/test-config/getssl-duckdns01.cfg diff --git a/dns_scripts/dns_add_duckdns b/dns_scripts/dns_add_duckdns new file mode 100644 index 0000000..ef40efe --- /dev/null +++ b/dns_scripts/dns_add_duckdns @@ -0,0 +1,19 @@ +#!/bin/bash + +# need to add your Token for duckdns below +token=${DUCKDNS_TOKEN:-} + +if [ -z "$token" ]; then + echo "DUCKDNS_TOKEN not set" + exit 1 +fi + +domain="$1" +txtvalue="$2" + +response=$(curl --silent "https://www.duckdns.org/update?domains=${domain}&token=${token}&txt=${txtvalue}") +if [ "$response" != "OK" ]; then + echo "Failed to update TXT record for ${domain} at duckdns.org (is the TOKEN valid?)" + echo "Response: $response" + exit 1 +fi diff --git a/dns_scripts/dns_del_duckdns b/dns_scripts/dns_del_duckdns new file mode 100644 index 0000000..b9b9f9f --- /dev/null +++ b/dns_scripts/dns_del_duckdns @@ -0,0 +1,12 @@ +#!/bin/bash + +# need to add your Token for duckdns below +token=${DUCKDNS_TOKEN:-} +domain="$1" + +response=$(curl --silent "https://www.duckdns.org/update?domains=${domain}&token=${token}&txt=&clear=true") +if [ "$response" != "OK" ]; then + echo "Failed to update TXT record for ${domain} at duckdns.org (is the TOKEN valid?)" + echo "$response" + exit 1 +fi diff --git a/docker-compose.yml b/docker-compose.yml index 09a4264..8f9e4cb 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -156,7 +156,24 @@ services: - i.ubuntu18.getssl.test - j.ubuntu18.getssl.test - k.ubuntu18.getssl.test - + getssl-duckdns: + build: + context: . + dockerfile: test/Dockerfile-ubuntu + container_name: getssl-duckdns + volumes: + - .:/getssl + environment: + GETSSL_HOST: getssl.duckdns.org + GETSSL_IP: 10.30.50.15 + NGINX_CONFIG: /etc/nginx/sites-enabled/default + DUCKDNS_TOKEN: $DUCKDNS_TOKEN + STAGING: "true" + networks: + acmenet: + ipv4_address: 10.30.50.15 + aliases: + - getssl.duckdns.org networks: diff --git a/test/1-simple-http01.bats b/test/1-simple-http01.bats index 4c55304..fd96a8a 100644 --- a/test/1-simple-http01.bats +++ b/test/1-simple-http01.bats @@ -12,6 +12,9 @@ setup() { @test "Create new certificate using HTTP-01 verification" { + if [ -n "$STAGING" ]; then + skip "Using staging server, skipping internal test" + fi CONFIG_FILE="getssl-http01.cfg" setup_environment init_getssl @@ -24,7 +27,9 @@ setup() { @test "Force renewal of certificate using HTTP-01" { - #!FIXME test certificate has been updated + if [ -n "$STAGING" ]; then + skip "Using staging server, skipping internal test" + fi run ${CODE_DIR}/getssl -f $GETSSL_HOST assert_success refute_output --regexp '[Ff][Aa][Ii][Ll][Ee][Dd]' diff --git a/test/2-simple-dns01.bats b/test/2-simple-dns01.bats index 9d9f44b..ffd0b9e 100644 --- a/test/2-simple-dns01.bats +++ b/test/2-simple-dns01.bats @@ -12,6 +12,10 @@ setup() { @test "Create new certificate using DNS-01 verification" { + if [ -n "$STAGING" ]; then + skip "Using staging server, skipping internal test" + fi + CONFIG_FILE="getssl-dns01.cfg" setup_environment init_getssl @@ -24,7 +28,9 @@ setup() { @test "Force renewal of certificate using DNS-01" { - #!FIXME test certificate has been updated + if [ -n "$STAGING" ]; then + skip "Using staging server, skipping internal test" + fi run ${CODE_DIR}/getssl -f $GETSSL_HOST assert_success refute_output --regexp '[Ff][Aa][Ii][Ll][Ee][Dd]' diff --git a/test/3-dual-rsa-ecdsa.bats b/test/3-dual-rsa-ecdsa.bats index 2dc9257..7820a96 100644 --- a/test/3-dual-rsa-ecdsa.bats +++ b/test/3-dual-rsa-ecdsa.bats @@ -12,6 +12,9 @@ setup() { @test "Create dual certificates using HTTP-01 verification" { + if [ -n "$STAGING" ]; then + skip "Using staging server, skipping internal test" + fi CONFIG_FILE="getssl-http01-dual-rsa-ecdsa.cfg" setup_environment init_getssl @@ -21,12 +24,17 @@ setup() { @test "Force renewal of dual certificates using HTTP-01" { - #!FIXME test certificate has been updated + if [ -n "$STAGING" ]; then + skip "Using staging server, skipping internal test" + fi run ${CODE_DIR}/getssl -f $GETSSL_HOST assert_success } @test "Create dual certificates using DNS-01 verification" { + if [ -n "$STAGING" ]; then + skip "Using staging server, skipping internal test" + fi CONFIG_FILE="getssl-dns01-dual-rsa-ecdsa.cfg" setup_environment init_getssl @@ -36,7 +44,9 @@ setup() { @test "Force renewal of dual certificates using DNS-01" { - #!FIXME test certificate has been updated + if [ -n "$STAGING" ]; then + skip "Using staging server, skipping internal test" + fi run ${CODE_DIR}/getssl -f $GETSSL_HOST assert_success cleanup_environment diff --git a/test/4-more-than-10-hosts.bats b/test/4-more-than-10-hosts.bats index ff61d52..5bdfc2a 100644 --- a/test/4-more-than-10-hosts.bats +++ b/test/4-more-than-10-hosts.bats @@ -12,6 +12,9 @@ setup() { @test "Create certificates for more than 10 hosts using HTTP-01 verification" { + if [ -n "$STAGING" ]; then + skip "Using staging server, skipping internal test" + fi CONFIG_FILE="getssl-http01-10-hosts.cfg" setup_environment @@ -30,7 +33,9 @@ setup() { @test "Force renewal of more than 10 certificates using HTTP-01" { - #!FIXME test certificate has been updated + if [ -n "$STAGING" ]; then + skip "Using staging server, skipping internal test" + fi run ${CODE_DIR}/getssl -f $GETSSL_HOST assert_success refute_output --regexp '[Ff][Aa][Ii][Ll][Ee][Dd]' diff --git a/test/5-secp384-http01.bats b/test/5-secp384-http01.bats index 9010d58..29da2da 100644 --- a/test/5-secp384-http01.bats +++ b/test/5-secp384-http01.bats @@ -12,6 +12,9 @@ setup() { @test "Create new secp384r1 certificate using HTTP-01 verification" { + if [ -n "$STAGING" ]; then + skip "Using staging server, skipping internal test" + fi CONFIG_FILE="getssl-http01-secp384.cfg" setup_environment init_getssl @@ -21,12 +24,18 @@ setup() { @test "Force renewal of secp384r1 certificate using HTTP-01" { + if [ -n "$STAGING" ]; then + skip "Using staging server, skipping internal test" + fi run ${CODE_DIR}/getssl -f $GETSSL_HOST assert_success } @test "Create new secp521r1 certificate using HTTP-01 verification" { + if [ -n "$STAGING" ]; then + skip "Using staging server, skipping internal test" + fi CONFIG_FILE="getssl-http01-secp521.cfg" setup_environment init_getssl @@ -36,6 +45,9 @@ setup() { @test "Force renewal of secp521r1 certificate using HTTP-01" { + if [ -n "$STAGING" ]; then + skip "Using staging server, skipping internal test" + fi run ${CODE_DIR}/getssl -f $GETSSL_HOST assert_success } diff --git a/test/6-dual-rsa-ecdsa-copy-2-locations.bats b/test/6-dual-rsa-ecdsa-copy-2-locations.bats index 4e64043..aae21bb 100644 --- a/test/6-dual-rsa-ecdsa-copy-2-locations.bats +++ b/test/6-dual-rsa-ecdsa-copy-2-locations.bats @@ -7,17 +7,24 @@ load '/getssl/test/test_helper.bash' # These are run for every test, not once per file setup() { - export CURL_CA_BUNDLE=/root/pebble-ca-bundle.crt - curl --silent -X POST -d '{"host":"'a.$GETSSL_HOST'", "addresses":["'$GETSSL_IP'"]}' http://10.30.50.3:8055/add-a + if [ -z "$STAGING" ]; then + export CURL_CA_BUNDLE=/root/pebble-ca-bundle.crt + curl --silent -X POST -d '{"host":"'a.$GETSSL_HOST'", "addresses":["'$GETSSL_IP'"]}' http://10.30.50.3:8055/add-a + fi } teardown() { - curl --silent -X POST -d '{"host":"'a.$GETSSL_HOST'", "addresses":["'$GETSSL_IP'"]}' http://10.30.50.3:8055/del-a + if [ -z "$STAGING" ]; then + curl --silent -X POST -d '{"host":"'a.$GETSSL_HOST'", "addresses":["'$GETSSL_IP'"]}' http://10.30.50.3:8055/del-a + fi } @test "Create dual certificates and copy RSA and ECDSA chain and key to two locations" { + if [ -n "$STAGING" ]; then + skip "Using staging server, skipping internal test" + fi CONFIG_FILE="getssl-http01-dual-rsa-ecdsa-2-locations.cfg" setup_environment mkdir -p /root/a.${GETSSL_HOST} diff --git a/test/7-duckdns-dns01.bats b/test/7-duckdns-dns01.bats new file mode 100644 index 0000000..e81b414 --- /dev/null +++ b/test/7-duckdns-dns01.bats @@ -0,0 +1,42 @@ +#! /usr/bin/env bats + +load '/bats-support/load.bash' +load '/bats-assert/load.bash' +load '/getssl/test/test_helper.bash' + + +# These are run for every test, not once per file +setup() { + if [ -n "$STAGING" ]; then + export GETSSL_HOST=getssl.duckdns.org + fi +} + + +@test "Create new certificate using staging server and DuckDNS" { + if [ -z "$STAGING" ]; then + skip "Running internal tests, skipping external test" + fi + CONFIG_FILE="getssl-duckdns01.cfg" + + setup_environment + init_getssl + create_certificate + assert_success + refute_output --regexp '[Ff][Aa][Ii][Ll][Ee][Dd]' + refute_output --regexp '[Ee][Rr][Rr][Oo][Rr]' + refute_output --regexp '[Ww][Aa][Rr][Nn][Ii][Nn][Gg]' +} + +@test "Force renewal of certificate using staging server and DuckDNS" { + if [ -z "$STAGING" ]; then + skip "Running internal tests, skipping external test" + fi + run ${CODE_DIR}/getssl -f $GETSSL_HOST + assert_success + refute_output --regexp '[Ff][Aa][Ii][Ll][Ee][Dd]' + refute_output --regexp '[Ee][Rr][Rr][Oo][Rr]' + refute_output --regexp '[Ww][Aa][Rr][Nn][Ii][Nn][Gg]' + cleanup_environment + curl --silent -X POST -d '{"host":"getssl.duckdns.org", "addresses":["'$GETSSL_IP'"]}' http://10.30.50.3:8055/del-a +} diff --git a/test/8-duckdns-ecdsa.bats b/test/8-duckdns-ecdsa.bats new file mode 100644 index 0000000..f50dd05 --- /dev/null +++ b/test/8-duckdns-ecdsa.bats @@ -0,0 +1,103 @@ +#! /usr/bin/env bats + +load '/bats-support/load.bash' +load '/bats-assert/load.bash' +load '/getssl/test/test_helper.bash' + + +# These are run for every test, not once per file +setup() { + if [ -n "$STAGING" ]; then + export GETSSL_HOST=getssl.duckdns.org + fi +} + + +@test "Create new certificate using staging server and prime256v1" { + if [ -z "$STAGING" ]; then + skip "Running internal tests, skipping external test" + fi + CONFIG_FILE="getssl-duckdns01.cfg" + GETSSL_HOST=getssl.duckdns.org + + setup_environment + init_getssl + sed -e 's/rsa/prime256v1/g' < "${CODE_DIR}/test/test-config/${CONFIG_FILE}" > "${INSTALL_DIR}/.getssl/${GETSSL_HOST}/getssl.cfg" + run ${CODE_DIR}/getssl "$GETSSL_HOST" + assert_success + refute_output --regexp '[Ff][Aa][Ii][Ll][Ee][Dd]' + refute_output --regexp '[Ee][Rr][Rr][Oo][Rr]' + refute_output --regexp '[Ww][Aa][Rr][Nn][Ii][Nn][Gg]' +} + + +@test "Force renewal of certificate using staging server and prime256v1" { + if [ -z "$STAGING" ]; then + skip "Running internal tests, skipping external test" + fi + run ${CODE_DIR}/getssl -f $GETSSL_HOST + assert_success + refute_output --regexp '[Ff][Aa][Ii][Ll][Ee][Dd]' + refute_output --regexp '[Ee][Rr][Rr][Oo][Rr]' + refute_output --regexp '[Ww][Aa][Rr][Nn][Ii][Nn][Gg]' + cleanup_environment +} + + +@test "Create new certificate using staging server and secp384r1" { + if [ -z "$STAGING" ]; then + skip "Running internal tests, skipping external test" + fi + CONFIG_FILE="getssl-duckdns01.cfg" + GETSSL_HOST=getssl.duckdns.org + + setup_environment + init_getssl + sed -e 's/rsa/secp384r1/g' < "${CODE_DIR}/test/test-config/${CONFIG_FILE}" > "${INSTALL_DIR}/.getssl/${GETSSL_HOST}/getssl.cfg" + run ${CODE_DIR}/getssl "$GETSSL_HOST" + assert_success + refute_output --regexp '[Ff][Aa][Ii][Ll][Ee][Dd]' + refute_output --regexp '[Ee][Rr][Rr][Oo][Rr]' + refute_output --regexp '[Ww][Aa][Rr][Nn][Ii][Nn][Gg]' +} + + +@test "Force renewal of certificate using staging server and secp384r1" { + if [ -z "$STAGING" ]; then + skip "Running internal tests, skipping external test" + fi + run ${CODE_DIR}/getssl -f $GETSSL_HOST + assert_success + refute_output --regexp '[Ff][Aa][Ii][Ll][Ee][Dd]' + refute_output --regexp '[Ee][Rr][Rr][Oo][Rr]' + refute_output --regexp '[Ww][Aa][Rr][Nn][Ii][Nn][Gg]' + cleanup_environment +} + + +@test "Create new certificate using staging server and secp521r1" { + skip "The staging server returns 'ECDSA curve P-521 not allowed'" + + CONFIG_FILE="getssl-duckdns01.cfg" + GETSSL_HOST=getssl.duckdns.org + + setup_environment + init_getssl + sed -e 's/rsa/secp521r1/g' < "${CODE_DIR}/test/test-config/${CONFIG_FILE}" > "${INSTALL_DIR}/.getssl/${GETSSL_HOST}/getssl.cfg" + run ${CODE_DIR}/getssl "$GETSSL_HOST" + assert_success + refute_output --regexp '[Ff][Aa][Ii][Ll][Ee][Dd]' + refute_output --regexp '[Ee][Rr][Rr][Oo][Rr]' + refute_output --regexp '[Ww][Aa][Rr][Nn][Ii][Nn][Gg]' +} + + +@test "Force renewal of certificate using staging server and secp521r1" { + skip "The staging server returns 'ECDSA curve P-521 not allowed'" + run ${CODE_DIR}/getssl -f $GETSSL_HOST + assert_success + refute_output --regexp '[Ff][Aa][Ii][Ll][Ee][Dd]' + refute_output --regexp '[Ee][Rr][Rr][Oo][Rr]' + refute_output --regexp '[Ww][Aa][Rr][Nn][Ii][Nn][Gg]' + cleanup_environment +} diff --git a/test/debug-test.sh b/test/debug-test.sh index ab00666..1327fde 100644 --- a/test/debug-test.sh +++ b/test/debug-test.sh @@ -13,10 +13,16 @@ CONFIG_FILE=$1 if [ ! -e "$CONFIG_FILE" ]; then CONFIG_FILE=${CODE_DIR}/test/test-config/${CONFIG_FILE} fi + +#shellcheck disable=SC1091 source /getssl/test/test_helper.bash setup_environment 3>&1 -export CURL_CA_BUNDLE=/root/pebble-ca-bundle.crt + +# Only add the pebble CA to the cert bundle if using pebble +if [ "$(grep -q pebble "${CONFIG_FILE}")" = 0 ]; then + export CURL_CA_BUNDLE=/root/pebble-ca-bundle.crt +fi "${CODE_DIR}/getssl" -c "$GETSSL_HOST" 3>&1 cp "${CONFIG_FILE}" "${INSTALL_DIR}/.getssl/${GETSSL_HOST}/getssl.cfg" diff --git a/test/run-all-tests.cmd b/test/run-all-tests.cmd index 16c6fd5..2c5ff6c 100644 --- a/test/run-all-tests.cmd +++ b/test/run-all-tests.cmd @@ -3,3 +3,4 @@ docker exec -it getssl-centos6 bats /getssl/test docker exec -it getssl-debian bats /getssl/test docker exec -it getssl-ubuntu bats /getssl/test docker exec -it getssl-ubuntu18 bats /getssl/test +docker exec -it getssl-duckdns bats /getssl/test diff --git a/test/run-all-tests.sh b/test/run-all-tests.sh index b526c63..6a0eb8a 100644 --- a/test/run-all-tests.sh +++ b/test/run-all-tests.sh @@ -5,3 +5,4 @@ docker exec -it getssl-centos6 bats /getssl/test docker exec -it getssl-debian bats /getssl/test docker exec -it getssl-ubuntu bats /getssl/test docker exec -it getssl-ubuntu18 bats /getssl/test +docker exec -it getssl-duckdns bats /getssl/test diff --git a/test/test-config/getssl-duckdns01.cfg b/test/test-config/getssl-duckdns01.cfg new file mode 100644 index 0000000..4a37bcd --- /dev/null +++ b/test/test-config/getssl-duckdns01.cfg @@ -0,0 +1,37 @@ +# Test that the script works with external dns provider and staging server +# +CA="https://acme-staging-v02.api.letsencrypt.org/directory" + +VALIDATE_VIA_DNS=true +DNS_ADD_COMMAND="/getssl/dns_scripts/dns_add_duckdns" +DNS_DEL_COMMAND="/getssl/dns_scripts/dns_del_duckdns" +AUTH_DNS_SERVER=1.1.1.1 +CHECK_ALL_AUTH_DNS=false +DNS_EXTRA_WAIT=20 + +ACCOUNT_KEY_TYPE="rsa" +PRIVATE_KEY_ALG="rsa" + +# Additional domains - this could be multiple domains / subdomains in a comma separated list +SANS="" + +# Acme Challenge Location. The first line for the domain, the following ones for each additional domain. +ACL=('/var/www/html/.well-known/acme-challenge') + +#Set USE_SINGLE_ACL="true" to use a single ACL for all checks +USE_SINGLE_ACL="false" + +# Location for all your certs, these can either be on the server (full path name) +# or using ssh /sftp as for the ACL +DOMAIN_CERT_LOCATION="/etc/nginx/pki/server.crt" +DOMAIN_KEY_LOCATION="/etc/nginx/pki/private/server.key" +CA_CERT_LOCATION="/etc/nginx/pki/chain.crt" +DOMAIN_CHAIN_LOCATION="" # this is the domain cert and CA cert +DOMAIN_PEM_LOCATION="" # this is the domain_key, domain cert and CA cert + +# The command needed to reload apache / nginx or whatever you use +RELOAD_CMD="cp /getssl/test/test-config/nginx-ubuntu-ssl ${NGINX_CONFIG} && /getssl/test/restart-nginx" + +# Define the server type and confirm correct certificate is installed (using a custom port) +SERVER_TYPE="https" +CHECK_REMOTE="true" From 6fea6179baa512cc97b47aec37e1fb2385ea4f84 Mon Sep 17 00:00:00 2001 From: Tim Kimber Date: Sat, 22 Feb 2020 18:08:42 +0000 Subject: [PATCH 046/110] Merge pull request #513 from 532910/nsupdate_options --- dns_scripts/dns_add_nsupdate | 24 +++++++++++++----------- dns_scripts/dns_del_nsupdate | 24 +++++++++++++----------- 2 files changed, 26 insertions(+), 22 deletions(-) diff --git a/dns_scripts/dns_add_nsupdate b/dns_scripts/dns_add_nsupdate index 13b0fc9..98f5e7f 100755 --- a/dns_scripts/dns_add_nsupdate +++ b/dns_scripts/dns_add_nsupdate @@ -14,26 +14,28 @@ token="$2" if [ -n "${DNS_NSUPDATE_KEYFILE}" ]; then - if [ -n "${DNS_NSUPDATE_KEY_HOOK}" ] && ! ${DNS_NSUPDATE_KEY_HOOK} 'add' 'open' "${fulldomain}" ; then - exit $(( $? + 128 )) - fi + if [ -n "${DNS_NSUPDATE_KEY_HOOK}" ] && ! ${DNS_NSUPDATE_KEY_HOOK} 'add' 'open' "${fulldomain}" ; then + exit $(( $? + 128 )) + fi - options="-k ${DNS_NSUPDATE_KEYFILE}" + options="-k ${DNS_NSUPDATE_KEYFILE}" fi -# Note that blank line is a "send" command to nsupdate +if [ -n "${DNS_SERVER}" ]; then + cmd+="server ${DNS_SERVER}\n" +fi -nsupdate "${options}" -v < Date: Sat, 22 Feb 2020 18:10:30 +0000 Subject: [PATCH 047/110] Update revision history --- getssl | 1 + 1 file changed, 1 insertion(+) diff --git a/getssl b/getssl index d64b276..f6e3972 100755 --- a/getssl +++ b/getssl @@ -212,6 +212,7 @@ # 2020-02-12 Fix #424 - Sporadic "error in EC signing couldn't get R from ..." (2.18) # 2020-02-12 Fix "Registration key already in use" (2.19) # 2020-02-13 Fix bug with copying to all locations when creating RSA and ECDSA certs (2.20) +# 2020-02-22 Change sign_string to use openssl asn1parse (better fix for #424) # ---------------------------------------------------------------------------------------- PROGNAME=${0##*/} From 2a9cc14f510156e640eaf61350e4d2d5336acb6c Mon Sep 17 00:00:00 2001 From: sergio Date: Sat, 22 Feb 2020 23:36:26 +0300 Subject: [PATCH 048/110] add vim lines for cfg files --- getssl | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/getssl b/getssl index f6e3972..03a20d9 100755 --- a/getssl +++ b/getssl @@ -1870,6 +1870,8 @@ usage() { # echos out the program usage write_domain_template() { # write out a template file for a domain. cat > "$1" <<- _EOF_domain_ + # vim: filetype=sh + # # This file is read second (and per domain if running with the -a option) # and overwrites any settings from the first file # @@ -1938,6 +1940,8 @@ write_domain_template() { # write out a template file for a domain. write_getssl_template() { # write out the main template file cat > "$1" <<- _EOF_getssl_ + # vim: filetype=sh + # # This file is read first and is common to all domains # # Uncomment and modify any variables you need From 0278ae50494d64d15f06322e68143a90b3b85abb Mon Sep 17 00:00:00 2001 From: Scott Gustafson Date: Sun, 23 Feb 2020 09:32:13 -0700 Subject: [PATCH 049/110] Fix the merge to include changes on the same lines I ignored. Add dated comment line. --- getssl | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/getssl b/getssl index bd2f296..e807933 100755 --- a/getssl +++ b/getssl @@ -213,6 +213,7 @@ # 2020-02-12 Fix "Registration key already in use" (2.19) # 2020-02-13 Fix bug with copying to all locations when creating RSA and ECDSA certs (2.20) # 2020-02-22 Change sign_string to use openssl asn1parse (better fix for #424) +# 2020-02-23 Add dig to config check for systems without drill (ubuntu) # ---------------------------------------------------------------------------------------- PROGNAME=${0##*/} @@ -433,9 +434,9 @@ check_config() { # check the config files for all obvious errors info "${DOMAIN}: ACL location not specified for domain $d in $DOMAIN_DIR/getssl.cfg" config_errors=true fi - # check domain exist + # check domain exists if [[ "$DNS_CHECK_FUNC" == "drill" ]]; then - if [[ "$($DNS_CHECK_FUNC "${d}" SOA|grep -c "^${d}")" -ge 1 ]]; then + if [[ "$($DNS_CHECK_FUNC "${d}" |grep -c "${d}")" -ge 1 ]]; then debug "found IP for ${d}" else info "${DOMAIN}: DNS lookup failed for ${d}" From 5beb0f8b9c454025a2e51b1ac7ee5e52bd5eaaf3 Mon Sep 17 00:00:00 2001 From: Tim Kimber Date: Tue, 25 Feb 2020 17:05:22 +0000 Subject: [PATCH 050/110] Fix test breakage from using dig by default --- docker-compose.yml | 4 ++-- test/debug-test.sh | 2 +- test/test-config/getssl-dns01-dual-rsa-ecdsa.cfg | 1 + test/test-config/getssl-dns01.cfg | 1 + 4 files changed, 5 insertions(+), 3 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 8f9e4cb..3eb81b7 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -3,7 +3,7 @@ services: pebble: image: letsencrypt/pebble:latest # TODO enable -strict - command: pebble -config /test/config/pebble-config.json -dnsserver 10.30.50.3:8053 + command: pebble -config /test/config/pebble-config.json -dnsserver 10.30.50.3:53 environment: # with Go 1.13.x which defaults TLS 1.3 to on GODEBUG: "tls13=1" @@ -15,7 +15,7 @@ services: ipv4_address: 10.30.50.2 challtestsrv: image: letsencrypt/pebble-challtestsrv:latest - command: pebble-challtestsrv -defaultIPv6 "" -defaultIPv4 10.30.50.3 + command: pebble-challtestsrv -defaultIPv6 "" -defaultIPv4 10.30.50.3 -dns01 ":53" ports: - 8055:8055 # HTTP Management API networks: diff --git a/test/debug-test.sh b/test/debug-test.sh index 1327fde..8807670 100644 --- a/test/debug-test.sh +++ b/test/debug-test.sh @@ -20,7 +20,7 @@ source /getssl/test/test_helper.bash setup_environment 3>&1 # Only add the pebble CA to the cert bundle if using pebble -if [ "$(grep -q pebble "${CONFIG_FILE}")" = 0 ]; then +if grep -q pebble "${CONFIG_FILE}"; then export CURL_CA_BUNDLE=/root/pebble-ca-bundle.crt fi diff --git a/test/test-config/getssl-dns01-dual-rsa-ecdsa.cfg b/test/test-config/getssl-dns01-dual-rsa-ecdsa.cfg index 042ed15..543c201 100644 --- a/test/test-config/getssl-dns01-dual-rsa-ecdsa.cfg +++ b/test/test-config/getssl-dns01-dual-rsa-ecdsa.cfg @@ -7,6 +7,7 @@ CA="https://pebble:14000/dir" VALIDATE_VIA_DNS=true DNS_ADD_COMMAND="/getssl/dns_scripts/dns_add_challtestsrv" DNS_DEL_COMMAND="/getssl/dns_scripts/dns_del_challtestsrv" +AUTH_DNS_SERVER=10.30.50.3 DUAL_RSA_ECDSA="true" ACCOUNT_KEY_TYPE="prime256v1" diff --git a/test/test-config/getssl-dns01.cfg b/test/test-config/getssl-dns01.cfg index 98637b0..7e26b98 100644 --- a/test/test-config/getssl-dns01.cfg +++ b/test/test-config/getssl-dns01.cfg @@ -7,6 +7,7 @@ CA="https://pebble:14000/dir" VALIDATE_VIA_DNS=true DNS_ADD_COMMAND="/getssl/dns_scripts/dns_add_challtestsrv" DNS_DEL_COMMAND="/getssl/dns_scripts/dns_del_challtestsrv" +AUTH_DNS_SERVER=10.30.50.3 # Additional domains - this could be multiple domains / subdomains in a comma separated list SANS="" From fef7454746f19ebf56fc834a50876d6846e38ce5 Mon Sep 17 00:00:00 2001 From: Tim Kimber Date: Tue, 25 Feb 2020 17:06:00 +0000 Subject: [PATCH 051/110] Remove TTL from grep when parsing dig output --- getssl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/getssl b/getssl index e807933..09da52f 100755 --- a/getssl +++ b/getssl @@ -998,7 +998,7 @@ if [[ $VALIDATE_VIA_DNS == "true" ]]; then | grep '"'|awk -F'"' '{ print $2}') elif [[ "$DNS_CHECK_FUNC" == "drill" ]] || [[ "$DNS_CHECK_FUNC" == "dig" ]]; then check_result=$($DNS_CHECK_FUNC TXT "_acme-challenge.${d}" "@${ns}" \ - | grep '300 IN TXT'|awk -F'"' '{ print $2}') + | grep 'IN TXT'|awk -F'"' '{ print $2}') elif [[ "$DNS_CHECK_FUNC" == "host" ]]; then check_result=$($DNS_CHECK_FUNC -t TXT "_acme-challenge.${d}" "${ns}" \ | grep 'descriptive text'|awk -F'"' '{ print $2}') From 9420d8fd565beb1c88e3d27a91d08e185b398f89 Mon Sep 17 00:00:00 2001 From: Tim Kimber Date: Wed, 26 Feb 2020 11:22:53 +0000 Subject: [PATCH 052/110] Create stale2.yml --- .github/workflows/stale2.yml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 .github/workflows/stale2.yml diff --git a/.github/workflows/stale2.yml b/.github/workflows/stale2.yml new file mode 100644 index 0000000..cd79668 --- /dev/null +++ b/.github/workflows/stale2.yml @@ -0,0 +1,20 @@ +name: Mark stale issues and pull requests + +on: + schedule: + - cron: "0 0 * * *" + +jobs: + stale: + + runs-on: ubuntu-latest + + steps: + - uses: actions/stale@v1 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + stale-issue-message: 'This issue will be closed as no response' + stale-issue-label: 'needs more information' + exempt-issue-label: 'enhancement' + days-before-stale: 60 + days-before-close: 30 From d68e317798d0021ffe41f666369dac16531f66b1 Mon Sep 17 00:00:00 2001 From: Tim Kimber Date: Wed, 26 Feb 2020 11:24:18 +0000 Subject: [PATCH 053/110] Delete stale.yml --- .github/workflows/stale.yml | 17 ----------------- 1 file changed, 17 deletions(-) delete mode 100644 .github/workflows/stale.yml diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml deleted file mode 100644 index 8699ac5..0000000 --- a/.github/workflows/stale.yml +++ /dev/null @@ -1,17 +0,0 @@ -name: "Close stale issues" -on: - schedule: - - cron: "0 0 * * *" - -jobs: - stale: - runs-on: ubuntu-latest - steps: - - uses: actions/stale@v1 - with: - repo-token: ${{ secrets.GITHUB_TOKEN }} - stale-issue-message: 'This issue will be closed as no response' - stale-issue-label: 'needs more information' - exempt-issue-label: 'enhancement' - days-before-stale: 60 - days-before-close: 30 From 0b3bff9082752df6e7703c6e302eff8385e21351 Mon Sep 17 00:00:00 2001 From: Tim Kimber Date: Wed, 26 Feb 2020 23:12:49 +0000 Subject: [PATCH 054/110] Test using nslookup and on ubuntu16 --- docker-compose.yml | 35 +++++++++++++-- ...ple-dns01.bats => 2-simple-dns01-dig.bats} | 14 +++--- test/2-simple-dns01-nslookup.bats | 34 +++++++++++++++ test/8-duckdns-ecdsa.bats | 43 ++++--------------- test/Dockerfile-ubuntu16 | 25 +++++++++++ test/debug-test.sh | 6 +-- test/run-all-tests.cmd | 9 ++++ test/test-config/getssl-duckdns01.cfg | 2 +- test/test_helper.bash | 3 +- 9 files changed, 122 insertions(+), 49 deletions(-) rename test/{2-simple-dns01.bats => 2-simple-dns01-dig.bats} (63%) create mode 100644 test/2-simple-dns01-nslookup.bats create mode 100644 test/Dockerfile-ubuntu16 diff --git a/docker-compose.yml b/docker-compose.yml index 3eb81b7..b493888 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -129,6 +129,33 @@ services: - i.ubuntu.getssl.test - j.ubuntu.getssl.test - k.ubuntu.getssl.test + getssl-ubuntu16: + build: + context: . + dockerfile: test/Dockerfile-ubuntu16 + container_name: getssl-ubuntu16 + volumes: + - .:/getssl + environment: + GETSSL_HOST: ubuntu16.getssl.test + GETSSL_IP: 10.30.50.14 + NGINX_CONFIG: /etc/nginx/sites-enabled/default + networks: + acmenet: + ipv4_address: 10.30.50.14 + aliases: + - ubuntu16.getssl.test + - a.ubuntu16.getssl.test + - b.ubuntu16.getssl.test + - c.ubuntu16.getssl.test + - d.ubuntu16.getssl.test + - e.ubuntu16.getssl.test + - f.ubuntu16.getssl.test + - g.ubuntu16.getssl.test + - h.ubuntu16.getssl.test + - i.ubuntu16.getssl.test + - j.ubuntu16.getssl.test + - k.ubuntu16.getssl.test getssl-ubuntu18: build: context: . @@ -138,11 +165,11 @@ services: - .:/getssl environment: GETSSL_HOST: ubuntu18.getssl.test - GETSSL_IP: 10.30.50.14 + GETSSL_IP: 10.30.50.15 NGINX_CONFIG: /etc/nginx/sites-enabled/default networks: acmenet: - ipv4_address: 10.30.50.14 + ipv4_address: 10.30.50.15 aliases: - ubuntu18.getssl.test - a.ubuntu18.getssl.test @@ -165,13 +192,13 @@ services: - .:/getssl environment: GETSSL_HOST: getssl.duckdns.org - GETSSL_IP: 10.30.50.15 + GETSSL_IP: 10.30.50.16 NGINX_CONFIG: /etc/nginx/sites-enabled/default DUCKDNS_TOKEN: $DUCKDNS_TOKEN STAGING: "true" networks: acmenet: - ipv4_address: 10.30.50.15 + ipv4_address: 10.30.50.16 aliases: - getssl.duckdns.org diff --git a/test/2-simple-dns01.bats b/test/2-simple-dns01-dig.bats similarity index 63% rename from test/2-simple-dns01.bats rename to test/2-simple-dns01-dig.bats index ffd0b9e..cbac598 100644 --- a/test/2-simple-dns01.bats +++ b/test/2-simple-dns01-dig.bats @@ -11,7 +11,7 @@ setup() { } -@test "Create new certificate using DNS-01 verification" { +@test "Create new certificate using DNS-01 verification (dig)" { if [ -n "$STAGING" ]; then skip "Using staging server, skipping internal test" fi @@ -19,22 +19,24 @@ setup() { CONFIG_FILE="getssl-dns01.cfg" setup_environment init_getssl - create_certificate + create_certificate -d assert_success + assert_output --partial "dig" refute_output --regexp '[Ff][Aa][Ii][Ll][Ee][Dd]' - refute_output --regexp '[Ee][Rr][Rr][Oo][Rr]' + refute_output --regexp '[^:][Ee][Rr][Rr][Oo][Rr][^:]' # don't fail for :error:badNonce refute_output --regexp '[Ww][Aa][Rr][Nn][Ii][Nn][Gg]' } -@test "Force renewal of certificate using DNS-01" { +@test "Force renewal of certificate using DNS-01 (dig)" { if [ -n "$STAGING" ]; then skip "Using staging server, skipping internal test" fi - run ${CODE_DIR}/getssl -f $GETSSL_HOST + run ${CODE_DIR}/getssl -d -f $GETSSL_HOST assert_success + assert_output --partial "dig" refute_output --regexp '[Ff][Aa][Ii][Ll][Ee][Dd]' - refute_output --regexp '[Ee][Rr][Rr][Oo][Rr]' + refute_output --regexp '[^:][Ee][Rr][Rr][Oo][Rr][^:]' # don't fail for :error:badNonce refute_output --regexp '[Ww][Aa][Rr][Nn][Ii][Nn][Gg]' cleanup_environment } diff --git a/test/2-simple-dns01-nslookup.bats b/test/2-simple-dns01-nslookup.bats new file mode 100644 index 0000000..f92d817 --- /dev/null +++ b/test/2-simple-dns01-nslookup.bats @@ -0,0 +1,34 @@ +#! /usr/bin/env bats + +load '/bats-support/load.bash' +load '/bats-assert/load.bash' +load '/getssl/test/test_helper.bash' + + +# This is run for every test +setup() { + export CURL_CA_BUNDLE=/root/pebble-ca-bundle.crt + mv /usr/bin/dig /usr/bin/dig.getssl.bak +} + + +teardown() { + mv /usr/bin/dig.getssl.bak /usr/bin/dig +} + + +@test "Create new certificate using DNS-01 verification (nslookup)" { + if [ -n "$STAGING" ]; then + skip "Using staging server, skipping internal test" + fi + + CONFIG_FILE="getssl-dns01.cfg" + setup_environment + init_getssl + create_certificate -d + assert_success + assert_output --partial "nslookup" + refute_output --regexp '[Ff][Aa][Ii][Ll][Ee][Dd]' + refute_output --regexp '[^:][Ee][Rr][Rr][Oo][Rr][^:]' # don't fail for :error:badNonce + refute_output --regexp '[Ww][Aa][Rr][Nn][Ii][Nn][Gg]' +} diff --git a/test/8-duckdns-ecdsa.bats b/test/8-duckdns-ecdsa.bats index f50dd05..0950d96 100644 --- a/test/8-duckdns-ecdsa.bats +++ b/test/8-duckdns-ecdsa.bats @@ -23,10 +23,10 @@ setup() { setup_environment init_getssl sed -e 's/rsa/prime256v1/g' < "${CODE_DIR}/test/test-config/${CONFIG_FILE}" > "${INSTALL_DIR}/.getssl/${GETSSL_HOST}/getssl.cfg" - run ${CODE_DIR}/getssl "$GETSSL_HOST" + run ${CODE_DIR}/getssl -d "$GETSSL_HOST" assert_success refute_output --regexp '[Ff][Aa][Ii][Ll][Ee][Dd]' - refute_output --regexp '[Ee][Rr][Rr][Oo][Rr]' + refute_output --regexp '[^:][Ee][Rr][Rr][Oo][Rr][^:]' refute_output --regexp '[Ww][Aa][Rr][Nn][Ii][Nn][Gg]' } @@ -35,10 +35,10 @@ setup() { if [ -z "$STAGING" ]; then skip "Running internal tests, skipping external test" fi - run ${CODE_DIR}/getssl -f $GETSSL_HOST + run ${CODE_DIR}/getssl -d -f $GETSSL_HOST assert_success refute_output --regexp '[Ff][Aa][Ii][Ll][Ee][Dd]' - refute_output --regexp '[Ee][Rr][Rr][Oo][Rr]' + refute_output --regexp '[^:][Ee][Rr][Rr][Oo][Rr][^:]' refute_output --regexp '[Ww][Aa][Rr][Nn][Ii][Nn][Gg]' cleanup_environment } @@ -54,10 +54,10 @@ setup() { setup_environment init_getssl sed -e 's/rsa/secp384r1/g' < "${CODE_DIR}/test/test-config/${CONFIG_FILE}" > "${INSTALL_DIR}/.getssl/${GETSSL_HOST}/getssl.cfg" - run ${CODE_DIR}/getssl "$GETSSL_HOST" + run ${CODE_DIR}/getssl -d "$GETSSL_HOST" assert_success refute_output --regexp '[Ff][Aa][Ii][Ll][Ee][Dd]' - refute_output --regexp '[Ee][Rr][Rr][Oo][Rr]' + refute_output --regexp '[^:][Ee][Rr][Rr][Oo][Rr][^:]' refute_output --regexp '[Ww][Aa][Rr][Nn][Ii][Nn][Gg]' } @@ -66,38 +66,13 @@ setup() { if [ -z "$STAGING" ]; then skip "Running internal tests, skipping external test" fi - run ${CODE_DIR}/getssl -f $GETSSL_HOST + run ${CODE_DIR}/getssl -d -f $GETSSL_HOST assert_success refute_output --regexp '[Ff][Aa][Ii][Ll][Ee][Dd]' - refute_output --regexp '[Ee][Rr][Rr][Oo][Rr]' + refute_output --regexp '[^:][Ee][Rr][Rr][Oo][Rr][^:]' refute_output --regexp '[Ww][Aa][Rr][Nn][Ii][Nn][Gg]' cleanup_environment } -@test "Create new certificate using staging server and secp521r1" { - skip "The staging server returns 'ECDSA curve P-521 not allowed'" - - CONFIG_FILE="getssl-duckdns01.cfg" - GETSSL_HOST=getssl.duckdns.org - - setup_environment - init_getssl - sed -e 's/rsa/secp521r1/g' < "${CODE_DIR}/test/test-config/${CONFIG_FILE}" > "${INSTALL_DIR}/.getssl/${GETSSL_HOST}/getssl.cfg" - run ${CODE_DIR}/getssl "$GETSSL_HOST" - assert_success - refute_output --regexp '[Ff][Aa][Ii][Ll][Ee][Dd]' - refute_output --regexp '[Ee][Rr][Rr][Oo][Rr]' - refute_output --regexp '[Ww][Aa][Rr][Nn][Ii][Nn][Gg]' -} - - -@test "Force renewal of certificate using staging server and secp521r1" { - skip "The staging server returns 'ECDSA curve P-521 not allowed'" - run ${CODE_DIR}/getssl -f $GETSSL_HOST - assert_success - refute_output --regexp '[Ff][Aa][Ii][Ll][Ee][Dd]' - refute_output --regexp '[Ee][Rr][Rr][Oo][Rr]' - refute_output --regexp '[Ww][Aa][Rr][Nn][Ii][Nn][Gg]' - cleanup_environment -} +# Note letsencrypt doesn't support ECDSA curve P-521 as it's being deprecated diff --git a/test/Dockerfile-ubuntu16 b/test/Dockerfile-ubuntu16 new file mode 100644 index 0000000..958bb6f --- /dev/null +++ b/test/Dockerfile-ubuntu16 @@ -0,0 +1,25 @@ +FROM ubuntu:xenial +# xenial = 16 + +# Note this image uses mawk + +# Update and install required software +RUN apt-get update --fix-missing +RUN apt-get install -y git curl dnsutils wget nginx-light + +WORKDIR /root +RUN mkdir /etc/nginx/pki +RUN mkdir /etc/nginx/pki/private +COPY ./test/test-config/nginx-ubuntu-no-ssl /etc/nginx/sites-enabled/default + +# Prevent "Can't load /root/.rnd into RNG" error from openssl +# RUN touch /root/.rnd + +# BATS (Bash Automated Testings) +RUN git clone https://github.com/bats-core/bats-core.git /bats-core +RUN git clone https://github.com/jasonkarns/bats-support /bats-support +RUN git clone https://github.com/jasonkarns/bats-assert-1 /bats-assert +RUN /bats-core/install.sh /usr/local + +# Run eternal loop - for testing +CMD tail -f /dev/null diff --git a/test/debug-test.sh b/test/debug-test.sh index 8807670..890366b 100644 --- a/test/debug-test.sh +++ b/test/debug-test.sh @@ -9,14 +9,14 @@ if [ $# -eq 2 ]; then shift fi +#shellcheck disable=SC1091 +source /getssl/test/test_helper.bash + CONFIG_FILE=$1 if [ ! -e "$CONFIG_FILE" ]; then CONFIG_FILE=${CODE_DIR}/test/test-config/${CONFIG_FILE} fi -#shellcheck disable=SC1091 -source /getssl/test/test_helper.bash - setup_environment 3>&1 # Only add the pebble CA to the cert bundle if using pebble diff --git a/test/run-all-tests.cmd b/test/run-all-tests.cmd index 2c5ff6c..e887b6e 100644 --- a/test/run-all-tests.cmd +++ b/test/run-all-tests.cmd @@ -1,6 +1,15 @@ +echo %time% docker exec -it getssl-alpine bats /getssl/test +echo %time% docker exec -it getssl-centos6 bats /getssl/test +echo %time% docker exec -it getssl-debian bats /getssl/test +echo %time% docker exec -it getssl-ubuntu bats /getssl/test +echo %time% docker exec -it getssl-ubuntu18 bats /getssl/test +echo %time% +docker exec -it getssl-ubuntu16 bats /getssl/test +echo %time% docker exec -it getssl-duckdns bats /getssl/test +echo %time% diff --git a/test/test-config/getssl-duckdns01.cfg b/test/test-config/getssl-duckdns01.cfg index 4a37bcd..10ac366 100644 --- a/test/test-config/getssl-duckdns01.cfg +++ b/test/test-config/getssl-duckdns01.cfg @@ -7,7 +7,7 @@ DNS_ADD_COMMAND="/getssl/dns_scripts/dns_add_duckdns" DNS_DEL_COMMAND="/getssl/dns_scripts/dns_del_duckdns" AUTH_DNS_SERVER=1.1.1.1 CHECK_ALL_AUTH_DNS=false -DNS_EXTRA_WAIT=20 +DNS_EXTRA_WAIT=30 ACCOUNT_KEY_TYPE="rsa" PRIVATE_KEY_ALG="rsa" diff --git a/test/test_helper.bash b/test/test_helper.bash index 554d60a..0d106fa 100644 --- a/test/test_helper.bash +++ b/test/test_helper.bash @@ -39,5 +39,6 @@ init_getssl() { create_certificate() { # Create certificate cp "${CODE_DIR}/test/test-config/${CONFIG_FILE}" "${INSTALL_DIR}/.getssl/${GETSSL_HOST}/getssl.cfg" - run ${CODE_DIR}/getssl "$GETSSL_HOST" + # shellcheck disable=SC2086 + run ${CODE_DIR}/getssl $1 "$GETSSL_HOST" } From 4ed430562d9e2b7b94d65af699e2a635803b0c2f Mon Sep 17 00:00:00 2001 From: Tim Kimber Date: Thu, 27 Feb 2020 09:21:12 +0000 Subject: [PATCH 055/110] Change to using gatsbyjs version (multiple exempt tags) --- .github/workflows/stale2.yml | 38 +++++++++++++++++++++++------------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/.github/workflows/stale2.yml b/.github/workflows/stale2.yml index cd79668..14a3d06 100644 --- a/.github/workflows/stale2.yml +++ b/.github/workflows/stale2.yml @@ -1,20 +1,30 @@ -name: Mark stale issues and pull requests - on: schedule: - - cron: "0 0 * * *" + - cron: "0 0 * * *" + +name: Run Stale Bot on Issue Comments jobs: - stale: - + build: + name: stale runs-on: ubuntu-latest - steps: - - uses: actions/stale@v1 - with: - repo-token: ${{ secrets.GITHUB_TOKEN }} - stale-issue-message: 'This issue will be closed as no response' - stale-issue-label: 'needs more information' - exempt-issue-label: 'enhancement' - days-before-stale: 60 - days-before-close: 30 + - uses: actions/checkout@master + - name: stale + uses: gatsbyjs/stale@master + with: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + DRY_RUN: true + DAYS_BEFORE_STALE: 60 + DAYS_BEFORE_CLOSE: 30 + STALE_ISSUE_LABEL: 'stale' + STALE_PR_LABEL: 'stale' + OPERATIONS_PER_RUN: 30 + STALE_ISSUE_MESSAGE: 'This issue will be closed as no response' + CLOSE_MESSAGE: 'Closing stale issue' + EXEMPT_ISSUE_LABELS: | + enhancement + bug + feature + help wanted + documentation From f3de9a8cc474b214e049930f22c0a97cbedb881b Mon Sep 17 00:00:00 2001 From: Tim Kimber Date: Thu, 27 Feb 2020 09:28:52 +0000 Subject: [PATCH 056/110] Add rfc to exempt tags --- .github/workflows/stale2.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/stale2.yml b/.github/workflows/stale2.yml index 14a3d06..4a676c6 100644 --- a/.github/workflows/stale2.yml +++ b/.github/workflows/stale2.yml @@ -23,8 +23,9 @@ jobs: STALE_ISSUE_MESSAGE: 'This issue will be closed as no response' CLOSE_MESSAGE: 'Closing stale issue' EXEMPT_ISSUE_LABELS: | - enhancement bug + documentation + enhancement feature help wanted - documentation + rfc From 3119ea986cd005d997bf18bcc40d3d9c648b417a Mon Sep 17 00:00:00 2001 From: Tim Kimber Date: Thu, 27 Feb 2020 09:29:56 +0000 Subject: [PATCH 057/110] Better stale/close messages --- .github/workflows/stale2.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/stale2.yml b/.github/workflows/stale2.yml index 4a676c6..ce6f9c1 100644 --- a/.github/workflows/stale2.yml +++ b/.github/workflows/stale2.yml @@ -20,8 +20,8 @@ jobs: STALE_ISSUE_LABEL: 'stale' STALE_PR_LABEL: 'stale' OPERATIONS_PER_RUN: 30 - STALE_ISSUE_MESSAGE: 'This issue will be closed as no response' - CLOSE_MESSAGE: 'Closing stale issue' + STALE_ISSUE_MESSAGE: 'This issue will be closed as no updates for 60 days' + CLOSE_MESSAGE: 'Closing stale issue after 90 days of inactivity' EXEMPT_ISSUE_LABELS: | bug documentation From 1c8937d0542cc044fc8206e234012ec811f95b60 Mon Sep 17 00:00:00 2001 From: Tim Kimber Date: Thu, 27 Feb 2020 14:39:56 +0000 Subject: [PATCH 058/110] Fix execution permissions --- dns_scripts/dns_add_challtestsrv | 0 dns_scripts/dns_add_duckdns | 0 dns_scripts/dns_del_challtestsrv | 0 dns_scripts/dns_del_duckdns | 0 test/restart-nginx | 0 5 files changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 dns_scripts/dns_add_challtestsrv mode change 100644 => 100755 dns_scripts/dns_add_duckdns mode change 100644 => 100755 dns_scripts/dns_del_challtestsrv mode change 100644 => 100755 dns_scripts/dns_del_duckdns mode change 100644 => 100755 test/restart-nginx diff --git a/dns_scripts/dns_add_challtestsrv b/dns_scripts/dns_add_challtestsrv old mode 100644 new mode 100755 diff --git a/dns_scripts/dns_add_duckdns b/dns_scripts/dns_add_duckdns old mode 100644 new mode 100755 diff --git a/dns_scripts/dns_del_challtestsrv b/dns_scripts/dns_del_challtestsrv old mode 100644 new mode 100755 diff --git a/dns_scripts/dns_del_duckdns b/dns_scripts/dns_del_duckdns old mode 100644 new mode 100755 diff --git a/test/restart-nginx b/test/restart-nginx old mode 100644 new mode 100755 From 1ab68d7c3b02a8fc0a84de7d0739f607d0ab96ec Mon Sep 17 00:00:00 2001 From: Tim Kimber Date: Thu, 27 Feb 2020 14:40:24 +0000 Subject: [PATCH 059/110] Run tests on PR and push --- .github/workflows/run-all-tests.yml | 17 +++++++++++++++++ test/run-all-tests.sh | 12 ++++++------ 2 files changed, 23 insertions(+), 6 deletions(-) create mode 100644 .github/workflows/run-all-tests.yml mode change 100644 => 100755 test/run-all-tests.sh diff --git a/.github/workflows/run-all-tests.yml b/.github/workflows/run-all-tests.yml new file mode 100644 index 0000000..0e43289 --- /dev/null +++ b/.github/workflows/run-all-tests.yml @@ -0,0 +1,17 @@ +name: Run all tests +on: + push: + branches: + - master + pull_request: + branches: + - master +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - name: Build the docker-compose stack + run: docker-compose up -d --build + - name: Run test suite + run: test/run-all-tests.sh diff --git a/test/run-all-tests.sh b/test/run-all-tests.sh old mode 100644 new mode 100755 index 6a0eb8a..ee2f1db --- a/test/run-all-tests.sh +++ b/test/run-all-tests.sh @@ -1,8 +1,8 @@ #!/usr/bin/env bash -docker exec -it getssl-alpine bats /getssl/test -docker exec -it getssl-centos6 bats /getssl/test -docker exec -it getssl-debian bats /getssl/test -docker exec -it getssl-ubuntu bats /getssl/test -docker exec -it getssl-ubuntu18 bats /getssl/test -docker exec -it getssl-duckdns bats /getssl/test +docker exec getssl-alpine bats /getssl/test +docker exec getssl-centos6 bats /getssl/test +docker exec getssl-debian bats /getssl/test +docker exec getssl-ubuntu bats /getssl/test +docker exec getssl-ubuntu18 bats /getssl/test +docker exec getssl-duckdns bats /getssl/test From 9b9784489b2c816e1fb4347d7c4398f7a352f84b Mon Sep 17 00:00:00 2001 From: Tim Kimber Date: Wed, 4 Mar 2020 18:16:26 +0000 Subject: [PATCH 060/110] Parallelize the gitactions tests --- .github/workflows/run-all-tests.yml | 54 +++++- docker-compose.yml | 180 ------------------ test/4-more-than-10-hosts.bats | 2 +- test/6-dual-rsa-ecdsa-copy-2-locations.bats | 2 +- test/7-duckdns-dns01.bats | 2 +- test/Dockerfile-alpine | 4 +- test/Dockerfile-duckdns | 25 +++ test/README-Testing.md | 35 ++++ test/README.md | 40 ---- test/restart-nginx | 2 +- test/run-all-tests.cmd | 15 -- test/run-all-tests.sh | 8 - test/run-test.cmd | 52 +++++ test/run-test.sh | 46 +++++ .../{ => test-config}/alpine-supervisord.conf | 28 +-- test/test-config/getssl-duckdns01.cfg | 2 +- test/test_helper.bash | 41 +++- 17 files changed, 261 insertions(+), 277 deletions(-) create mode 100644 test/Dockerfile-duckdns create mode 100644 test/README-Testing.md delete mode 100644 test/README.md delete mode 100644 test/run-all-tests.cmd delete mode 100755 test/run-all-tests.sh create mode 100644 test/run-test.cmd create mode 100644 test/run-test.sh rename test/{ => test-config}/alpine-supervisord.conf (80%) diff --git a/.github/workflows/run-all-tests.yml b/.github/workflows/run-all-tests.yml index 0e43289..5a02eda 100644 --- a/.github/workflows/run-all-tests.yml +++ b/.github/workflows/run-all-tests.yml @@ -7,11 +7,59 @@ on: branches: - master jobs: - build: + test-alpine: runs-on: ubuntu-latest steps: - uses: actions/checkout@v1 - name: Build the docker-compose stack run: docker-compose up -d --build - - name: Run test suite - run: test/run-all-tests.sh + - name: Run test suite on Alpine + run: test/run-test.sh alpine + test-centos6: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - name: Build the docker-compose stack + run: docker-compose up -d --build + - name: Run test suite on centos6 + run: test/run-test.sh centos6 + test-debian: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - name: Build the docker-compose stack + run: docker-compose up -d --build + - name: Run test suite on Debian + run: test/run-test.sh debian + test-duckdns: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - name: Build the docker-compose stack + run: docker-compose up -d --build + - name: Run test suite on Ubuntu using DuckDNS + run: test/run-test.sh duckdns + test-ubuntu: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - name: Build the docker-compose stack + run: docker-compose up -d --build + - name: Run test suite on Ubuntu + run: test/run-test.sh ubuntu + test-ubuntu16: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - name: Build the docker-compose stack + run: docker-compose up -d --build + - name: Run test suite on Ubuntu16 + run: test/run-test.sh ubuntu16 + test-ubuntu18: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - name: Build the docker-compose stack + run: docker-compose up -d --build + - name: Run test suite on Ubuntu18 + run: test/run-test.sh ubuntu18 diff --git a/docker-compose.yml b/docker-compose.yml index b493888..ec5c24a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -21,186 +21,6 @@ services: networks: acmenet: ipv4_address: 10.30.50.3 - getssl-alpine: - build: - context: . - dockerfile: test/Dockerfile-alpine - container_name: getssl-alpine - volumes: - - .:/getssl - environment: - GETSSL_HOST: alpine.getssl.test - GETSSL_IP: 10.30.50.10 - NGINX_CONFIG: /etc/nginx/conf.d/default.conf - networks: - acmenet: - ipv4_address: 10.30.50.10 - aliases: - - alpine.getssl.test - - a.alpine.getssl.test - - b.alpine.getssl.test - - c.alpine.getssl.test - - d.alpine.getssl.test - - e.alpine.getssl.test - - f.alpine.getssl.test - - g.alpine.getssl.test - - h.alpine.getssl.test - - i.alpine.getssl.test - - j.alpine.getssl.test - - k.alpine.getssl.test - getssl-centos6: - build: - context: . - dockerfile: test/Dockerfile-centos6 - container_name: getssl-centos6 - volumes: - - .:/getssl - environment: - GETSSL_HOST: centos6.getssl.test - GETSSL_IP: 10.30.50.11 - NGINX_CONFIG: /etc/nginx/conf.d/default.conf - networks: - acmenet: - ipv4_address: 10.30.50.11 - aliases: - - centos6.getssl.test - - a.centos6.getssl.test - - b.centos6.getssl.test - - c.centos6.getssl.test - - d.centos6.getssl.test - - e.centos6.getssl.test - - f.centos6.getssl.test - - g.centos6.getssl.test - - h.centos6.getssl.test - - i.centos6.getssl.test - - j.centos6.getssl.test - - k.centos6.getssl.test - getssl-debian: - build: - context: . - dockerfile: test/Dockerfile-debian - container_name: getssl-debian - volumes: - - .:/getssl - environment: - GETSSL_HOST: debian.getssl.test - GETSSL_IP: 10.30.50.12 - NGINX_CONFIG: /etc/nginx/sites-enabled/default - networks: - acmenet: - ipv4_address: 10.30.50.12 - aliases: - - debian.getssl.test - - a.debian.getssl.test - - b.debian.getssl.test - - c.debian.getssl.test - - d.debian.getssl.test - - e.debian.getssl.test - - f.debian.getssl.test - - g.debian.getssl.test - - h.debian.getssl.test - - i.debian.getssl.test - - j.debian.getssl.test - - k.debian.getssl.test - getssl-ubuntu: - build: - context: . - dockerfile: test/Dockerfile-ubuntu - container_name: getssl-ubuntu - volumes: - - .:/getssl - environment: - GETSSL_HOST: ubuntu.getssl.test - GETSSL_IP: 10.30.50.13 - NGINX_CONFIG: /etc/nginx/sites-enabled/default - networks: - acmenet: - ipv4_address: 10.30.50.13 - aliases: - - ubuntu.getssl.test - - a.ubuntu.getssl.test - - b.ubuntu.getssl.test - - c.ubuntu.getssl.test - - d.ubuntu.getssl.test - - e.ubuntu.getssl.test - - f.ubuntu.getssl.test - - g.ubuntu.getssl.test - - h.ubuntu.getssl.test - - i.ubuntu.getssl.test - - j.ubuntu.getssl.test - - k.ubuntu.getssl.test - getssl-ubuntu16: - build: - context: . - dockerfile: test/Dockerfile-ubuntu16 - container_name: getssl-ubuntu16 - volumes: - - .:/getssl - environment: - GETSSL_HOST: ubuntu16.getssl.test - GETSSL_IP: 10.30.50.14 - NGINX_CONFIG: /etc/nginx/sites-enabled/default - networks: - acmenet: - ipv4_address: 10.30.50.14 - aliases: - - ubuntu16.getssl.test - - a.ubuntu16.getssl.test - - b.ubuntu16.getssl.test - - c.ubuntu16.getssl.test - - d.ubuntu16.getssl.test - - e.ubuntu16.getssl.test - - f.ubuntu16.getssl.test - - g.ubuntu16.getssl.test - - h.ubuntu16.getssl.test - - i.ubuntu16.getssl.test - - j.ubuntu16.getssl.test - - k.ubuntu16.getssl.test - getssl-ubuntu18: - build: - context: . - dockerfile: test/Dockerfile-ubuntu18 - container_name: getssl-ubuntu18 - volumes: - - .:/getssl - environment: - GETSSL_HOST: ubuntu18.getssl.test - GETSSL_IP: 10.30.50.15 - NGINX_CONFIG: /etc/nginx/sites-enabled/default - networks: - acmenet: - ipv4_address: 10.30.50.15 - aliases: - - ubuntu18.getssl.test - - a.ubuntu18.getssl.test - - b.ubuntu18.getssl.test - - c.ubuntu18.getssl.test - - d.ubuntu18.getssl.test - - e.ubuntu18.getssl.test - - f.ubuntu18.getssl.test - - g.ubuntu18.getssl.test - - h.ubuntu18.getssl.test - - i.ubuntu18.getssl.test - - j.ubuntu18.getssl.test - - k.ubuntu18.getssl.test - getssl-duckdns: - build: - context: . - dockerfile: test/Dockerfile-ubuntu - container_name: getssl-duckdns - volumes: - - .:/getssl - environment: - GETSSL_HOST: getssl.duckdns.org - GETSSL_IP: 10.30.50.16 - NGINX_CONFIG: /etc/nginx/sites-enabled/default - DUCKDNS_TOKEN: $DUCKDNS_TOKEN - STAGING: "true" - networks: - acmenet: - ipv4_address: 10.30.50.16 - aliases: - - getssl.duckdns.org networks: diff --git a/test/4-more-than-10-hosts.bats b/test/4-more-than-10-hosts.bats index 5bdfc2a..bd93adc 100644 --- a/test/4-more-than-10-hosts.bats +++ b/test/4-more-than-10-hosts.bats @@ -44,6 +44,6 @@ setup() { # Remove all the dns aliases cleanup_environment for prefix in a b c d e f g h i j k; do - curl --silent -X POST -d '{"host":"'$prefix.$GETSSL_HOST'", "addresses":["'$GETSSL_IP'"]}' http://10.30.50.3:8055/del-a + curl --silent -X POST -d '{"host":"'$prefix.$GETSSL_HOST'"}' http://10.30.50.3:8055/clear-a done } diff --git a/test/6-dual-rsa-ecdsa-copy-2-locations.bats b/test/6-dual-rsa-ecdsa-copy-2-locations.bats index aae21bb..73363ec 100644 --- a/test/6-dual-rsa-ecdsa-copy-2-locations.bats +++ b/test/6-dual-rsa-ecdsa-copy-2-locations.bats @@ -16,7 +16,7 @@ setup() { teardown() { if [ -z "$STAGING" ]; then - curl --silent -X POST -d '{"host":"'a.$GETSSL_HOST'", "addresses":["'$GETSSL_IP'"]}' http://10.30.50.3:8055/del-a + curl --silent -X POST -d '{"host":"'a.$GETSSL_HOST'"}' http://10.30.50.3:8055/clear-a fi } diff --git a/test/7-duckdns-dns01.bats b/test/7-duckdns-dns01.bats index e81b414..9466f05 100644 --- a/test/7-duckdns-dns01.bats +++ b/test/7-duckdns-dns01.bats @@ -38,5 +38,5 @@ setup() { refute_output --regexp '[Ee][Rr][Rr][Oo][Rr]' refute_output --regexp '[Ww][Aa][Rr][Nn][Ii][Nn][Gg]' cleanup_environment - curl --silent -X POST -d '{"host":"getssl.duckdns.org", "addresses":["'$GETSSL_IP'"]}' http://10.30.50.3:8055/del-a + curl --silent -X POST -d '{"host":"getssl.duckdns.org"}' http://10.30.50.3:8055/clear-a } diff --git a/test/Dockerfile-alpine b/test/Dockerfile-alpine index ff69490..0c166cb 100644 --- a/test/Dockerfile-alpine +++ b/test/Dockerfile-alpine @@ -18,5 +18,5 @@ RUN git clone https://github.com/jasonkarns/bats-assert-1 /bats-assert RUN /bats-core/install.sh /usr/local # Use supervisord to run nginx in the background -COPY ./test/alpine-supervisord.conf /etc/supervisord.conf -ENTRYPOINT /usr/bin/supervisord -c /etc/supervisord.conf +COPY ./test/test-config/alpine-supervisord.conf /etc/supervisord.conf +CMD tail -f /dev/null diff --git a/test/Dockerfile-duckdns b/test/Dockerfile-duckdns new file mode 100644 index 0000000..0bdc1f8 --- /dev/null +++ b/test/Dockerfile-duckdns @@ -0,0 +1,25 @@ +FROM ubuntu:latest + +# Note this image uses mawk1.3 + +ENV staging "true" +ENV DUCKDNS_TOKEN 1d616aa9-b8e4-4bb4-b312-3289de82badb +# Update and install required software +RUN apt-get update --fix-missing +RUN apt-get install -y git curl dnsutils wget nginx-light +RUN apt-get install -y vim dos2unix # for debugging +# TODO test with drill, dig, host + +WORKDIR /root + +# Prevent "Can't load /root/.rnd into RNG" error from openssl +RUN touch /root/.rnd + +# BATS (Bash Automated Testings) +RUN git clone https://github.com/bats-core/bats-core.git /bats-core +RUN git clone https://github.com/jasonkarns/bats-support /bats-support +RUN git clone https://github.com/jasonkarns/bats-assert-1 /bats-assert +RUN /bats-core/install.sh /usr/local + +# Run eternal loop - for testing +CMD tail -f /dev/null diff --git a/test/README-Testing.md b/test/README-Testing.md new file mode 100644 index 0000000..3cd4b2c --- /dev/null +++ b/test/README-Testing.md @@ -0,0 +1,35 @@ +# Testing + +## Continuous Integration + +For continuous integration testing we have the following: + +`gitactions` script which runs whenever a PR is pushed: + +1. Uses `docker-compose` to start `pebble` (letsencrypt test server) and `challtestsrv` (minimal dns client for pebble) +2. Then runs the `bats` test scripts (all the files with a ".bats" extension) for each OS (alpine, centos6, debian, ubuntu) +3. Runs the `bats` test script against the staging server (using nn ubuntu docker image and duckdns.org) + +## To run all the tests on a single OS + +1. Start `pebble` and `challtestsrv` using ```docker-compose up -d --build``` +2. Run the test suite ```run-test.sh []``` +3. eg. `run-test.sh ubuntu16` + +## To run a single bats test on a single OS + +1. Start `pebble` and `challtestsrv` using ```docker-compose up -d --build``` +2. ```run-test.sh bats ``` +3. e.g. `run-test.sh ubuntu bats /getssl/test/1-simple-http01.bats` + +## To debug a test + +1. Start `pebble` and `challtestsrv` using ```docker-compose up -d --build``` +2. ```run-test.sh /getssl/test/debug-test.sh ``` +3. e.g. `run-test.sh ubuntu /getssl/test/debug-test.sh -d /getssl/test/test-config/getssl-http01-cfg` + +## TODO + +1. Test wildcards +2. Test SSH, SFTP, SCP +3. Test change of key algorithm (should automatically delete and re-create account.key) diff --git a/test/README.md b/test/README.md deleted file mode 100644 index 7648f17..0000000 --- a/test/README.md +++ /dev/null @@ -1,40 +0,0 @@ -# Testing - -This directory contains a simple test script which tests creating -certificates with Pebble (testing version of the LetsEncrypt server) - -Start up pebble, the challdnstest server for DNS challenges - -```sh -docker-compose -f "docker-compose.yml" up -d --build -``` - -Run the tests - -```sh -test/run-all-tests.sh -``` - -Run individual test - -```sh -docker exec -it getssl bats /getssl/test/ -``` - -Debug (uses helper script to set `CURL_CA_BUNDLE` as pebble uses a local certificate, -otherwise you get a "unknown API version" error) - -```sh -docker exec -it getssl- /getssl/test/debug-test.sh ` - -eg. - -```sh -docker exec -it getssl-ubuntu18 /getssl/test/debug-test.sh getssl-http01.cfg -``` - -## TODO - -1. Test wildcards -2. Test SSH, SFTP, SCP -3. Test change of key algorithm diff --git a/test/restart-nginx b/test/restart-nginx index d35f60f..f947d8d 100755 --- a/test/restart-nginx +++ b/test/restart-nginx @@ -4,5 +4,5 @@ if [ "$GETSSL_HOST" = "alpine.getssl.test" ]; then killall -HUP nginx >&3- sleep 5 else - service nginx restart >&3- + service nginx restart >/dev/null >&3- fi diff --git a/test/run-all-tests.cmd b/test/run-all-tests.cmd deleted file mode 100644 index e887b6e..0000000 --- a/test/run-all-tests.cmd +++ /dev/null @@ -1,15 +0,0 @@ -echo %time% -docker exec -it getssl-alpine bats /getssl/test -echo %time% -docker exec -it getssl-centos6 bats /getssl/test -echo %time% -docker exec -it getssl-debian bats /getssl/test -echo %time% -docker exec -it getssl-ubuntu bats /getssl/test -echo %time% -docker exec -it getssl-ubuntu18 bats /getssl/test -echo %time% -docker exec -it getssl-ubuntu16 bats /getssl/test -echo %time% -docker exec -it getssl-duckdns bats /getssl/test -echo %time% diff --git a/test/run-all-tests.sh b/test/run-all-tests.sh deleted file mode 100755 index ee2f1db..0000000 --- a/test/run-all-tests.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env bash - -docker exec getssl-alpine bats /getssl/test -docker exec getssl-centos6 bats /getssl/test -docker exec getssl-debian bats /getssl/test -docker exec getssl-ubuntu bats /getssl/test -docker exec getssl-ubuntu18 bats /getssl/test -docker exec getssl-duckdns bats /getssl/test diff --git a/test/run-test.cmd b/test/run-test.cmd new file mode 100644 index 0000000..e951c44 --- /dev/null +++ b/test/run-test.cmd @@ -0,0 +1,52 @@ +@echo off +IF %1.==. GOTO NoOS +set OS=%1 + +:CheckCommand +IF %2.==. GOTO NoCmd +set COMMAND=%2 %3 + +:CheckAlias +IF %OS%==duckdns GOTO duckdns +set ALIAS=%OS%.getssl.test +set STAGING= +GOTO Run + +:NoOS +set OS=ubuntu +GOTO CheckCommand + +:NoCmd +REM set COMMAND=/getssl/test/run-bats.sh +set COMMAND=bats /getssl/test +GOTO CheckAlias + +:duckdns +set ALIAS=getssl.duckdns.org +set STAGING=--env STAGING=true + +:Run +for %%I in (.) do set CurrDirName=%%~nxI + +docker build --rm -f "test\Dockerfile-%OS%" -t getssl-%OS% . +@echo on +docker run -it ^ + --env GETSSL_HOST=%ALIAS% %STAGING% ^ + -v %cd%:/getssl ^ + --rm ^ + --network %CurrDirName%_acmenet ^ + --network-alias %ALIAS% ^ + --network-alias a.%OS%.getssl.test ^ + --network-alias b.%OS%.getssl.test ^ + --network-alias c.%OS%.getssl.test ^ + --network-alias d.%OS%.getssl.test ^ + --network-alias e.%OS%.getssl.test ^ + --network-alias f.%OS%.getssl.test ^ + --network-alias g.%OS%.getssl.test ^ + --network-alias h.%OS%.getssl.test ^ + --network-alias i.%OS%.getssl.test ^ + --network-alias j.%OS%.getssl.test ^ + --network-alias k.%OS%.getssl.test ^ + --name getssl-%OS% ^ + getssl-%OS% ^ + %COMMAND% diff --git a/test/run-test.sh b/test/run-test.sh new file mode 100644 index 0000000..d85730f --- /dev/null +++ b/test/run-test.sh @@ -0,0 +1,46 @@ +#! /usr/bin/env bash + +if [ $# -eq 0 ]; then + echo "Usage: $(basename "$0") []" + echo "e.g. $(basename "$0") alpine bats /getssl/test" + exit 1 +fi +OS=$1 + +if [ $# -gt 1 ]; then + shift + COMMAND=$* +else + COMMAND="bats /getssl/test" +fi + +if [ "$OS" == "duckdns" ]; then + ALIAS="getssl.duckdns.org" + STAGING="--env STAGING=true" +else + ALIAS="$OS.getssl.test" + STAGING="" +fi + +docker build --rm -f "test/Dockerfile-$OS" -t "getssl-$OS" . +# shellcheck disable=SC2086 +docker run \ + --env GETSSL_HOST="$OS.getssl.test" $STAGING \ + -v "$(pwd)":/getssl \ + --rm \ + --network ${PWD##*/}_acmenet \ + --network-alias $ALIAS \ + --network-alias "a.$OS.getssl.test" \ + --network-alias "b.$OS.getssl.test" \ + --network-alias "c.$OS.getssl.test" \ + --network-alias "d.$OS.getssl.test" \ + --network-alias "e.$OS.getssl.test" \ + --network-alias "f.$OS.getssl.test" \ + --network-alias "g.$OS.getssl.test" \ + --network-alias "h.$OS.getssl.test" \ + --network-alias "i.$OS.getssl.test" \ + --network-alias "j.$OS.getssl.test" \ + --network-alias "k.$OS.getssl.test" \ + --name "getssl-$OS" \ + "getssl-$OS" \ + $COMMAND diff --git a/test/alpine-supervisord.conf b/test/test-config/alpine-supervisord.conf similarity index 80% rename from test/alpine-supervisord.conf rename to test/test-config/alpine-supervisord.conf index 8eec585..9759570 100644 --- a/test/alpine-supervisord.conf +++ b/test/test-config/alpine-supervisord.conf @@ -1,14 +1,14 @@ -[supervisord] -nodaemon=true -logfile=/tmp/supervisord.log -childlogdir=/tmp -pidfile = /tmp/supervisord.pid - -[program:nginx] -command=nginx -g 'daemon off;' -stdout_logfile=/dev/stdout -stdout_logfile_maxbytes=0 -stderr_logfile=/dev/stderr -stderr_logfile_maxbytes=0 -autorestart=false -startretries=0 +[supervisord] +nodaemon=false +logfile=/tmp/supervisord.log +childlogdir=/tmp +pidfile = /tmp/supervisord.pid + +[program:nginx] +command=nginx +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 +autorestart=false +startretries=0 diff --git a/test/test-config/getssl-duckdns01.cfg b/test/test-config/getssl-duckdns01.cfg index 10ac366..517aaeb 100644 --- a/test/test-config/getssl-duckdns01.cfg +++ b/test/test-config/getssl-duckdns01.cfg @@ -7,7 +7,7 @@ DNS_ADD_COMMAND="/getssl/dns_scripts/dns_add_duckdns" DNS_DEL_COMMAND="/getssl/dns_scripts/dns_del_duckdns" AUTH_DNS_SERVER=1.1.1.1 CHECK_ALL_AUTH_DNS=false -DNS_EXTRA_WAIT=30 +DNS_EXTRA_WAIT=60 ACCOUNT_KEY_TYPE="rsa" PRIVATE_KEY_ALG="rsa" diff --git a/test/test_helper.bash b/test/test_helper.bash index 0d106fa..d151d5a 100644 --- a/test/test_helper.bash +++ b/test/test_helper.bash @@ -8,15 +8,6 @@ setup_environment() { rm -r ${INSTALL_DIR}/.getssl fi - if [ ! -f ${INSTALL_DIR}/pebble.minica.pem ]; then - wget --quiet --no-clobber https://raw.githubusercontent.com/letsencrypt/pebble/master/test/certs/pebble.minica.pem 2>&1 - CERT_FILE=/etc/ssl/certs/ca-certificates.crt - if [ ! -f $CERT_FILE ]; then - CERT_FILE=/etc/pki/tls/certs/ca-bundle.crt - fi - cat $CERT_FILE ${INSTALL_DIR}/pebble.minica.pem > ${INSTALL_DIR}/pebble-ca-bundle.crt - fi - curl --silent -X POST -d '{"host":"'"$GETSSL_HOST"'", "addresses":["'"$GETSSL_IP"'"]}' http://10.30.50.3:8055/add-a cp ${CODE_DIR}/test/test-config/nginx-ubuntu-no-ssl "${NGINX_CONFIG}" /getssl/test/restart-nginx @@ -24,7 +15,7 @@ setup_environment() { cleanup_environment() { - curl --silent -X POST -d '{"host":"'"$GETSSL_HOST"'", "addresses":["'"$GETSSL_IP"'"]}' http://10.30.50.3:8055/del-a + curl --silent -X POST -d '{"host":"'"$GETSSL_HOST"'"}' http://10.30.50.3:8055/clear-a } @@ -42,3 +33,33 @@ create_certificate() { # shellcheck disable=SC2086 run ${CODE_DIR}/getssl $1 "$GETSSL_HOST" } + +# start nginx in background on alpine via supervisord +if [[ -f /usr/bin/supervisord && -f /etc/supervisord.conf ]]; then + if [[ ! $(pgrep supervisord) ]]; then + /usr/bin/supervisord -c /etc/supervisord.conf >&3- + fi +fi + +# Find NGINX configuration directory for HTTP-01 testing (need to add SSL to config) +if [[ -f /etc/nginx/conf.d/default.conf ]]; then + export NGINX_CONFIG=/etc/nginx/conf.d/default.conf +elif [[ -f /etc/nginx/sites-enabled/default ]]; then + export NGINX_CONFIG=/etc/nginx/sites-enabled/default +else + echo "Can't find NGINX directory" + exit 1 +fi + +# Find IP address +GETSSL_IP=$(ip address | awk '/10.30.50/ { print $2 }' | awk -F/ '{ print $1 }') +export GETSSL_IP + +if [ ! -f ${INSTALL_DIR}/pebble.minica.pem ]; then + wget --quiet --no-clobber https://raw.githubusercontent.com/letsencrypt/pebble/master/test/certs/pebble.minica.pem 2>&1 + CERT_FILE=/etc/ssl/certs/ca-certificates.crt + if [ ! -f $CERT_FILE ]; then + CERT_FILE=/etc/pki/tls/certs/ca-bundle.crt + fi + cat $CERT_FILE ${INSTALL_DIR}/pebble.minica.pem > ${INSTALL_DIR}/pebble-ca-bundle.crt +fi From a67dc5db09215d3b5ad6612b50a3cc1c43fbe3de Mon Sep 17 00:00:00 2001 From: Tim Kimber Date: Wed, 4 Mar 2020 18:18:46 +0000 Subject: [PATCH 061/110] Add +x for scripts used in testing --- dns_scripts/dns_freedns.sh | 0 test/debug-test.sh | 0 test/run-test.sh | 0 3 files changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 dns_scripts/dns_freedns.sh mode change 100644 => 100755 test/debug-test.sh mode change 100644 => 100755 test/run-test.sh diff --git a/dns_scripts/dns_freedns.sh b/dns_scripts/dns_freedns.sh old mode 100644 new mode 100755 diff --git a/test/debug-test.sh b/test/debug-test.sh old mode 100644 new mode 100755 diff --git a/test/run-test.sh b/test/run-test.sh old mode 100644 new mode 100755 From 95f292ce058af360c23aaa3776e58502fcd92f2f Mon Sep 17 00:00:00 2001 From: Tim Kimber Date: Thu, 5 Mar 2020 07:09:16 +0000 Subject: [PATCH 062/110] Add test status badge --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 1d0d3fc..d0aa22c 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,4 @@ +![Run all tests](https://github.com/srvrco/getssl/workflows/Run%20all%20tests/badge.svg) # getssl Obtain SSL certificates from the letsencrypt.org ACME server. Suitable From 5ad1f96ee1e49a59ab80eeade75075101129c22c Mon Sep 17 00:00:00 2001 From: Tim Kimber Date: Thu, 5 Mar 2020 13:52:35 +0000 Subject: [PATCH 063/110] Create shellcheck.yml --- .github/workflows/shellcheck.yml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 .github/workflows/shellcheck.yml diff --git a/.github/workflows/shellcheck.yml b/.github/workflows/shellcheck.yml new file mode 100644 index 0000000..d5adbf5 --- /dev/null +++ b/.github/workflows/shellcheck.yml @@ -0,0 +1,17 @@ +name: shellcheck + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - name: Lint check + uses: azohra/shell-linter@v0.2.0 + with: + path: "getssl" From f51fa14c40839fd4e23aacee025f34058008837d Mon Sep 17 00:00:00 2001 From: Tim Kimber Date: Thu, 5 Mar 2020 13:54:27 +0000 Subject: [PATCH 064/110] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d0aa22c..c359239 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -![Run all tests](https://github.com/srvrco/getssl/workflows/Run%20all%20tests/badge.svg) +![Run all tests](https://github.com/srvrco/getssl/workflows/Run%20all%20tests/badge.svg) ![shellcheck](https://github.com/srvrco/getssl/workflows/shellcheck/badge.svg) # getssl Obtain SSL certificates from the letsencrypt.org ACME server. Suitable From 1dec15f1b5f626f5c2f3038817228b29527250b7 Mon Sep 17 00:00:00 2001 From: Tim Kimber Date: Fri, 6 Mar 2020 13:50:24 +0000 Subject: [PATCH 065/110] Test on centos7 against staging server --- .github/workflows/run-all-tests.yml | 20 +++++++--- test/7-duckdns-dns01.bats | 8 ---- test/8-duckdns-ecdsa.bats | 8 ---- test/Dockerfile-centos7-duckdns | 28 ++++++++++++++ ...file-duckdns => Dockerfile-ubuntu-duckdns} | 0 test/restart-nginx | 5 ++- test/run-test.cmd | 6 ++- test/run-test.sh | 5 ++- test/test-config/nginx-centos7.conf | 37 +++++++++++++++++++ test/test_helper.bash | 16 +++++++- 10 files changed, 105 insertions(+), 28 deletions(-) create mode 100644 test/Dockerfile-centos7-duckdns rename test/{Dockerfile-duckdns => Dockerfile-ubuntu-duckdns} (100%) create mode 100644 test/test-config/nginx-centos7.conf diff --git a/.github/workflows/run-all-tests.yml b/.github/workflows/run-all-tests.yml index 5a02eda..e6d80ed 100644 --- a/.github/workflows/run-all-tests.yml +++ b/.github/workflows/run-all-tests.yml @@ -23,22 +23,22 @@ jobs: run: docker-compose up -d --build - name: Run test suite on centos6 run: test/run-test.sh centos6 - test-debian: + test-centos7-duckdns: runs-on: ubuntu-latest steps: - uses: actions/checkout@v1 - name: Build the docker-compose stack run: docker-compose up -d --build - - name: Run test suite on Debian - run: test/run-test.sh debian - test-duckdns: + - name: Run test suite on CentOS7 against Staging using DuckDNS + run: test/run-test.sh centos7-duckdns + test-debian: runs-on: ubuntu-latest steps: - uses: actions/checkout@v1 - name: Build the docker-compose stack run: docker-compose up -d --build - - name: Run test suite on Ubuntu using DuckDNS - run: test/run-test.sh duckdns + - name: Run test suite on Debian + run: test/run-test.sh debian test-ubuntu: runs-on: ubuntu-latest steps: @@ -63,3 +63,11 @@ jobs: run: docker-compose up -d --build - name: Run test suite on Ubuntu18 run: test/run-test.sh ubuntu18 + test-ubuntu-duckdns: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - name: Build the docker-compose stack + run: docker-compose up -d --build + - name: Run test suite on Ubuntu against Staging using DuckDNS + run: test/run-test.sh ubuntu-duckdns diff --git a/test/7-duckdns-dns01.bats b/test/7-duckdns-dns01.bats index 9466f05..0c680ea 100644 --- a/test/7-duckdns-dns01.bats +++ b/test/7-duckdns-dns01.bats @@ -5,13 +5,6 @@ load '/bats-assert/load.bash' load '/getssl/test/test_helper.bash' -# These are run for every test, not once per file -setup() { - if [ -n "$STAGING" ]; then - export GETSSL_HOST=getssl.duckdns.org - fi -} - @test "Create new certificate using staging server and DuckDNS" { if [ -z "$STAGING" ]; then @@ -38,5 +31,4 @@ setup() { refute_output --regexp '[Ee][Rr][Rr][Oo][Rr]' refute_output --regexp '[Ww][Aa][Rr][Nn][Ii][Nn][Gg]' cleanup_environment - curl --silent -X POST -d '{"host":"getssl.duckdns.org"}' http://10.30.50.3:8055/clear-a } diff --git a/test/8-duckdns-ecdsa.bats b/test/8-duckdns-ecdsa.bats index 0950d96..dfe84fe 100644 --- a/test/8-duckdns-ecdsa.bats +++ b/test/8-duckdns-ecdsa.bats @@ -5,12 +5,6 @@ load '/bats-assert/load.bash' load '/getssl/test/test_helper.bash' -# These are run for every test, not once per file -setup() { - if [ -n "$STAGING" ]; then - export GETSSL_HOST=getssl.duckdns.org - fi -} @test "Create new certificate using staging server and prime256v1" { @@ -18,7 +12,6 @@ setup() { skip "Running internal tests, skipping external test" fi CONFIG_FILE="getssl-duckdns01.cfg" - GETSSL_HOST=getssl.duckdns.org setup_environment init_getssl @@ -49,7 +42,6 @@ setup() { skip "Running internal tests, skipping external test" fi CONFIG_FILE="getssl-duckdns01.cfg" - GETSSL_HOST=getssl.duckdns.org setup_environment init_getssl diff --git a/test/Dockerfile-centos7-duckdns b/test/Dockerfile-centos7-duckdns new file mode 100644 index 0000000..839ff76 --- /dev/null +++ b/test/Dockerfile-centos7-duckdns @@ -0,0 +1,28 @@ +FROM centos:centos7 + +# Note this image uses gawk + +# Update and install required software +RUN yum -y update +RUN yum -y install epel-release +RUN yum -y install git curl bind-utils wget which nginx + +ENV staging "true" +ENV DUCKDNS_TOKEN 1d616aa9-b8e4-4bb4-b312-3289de82badb + +WORKDIR /root +RUN mkdir /etc/nginx/pki +RUN mkdir /etc/nginx/pki/private +COPY ./test/test-config/nginx-ubuntu-no-ssl /etc/nginx/conf.d/default.conf +COPY ./test/test-config/nginx-centos7.conf /etc/nginx/nginx.conf + +# BATS (Bash Automated Testings) +RUN git clone https://github.com/bats-core/bats-core.git /bats-core +RUN git clone https://github.com/jasonkarns/bats-support /bats-support +RUN git clone https://github.com/jasonkarns/bats-assert-1 /bats-assert +RUN /bats-core/install.sh /usr/local + +EXPOSE 80 443 + +# Run eternal loop - for testing +CMD tail -f /dev/null diff --git a/test/Dockerfile-duckdns b/test/Dockerfile-ubuntu-duckdns similarity index 100% rename from test/Dockerfile-duckdns rename to test/Dockerfile-ubuntu-duckdns diff --git a/test/restart-nginx b/test/restart-nginx index f947d8d..e62433d 100755 --- a/test/restart-nginx +++ b/test/restart-nginx @@ -1,8 +1,11 @@ #!/usr/bin/env bash -if [ "$GETSSL_HOST" = "alpine.getssl.test" ]; then +if [ "$GETSSL_OS" = "alpine" ]; then killall -HUP nginx >&3- sleep 5 +elif [ "$GETSSL_OS" == "centos7" ]; then + pgrep nginx | head -1 | xargs kill -HUP + sleep 5 else service nginx restart >/dev/null >&3- fi diff --git a/test/run-test.cmd b/test/run-test.cmd index e951c44..d1cf263 100644 --- a/test/run-test.cmd +++ b/test/run-test.cmd @@ -7,7 +7,8 @@ IF %2.==. GOTO NoCmd set COMMAND=%2 %3 :CheckAlias -IF %OS%==duckdns GOTO duckdns +REM check if OS *contains* duckdns +IF NOT x%OS:duckdns=%==x%OS% GOTO duckdns set ALIAS=%OS%.getssl.test set STAGING= GOTO Run @@ -22,7 +23,7 @@ set COMMAND=bats /getssl/test GOTO CheckAlias :duckdns -set ALIAS=getssl.duckdns.org +set ALIAS=%OS:-duckdns=%-getssl.duckdns.org set STAGING=--env STAGING=true :Run @@ -32,6 +33,7 @@ docker build --rm -f "test\Dockerfile-%OS%" -t getssl-%OS% . @echo on docker run -it ^ --env GETSSL_HOST=%ALIAS% %STAGING% ^ + --env GETSSL_OS=%OS:-duckdns=% ^ -v %cd%:/getssl ^ --rm ^ --network %CurrDirName%_acmenet ^ diff --git a/test/run-test.sh b/test/run-test.sh index d85730f..07a1a49 100755 --- a/test/run-test.sh +++ b/test/run-test.sh @@ -14,8 +14,8 @@ else COMMAND="bats /getssl/test" fi -if [ "$OS" == "duckdns" ]; then - ALIAS="getssl.duckdns.org" +if [[ "$OS" == *"duckdns"* ]]; then + ALIAS="${OS%-duckdns}-getssl.duckdns.org" STAGING="--env STAGING=true" else ALIAS="$OS.getssl.test" @@ -26,6 +26,7 @@ docker build --rm -f "test/Dockerfile-$OS" -t "getssl-$OS" . # shellcheck disable=SC2086 docker run \ --env GETSSL_HOST="$OS.getssl.test" $STAGING \ + --env GETSSL_OS=${OS%-duckdns} \ -v "$(pwd)":/getssl \ --rm \ --network ${PWD##*/}_acmenet \ diff --git a/test/test-config/nginx-centos7.conf b/test/test-config/nginx-centos7.conf new file mode 100644 index 0000000..2327039 --- /dev/null +++ b/test/test-config/nginx-centos7.conf @@ -0,0 +1,37 @@ +# For more information on configuration, see: +# * Official English Documentation: http://nginx.org/en/docs/ +# * Official Russian Documentation: http://nginx.org/ru/docs/ + +user nginx; +worker_processes auto; +error_log /var/log/nginx/error.log; +pid /run/nginx.pid; + +# Load dynamic modules. See /usr/share/doc/nginx/README.dynamic. +include /usr/share/nginx/modules/*.conf; + +events { + worker_connections 1024; +} + +http { + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + access_log /var/log/nginx/access.log main; + + sendfile on; + tcp_nopush on; + tcp_nodelay on; + keepalive_timeout 65; + types_hash_max_size 2048; + + include /etc/nginx/mime.types; + default_type application/octet-stream; + + # Load modular configuration files from the /etc/nginx/conf.d directory. + # See http://nginx.org/en/docs/ngx_core_module.html#include + # for more information. + include /etc/nginx/conf.d/*.conf; +} diff --git a/test/test_helper.bash b/test/test_helper.bash index d151d5a..3ffcf51 100644 --- a/test/test_helper.bash +++ b/test/test_helper.bash @@ -35,10 +35,15 @@ create_certificate() { } # start nginx in background on alpine via supervisord +# shellcheck disable=SC2153 # Ignore GETSSL_OS looks like typo of GETSSL_IP if [[ -f /usr/bin/supervisord && -f /etc/supervisord.conf ]]; then if [[ ! $(pgrep supervisord) ]]; then /usr/bin/supervisord -c /etc/supervisord.conf >&3- fi +elif [ "$GETSSL_OS" == "centos7" ]; then + if [ -z "$(pgrep nginx)" ]; then + nginx >&3- + fi fi # Find NGINX configuration directory for HTTP-01 testing (need to add SSL to config) @@ -52,7 +57,16 @@ else fi # Find IP address -GETSSL_IP=$(ip address | awk '/10.30.50/ { print $2 }' | awk -F/ '{ print $1 }') +if [[ -n "$(command -v ip)" ]]; then + IP=$(ip address) +elif [[ -n "$(command -v hostname)" ]]; then + IP=$(hostname -I) +else + echo "Cannot find IP address" + exit 1 +fi + +GETSSL_IP=$(echo "$IP" | awk '/10.30.50/ { print $2 }' | awk -F/ '{ print $1 }') export GETSSL_IP if [ ! -f ${INSTALL_DIR}/pebble.minica.pem ]; then From 99bd342a5901326f7854a9ed6f3c79812ca297eb Mon Sep 17 00:00:00 2001 From: Tim Kimber Date: Fri, 6 Mar 2020 14:04:19 +0000 Subject: [PATCH 066/110] Update for multiple duckdns domains --- test/run-test.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/run-test.sh b/test/run-test.sh index 07a1a49..97842a5 100755 --- a/test/run-test.sh +++ b/test/run-test.sh @@ -25,7 +25,7 @@ fi docker build --rm -f "test/Dockerfile-$OS" -t "getssl-$OS" . # shellcheck disable=SC2086 docker run \ - --env GETSSL_HOST="$OS.getssl.test" $STAGING \ + --env GETSSL_HOST=$ALIAS $STAGING \ --env GETSSL_OS=${OS%-duckdns} \ -v "$(pwd)":/getssl \ --rm \ From e724424df8e62eafd5993d71f79f0f9f23c21a7c Mon Sep 17 00:00:00 2001 From: Tim Kimber Date: Sat, 7 Mar 2020 12:21:37 +0000 Subject: [PATCH 067/110] Allow any amount of whitespace in dig/drill output --- getssl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/getssl b/getssl index 96f7423..c23adea 100755 --- a/getssl +++ b/getssl @@ -997,8 +997,9 @@ if [[ $VALIDATE_VIA_DNS == "true" ]]; then | grep ^_acme -A2\ | grep '"'|awk -F'"' '{ print $2}') elif [[ "$DNS_CHECK_FUNC" == "drill" ]] || [[ "$DNS_CHECK_FUNC" == "dig" ]]; then + debug "$DNS_CHECK_FUNC" TXT "_acme-challenge.${d}" "@${ns}" check_result=$($DNS_CHECK_FUNC TXT "_acme-challenge.${d}" "@${ns}" \ - | grep 'IN TXT'|awk -F'"' '{ print $2}') + | grep 'IN\WTXT'|awk -F'"' '{ print $2}') elif [[ "$DNS_CHECK_FUNC" == "host" ]]; then check_result=$($DNS_CHECK_FUNC -t TXT "_acme-challenge.${d}" "${ns}" \ | grep 'descriptive text'|awk -F'"' '{ print $2}') From 0ee5f69783558e9f7080d3d9b3a5ac56190c02cb Mon Sep 17 00:00:00 2001 From: Tim Kimber Date: Sun, 8 Mar 2020 15:03:46 +0000 Subject: [PATCH 068/110] Fix dig CNAME lookup in get_auth_dns --- getssl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/getssl b/getssl index c23adea..d801b26 100755 --- a/getssl +++ b/getssl @@ -1069,10 +1069,10 @@ get_auth_dns() { # get the authoritative dns server for a domain (sets primary_n fi if [[ "$DNS_CHECK_FUNC" == "drill" ]] || [[ "$DNS_CHECK_FUNC" == "dig" ]]; then - if [[ -z "$gad_s" ]]; then #checking for CNAMEs - res=$($DNS_CHECK_FUNC CNAME "$gad_d"| grep "^$gad_d") + if [[ -z "$gad_s" ]]; then #checking for CNAMEs (grep for CNAME required because if no CNAME then dig returns SOA record) + res=$($DNS_CHECK_FUNC CNAME "$gad_d"| grep "^$gad_d" | grep CNAME) else - res=$($DNS_CHECK_FUNC CNAME "$gad_d" "@$gad_s"| grep "^$gad_d") + res=$($DNS_CHECK_FUNC CNAME "$gad_d" "@$gad_s"| grep "^$gad_d" | grep CNAME) fi if [[ -n "$res" ]]; then # domain is a CNAME so get main domain gad_d=$(echo "$res"| awk '{print $5}' |sed 's/\.$//g') From d0c19f2df45ac0ef0d2f08fd7a0940de2cb36b21 Mon Sep 17 00:00:00 2001 From: Tim Kimber Date: Tue, 10 Mar 2020 15:24:27 +0000 Subject: [PATCH 069/110] Use dig +trace to find primary_ns and fix dig NS parsing (used as fallback) --- getssl | 37 +++++++++++++++++++++++++++---------- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/getssl b/getssl index d801b26..c222bd2 100755 --- a/getssl +++ b/getssl @@ -1069,18 +1069,35 @@ get_auth_dns() { # get the authoritative dns server for a domain (sets primary_n fi if [[ "$DNS_CHECK_FUNC" == "drill" ]] || [[ "$DNS_CHECK_FUNC" == "dig" ]]; then - if [[ -z "$gad_s" ]]; then #checking for CNAMEs (grep for CNAME required because if no CNAME then dig returns SOA record) - res=$($DNS_CHECK_FUNC CNAME "$gad_d"| grep "^$gad_d" | grep CNAME) + # Use SOA +trace to find the name server + if [[ -z "$gad_s" ]]; then + res=$($DNS_CHECK_FUNC SOA +trace +nocomments "$gad_d" 2>/dev/null | grep "IN\WNS\W" | tail -1) else - res=$($DNS_CHECK_FUNC CNAME "$gad_d" "@$gad_s"| grep "^$gad_d" | grep CNAME) - fi - if [[ -n "$res" ]]; then # domain is a CNAME so get main domain - gad_d=$(echo "$res"| awk '{print $5}' |sed 's/\.$//g') + res=$($DNS_CHECK_FUNC SOA +trace +nocomments "$gad_d" "@$gad_s" 2>/dev/null | grep "IN\WNS\W" | tail -1) fi - if [[ -z "$gad_s" ]]; then #checking for CNAMEs - res=$($DNS_CHECK_FUNC NS "$gad_d"| grep "^$gad_d") - else - res=$($DNS_CHECK_FUNC NS "$gad_d" "@$gad_s"| grep "^$gad_d") + + # fallback to existing code + if [[ -z "$res" ]]; then + if [[ -z "$gad_s" ]]; then #checking for CNAMEs + res=$($DNS_CHECK_FUNC CNAME "$gad_d"| grep "^$gad_d" ) + else + res=$($DNS_CHECK_FUNC CNAME "$gad_d" "@$gad_s"| grep "^$gad_d" ) + fi + if [[ -n "$res" ]]; then # domain is a CNAME so get main domain + gad_d=$(echo "$res"| awk '{print $5}' |sed 's/\.$//g') + fi + # If gad_d is an A record then this returns the SOA for the root domain, e.g. without the www + # dig NS ubuntu.getssl.text + # > getssl.test. IN SOA ns1.duckdns.org + # If gad_d is a CNAME record then this returns the NS for the domain pointed to by $gad_d + # dig NS www.getssl.text + # > www.getssl.test. IN CNAME getssl.test + # > getssl.test. IN NS ns1.duckdns.org + if [[ -z "$gad_s" ]]; then + res=$($DNS_CHECK_FUNC NS "$gad_d"| grep -E "IN\W(NS|SOA)\W" | tail -1) + else + res=$($DNS_CHECK_FUNC NS "$gad_d" "@$gad_s"| grep -E "IN\W(NS|SOA)\W" | tail -1) + fi fi if [[ -z "$res" ]]; then error_exit "couldn't find primary DNS server - please set AUTH_DNS_SERVER in config" From 0d9b0f402465855457a8581e384b2d06995db635 Mon Sep 17 00:00:00 2001 From: Tim Kimber Date: Tue, 10 Mar 2020 15:26:07 +0000 Subject: [PATCH 070/110] Add centos7 test --- .github/workflows/run-all-tests.yml | 10 +++++++++- test/2-simple-dns01-nslookup.bats | 8 ++++++-- test/Dockerfile-centos7 | 20 ++++++++++++++++++++ test/debug-test.sh | 2 +- test/test_helper.bash | 5 ++--- 5 files changed, 38 insertions(+), 7 deletions(-) create mode 100644 test/Dockerfile-centos7 diff --git a/.github/workflows/run-all-tests.yml b/.github/workflows/run-all-tests.yml index e6d80ed..1ff795d 100644 --- a/.github/workflows/run-all-tests.yml +++ b/.github/workflows/run-all-tests.yml @@ -21,8 +21,16 @@ jobs: - uses: actions/checkout@v1 - name: Build the docker-compose stack run: docker-compose up -d --build - - name: Run test suite on centos6 + - name: Run test suite on CentOS6 run: test/run-test.sh centos6 + test-centos7: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - name: Build the docker-compose stack + run: docker-compose up -d --build + - name: Run test suite on CentOS7 + run: test/run-test.sh centos7 test-centos7-duckdns: runs-on: ubuntu-latest steps: diff --git a/test/2-simple-dns01-nslookup.bats b/test/2-simple-dns01-nslookup.bats index f92d817..c8d5cc6 100644 --- a/test/2-simple-dns01-nslookup.bats +++ b/test/2-simple-dns01-nslookup.bats @@ -8,12 +8,16 @@ load '/getssl/test/test_helper.bash' # This is run for every test setup() { export CURL_CA_BUNDLE=/root/pebble-ca-bundle.crt - mv /usr/bin/dig /usr/bin/dig.getssl.bak + if [ -f /usr/bin/dig ]; then + mv /usr/bin/dig /usr/bin/dig.getssl.bak + fi } teardown() { - mv /usr/bin/dig.getssl.bak /usr/bin/dig + if [ -f /usr/bin/dig.getssl.bak ]; then + mv /usr/bin/dig.getssl.bak /usr/bin/dig + fi } diff --git a/test/Dockerfile-centos7 b/test/Dockerfile-centos7 new file mode 100644 index 0000000..8a34bc5 --- /dev/null +++ b/test/Dockerfile-centos7 @@ -0,0 +1,20 @@ +FROM centos:centos7 + +# Note this image uses drill, does not have dig or nslookup installed + +# Update and install required software +RUN yum -y update +RUN yum -y install epel-release +RUN yum -y install git curl ldns wget which nginx + +WORKDIR /root +RUN mkdir /etc/nginx/pki +RUN mkdir /etc/nginx/pki/private +COPY ./test/test-config/nginx-ubuntu-no-ssl /etc/nginx/conf.d/default.conf +COPY ./test/test-config/nginx-centos7.conf /etc/nginx/nginx.conf + +# BATS (Bash Automated Testings) +RUN git clone https://github.com/bats-core/bats-core.git /bats-core +RUN git clone https://github.com/jasonkarns/bats-support /bats-support +RUN git clone https://github.com/jasonkarns/bats-assert-1 /bats-assert +RUN /bats-core/install.sh /usr/local diff --git a/test/debug-test.sh b/test/debug-test.sh index 890366b..ac94b53 100755 --- a/test/debug-test.sh +++ b/test/debug-test.sh @@ -10,7 +10,7 @@ if [ $# -eq 2 ]; then fi #shellcheck disable=SC1091 -source /getssl/test/test_helper.bash +source /getssl/test/test_helper.bash 3>&1 CONFIG_FILE=$1 if [ ! -e "$CONFIG_FILE" ]; then diff --git a/test/test_helper.bash b/test/test_helper.bash index 3ffcf51..f311b18 100644 --- a/test/test_helper.bash +++ b/test/test_helper.bash @@ -58,15 +58,14 @@ fi # Find IP address if [[ -n "$(command -v ip)" ]]; then - IP=$(ip address) + GETSSL_IP=$(ip address | awk '/10.30.50/ { print $2 }' | awk -F/ '{ print $1 }') elif [[ -n "$(command -v hostname)" ]]; then - IP=$(hostname -I) + GETSSL_IP=$(hostname -I | sed -e 's/[[:space:]]*$//') else echo "Cannot find IP address" exit 1 fi -GETSSL_IP=$(echo "$IP" | awk '/10.30.50/ { print $2 }' | awk -F/ '{ print $1 }') export GETSSL_IP if [ ! -f ${INSTALL_DIR}/pebble.minica.pem ]; then From a5393a58d778ea272b44095de58f2c5f477c1134 Mon Sep 17 00:00:00 2001 From: Tim Kimber Date: Tue, 10 Mar 2020 21:04:55 +0000 Subject: [PATCH 071/110] Added instructions on how to upgrade from v01 to v02 --- README.md | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index c359239..3882b50 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,16 @@ Obtain SSL certificates from the letsencrypt.org ACME server. Suitable for automating the process on remote servers. +## Upgrading from ACME v01 to ACME v02 + +Find the following line in your `getssl.cfg` file: + +```CA="https://acme-v01.api.letsencrypt.org"``` + +and change it to: + +```CA="https://acme-v02.api.letsencrypt.org"``` + ## Features * **Bash** - It runs on virtually all unix machines, including BSD, most @@ -177,9 +187,9 @@ simple bash file containing variables, an example of which is: ```getssl # Uncomment and modify any variables you need # The staging server is best for testing (hence set as default) -CA="https://acme-staging.api.letsencrypt.org" +CA="https://acme-staging-v02.api.letsencrypt.org" # This server issues full certificates, however has rate limits -#CA="https://acme-v01.api.letsencrypt.org" +#CA="https://acme-v02.api.letsencrypt.org" AGREEMENT="https://letsencrypt.org/documents/LE-SA-v1.0.1-July-27-2015.pdf" @@ -206,9 +216,9 @@ config file (again called `getssl.cfg`). An example of which is: # see https://github.com/srvrco/getssl/wiki/Example-config-files for example configs # # The staging server is best for testing -#CA="https://acme-staging.api.letsencrypt.org" +#CA="https://acme-staging-v02.api.letsencrypt.org" # This server issues full certificates, however has rate limits -#CA="https://acme-v01.api.letsencrypt.org" +#CA="https://acme-v02.api.letsencrypt.org" #AGREEMENT="https://letsencrypt.org/documents/LE-SA-v1.1.1-August-1-2016.pdf" @@ -273,9 +283,9 @@ same server would be: ```getssl # uncomment and modify any variables you need # The staging server is best for testing -CA="https://acme-staging.api.letsencrypt.org" +CA="https://acme-staging-v02.api.letsencrypt.org" # This server issues full certificates, however has rate limits -#CA="https://acme-v01.api.letsencrypt.org" +#CA="https://acme-v02.api.letsencrypt.org" # additional domains - this could be multiple domains / subdomains in a comma separated list SANS="www.example.com" From 080a9ad941904e8572675bfc53029ee3e84af98b Mon Sep 17 00:00:00 2001 From: Tim Kimber Date: Wed, 11 Mar 2020 10:31:51 +0000 Subject: [PATCH 072/110] Update revision history --- getssl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/getssl b/getssl index c222bd2..a73a601 100755 --- a/getssl +++ b/getssl @@ -214,10 +214,11 @@ # 2020-02-13 Fix bug with copying to all locations when creating RSA and ECDSA certs (2.20) # 2020-02-22 Change sign_string to use openssl asn1parse (better fix for #424) # 2020-02-23 Add dig to config check for systems without drill (ubuntu) +# 2020-03-11 Use dig +trace to find primary name server and improve dig parsing of CNAME (2.21) # ---------------------------------------------------------------------------------------- PROGNAME=${0##*/} -VERSION="2.20" +VERSION="2.21" # defaults ACCOUNT_KEY_LENGTH=4096 From 953bfd25c2a07bc52db2326397fc5144724cda88 Mon Sep 17 00:00:00 2001 From: Tim Kimber Date: Thu, 12 Mar 2020 09:33:59 +0000 Subject: [PATCH 073/110] Fix bug with DNS-01 and multiple domains --- dns_scripts/dns_add_challtestsrv | 2 +- getssl | 49 ++++++++++++++----- test/9-multiple-domains-dns01.bats | 47 ++++++++++++++++++ .../getssl-multiple-domains-dns01.cfg | 37 ++++++++++++++ 4 files changed, 121 insertions(+), 14 deletions(-) create mode 100644 test/9-multiple-domains-dns01.bats create mode 100644 test/test-config/getssl-multiple-domains-dns01.cfg diff --git a/dns_scripts/dns_add_challtestsrv b/dns_scripts/dns_add_challtestsrv index 601bcfc..98444b5 100755 --- a/dns_scripts/dns_add_challtestsrv +++ b/dns_scripts/dns_add_challtestsrv @@ -4,4 +4,4 @@ fulldomain="${1}" token="${2}" -curl -X POST -d "{\"host\":\"_acme-challenge.${fulldomain}.\", \"value\": \"${token}\"}" http://10.30.50.3:8055/set-txt +curl --silent -X POST -d "{\"host\":\"_acme-challenge.${fulldomain}.\", \"value\": \"${token}\"}" http://10.30.50.3:8055/set-txt diff --git a/getssl b/getssl index a73a601..93ff02d 100755 --- a/getssl +++ b/getssl @@ -750,13 +750,37 @@ create_order() { OrderLink=$(echo "$responseHeaders" | grep -i location | awk '{print $2}'| tr -d '\r\n ') debug "Order link $OrderLink" FinalizeLink=$(json_get "$response" "finalize") - dn=0 - for d in $alldomains; do - # get authorizations link - AuthLink[$dn]=$(json_get "$response" "identifiers" "value" "$d" "authorizations" "x") - debug "authorizations link for $d - ${AuthLink[$dn]}" - ((dn++)) - done + + if [[ $API -eq 1 ]]; then + dn=0 + for d in $alldomains; do + # get authorizations link + AuthLink[$dn]=$(json_get "$response" "identifiers" "value" "$d" "authorizations" "x") + debug "authorizations link for $d - ${AuthLink[$dn]}" + ((dn++)) + done + else + # Authorization links are unsorted, so fetch the authorization link, find the domain, save response in the correct array position + AuthLinks=$(json_get "$response" "authorizations") + AuthLinkResponse=() + AuthLinkResponseHeader=() + for l in $AuthLinks; do + debug "Requesting authorizations link for $l" + send_signed_request "$l" "" + # Get domain from response + authdomain=$(json_get "$response" "identifier" "value") + # find array position (This is O(n2) but that doubt we'll see performance issues) + dn=0 + for d in $alldomains; do + if [ "$d" == "$authdomain" ]; then + debug "Saving authorization response for $authdomain for domain alldomains[$dn]" + AuthLinkResponse[$dn]=$response + AuthLinkResponseHeader[$dn]=$responseHeaders + fi + ((dn++)) + done + done + fi } date_epoc() { # convert the date into epoch time @@ -823,7 +847,9 @@ for d in $alldomains; do error_exit "new-authz error: $response" fi else - send_signed_request "${AuthLink[$dn]}" "" + response=${AuthLinkResponse[$dn]} + responseHeaders=${AuthLinkResponseHeader[$dn]} + response_status=$(json_get "$response" status) fi if [[ $response_status == "valid" ]]; then @@ -841,16 +867,14 @@ for d in $alldomains; do if [[ $VALIDATE_VIA_DNS == "true" ]]; then # set up the correct DNS token for verification if [[ $API -eq 1 ]]; then # get the dns component of the ACME response - # get the token from the dns component + # get the token and uri from the dns component token=$(json_get "$response" "token" "dns-01") - # get the uri from the dns component uri=$(json_get "$response" "uri" "dns-01") debug uri "$uri" else # APIv2 debug "authlink response = $response" - # get the token from the http-01 component + # get the token and uri from the dns-01 component token=$(json_get "$response" "challenges" "type" "dns-01" "token") - # get the uri from the http component uri=$(json_get "$response" "challenges" "type" "dns-01" "url") debug uri "$uri" fi @@ -901,7 +925,6 @@ for d in $alldomains; do uri=$(json_get "$response" "uri" "http-01") debug uri "$uri" else # APIv2 - send_signed_request "${AuthLink[$dn]}" "" debug "authlink response = $response" # get the token from the http-01 component token=$(json_get "$response" "challenges" "type" "http-01" "token") diff --git a/test/9-multiple-domains-dns01.bats b/test/9-multiple-domains-dns01.bats new file mode 100644 index 0000000..1b2cd9e --- /dev/null +++ b/test/9-multiple-domains-dns01.bats @@ -0,0 +1,47 @@ +#! /usr/bin/env bats + +load '/bats-support/load.bash' +load '/bats-assert/load.bash' +load '/getssl/test/test_helper.bash' + + +# This is run for every test +setup() { + export CURL_CA_BUNDLE=/root/pebble-ca-bundle.crt +} + + +@test "Create certificates for multi-level domains using DNS-01 verification" { + # This tests we can create a certificate for .getssl.test and getssl.test (in SANS) + if [ -n "$STAGING" ]; then + skip "Using staging server, skipping internal test" + fi + CONFIG_FILE="getssl-multiple-domains-dns01.cfg" + setup_environment + + # Add top level domain from SANS to DNS + curl --silent -X POST -d '{"host":"getssl.test", "addresses":["'$GETSSL_IP'"]}' http://10.30.50.3:8055/add-a + + init_getssl + create_certificate + assert_success + refute_output --regexp '[Ff][Aa][Ii][Ll][Ee][Dd]' + refute_output --regexp '[Ee][Rr][Rr][Oo][Rr]' + refute_output --regexp '[Ww][Aa][Rr][Nn][Ii][Nn][Gg]' +} + + +@test "Force renewal of multi-level domains using DNS-01" { + # This tests we can renew a certificate for .getssl.test and getssl.test (in SANS) + if [ -n "$STAGING" ]; then + skip "Using staging server, skipping internal test" + fi + run ${CODE_DIR}/getssl -f $GETSSL_HOST + assert_success + refute_output --regexp '[Ff][Aa][Ii][Ll][Ee][Dd]' + refute_output --regexp '[Ee][Rr][Rr][Oo][Rr]' + refute_output --regexp '[Ww][Aa][Rr][Nn][Ii][Nn][Gg]' + # Remove all the dns aliases + cleanup_environment + curl --silent -X POST -d '{"host":"getssl.tst"}' http://10.30.50.3:8055/clear-a +} diff --git a/test/test-config/getssl-multiple-domains-dns01.cfg b/test/test-config/getssl-multiple-domains-dns01.cfg new file mode 100644 index 0000000..e0d596c --- /dev/null +++ b/test/test-config/getssl-multiple-domains-dns01.cfg @@ -0,0 +1,37 @@ +# Uncomment and modify any variables you need +# see https://github.com/srvrco/getssl/wiki/Config-variables for details +# see https://github.com/srvrco/getssl/wiki/Example-config-files for example configs +# +CA="https://pebble:14000/dir" + +VALIDATE_VIA_DNS=true +DNS_ADD_COMMAND="/getssl/dns_scripts/dns_add_challtestsrv" +DNS_DEL_COMMAND="/getssl/dns_scripts/dns_del_challtestsrv" +AUTH_DNS_SERVER=10.30.50.3 + +# Additional domains - this could be multiple domains / subdomains in a comma separated list +SANS="getssl.test" + +# Acme Challenge Location. The first line for the domain, the following ones for each additional domain. +ACL=( + '/var/www/html/.well-known/acme-challenge' + '/var/www/html/.well-known/acme-challenge' +) + +#Set USE_SINGLE_ACL="true" to use a single ACL for all checks +USE_SINGLE_ACL="false" + +# Location for all your certs, these can either be on the server (full path name) +# or using ssh /sftp as for the ACL +DOMAIN_CERT_LOCATION="/etc/nginx/pki/server.crt" +DOMAIN_KEY_LOCATION="/etc/nginx/pki/private/server.key" +CA_CERT_LOCATION="/etc/nginx/pki/chain.crt" +DOMAIN_CHAIN_LOCATION="" # this is the domain cert and CA cert +DOMAIN_PEM_LOCATION="" # this is the domain_key, domain cert and CA cert + +# The command needed to reload apache / nginx or whatever you use +RELOAD_CMD="cp /getssl/test/test-config/nginx-ubuntu-ssl ${NGINX_CONFIG} && /getssl/test/restart-nginx" + +# Define the server type and confirm correct certificate is installed +SERVER_TYPE="https" +CHECK_REMOTE="true" From ba59e4452b74195a518f4a7d5b52e1106178105a Mon Sep 17 00:00:00 2001 From: Heiko Date: Mon, 23 Mar 2020 22:52:34 +0800 Subject: [PATCH 074/110] Fix staging server URL in domain template The trailing slash of the Let's Encrypt staging server URL broke API discovery --- getssl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/getssl b/getssl index 96f7423..eb41c9d 100755 --- a/getssl +++ b/getssl @@ -1890,7 +1890,7 @@ write_domain_template() { # write out a template file for a domain. # see https://github.com/srvrco/getssl/wiki/Example-config-files for example configs # # The staging server is best for testing - #CA="https://acme-staging-v02.api.letsencrypt.org/" + #CA="https://acme-staging-v02.api.letsencrypt.org" # This server issues full certificates, however has rate limits #CA="https://acme-v02.api.letsencrypt.org" From 6fe3119c90ec99c0c39085f9ea7ef43fe353de73 Mon Sep 17 00:00:00 2001 From: Heiko Date: Mon, 23 Mar 2020 22:56:53 +0800 Subject: [PATCH 075/110] Bump version number and change log --- getssl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/getssl b/getssl index eb41c9d..b870e68 100755 --- a/getssl +++ b/getssl @@ -214,10 +214,11 @@ # 2020-02-13 Fix bug with copying to all locations when creating RSA and ECDSA certs (2.20) # 2020-02-22 Change sign_string to use openssl asn1parse (better fix for #424) # 2020-02-23 Add dig to config check for systems without drill (ubuntu) +# 2020-03-23 Fix staging server URL in domain template # ---------------------------------------------------------------------------------------- PROGNAME=${0##*/} -VERSION="2.20" +VERSION="2.21" # defaults ACCOUNT_KEY_LENGTH=4096 From 665f72550fa665f6a3d8ed03fc004a329333c4f6 Mon Sep 17 00:00:00 2001 From: Tim Kimber Date: Tue, 24 Mar 2020 21:54:36 +0000 Subject: [PATCH 076/110] Find primary ns using all dns utils (dig, host, nslookup) --- getssl | 169 +++++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 110 insertions(+), 59 deletions(-) diff --git a/getssl b/getssl index 93ff02d..010b755 100755 --- a/getssl +++ b/getssl @@ -214,7 +214,8 @@ # 2020-02-13 Fix bug with copying to all locations when creating RSA and ECDSA certs (2.20) # 2020-02-22 Change sign_string to use openssl asn1parse (better fix for #424) # 2020-02-23 Add dig to config check for systems without drill (ubuntu) -# 2020-03-11 Use dig +trace to find primary name server and improve dig parsing of CNAME (2.21) +# 2020-03-11 Use dig +trace to find primary name server and improve dig parsing of CNAME +# 2020-03-24 Find primary ns using all dns utils (dig, host, nslookup) (2.21) # ---------------------------------------------------------------------------------------- PROGNAME=${0##*/} @@ -234,7 +235,7 @@ CSR_SUBJECT="/" CURL_USERAGENT="${PROGNAME}/${VERSION}" DEACTIVATE_AUTH="false" DEFAULT_REVOKE_CA="https://acme-v02.api.letsencrypt.org" -DNS_EXTRA_WAIT="" +DNS_EXTRA_WAIT=60 DNS_WAIT=10 DOMAIN_KEY_LENGTH=4096 DUAL_RSA_ECDSA="false" @@ -825,6 +826,29 @@ error_exit() { # give error message on error exit exit 1 } +find_dns_utils() { + HAS_NSLOOKUP=false + HAS_DIG_OR_DRILL="" + HAS_HOST=false + if [[ -n "$(command -v nslookup)" ]]; then + debug "HAS NSLOOKUP=true" + HAS_NSLOOKUP=true + fi + + if [[ -n "$(command -v drill)" ]]; then + debug "HAS DIG_OR_DRILL=drill" + HAS_DIG_OR_DRILL="drill" + elif [[ -n "$(command -v dig)" ]]; then + debug "HAS DIG_OR_DRILL=dig" + HAS_DIG_OR_DRILL="dig" + fi + + if [[ -n "$(command -v host)" ]]; then + debug "HAS HOST=true" + HAS_HOST=true + fi +} + fulfill_challenges() { dn=0 for d in $alldomains; do @@ -1078,10 +1102,11 @@ fi } get_auth_dns() { # get the authoritative dns server for a domain (sets primary_ns ) - gad_d="$1" # domain name + orig_gad_d="$1" # domain name gad_s="$PUBLIC_DNS_SERVER" # start with PUBLIC_DNS_SERVER if [[ "$os" == "cygwin" ]]; then + gad_d="$orig_gad_d" all_auth_dns_servers=$(nslookup -type=soa "${d}" ${PUBLIC_DNS_SERVER} 2>/dev/null \ | grep "primary name server" \ | awk '{print $NF}') @@ -1092,23 +1117,27 @@ get_auth_dns() { # get the authoritative dns server for a domain (sets primary_n return fi - if [[ "$DNS_CHECK_FUNC" == "drill" ]] || [[ "$DNS_CHECK_FUNC" == "dig" ]]; then + if [[ -n "$HAS_DIG_OR_DRILL" ]]; then + gad_d="$orig_gad_d" + debug Using "$HAS_DIG_OR_DRILL SOA +trace +nocomments $gad_d @$gad_s" to find primary nameserver # Use SOA +trace to find the name server if [[ -z "$gad_s" ]]; then - res=$($DNS_CHECK_FUNC SOA +trace +nocomments "$gad_d" 2>/dev/null | grep "IN\WNS\W" | tail -1) + res=$($HAS_DIG_OR_DRILL SOA +trace +nocomments "$gad_d" 2>/dev/null | grep "IN\WNS\W" | tail -1) else - res=$($DNS_CHECK_FUNC SOA +trace +nocomments "$gad_d" "@$gad_s" 2>/dev/null | grep "IN\WNS\W" | tail -1) + res=$($HAS_DIG_OR_DRILL SOA +trace +nocomments "$gad_d" "@$gad_s" 2>/dev/null | grep "IN\WNS\W" | tail -1) fi # fallback to existing code if [[ -z "$res" ]]; then - if [[ -z "$gad_s" ]]; then #checking for CNAMEs - res=$($DNS_CHECK_FUNC CNAME "$gad_d"| grep "^$gad_d" ) + debug Checking for CNAME using "$HAS_DIG_OR_DRILL CNAME $gad_d @$gad_s" + if [[ -z "$gad_s" ]]; then #checking for CNAMEs (need grep as dig 9.11 sometimes returns everything not just CNAME entries) + res=$($HAS_DIG_OR_DRILL CNAME "$gad_d"| grep "^$gad_d" | grep CNAME) else - res=$($DNS_CHECK_FUNC CNAME "$gad_d" "@$gad_s"| grep "^$gad_d" ) + res=$($HAS_DIG_OR_DRILL CNAME "$gad_d" "@$gad_s"| grep "^$gad_d" | grep CNAME) fi if [[ -n "$res" ]]; then # domain is a CNAME so get main domain gad_d=$(echo "$res"| awk '{print $5}' |sed 's/\.$//g') + debug Domain is a CNAME, actual domain is "$gad_d" fi # If gad_d is an A record then this returns the SOA for the root domain, e.g. without the www # dig NS ubuntu.getssl.text @@ -1117,77 +1146,96 @@ get_auth_dns() { # get the authoritative dns server for a domain (sets primary_n # dig NS www.getssl.text # > www.getssl.test. IN CNAME getssl.test # > getssl.test. IN NS ns1.duckdns.org + debug Using "$HAS_DIG_OR_DRILL NS $gad_d @$gad_s" to find primary nameserver if [[ -z "$gad_s" ]]; then - res=$($DNS_CHECK_FUNC NS "$gad_d"| grep -E "IN\W(NS|SOA)\W" | tail -1) + res=$($HAS_DIG_OR_DRILL NS "$gad_d"| grep -E "IN\W(NS|SOA)\W" | tail -1) else - res=$($DNS_CHECK_FUNC NS "$gad_d" "@$gad_s"| grep -E "IN\W(NS|SOA)\W" | tail -1) + res=$($HAS_DIG_OR_DRILL NS "$gad_d" "@$gad_s"| grep -E "IN\W(NS|SOA)\W" | tail -1) fi fi - if [[ -z "$res" ]]; then - error_exit "couldn't find primary DNS server - please set AUTH_DNS_SERVER in config" - else + if [[ -n "$res" ]]; then all_auth_dns_servers=$(echo "$res" | awk '$4 ~ "NS" {print $5}' | sed 's/\.$//g'|tr '\n' ' ') + if [[ $CHECK_ALL_AUTH_DNS == "true" ]]; then + primary_ns="$all_auth_dns_servers" + else + primary_ns=$(echo "$all_auth_dns_servers" | awk '{print $1}') + fi + return fi - if [[ $CHECK_ALL_AUTH_DNS == "true" ]]; then - primary_ns="$all_auth_dns_servers" - else - primary_ns=$(echo "$all_auth_dns_servers" | awk '{print $1}') - fi - return fi - if [[ "$DNS_CHECK_FUNC" == "host" ]]; then + if [[ "$HAS_HOST" == true ]]; then + gad_d="$orig_gad_d" + debug Using "host -t NS" to find primary name server for "$gad_d" if [[ -z "$gad_s" ]]; then - res=$($DNS_CHECK_FUNC -t NS "$gad_d"| grep "name server") + res=$(host -t NS "$gad_d"| grep "name server") else - res=$($DNS_CHECK_FUNC -t NS "$gad_d" "$gad_s"| grep "name server") + res=$(host -t NS "$gad_d" "$gad_s"| grep "name server") fi - if [[ -z "$res" ]]; then - error_exit "couldn't find primary DNS server - please set AUTH_DNS_SERVER in config" - else + if [[ -n "$res" ]]; then all_auth_dns_servers=$(echo "$res" | awk '{print $4}' | sed 's/\.$//g'|tr '\n' ' ') + if [[ $CHECK_ALL_AUTH_DNS == "true" ]]; then + primary_ns="$all_auth_dns_servers" + else + primary_ns=$(echo "$all_auth_dns_servers" | awk '{print $1}') + fi + return fi - if [[ $CHECK_ALL_AUTH_DNS == "true" ]]; then - primary_ns="$all_auth_dns_servers" - else - primary_ns=$(echo "$all_auth_dns_servers" | awk '{print $1}') - fi - return fi - res=$(nslookup -debug -type=soa -type=ns "$gad_d" ${gad_s}) + if [[ "$HAS_NSLOOKUP" == true ]]; then + gad_d="$orig_gad_d" + debug Using "nslookup -debug -type=soa -type=ns $gad_d $gad_s" to find primary name server + res=$(nslookup -debug -type=soa -type=ns "$gad_d" ${gad_s}) + + if [[ "$(echo "$res" | grep -c "Non-authoritative")" -gt 0 ]]; then + # this is a Non-authoritative server, need to check for an authoritative one. + gad_s=$(echo "$res" | awk '$2 ~ "nameserver" {print $4; exit }' |sed 's/\.$//g') + if [[ "$(echo "$res" | grep -c "an't find")" -gt 0 ]]; then + # if domain name doesn't exist, then find auth servers for next level up + gad_s=$(echo "$res" | awk '$1 ~ "origin" {print $3; exit }') + gad_d=$(echo "$res" | awk '$1 ~ "->" {print $2; exit}') + # handle scenario where awk returns nothing + if [[ -z "$gad_d" ]]; then + gad_d="$orig_gad_d" + fi + fi - if [[ "$(echo "$res" | grep -c "Non-authoritative")" -gt 0 ]]; then - # this is a Non-authoritative server, need to check for an authoritative one. - gad_s=$(echo "$res" | awk '$2 ~ "nameserver" {print $4; exit }' |sed 's/\.$//g') - if [[ "$(echo "$res" | grep -c "an't find")" -gt 0 ]]; then - # if domain name doesn't exist, then find auth servers for next level up - gad_s=$(echo "$res" | awk '$1 ~ "origin" {print $3; exit }') - gad_d=$(echo "$res" | awk '$1 ~ "->" {print $2; exit}') + # shellcheck disable=SC2086 + res=$(nslookup -debug -type=soa -type=ns "$gad_d" ${gad_s}) fi - fi - if [[ -z "$gad_s" ]]; then - res=$(nslookup -debug -type=soa -type=ns "$gad_d") - else - res=$(nslookup -debug -type=soa -type=ns "$gad_d" "${gad_s}") - fi + if [[ "$(echo "$res" | grep -c "canonical name")" -gt 0 ]]; then + gad_d=$(echo "$res" | awk ' $2 ~ "canonical" {print $5; exit }' |sed 's/\.$//g') + elif [[ "$(echo "$res" | grep -c "an't find")" -gt 0 ]]; then + gad_s=$(echo "$res" | awk ' $1 ~ "origin" {print $3; exit }') + gad_d=$(echo "$res"| awk '$1 ~ "->" {print $2; exit}') + # handle scenario where awk returns nothing + if [[ -z "$gad_d" ]]; then + gad_d="$orig_gad_d" + fi + fi - if [[ "$(echo "$res" | grep -c "canonical name")" -gt 0 ]]; then - gad_d=$(echo "$res" | awk ' $2 ~ "canonical" {print $5; exit }' |sed 's/\.$//g') - elif [[ "$(echo "$res" | grep -c "an't find")" -gt 0 ]]; then - gad_s=$(echo "$res" | awk ' $1 ~ "origin" {print $3; exit }') - gad_d=$(echo "$res"| awk '$1 ~ "->" {print $2; exit}') - fi + # shellcheck disable=SC2086 + # not quoting gad_s fixes the nslookup: couldn't get address for '': not found warning (#332) + all_auth_dns_servers=$(nslookup -debug -type=soa -type=ns "$gad_d" $gad_s \ + | awk '$1 ~ "nameserver" {print $3}' \ + | sed 's/\.$//g'| tr '\n' ' ') - all_auth_dns_servers=$(nslookup -type=soa -type=ns "$gad_d" "$gad_s" \ - | awk ' $2 ~ "nameserver" {print $4}' \ - | sed 's/\.$//g'| tr '\n' ' ') - if [[ $CHECK_ALL_AUTH_DNS == "true" ]]; then - primary_ns="$all_auth_dns_servers" - else - primary_ns=$(echo "$all_auth_dns_servers" | awk '{print $1}') + if [[ -n "$all_auth_dns_servers" ]]; then + if [[ $CHECK_ALL_AUTH_DNS == "true" ]]; then + primary_ns="$all_auth_dns_servers" + else + primary_ns=$(echo "$all_auth_dns_servers" | awk '{print $1}') + fi + return + fi fi + + # nslookup on alpine/ubuntu containers doesn't support -debug, print a warning in this case + # This means getssl cannot check that the DNS record has been updated on the primary name server + info "Warning: Couldn't find primary DNS server - please set PUBLIC_DNS_SERVER or AUTH_DNS_SERVER in config" + info "This means getssl cannot check the DNS entry has been updated" } get_certificate() { # get certificate for csr, if all domains validated. @@ -2289,6 +2337,9 @@ set_server_type # check config for typical errors. check_config +# check what dns utils are installed +find_dns_utils + if [[ -e "$DOMAIN_DIR/FORCE_RENEWAL" ]]; then rm -f "$DOMAIN_DIR/FORCE_RENEWAL" || error_exit "problem deleting file $DOMAIN_DIR/FORCE_RENEWAL" _FORCE_RENEW=1 From 8a8fe446e7f3e527b85642f0396a80aedf0237a9 Mon Sep 17 00:00:00 2001 From: Tim Kimber Date: Tue, 24 Mar 2020 21:55:24 +0000 Subject: [PATCH 077/110] Changes to ensure get_auth_dns is tested --- test/2-simple-dns01-nslookup.bats | 3 +- ...ns-dns01.bats => 7-duckdns-dns01-dig.bats} | 4 +- test/7-duckdns-dns01-nslookup.bats | 49 +++++++++++++++++++ test/8-duckdns-ecdsa.bats | 8 +-- .../getssl-dns01-dual-rsa-ecdsa.cfg | 3 +- test/test-config/getssl-dns01.cfg | 3 +- test/test-config/getssl-duckdns01.cfg | 4 +- .../getssl-multiple-domains-dns01.cfg | 3 +- 8 files changed, 65 insertions(+), 12 deletions(-) rename test/{7-duckdns-dns01.bats => 7-duckdns-dns01-dig.bats} (85%) create mode 100644 test/7-duckdns-dns01-nslookup.bats diff --git a/test/2-simple-dns01-nslookup.bats b/test/2-simple-dns01-nslookup.bats index c8d5cc6..482be2a 100644 --- a/test/2-simple-dns01-nslookup.bats +++ b/test/2-simple-dns01-nslookup.bats @@ -34,5 +34,6 @@ teardown() { assert_output --partial "nslookup" refute_output --regexp '[Ff][Aa][Ii][Ll][Ee][Dd]' refute_output --regexp '[^:][Ee][Rr][Rr][Oo][Rr][^:]' # don't fail for :error:badNonce - refute_output --regexp '[Ww][Aa][Rr][Nn][Ii][Nn][Gg]' + # don't check for "Warnings:" as there might be a warning message if nslookup doesn't support -debug (alpine/ubuntu) + refute_output --regexp '[Ww][Aa][Rr][Nn][Ii][Nn][Gg][^:]' } diff --git a/test/7-duckdns-dns01.bats b/test/7-duckdns-dns01-dig.bats similarity index 85% rename from test/7-duckdns-dns01.bats rename to test/7-duckdns-dns01-dig.bats index 0c680ea..c6d8f56 100644 --- a/test/7-duckdns-dns01.bats +++ b/test/7-duckdns-dns01-dig.bats @@ -6,7 +6,7 @@ load '/getssl/test/test_helper.bash' -@test "Create new certificate using staging server and DuckDNS" { +@test "Create new certificate using staging server, dig and DuckDNS" { if [ -z "$STAGING" ]; then skip "Running internal tests, skipping external test" fi @@ -21,7 +21,7 @@ load '/getssl/test/test_helper.bash' refute_output --regexp '[Ww][Aa][Rr][Nn][Ii][Nn][Gg]' } -@test "Force renewal of certificate using staging server and DuckDNS" { +@test "Force renewal of certificate using staging server, dig and DuckDNS" { if [ -z "$STAGING" ]; then skip "Running internal tests, skipping external test" fi diff --git a/test/7-duckdns-dns01-nslookup.bats b/test/7-duckdns-dns01-nslookup.bats new file mode 100644 index 0000000..81c921e --- /dev/null +++ b/test/7-duckdns-dns01-nslookup.bats @@ -0,0 +1,49 @@ +#! /usr/bin/env bats + +load '/bats-support/load.bash' +load '/bats-assert/load.bash' +load '/getssl/test/test_helper.bash' + +# This is run for every test +setup() { + export CURL_CA_BUNDLE=/root/pebble-ca-bundle.crt + if [ -f /usr/bin/dig ]; then + mv /usr/bin/dig /usr/bin/dig.getssl.bak + fi +} + + +teardown() { + if [ -f /usr/bin/dig.getssl.bak ]; then + mv /usr/bin/dig.getssl.bak /usr/bin/dig + fi +} + + +@test "Create new certificate using staging server, nslookup and DuckDNS" { + if [ -z "$STAGING" ]; then + skip "Running internal tests, skipping external test" + fi + CONFIG_FILE="getssl-duckdns01.cfg" + + setup_environment + init_getssl + create_certificate + assert_success + refute_output --regexp '[Ff][Aa][Ii][Ll][Ee][Dd]' + refute_output --regexp '[Ee][Rr][Rr][Oo][Rr]' + refute_output --regexp '[Ww][Aa][Rr][Nn][Ii][Nn][Gg][^:]' # ignore nslookup warnings +} + + +@test "Force renewal of certificate using staging server, nslookup and DuckDNS" { + if [ -z "$STAGING" ]; then + skip "Running internal tests, skipping external test" + fi + run ${CODE_DIR}/getssl -f $GETSSL_HOST + assert_success + refute_output --regexp '[Ff][Aa][Ii][Ll][Ee][Dd]' + refute_output --regexp '[Ee][Rr][Rr][Oo][Rr]' + refute_output --regexp '[Ww][Aa][Rr][Nn][Ii][Nn][Gg][^:]' # ignore nslookup warnings + cleanup_environment +} diff --git a/test/8-duckdns-ecdsa.bats b/test/8-duckdns-ecdsa.bats index dfe84fe..2e10512 100644 --- a/test/8-duckdns-ecdsa.bats +++ b/test/8-duckdns-ecdsa.bats @@ -20,7 +20,7 @@ load '/getssl/test/test_helper.bash' assert_success refute_output --regexp '[Ff][Aa][Ii][Ll][Ee][Dd]' refute_output --regexp '[^:][Ee][Rr][Rr][Oo][Rr][^:]' - refute_output --regexp '[Ww][Aa][Rr][Nn][Ii][Nn][Gg]' + refute_output --regexp '[Ww][Aa][Rr][Nn][Ii][Nn][Gg][^:]' # ignore nslookup warnings } @@ -32,7 +32,7 @@ load '/getssl/test/test_helper.bash' assert_success refute_output --regexp '[Ff][Aa][Ii][Ll][Ee][Dd]' refute_output --regexp '[^:][Ee][Rr][Rr][Oo][Rr][^:]' - refute_output --regexp '[Ww][Aa][Rr][Nn][Ii][Nn][Gg]' + refute_output --regexp '[Ww][Aa][Rr][Nn][Ii][Nn][Gg][^:]' # ignore nslookup warnings cleanup_environment } @@ -50,7 +50,7 @@ load '/getssl/test/test_helper.bash' assert_success refute_output --regexp '[Ff][Aa][Ii][Ll][Ee][Dd]' refute_output --regexp '[^:][Ee][Rr][Rr][Oo][Rr][^:]' - refute_output --regexp '[Ww][Aa][Rr][Nn][Ii][Nn][Gg]' + refute_output --regexp '[Ww][Aa][Rr][Nn][Ii][Nn][Gg][^:]' } @@ -62,7 +62,7 @@ load '/getssl/test/test_helper.bash' assert_success refute_output --regexp '[Ff][Aa][Ii][Ll][Ee][Dd]' refute_output --regexp '[^:][Ee][Rr][Rr][Oo][Rr][^:]' - refute_output --regexp '[Ww][Aa][Rr][Nn][Ii][Nn][Gg]' + refute_output --regexp '[Ww][Aa][Rr][Nn][Ii][Nn][Gg][^:]' cleanup_environment } diff --git a/test/test-config/getssl-dns01-dual-rsa-ecdsa.cfg b/test/test-config/getssl-dns01-dual-rsa-ecdsa.cfg index 543c201..8f29088 100644 --- a/test/test-config/getssl-dns01-dual-rsa-ecdsa.cfg +++ b/test/test-config/getssl-dns01-dual-rsa-ecdsa.cfg @@ -7,7 +7,8 @@ CA="https://pebble:14000/dir" VALIDATE_VIA_DNS=true DNS_ADD_COMMAND="/getssl/dns_scripts/dns_add_challtestsrv" DNS_DEL_COMMAND="/getssl/dns_scripts/dns_del_challtestsrv" -AUTH_DNS_SERVER=10.30.50.3 +PUBLIC_DNS_SERVER=10.30.50.3 +DNS_EXTRA_WAIT="" DUAL_RSA_ECDSA="true" ACCOUNT_KEY_TYPE="prime256v1" diff --git a/test/test-config/getssl-dns01.cfg b/test/test-config/getssl-dns01.cfg index 7e26b98..0b816b1 100644 --- a/test/test-config/getssl-dns01.cfg +++ b/test/test-config/getssl-dns01.cfg @@ -7,7 +7,8 @@ CA="https://pebble:14000/dir" VALIDATE_VIA_DNS=true DNS_ADD_COMMAND="/getssl/dns_scripts/dns_add_challtestsrv" DNS_DEL_COMMAND="/getssl/dns_scripts/dns_del_challtestsrv" -AUTH_DNS_SERVER=10.30.50.3 +PUBLIC_DNS_SERVER=10.30.50.3 +DNS_EXTRA_WAIT="" # Additional domains - this could be multiple domains / subdomains in a comma separated list SANS="" diff --git a/test/test-config/getssl-duckdns01.cfg b/test/test-config/getssl-duckdns01.cfg index 517aaeb..8c12ee1 100644 --- a/test/test-config/getssl-duckdns01.cfg +++ b/test/test-config/getssl-duckdns01.cfg @@ -5,8 +5,8 @@ CA="https://acme-staging-v02.api.letsencrypt.org/directory" VALIDATE_VIA_DNS=true DNS_ADD_COMMAND="/getssl/dns_scripts/dns_add_duckdns" DNS_DEL_COMMAND="/getssl/dns_scripts/dns_del_duckdns" -AUTH_DNS_SERVER=1.1.1.1 -CHECK_ALL_AUTH_DNS=false +PUBLIC_DNS_SERVER=ns2.duckdns.org +CHECK_ALL_AUTH_DNS=true DNS_EXTRA_WAIT=60 ACCOUNT_KEY_TYPE="rsa" diff --git a/test/test-config/getssl-multiple-domains-dns01.cfg b/test/test-config/getssl-multiple-domains-dns01.cfg index e0d596c..8754677 100644 --- a/test/test-config/getssl-multiple-domains-dns01.cfg +++ b/test/test-config/getssl-multiple-domains-dns01.cfg @@ -7,7 +7,8 @@ CA="https://pebble:14000/dir" VALIDATE_VIA_DNS=true DNS_ADD_COMMAND="/getssl/dns_scripts/dns_add_challtestsrv" DNS_DEL_COMMAND="/getssl/dns_scripts/dns_del_challtestsrv" -AUTH_DNS_SERVER=10.30.50.3 +PUBLIC_DNS_SERVER=10.30.50.3 +DNS_EXTRA_WAIT="" # Additional domains - this could be multiple domains / subdomains in a comma separated list SANS="getssl.test" From 125d9e25e857727c9fde890cdf90349be21d0374 Mon Sep 17 00:00:00 2001 From: Tim Kimber Date: Tue, 24 Mar 2020 21:58:53 +0000 Subject: [PATCH 078/110] Add test for IGNORE_DIRECTORY_DOMAIN --- test/9-multiple-domains-dns01.bats | 19 +++++++++ .../getssl-ignore-directory-domain.cfg | 39 +++++++++++++++++++ 2 files changed, 58 insertions(+) create mode 100644 test/test-config/getssl-ignore-directory-domain.cfg diff --git a/test/9-multiple-domains-dns01.bats b/test/9-multiple-domains-dns01.bats index 1b2cd9e..2a9344f 100644 --- a/test/9-multiple-domains-dns01.bats +++ b/test/9-multiple-domains-dns01.bats @@ -45,3 +45,22 @@ setup() { cleanup_environment curl --silent -X POST -d '{"host":"getssl.tst"}' http://10.30.50.3:8055/clear-a } + +@test "Test IGNORE_DIRECTORY_DOMAIN using DNS-01 verification" { + # This tests we can create a certificate for getssl.test and .getssl.test (*both* in SANS) + if [ -n "$STAGING" ]; then + skip "Using staging server, skipping internal test" + fi + CONFIG_FILE="getssl-ignore-directory-domain.cfg" + setup_environment + + # Add top level domain from SANS to DNS + curl --silent -X POST -d '{"host":"getssl.test", "addresses":["'$GETSSL_IP'"]}' http://10.30.50.3:8055/add-a + + init_getssl + create_certificate + assert_success + refute_output --regexp '[Ff][Aa][Ii][Ll][Ee][Dd]' + refute_output --regexp '[Ee][Rr][Rr][Oo][Rr]' + refute_output --regexp '[Ww][Aa][Rr][Nn][Ii][Nn][Gg]' +} diff --git a/test/test-config/getssl-ignore-directory-domain.cfg b/test/test-config/getssl-ignore-directory-domain.cfg new file mode 100644 index 0000000..9777891 --- /dev/null +++ b/test/test-config/getssl-ignore-directory-domain.cfg @@ -0,0 +1,39 @@ +# Uncomment and modify any variables you need +# see https://github.com/srvrco/getssl/wiki/Config-variables for details +# see https://github.com/srvrco/getssl/wiki/Example-config-files for example configs +# +CA="https://pebble:14000/dir" + +VALIDATE_VIA_DNS=true +DNS_ADD_COMMAND="/getssl/dns_scripts/dns_add_challtestsrv" +DNS_DEL_COMMAND="/getssl/dns_scripts/dns_del_challtestsrv" +PUBLIC_DNS_SERVER=10.30.50.3 +DNS_EXTRA_WAIT="" + +# Ignore directory domain (i.e. the domain passed on the command line), and just use the domains in the SANS list +IGNORE_DIRECTORY_DOMAIN="true" +SANS="getssl.test,$GETSSL_HOST" + +# Acme Challenge Location. The first line for the domain, the following ones for each additional domain. +ACL=( + '/var/www/html/.well-known/acme-challenge' + '/var/www/html/.well-known/acme-challenge' +) + +#Set USE_SINGLE_ACL="true" to use a single ACL for all checks +USE_SINGLE_ACL="false" + +# Location for all your certs, these can either be on the server (full path name) +# or using ssh /sftp as for the ACL +DOMAIN_CERT_LOCATION="/etc/nginx/pki/server.crt" +DOMAIN_KEY_LOCATION="/etc/nginx/pki/private/server.key" +CA_CERT_LOCATION="/etc/nginx/pki/chain.crt" +DOMAIN_CHAIN_LOCATION="" # this is the domain cert and CA cert +DOMAIN_PEM_LOCATION="" # this is the domain_key, domain cert and CA cert + +# The command needed to reload apache / nginx or whatever you use +RELOAD_CMD="cp /getssl/test/test-config/nginx-ubuntu-ssl ${NGINX_CONFIG} && /getssl/test/restart-nginx" + +# Define the server type and confirm correct certificate is installed +SERVER_TYPE="https" +CHECK_REMOTE="true" From 804c71ffece2aabb3d7c3b53bbb6fd8691234569 Mon Sep 17 00:00:00 2001 From: Tim Kimber Date: Wed, 25 Mar 2020 21:26:45 +0000 Subject: [PATCH 079/110] Add test for IGNORE_DIRECTORY_DOMAIN --- getssl | 1 + test/9-multiple-domains-dns01.bats | 19 +++++++++ .../getssl-ignore-directory-domain.cfg | 39 +++++++++++++++++++ 3 files changed, 59 insertions(+) create mode 100644 test/test-config/getssl-ignore-directory-domain.cfg diff --git a/getssl b/getssl index 010b755..fc4924c 100755 --- a/getssl +++ b/getssl @@ -215,6 +215,7 @@ # 2020-02-22 Change sign_string to use openssl asn1parse (better fix for #424) # 2020-02-23 Add dig to config check for systems without drill (ubuntu) # 2020-03-11 Use dig +trace to find primary name server and improve dig parsing of CNAME +# 2020-03-12 Fix bug with DNS validation and multiple domains (#524) # 2020-03-24 Find primary ns using all dns utils (dig, host, nslookup) (2.21) # ---------------------------------------------------------------------------------------- diff --git a/test/9-multiple-domains-dns01.bats b/test/9-multiple-domains-dns01.bats index 1b2cd9e..2a9344f 100644 --- a/test/9-multiple-domains-dns01.bats +++ b/test/9-multiple-domains-dns01.bats @@ -45,3 +45,22 @@ setup() { cleanup_environment curl --silent -X POST -d '{"host":"getssl.tst"}' http://10.30.50.3:8055/clear-a } + +@test "Test IGNORE_DIRECTORY_DOMAIN using DNS-01 verification" { + # This tests we can create a certificate for getssl.test and .getssl.test (*both* in SANS) + if [ -n "$STAGING" ]; then + skip "Using staging server, skipping internal test" + fi + CONFIG_FILE="getssl-ignore-directory-domain.cfg" + setup_environment + + # Add top level domain from SANS to DNS + curl --silent -X POST -d '{"host":"getssl.test", "addresses":["'$GETSSL_IP'"]}' http://10.30.50.3:8055/add-a + + init_getssl + create_certificate + assert_success + refute_output --regexp '[Ff][Aa][Ii][Ll][Ee][Dd]' + refute_output --regexp '[Ee][Rr][Rr][Oo][Rr]' + refute_output --regexp '[Ww][Aa][Rr][Nn][Ii][Nn][Gg]' +} diff --git a/test/test-config/getssl-ignore-directory-domain.cfg b/test/test-config/getssl-ignore-directory-domain.cfg new file mode 100644 index 0000000..9777891 --- /dev/null +++ b/test/test-config/getssl-ignore-directory-domain.cfg @@ -0,0 +1,39 @@ +# Uncomment and modify any variables you need +# see https://github.com/srvrco/getssl/wiki/Config-variables for details +# see https://github.com/srvrco/getssl/wiki/Example-config-files for example configs +# +CA="https://pebble:14000/dir" + +VALIDATE_VIA_DNS=true +DNS_ADD_COMMAND="/getssl/dns_scripts/dns_add_challtestsrv" +DNS_DEL_COMMAND="/getssl/dns_scripts/dns_del_challtestsrv" +PUBLIC_DNS_SERVER=10.30.50.3 +DNS_EXTRA_WAIT="" + +# Ignore directory domain (i.e. the domain passed on the command line), and just use the domains in the SANS list +IGNORE_DIRECTORY_DOMAIN="true" +SANS="getssl.test,$GETSSL_HOST" + +# Acme Challenge Location. The first line for the domain, the following ones for each additional domain. +ACL=( + '/var/www/html/.well-known/acme-challenge' + '/var/www/html/.well-known/acme-challenge' +) + +#Set USE_SINGLE_ACL="true" to use a single ACL for all checks +USE_SINGLE_ACL="false" + +# Location for all your certs, these can either be on the server (full path name) +# or using ssh /sftp as for the ACL +DOMAIN_CERT_LOCATION="/etc/nginx/pki/server.crt" +DOMAIN_KEY_LOCATION="/etc/nginx/pki/private/server.key" +CA_CERT_LOCATION="/etc/nginx/pki/chain.crt" +DOMAIN_CHAIN_LOCATION="" # this is the domain cert and CA cert +DOMAIN_PEM_LOCATION="" # this is the domain_key, domain cert and CA cert + +# The command needed to reload apache / nginx or whatever you use +RELOAD_CMD="cp /getssl/test/test-config/nginx-ubuntu-ssl ${NGINX_CONFIG} && /getssl/test/restart-nginx" + +# Define the server type and confirm correct certificate is installed +SERVER_TYPE="https" +CHECK_REMOTE="true" From e7a67f39e609940a9169efc2f46743630ee05114 Mon Sep 17 00:00:00 2001 From: Tim Kimber Date: Mon, 30 Mar 2020 22:01:59 +0100 Subject: [PATCH 080/110] Fix problem with domain in mixed case --- getssl | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/getssl b/getssl index 010b755..72d3f36 100755 --- a/getssl +++ b/getssl @@ -773,6 +773,9 @@ create_order() { # find array position (This is O(n2) but that doubt we'll see performance issues) dn=0 for d in $alldomains; do + # Convert domain to lowercase as response from server will be in lowercase + # shellcheck disable=SC2018,SC2019 + d=$(echo "$d" | tr A-Z a-z) if [ "$d" == "$authdomain" ]; then debug "Saving authorization response for $authdomain for domain alldomains[$dn]" AuthLinkResponse[$dn]=$response @@ -913,8 +916,10 @@ for d in $alldomains; do | sed -e 's:=*$::g' -e 'y:+/:-_:') debug auth_key "$auth_key" - debug "adding dns via command: $DNS_ADD_COMMAND $d $auth_key" - if ! eval "$DNS_ADD_COMMAND" "$d" "$auth_key" ; then + # shellcheck disable=SC2018,SC2019 + lower_d=$(echo "$d" | tr A-Z a-z) + debug "adding dns via command: $DNS_ADD_COMMAND $lower_d $auth_key" + if ! eval "$DNS_ADD_COMMAND" "$lower_d" "$auth_key" ; then error_exit "DNS_ADD_COMMAND failed for domain $d" fi @@ -1091,7 +1096,9 @@ if [[ $VALIDATE_VIA_DNS == "true" ]]; then check_challenge_completion "$uri" "$d" "$keyauthorization" debug "remove DNS entry" - eval "$DNS_DEL_COMMAND" "$d" "$auth_key" + # shellcheck disable=SC2018,SC2019 + lower_d=$(echo "$d" | tr A-Z a-z) + eval "$DNS_DEL_COMMAND" "$lower_d" "$auth_key" # remove $dnsfile after each loop. rm -f "$dnsfile" fi From 0dfda4f64dd262c5dd5c2af10b00b7c1b518f903 Mon Sep 17 00:00:00 2001 From: Tim Kimber Date: Mon, 30 Mar 2020 22:02:57 +0100 Subject: [PATCH 081/110] Make "check domain exists" case insensitive --- getssl | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/getssl b/getssl index 72d3f36..41663da 100755 --- a/getssl +++ b/getssl @@ -438,29 +438,29 @@ check_config() { # check the config files for all obvious errors fi # check domain exists if [[ "$DNS_CHECK_FUNC" == "drill" ]]; then - if [[ "$($DNS_CHECK_FUNC "${d}" |grep -c "${d}")" -ge 1 ]]; then + if [[ "$($DNS_CHECK_FUNC "${d}" |grep -c -i "${d}")" -ge 1 ]]; then debug "found IP for ${d}" else info "${DOMAIN}: DNS lookup failed for ${d}" config_errors=true fi elif [[ "$DNS_CHECK_FUNC" == "dig" ]]; then - if [[ "$($DNS_CHECK_FUNC "${d}" -t SOA|grep -c "^${d}")" -ge 1 ]]; then + if [[ "$($DNS_CHECK_FUNC "${d}" -t SOA|grep -c -i "^${d}")" -ge 1 ]]; then debug "found SOA IP for ${d}" - elif [[ "$($DNS_CHECK_FUNC "${d}" -t A|grep -c "^${d}")" -ge 1 ]]; then + elif [[ "$($DNS_CHECK_FUNC "${d}" -t A|grep -c -i "^${d}")" -ge 1 ]]; then debug "found A IP for ${d}" else info "${DOMAIN}: DNS lookup failed for ${d}" config_errors=true fi elif [[ "$DNS_CHECK_FUNC" == "host" ]]; then - if [[ "$($DNS_CHECK_FUNC "${d}" |grep -c "^${d}")" -ge 1 ]]; then + if [[ "$($DNS_CHECK_FUNC "${d}" |grep -c -i "^${d}")" -ge 1 ]]; then debug "found IP for ${d}" else info "${DOMAIN}: DNS lookup failed for ${d}" config_errors=true fi - elif [[ "$(nslookup -query=AAAA "${d}"|grep -c "^${d}.*has AAAA address")" -ge 1 ]]; then + elif [[ "$(nslookup -query=AAAA "${d}"|grep -c -i "^${d}.*has AAAA address")" -ge 1 ]]; then debug "found IPv6 record for ${d}" elif [[ "$(nslookup "${d}"| grep -c ^Name)" -ge 1 ]]; then debug "found IPv4 record for ${d}" From ad28d693716d0e707b146912c509c53c20ea4431 Mon Sep 17 00:00:00 2001 From: Tim Kimber Date: Mon, 30 Mar 2020 22:04:29 +0100 Subject: [PATCH 082/110] Fix error messages in find_dns_utils from older versions of "command" --- getssl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/getssl b/getssl index 41663da..73ca097 100755 --- a/getssl +++ b/getssl @@ -833,20 +833,20 @@ find_dns_utils() { HAS_NSLOOKUP=false HAS_DIG_OR_DRILL="" HAS_HOST=false - if [[ -n "$(command -v nslookup)" ]]; then + if [[ -n "$(command -v nslookup 2>/dev/null)" ]]; then debug "HAS NSLOOKUP=true" HAS_NSLOOKUP=true fi - if [[ -n "$(command -v drill)" ]]; then + if [[ -n "$(command -v drill 2>/dev/null)" ]]; then debug "HAS DIG_OR_DRILL=drill" HAS_DIG_OR_DRILL="drill" - elif [[ -n "$(command -v dig)" ]]; then + elif [[ -n "$(command -v dig 2>/dev/null)" ]]; then debug "HAS DIG_OR_DRILL=dig" HAS_DIG_OR_DRILL="dig" fi - if [[ -n "$(command -v host)" ]]; then + if [[ -n "$(command -v host 2>/dev/null)" ]]; then debug "HAS HOST=true" HAS_HOST=true fi From 125fabdc33708eaf31aade340ab4e6ddd5bb5552 Mon Sep 17 00:00:00 2001 From: Tim Kimber Date: Mon, 30 Mar 2020 22:31:33 +0100 Subject: [PATCH 083/110] Add mixed case tests, rename duckdns tests --- .github/workflows/run-all-tests.yml | 8 ++-- test/10-mixed-case-staging.bats | 24 ++++++++++ test/10-mixed-case.bats | 45 +++++++++++++++++++ ...ns01-dig.bats => 7-staging-dns01-dig.bats} | 2 +- ...kup.bats => 7-staging-dns01-nslookup.bats} | 2 +- ...uckdns-ecdsa.bats => 8-staging-ecdsa.bats} | 4 +- test/9-test--all.bats | 34 ++++++++++++++ ...os7-duckdns => Dockerfile-centos7-staging} | 0 ...untu-duckdns => Dockerfile-ubuntu-staging} | 0 test/run-test.cmd | 10 ++--- test/run-test.sh | 6 +-- ...duckdns01.cfg => getssl-staging-dns01.cfg} | 0 test/test_helper.bash | 9 ++-- 13 files changed, 125 insertions(+), 19 deletions(-) create mode 100644 test/10-mixed-case-staging.bats create mode 100644 test/10-mixed-case.bats rename test/{7-duckdns-dns01-dig.bats => 7-staging-dns01-dig.bats} (95%) rename test/{7-duckdns-dns01-nslookup.bats => 7-staging-dns01-nslookup.bats} (96%) rename test/{8-duckdns-ecdsa.bats => 8-staging-ecdsa.bats} (96%) create mode 100644 test/9-test--all.bats rename test/{Dockerfile-centos7-duckdns => Dockerfile-centos7-staging} (100%) rename test/{Dockerfile-ubuntu-duckdns => Dockerfile-ubuntu-staging} (100%) rename test/test-config/{getssl-duckdns01.cfg => getssl-staging-dns01.cfg} (100%) diff --git a/.github/workflows/run-all-tests.yml b/.github/workflows/run-all-tests.yml index 1ff795d..ff0e121 100644 --- a/.github/workflows/run-all-tests.yml +++ b/.github/workflows/run-all-tests.yml @@ -31,14 +31,14 @@ jobs: run: docker-compose up -d --build - name: Run test suite on CentOS7 run: test/run-test.sh centos7 - test-centos7-duckdns: + test-centos7-staging: runs-on: ubuntu-latest steps: - uses: actions/checkout@v1 - name: Build the docker-compose stack run: docker-compose up -d --build - name: Run test suite on CentOS7 against Staging using DuckDNS - run: test/run-test.sh centos7-duckdns + run: test/run-test.sh centos7-staging test-debian: runs-on: ubuntu-latest steps: @@ -71,11 +71,11 @@ jobs: run: docker-compose up -d --build - name: Run test suite on Ubuntu18 run: test/run-test.sh ubuntu18 - test-ubuntu-duckdns: + test-ubuntu-staging: runs-on: ubuntu-latest steps: - uses: actions/checkout@v1 - name: Build the docker-compose stack run: docker-compose up -d --build - name: Run test suite on Ubuntu against Staging using DuckDNS - run: test/run-test.sh ubuntu-duckdns + run: test/run-test.sh ubuntu-staging diff --git a/test/10-mixed-case-staging.bats b/test/10-mixed-case-staging.bats new file mode 100644 index 0000000..c1bac0d --- /dev/null +++ b/test/10-mixed-case-staging.bats @@ -0,0 +1,24 @@ +#! /usr/bin/env bats + +load '/bats-support/load.bash' +load '/bats-assert/load.bash' +load '/getssl/test/test_helper.bash' + + +@test "Check can create certificate if domain is not lowercase using staging server and DuckDNS" { + if [ -z "$STAGING" ]; then + skip "Running internal tests, skipping external test" + fi + + CONFIG_FILE="getssl-staging-dns01.cfg" + GETSSL_CMD_HOST=$(echo $GETSSL_HOST | tr a-z A-Z) + + setup_environment + init_getssl + create_certificate + + assert_success + refute_output --regexp '[Ff][Aa][Ii][Ll][Ee][Dd]' + refute_output --regexp '[Ee][Rr][Rr][Oo][Rr]' + refute_output --regexp '[Ww][Aa][Rr][Nn][Ii][Nn][Gg]' +} diff --git a/test/10-mixed-case.bats b/test/10-mixed-case.bats new file mode 100644 index 0000000..2a4d6f3 --- /dev/null +++ b/test/10-mixed-case.bats @@ -0,0 +1,45 @@ +#! /usr/bin/env bats + +load '/bats-support/load.bash' +load '/bats-assert/load.bash' +load '/getssl/test/test_helper.bash' + + +# This is run for every test +setup() { + export CURL_CA_BUNDLE=/root/pebble-ca-bundle.crt +} + +@test "Check that HTTP-01 verification works if the domain is not lowercase" { + if [ -n "$STAGING" ]; then + skip "Using staging server, skipping internal test" + fi + + CONFIG_FILE="getssl-http01.cfg" + GETSSL_CMD_HOST=$(echo $GETSSL_HOST | tr a-z A-Z) + + setup_environment + init_getssl + create_certificate + + assert_success + refute_output --regexp '[Ff][Aa][Ii][Ll][Ee][Dd]' + refute_output --regexp '[Ee][Rr][Rr][Oo][Rr]' + refute_output --regexp '[Ww][Aa][Rr][Nn][Ii][Nn][Gg]' +} + +@test "Check that DNS-01 verification works if the domain is not lowercase" { + if [ -n "$STAGING" ]; then + skip "Using staging server, skipping internal test" + fi + CONFIG_FILE="getssl-dns01.cfg" + GETSSL_CMD_HOST=$(echo $GETSSL_HOST | tr a-z A-Z) + setup_environment + + init_getssl + create_certificate + assert_success + refute_output --regexp '[Ff][Aa][Ii][Ll][Ee][Dd]' + refute_output --regexp '[Ee][Rr][Rr][Oo][Rr]' + refute_output --regexp '[Ww][Aa][Rr][Nn][Ii][Nn][Gg]' +} diff --git a/test/7-duckdns-dns01-dig.bats b/test/7-staging-dns01-dig.bats similarity index 95% rename from test/7-duckdns-dns01-dig.bats rename to test/7-staging-dns01-dig.bats index c6d8f56..8c0d7f1 100644 --- a/test/7-duckdns-dns01-dig.bats +++ b/test/7-staging-dns01-dig.bats @@ -10,7 +10,7 @@ load '/getssl/test/test_helper.bash' if [ -z "$STAGING" ]; then skip "Running internal tests, skipping external test" fi - CONFIG_FILE="getssl-duckdns01.cfg" + CONFIG_FILE="getssl-staging-dns01.cfg" setup_environment init_getssl diff --git a/test/7-duckdns-dns01-nslookup.bats b/test/7-staging-dns01-nslookup.bats similarity index 96% rename from test/7-duckdns-dns01-nslookup.bats rename to test/7-staging-dns01-nslookup.bats index 81c921e..027a210 100644 --- a/test/7-duckdns-dns01-nslookup.bats +++ b/test/7-staging-dns01-nslookup.bats @@ -24,7 +24,7 @@ teardown() { if [ -z "$STAGING" ]; then skip "Running internal tests, skipping external test" fi - CONFIG_FILE="getssl-duckdns01.cfg" + CONFIG_FILE="getssl-staging-dns01.cfg" setup_environment init_getssl diff --git a/test/8-duckdns-ecdsa.bats b/test/8-staging-ecdsa.bats similarity index 96% rename from test/8-duckdns-ecdsa.bats rename to test/8-staging-ecdsa.bats index 2e10512..92c694a 100644 --- a/test/8-duckdns-ecdsa.bats +++ b/test/8-staging-ecdsa.bats @@ -11,7 +11,7 @@ load '/getssl/test/test_helper.bash' if [ -z "$STAGING" ]; then skip "Running internal tests, skipping external test" fi - CONFIG_FILE="getssl-duckdns01.cfg" + CONFIG_FILE="getssl-staging-dns01.cfg" setup_environment init_getssl @@ -41,7 +41,7 @@ load '/getssl/test/test_helper.bash' if [ -z "$STAGING" ]; then skip "Running internal tests, skipping external test" fi - CONFIG_FILE="getssl-duckdns01.cfg" + CONFIG_FILE="getssl-staging-dns01.cfg" setup_environment init_getssl diff --git a/test/9-test--all.bats b/test/9-test--all.bats new file mode 100644 index 0000000..94a2c28 --- /dev/null +++ b/test/9-test--all.bats @@ -0,0 +1,34 @@ +#! /usr/bin/env bats + +load '/bats-support/load.bash' +load '/bats-assert/load.bash' +load '/getssl/test/test_helper.bash' + + +# This is run for every test +setup() { + export CURL_CA_BUNDLE=/root/pebble-ca-bundle.crt + export PATH=$PATH:/getssl +} + + +@test "Create new certificate using --all" { + if [ -n "$STAGING" ]; then + skip "Using staging server, skipping internal test" + fi + + # Setup + CONFIG_FILE="getssl-http01.cfg" + setup_environment + init_getssl + cp "${CODE_DIR}/test/test-config/${CONFIG_FILE}" "${INSTALL_DIR}/.getssl/${GETSSL_HOST}/getssl.cfg" + + # Run test + run ${CODE_DIR}/getssl --all + + # Check success conditions + assert_success + refute_output --regexp '[Ff][Aa][Ii][Ll][Ee][Dd]' + refute_output --regexp '[Ee][Rr][Rr][Oo][Rr]' + refute_output --regexp '[Ww][Aa][Rr][Nn][Ii][Nn][Gg]' +} diff --git a/test/Dockerfile-centos7-duckdns b/test/Dockerfile-centos7-staging similarity index 100% rename from test/Dockerfile-centos7-duckdns rename to test/Dockerfile-centos7-staging diff --git a/test/Dockerfile-ubuntu-duckdns b/test/Dockerfile-ubuntu-staging similarity index 100% rename from test/Dockerfile-ubuntu-duckdns rename to test/Dockerfile-ubuntu-staging diff --git a/test/run-test.cmd b/test/run-test.cmd index d1cf263..43c4e40 100644 --- a/test/run-test.cmd +++ b/test/run-test.cmd @@ -7,8 +7,8 @@ IF %2.==. GOTO NoCmd set COMMAND=%2 %3 :CheckAlias -REM check if OS *contains* duckdns -IF NOT x%OS:duckdns=%==x%OS% GOTO duckdns +REM check if OS *contains* staging +IF NOT x%OS:staging=%==x%OS% GOTO staging set ALIAS=%OS%.getssl.test set STAGING= GOTO Run @@ -22,8 +22,8 @@ REM set COMMAND=/getssl/test/run-bats.sh set COMMAND=bats /getssl/test GOTO CheckAlias -:duckdns -set ALIAS=%OS:-duckdns=%-getssl.duckdns.org +:staging +set ALIAS=%OS:-staging=%-getssl.duckdns.org set STAGING=--env STAGING=true :Run @@ -33,7 +33,7 @@ docker build --rm -f "test\Dockerfile-%OS%" -t getssl-%OS% . @echo on docker run -it ^ --env GETSSL_HOST=%ALIAS% %STAGING% ^ - --env GETSSL_OS=%OS:-duckdns=% ^ + --env GETSSL_OS=%OS:-staging=% ^ -v %cd%:/getssl ^ --rm ^ --network %CurrDirName%_acmenet ^ diff --git a/test/run-test.sh b/test/run-test.sh index 97842a5..d99d5a3 100755 --- a/test/run-test.sh +++ b/test/run-test.sh @@ -14,8 +14,8 @@ else COMMAND="bats /getssl/test" fi -if [[ "$OS" == *"duckdns"* ]]; then - ALIAS="${OS%-duckdns}-getssl.duckdns.org" +if [[ "$OS" == *"staging"* ]]; then + ALIAS="${OS%-staging}-getssl.duckdns.org" STAGING="--env STAGING=true" else ALIAS="$OS.getssl.test" @@ -26,7 +26,7 @@ docker build --rm -f "test/Dockerfile-$OS" -t "getssl-$OS" . # shellcheck disable=SC2086 docker run \ --env GETSSL_HOST=$ALIAS $STAGING \ - --env GETSSL_OS=${OS%-duckdns} \ + --env GETSSL_OS=${OS%-staging} \ -v "$(pwd)":/getssl \ --rm \ --network ${PWD##*/}_acmenet \ diff --git a/test/test-config/getssl-duckdns01.cfg b/test/test-config/getssl-staging-dns01.cfg similarity index 100% rename from test/test-config/getssl-duckdns01.cfg rename to test/test-config/getssl-staging-dns01.cfg diff --git a/test/test_helper.bash b/test/test_helper.bash index f311b18..f4c62af 100644 --- a/test/test_helper.bash +++ b/test/test_helper.bash @@ -21,7 +21,7 @@ cleanup_environment() { init_getssl() { # Run initialisation (create account key, etc) - run ${CODE_DIR}/getssl -c "$GETSSL_HOST" + run ${CODE_DIR}/getssl -c "$GETSSL_CMD_HOST" assert_success [ -d "$INSTALL_DIR/.getssl" ] } @@ -29,9 +29,9 @@ init_getssl() { create_certificate() { # Create certificate - cp "${CODE_DIR}/test/test-config/${CONFIG_FILE}" "${INSTALL_DIR}/.getssl/${GETSSL_HOST}/getssl.cfg" + cp "${CODE_DIR}/test/test-config/${CONFIG_FILE}" "${INSTALL_DIR}/.getssl/${GETSSL_CMD_HOST}/getssl.cfg" # shellcheck disable=SC2086 - run ${CODE_DIR}/getssl $1 "$GETSSL_HOST" + run ${CODE_DIR}/getssl $1 "$GETSSL_CMD_HOST" } # start nginx in background on alpine via supervisord @@ -68,6 +68,9 @@ fi export GETSSL_IP +GETSSL_CMD_HOST=$GETSSL_HOST +export GETSSL_CMD_HOST + if [ ! -f ${INSTALL_DIR}/pebble.minica.pem ]; then wget --quiet --no-clobber https://raw.githubusercontent.com/letsencrypt/pebble/master/test/certs/pebble.minica.pem 2>&1 CERT_FILE=/etc/ssl/certs/ca-certificates.crt From e50362501d3bee59fb092891cdd1a84efe6aedb8 Mon Sep 17 00:00:00 2001 From: Tim Kimber Date: Wed, 8 Apr 2020 18:59:16 +0100 Subject: [PATCH 084/110] Update revision history and version number --- getssl | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/getssl b/getssl index 45d63cd..a0c530c 100755 --- a/getssl +++ b/getssl @@ -218,10 +218,12 @@ # 2020-03-12 Fix bug with DNS validation and multiple domains (#524) # 2020-03-24 Find primary ns using all dns utils (dig, host, nslookup) # 2020-03-23 Fix staging server URL in domain template (2.21) +# 2020-03-30 Fix error message find_dns_utils from over version of "command" +# 2020-03-30 Fix problems if domain name isn't in lowercase (2.22) # ---------------------------------------------------------------------------------------- PROGNAME=${0##*/} -VERSION="2.21" +VERSION="2.22" # defaults ACCOUNT_KEY_LENGTH=4096 From a73848c60eb3a675ff5f1b11c2f9d1b8db8050ad Mon Sep 17 00:00:00 2001 From: Tim Kimber Date: Wed, 8 Apr 2020 21:21:15 +0100 Subject: [PATCH 085/110] Check domain exists using all DNS utilities --- getssl | 63 ++++++++++++++++++++++++++++++---------------------------- 1 file changed, 33 insertions(+), 30 deletions(-) diff --git a/getssl b/getssl index a0c530c..4c83b53 100755 --- a/getssl +++ b/getssl @@ -440,39 +440,42 @@ check_config() { # check the config files for all obvious errors info "${DOMAIN}: ACL location not specified for domain $d in $DOMAIN_DIR/getssl.cfg" config_errors=true fi - # check domain exists - if [[ "$DNS_CHECK_FUNC" == "drill" ]]; then - if [[ "$($DNS_CHECK_FUNC "${d}" |grep -c -i "${d}")" -ge 1 ]]; then - debug "found IP for ${d}" - else - info "${DOMAIN}: DNS lookup failed for ${d}" - config_errors=true + + # check domain exists using all DNS utilities + found_ip=false + if [[ -n "$HAS_DIG_OR_DRILL" ]]; then + debug "DNS lookup using $HAS_DIG_OR_DRILL ${d}" + if [[ "$($HAS_DIG_OR_DRILL -t SOA "${d}" |grep -c -i "^${d}")" -ge 1 ]]; then + found_ip=true + elif [[ "$($HAS_DIG_OR_DRILL -t A "${d}"|grep -c -i "^${d}")" -ge 1 ]]; then + found_ip=true + elif [[ "$($HAS_DIG_OR_DRILL -t AAAA "${d}"|grep -c -i "^${d}")" -ge 1 ]]; then + found_ip=true fi - elif [[ "$DNS_CHECK_FUNC" == "dig" ]]; then - if [[ "$($DNS_CHECK_FUNC "${d}" -t SOA|grep -c -i "^${d}")" -ge 1 ]]; then - debug "found SOA IP for ${d}" - elif [[ "$($DNS_CHECK_FUNC "${d}" -t A|grep -c -i "^${d}")" -ge 1 ]]; then - debug "found A IP for ${d}" - else - info "${DOMAIN}: DNS lookup failed for ${d}" - config_errors=true + fi + + if [[ -n "$HAS_HOST" ]]; then + debug "DNS lookup using host ${d}" + if [[ "$(host "${d}" |grep -c -i "^${d}")" -ge 1 ]]; then + found_ip=true fi - elif [[ "$DNS_CHECK_FUNC" == "host" ]]; then - if [[ "$($DNS_CHECK_FUNC "${d}" |grep -c -i "^${d}")" -ge 1 ]]; then - debug "found IP for ${d}" - else - info "${DOMAIN}: DNS lookup failed for ${d}" - config_errors=true + fi + + if [[ -n "$HAS_NSLOOKUP" ]]; then + debug "DNS lookup using nslookup -query AAAA ${d}" + if [[ "$(nslookup -query=AAAA "${d}"|grep -c -i "^${d}.*has AAAA address")" -ge 1 ]]; then + debug "found IPv6 record for ${d}" + found_ip=true + elif [[ "$(nslookup "${d}"| grep -c ^Name)" -ge 1 ]]; then + debug "found IPv4 record for ${d}" fi - elif [[ "$(nslookup -query=AAAA "${d}"|grep -c -i "^${d}.*has AAAA address")" -ge 1 ]]; then - debug "found IPv6 record for ${d}" - elif [[ "$(nslookup "${d}"| grep -c ^Name)" -ge 1 ]]; then - debug "found IPv4 record for ${d}" - else + fi + + if [[ "$found_ip" == "false" ]]; then info "${DOMAIN}: DNS lookup failed for $d" config_errors=true fi - fi # end using http-01 challenge + fi # end using dns-01 challenge ((dn++)) done @@ -2345,12 +2348,12 @@ fi # from SERVER_TYPE set REMOTE_PORT and REMOTE_EXTRA set_server_type -# check config for typical errors. -check_config - # check what dns utils are installed find_dns_utils +# check config for typical errors. +check_config + if [[ -e "$DOMAIN_DIR/FORCE_RENEWAL" ]]; then rm -f "$DOMAIN_DIR/FORCE_RENEWAL" || error_exit "problem deleting file $DOMAIN_DIR/FORCE_RENEWAL" _FORCE_RENEW=1 From 17203b1ec1e9962d1fcbe88fa87a5efac73707a9 Mon Sep 17 00:00:00 2001 From: Juan Javier Baca Date: Thu, 16 Apr 2020 04:41:44 +0200 Subject: [PATCH 086/110] Add alternative working dirs Despite changing working dir from command line covers most usage cases, others defaults are also usefull like /etc/getssl, SCRIPTDIR/conf or SCRIPTDIR/.getssl. Last candidate (~/.getssl) is used if no config file was found in previous paths. --- getssl | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/getssl b/getssl index a0c530c..a465376 100755 --- a/getssl +++ b/getssl @@ -220,6 +220,7 @@ # 2020-03-23 Fix staging server URL in domain template (2.21) # 2020-03-30 Fix error message find_dns_utils from over version of "command" # 2020-03-30 Fix problems if domain name isn't in lowercase (2.22) +# 2020-04-16 Add alternative working dirs '/etc/getssl/' '${SCRIPTDIR}/conf' '${SCRIPTDIR}/.getssl' # ---------------------------------------------------------------------------------------- PROGNAME=${0##*/} @@ -261,7 +262,7 @@ TEMP_UPGRADE_FILE="" TOKEN_USER_ID="" USE_SINGLE_ACL="false" VALIDATE_VIA_DNS="" -WORKING_DIR=~/.getssl +WORKING_DIR_CANDIDATES=('/etc/getssl/' '${SCRIPTDIR}/conf' '${SCRIPTDIR}/.getssl' '~/.getssl') _CHECK_ALL=0 _CREATE_CONFIG=0 _FORCE_RENEW=0 @@ -2179,6 +2180,7 @@ requires which requires openssl requires curl requires dig nslookup drill host DNS_CHECK_FUNC +requires dirname requires awk requires tr requires date @@ -2216,6 +2218,22 @@ if [[ -z "$DOMAIN" ]] && [[ ${_CHECK_ALL} -ne 1 ]]; then graceful_exit fi +# Test working directory candidates if unset. Last candidate defaults (~/getssl/) +if [[ -z "${WORKING_DIR}" ]] +then + SCRIPTDIR="$(cd "$(dirname "$0")"; pwd -P;)" + for WDCC in $(seq 0 $((${#WORKING_DIR_CANDIDATES[@]}-1)) ) + do + WORKING_DIR="$(eval echo "${WORKING_DIR_CANDIDATES[$WDCC]}")" + + debug "Testing working dir location '${WORKING_DIR}'" + if [[ -s "$WORKING_DIR/getssl.cfg" ]] + then + break + fi + done +fi + # if the "working directory" doesn't exist, then create it. if [[ ! -d "$WORKING_DIR" ]]; then debug "Making working directory - $WORKING_DIR" From 9dfff30b9d53e8271e1bc4908ad0704ac593ec5d Mon Sep 17 00:00:00 2001 From: Juan Javier Baca Date: Thu, 16 Apr 2020 04:44:16 +0200 Subject: [PATCH 087/110] Add -i|--install command line option While testing or setting up getssl the installation of certificates could fail. Option -i allows to copy and reload service quicker. --- getssl | 155 ++++++++++++++++++++++++++++++++------------------------- 1 file changed, 86 insertions(+), 69 deletions(-) diff --git a/getssl b/getssl index a465376..8251911 100755 --- a/getssl +++ b/getssl @@ -221,6 +221,7 @@ # 2020-03-30 Fix error message find_dns_utils from over version of "command" # 2020-03-30 Fix problems if domain name isn't in lowercase (2.22) # 2020-04-16 Add alternative working dirs '/etc/getssl/' '${SCRIPTDIR}/conf' '${SCRIPTDIR}/.getssl' +# 2020-04-16 Add -i|--install command line option # ---------------------------------------------------------------------------------------- PROGNAME=${0##*/} @@ -305,6 +306,79 @@ cert_archive() { # Archive certificate file by copying files to dated archive d purge_archive "$DOMAIN_DIR" } +cert_install() { # copy certs to the correct location (creating concatenated files as required) + umask 077 + + copy_file_to_location "domain certificate" "$CERT_FILE" "$DOMAIN_CERT_LOCATION" + copy_file_to_location "private key" "$DOMAIN_DIR/${DOMAIN}.key" "$DOMAIN_KEY_LOCATION" + copy_file_to_location "CA certificate" "$CA_CERT" "$CA_CERT_LOCATION" + if [[ "$DUAL_RSA_ECDSA" == "true" ]]; then + if [[ -n "$DOMAIN_CERT_LOCATION" ]]; then + copy_file_to_location "ec domain certificate" \ + "${CERT_FILE%.*}.ec.crt" \ + "${DOMAIN_CERT_LOCATION}" \ + "ec" + fi + if [[ -n "$DOMAIN_KEY_LOCATION" ]]; then + copy_file_to_location "ec private key" \ + "$DOMAIN_DIR/${DOMAIN}.ec.key" \ + "${DOMAIN_KEY_LOCATION}" \ + "ec" + fi + if [[ -n "$CA_CERT_LOCATION" ]]; then + copy_file_to_location "ec CA certificate" \ + "${CA_CERT%.*}.ec.crt" \ + "${CA_CERT_LOCATION%.*}.crt" \ + "ec" + fi + fi + + # if DOMAIN_CHAIN_LOCATION is not blank, then create and copy file. + if [[ -n "$DOMAIN_CHAIN_LOCATION" ]]; then + if [[ "$(dirname "$DOMAIN_CHAIN_LOCATION")" == "." ]]; then + to_location="${DOMAIN_DIR}/${DOMAIN_CHAIN_LOCATION}" + else + to_location="${DOMAIN_CHAIN_LOCATION}" + fi + cat "$CERT_FILE" "$CA_CERT" > "$TEMP_DIR/${DOMAIN}_chain.pem" + copy_file_to_location "full chain" "$TEMP_DIR/${DOMAIN}_chain.pem" "$to_location" + if [[ "$DUAL_RSA_ECDSA" == "true" ]]; then + cat "${CERT_FILE%.*}.ec.crt" "${CA_CERT%.*}.ec.crt" > "$TEMP_DIR/${DOMAIN}_chain.pem.ec" + copy_file_to_location "full chain" "$TEMP_DIR/${DOMAIN}_chain.pem.ec" "${to_location}" "ec" + fi + fi + # if DOMAIN_KEY_CERT_LOCATION is not blank, then create and copy file. + if [[ -n "$DOMAIN_KEY_CERT_LOCATION" ]]; then + if [[ "$(dirname "$DOMAIN_KEY_CERT_LOCATION")" == "." ]]; then + to_location="${DOMAIN_DIR}/${DOMAIN_KEY_CERT_LOCATION}" + else + to_location="${DOMAIN_KEY_CERT_LOCATION}" + fi + cat "$DOMAIN_DIR/${DOMAIN}.key" "$CERT_FILE" > "$TEMP_DIR/${DOMAIN}_K_C.pem" + copy_file_to_location "private key and domain cert pem" "$TEMP_DIR/${DOMAIN}_K_C.pem" "$to_location" + if [[ "$DUAL_RSA_ECDSA" == "true" ]]; then + cat "$DOMAIN_DIR/${DOMAIN}.ec.key" "${CERT_FILE%.*}.ec.crt" > "$TEMP_DIR/${DOMAIN}_K_C.pem.ec" + copy_file_to_location "private ec key and domain cert pem" "$TEMP_DIR/${DOMAIN}_K_C.pem.ec" "${to_location}" "ec" + fi + fi + # if DOMAIN_PEM_LOCATION is not blank, then create and copy file. + if [[ -n "$DOMAIN_PEM_LOCATION" ]]; then + if [[ "$(dirname "$DOMAIN_PEM_LOCATION")" == "." ]]; then + to_location="${DOMAIN_DIR}/${DOMAIN_PEM_LOCATION}" + else + to_location="${DOMAIN_PEM_LOCATION}" + fi + cat "$DOMAIN_DIR/${DOMAIN}.key" "$CERT_FILE" "$CA_CERT" > "$TEMP_DIR/${DOMAIN}.pem" + copy_file_to_location "full key, cert and chain pem" "$TEMP_DIR/${DOMAIN}.pem" "$to_location" + if [[ "$DUAL_RSA_ECDSA" == "true" ]]; then + cat "$DOMAIN_DIR/${DOMAIN}.ec.key" "${CERT_FILE%.*}.ec.crt" "${CA_CERT%.*}.ec.crt" > "$TEMP_DIR/${DOMAIN}.pem.ec" + copy_file_to_location "full ec key, cert and chain pem" "$TEMP_DIR/${DOMAIN}.pem.ec" "${to_location}" "ec" + fi + fi + # end of copying certs. + umask "$ORIG_UMASK" +} + check_challenge_completion() { # checks with the ACME server if our challenge is OK uri=$1 domain=$2 @@ -1410,6 +1484,7 @@ help_message() { # print out the help message -c, --create Create default config files -f, --force Force renewal of cert (overrides expiry checks) -h, --help Display this help message and exit + -i, --install Install certificates and reload service -q, --quiet Quiet mode (only outputs on error, success of new cert, or getssl was upgraded) -Q, --mute Like -q, but also mute notification about successful upgrade -r, --revoke "cert" "key" [CA_server] Revoke a certificate (the cert and key are required) @@ -2146,6 +2221,8 @@ while [[ -n ${1+defined} ]]; do _UPGRADE=1 ;; -U | --nocheck) _UPGRADE_CHECK=0 ;; + -i | --install) + _CERT_INSTALL=1 ;; -w) shift; WORKING_DIR="$1" ;; -*) @@ -2369,6 +2446,14 @@ check_config # check what dns utils are installed find_dns_utils +# if -i|--install install certs, reload and exit +if [ "0${_CERT_INSTALL}" -eq 1 ] +then + cert_install + reload_service + graceful_exit +fi + if [[ -e "$DOMAIN_DIR/FORCE_RENEWAL" ]]; then rm -f "$DOMAIN_DIR/FORCE_RENEWAL" || error_exit "problem deleting file $DOMAIN_DIR/FORCE_RENEWAL" _FORCE_RENEW=1 @@ -2647,76 +2732,8 @@ cert_archive debug "Certificates obtained and archived locally, will now copy to specified locations" # copy certs to the correct location (creating concatenated files as required) -umask 077 - -copy_file_to_location "domain certificate" "$CERT_FILE" "$DOMAIN_CERT_LOCATION" -copy_file_to_location "private key" "$DOMAIN_DIR/${DOMAIN}.key" "$DOMAIN_KEY_LOCATION" -copy_file_to_location "CA certificate" "$CA_CERT" "$CA_CERT_LOCATION" -if [[ "$DUAL_RSA_ECDSA" == "true" ]]; then - if [[ -n "$DOMAIN_CERT_LOCATION" ]]; then - copy_file_to_location "ec domain certificate" \ - "${CERT_FILE%.*}.ec.crt" \ - "${DOMAIN_CERT_LOCATION}" \ - "ec" - fi - if [[ -n "$DOMAIN_KEY_LOCATION" ]]; then - copy_file_to_location "ec private key" \ - "$DOMAIN_DIR/${DOMAIN}.ec.key" \ - "${DOMAIN_KEY_LOCATION}" \ - "ec" - fi - if [[ -n "$CA_CERT_LOCATION" ]]; then - copy_file_to_location "ec CA certificate" \ - "${CA_CERT%.*}.ec.crt" \ - "${CA_CERT_LOCATION%.*}.crt" \ - "ec" - fi -fi +cert_install -# if DOMAIN_CHAIN_LOCATION is not blank, then create and copy file. -if [[ -n "$DOMAIN_CHAIN_LOCATION" ]]; then - if [[ "$(dirname "$DOMAIN_CHAIN_LOCATION")" == "." ]]; then - to_location="${DOMAIN_DIR}/${DOMAIN_CHAIN_LOCATION}" - else - to_location="${DOMAIN_CHAIN_LOCATION}" - fi - cat "$CERT_FILE" "$CA_CERT" > "$TEMP_DIR/${DOMAIN}_chain.pem" - copy_file_to_location "full chain" "$TEMP_DIR/${DOMAIN}_chain.pem" "$to_location" - if [[ "$DUAL_RSA_ECDSA" == "true" ]]; then - cat "${CERT_FILE%.*}.ec.crt" "${CA_CERT%.*}.ec.crt" > "$TEMP_DIR/${DOMAIN}_chain.pem.ec" - copy_file_to_location "full chain" "$TEMP_DIR/${DOMAIN}_chain.pem.ec" "${to_location}" "ec" - fi -fi -# if DOMAIN_KEY_CERT_LOCATION is not blank, then create and copy file. -if [[ -n "$DOMAIN_KEY_CERT_LOCATION" ]]; then - if [[ "$(dirname "$DOMAIN_KEY_CERT_LOCATION")" == "." ]]; then - to_location="${DOMAIN_DIR}/${DOMAIN_KEY_CERT_LOCATION}" - else - to_location="${DOMAIN_KEY_CERT_LOCATION}" - fi - cat "$DOMAIN_DIR/${DOMAIN}.key" "$CERT_FILE" > "$TEMP_DIR/${DOMAIN}_K_C.pem" - copy_file_to_location "private key and domain cert pem" "$TEMP_DIR/${DOMAIN}_K_C.pem" "$to_location" - if [[ "$DUAL_RSA_ECDSA" == "true" ]]; then - cat "$DOMAIN_DIR/${DOMAIN}.ec.key" "${CERT_FILE%.*}.ec.crt" > "$TEMP_DIR/${DOMAIN}_K_C.pem.ec" - copy_file_to_location "private ec key and domain cert pem" "$TEMP_DIR/${DOMAIN}_K_C.pem.ec" "${to_location}" "ec" - fi -fi -# if DOMAIN_PEM_LOCATION is not blank, then create and copy file. -if [[ -n "$DOMAIN_PEM_LOCATION" ]]; then - if [[ "$(dirname "$DOMAIN_PEM_LOCATION")" == "." ]]; then - to_location="${DOMAIN_DIR}/${DOMAIN_PEM_LOCATION}" - else - to_location="${DOMAIN_PEM_LOCATION}" - fi - cat "$DOMAIN_DIR/${DOMAIN}.key" "$CERT_FILE" "$CA_CERT" > "$TEMP_DIR/${DOMAIN}.pem" - copy_file_to_location "full key, cert and chain pem" "$TEMP_DIR/${DOMAIN}.pem" "$to_location" - if [[ "$DUAL_RSA_ECDSA" == "true" ]]; then - cat "$DOMAIN_DIR/${DOMAIN}.ec.key" "${CERT_FILE%.*}.ec.crt" "${CA_CERT%.*}.ec.crt" > "$TEMP_DIR/${DOMAIN}.pem.ec" - copy_file_to_location "full ec key, cert and chain pem" "$TEMP_DIR/${DOMAIN}.pem.ec" "${to_location}" "ec" - fi -fi -# end of copying certs. -umask "$ORIG_UMASK" # Run reload command to restart apache / nginx or whatever system reload_service From 17b13facdad9ed3cd74ee6da84dd3fb93501c009 Mon Sep 17 00:00:00 2001 From: Juan Javier Baca Date: Thu, 16 Apr 2020 04:45:08 +0200 Subject: [PATCH 088/110] Update revision history and version number --- getssl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/getssl b/getssl index 8251911..b278193 100755 --- a/getssl +++ b/getssl @@ -221,11 +221,11 @@ # 2020-03-30 Fix error message find_dns_utils from over version of "command" # 2020-03-30 Fix problems if domain name isn't in lowercase (2.22) # 2020-04-16 Add alternative working dirs '/etc/getssl/' '${SCRIPTDIR}/conf' '${SCRIPTDIR}/.getssl' -# 2020-04-16 Add -i|--install command line option +# 2020-04-16 Add -i|--install command line option (2.23) # ---------------------------------------------------------------------------------------- PROGNAME=${0##*/} -VERSION="2.22" +VERSION="2.23" # defaults ACCOUNT_KEY_LENGTH=4096 From 7e575fb030bf29a544c4650400667f2be0df384e Mon Sep 17 00:00:00 2001 From: Juan Javier Baca Moreno-Torres Date: Sat, 18 Apr 2020 01:46:02 +0200 Subject: [PATCH 089/110] Update shell-linter to 0.3.0 --- .github/workflows/shellcheck.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/shellcheck.yml b/.github/workflows/shellcheck.yml index d5adbf5..37b9cad 100644 --- a/.github/workflows/shellcheck.yml +++ b/.github/workflows/shellcheck.yml @@ -12,6 +12,6 @@ jobs: steps: - uses: actions/checkout@v1 - name: Lint check - uses: azohra/shell-linter@v0.2.0 + uses: azohra/shell-linter@v0.3.0 with: path: "getssl" From adcb9752065880efc03da23ea03efa4acb176304 Mon Sep 17 00:00:00 2001 From: Juan Javier Baca Moreno-Torres Date: Sat, 18 Apr 2020 02:10:09 +0200 Subject: [PATCH 090/110] Fix shellcheck warning and failures Clean code, avoid indirect variables and single quotes for delayed expansion. --- getssl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/getssl b/getssl index b278193..bcaa37f 100755 --- a/getssl +++ b/getssl @@ -220,11 +220,12 @@ # 2020-03-23 Fix staging server URL in domain template (2.21) # 2020-03-30 Fix error message find_dns_utils from over version of "command" # 2020-03-30 Fix problems if domain name isn't in lowercase (2.22) -# 2020-04-16 Add alternative working dirs '/etc/getssl/' '${SCRIPTDIR}/conf' '${SCRIPTDIR}/.getssl' +# 2020-04-16 Add alternative working dirs '/etc/getssl/' '${PROGDIR}/conf' '${PROGDIR}/.getssl' # 2020-04-16 Add -i|--install command line option (2.23) # ---------------------------------------------------------------------------------------- PROGNAME=${0##*/} +PROGDIR="$(cd "$(dirname "$0")" || exit; pwd -P;)" VERSION="2.23" # defaults @@ -263,7 +264,7 @@ TEMP_UPGRADE_FILE="" TOKEN_USER_ID="" USE_SINGLE_ACL="false" VALIDATE_VIA_DNS="" -WORKING_DIR_CANDIDATES=('/etc/getssl/' '${SCRIPTDIR}/conf' '${SCRIPTDIR}/.getssl' '~/.getssl') +WORKING_DIR_CANDIDATES=("/etc/getssl/" "${PROGDIR}/conf" "${PROGDIR}/.getssl" "${HOME}/.getssl") _CHECK_ALL=0 _CREATE_CONFIG=0 _FORCE_RENEW=0 @@ -2298,7 +2299,6 @@ fi # Test working directory candidates if unset. Last candidate defaults (~/getssl/) if [[ -z "${WORKING_DIR}" ]] then - SCRIPTDIR="$(cd "$(dirname "$0")"; pwd -P;)" for WDCC in $(seq 0 $((${#WORKING_DIR_CANDIDATES[@]}-1)) ) do WORKING_DIR="$(eval echo "${WORKING_DIR_CANDIDATES[$WDCC]}")" From 008a95dba778b2947c408444d93fb121a74621d3 Mon Sep 17 00:00:00 2001 From: Tim Kimber Date: Sat, 18 Apr 2020 14:36:33 +0100 Subject: [PATCH 091/110] Make ubuntu-staging depend on centos7-staging so they don't run at same time causing spurious errors --- .github/workflows/run-all-tests.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/run-all-tests.yml b/.github/workflows/run-all-tests.yml index ff0e121..08fb41b 100644 --- a/.github/workflows/run-all-tests.yml +++ b/.github/workflows/run-all-tests.yml @@ -72,6 +72,7 @@ jobs: - name: Run test suite on Ubuntu18 run: test/run-test.sh ubuntu18 test-ubuntu-staging: + needs: test-centos7-staging runs-on: ubuntu-latest steps: - uses: actions/checkout@v1 From 3341f674d422bdbc1ce7b5012278ce62b58596c3 Mon Sep 17 00:00:00 2001 From: Tim Kimber Date: Sat, 18 Apr 2020 14:36:59 +0100 Subject: [PATCH 092/110] Add tests for /etc/getssl and --install --- test/11-test--install.bats | 69 ++++++++++++++++++++++++ test/test-config/getssl-etc-template.cfg | 45 ++++++++++++++++ 2 files changed, 114 insertions(+) create mode 100644 test/11-test--install.bats create mode 100644 test/test-config/getssl-etc-template.cfg diff --git a/test/11-test--install.bats b/test/11-test--install.bats new file mode 100644 index 0000000..0b5bbc7 --- /dev/null +++ b/test/11-test--install.bats @@ -0,0 +1,69 @@ +#! /usr/bin/env bats + +load '/bats-support/load.bash' +load '/bats-assert/load.bash' +load '/getssl/test/test_helper.bash' + + +# This is run for every test +setup() { + export CURL_CA_BUNDLE=/root/pebble-ca-bundle.crt +} + +@test "Check that config files in /etc/getssl works" { + if [ -n "$STAGING" ]; then + skip "Using staging server, skipping internal test" + fi + + CONFIG_FILE="getssl-http01.cfg" + setup_environment + + # Create /etc/getssl/$DOMAIN + rm -rf /etc/getssl + mkdir -p /etc/getssl/${GETSSL_CMD_HOST} + + # Copy the config file to /etc/getssl + cp "${CODE_DIR}/test/test-config/${CONFIG_FILE}" "/etc/getssl/${GETSSL_CMD_HOST}/getssl.cfg" + cp "${CODE_DIR}/test/test-config/getssl-etc-template.cfg" "/etc/getssl/getssl.cfg" + + # Run getssl + run ${CODE_DIR}/getssl "$GETSSL_CMD_HOST" + + assert_success + refute_output --regexp '[Ff][Aa][Ii][Ll][Ee][Dd]' + refute_output --regexp '[Ee][Rr][Rr][Oo][Rr]' + refute_output --regexp '[Ww][Aa][Rr][Nn][Ii][Nn][Gg]' + assert_line 'Verification completed, obtaining certificate.' + assert_line 'Requesting certificate' + refute [ -d '$HOME/.getssl' ] +} + + +@test "Check that --install doesn't call the ACME server" { + if [ -n "$STAGING" ]; then + skip "Using staging server, skipping internal test" + fi + + CONFIG_FILE="getssl-http01.cfg" + #setup_environment + + # Create /etc/getssl/$DOMAIN + #mkdir -p /etc/getssl/${GETSSL_CMD_HOST} + + # Copy the config file to /etc/getssl + #cp "${CODE_DIR}/test/test-config/${CONFIG_FILE}" "/etc/getssl/${GETSSL_CMD_HOST}/getssl.cfg" + #cp "${CODE_DIR}/test/test-config/getssl-etc-template.cfg" "/etc/getssl/getssl.cfg" + + # Run getssl + run ${CODE_DIR}/getssl --install "$GETSSL_CMD_HOST" + + assert_success + refute_output --regexp '[Ff][Aa][Ii][Ll][Ee][Dd]' + refute_output --regexp '[Ee][Rr][Rr][Oo][Rr]' + refute_output --regexp '[Ww][Aa][Rr][Nn][Ii][Nn][Gg]' + refute_line 'Verification completed, obtaining certificate.' + refute_line 'Requesting certificate' + assert_line --partial 'copying domain certificate to' + assert_line --partial 'copying private key to' + assert_line --partial 'copying CA certificate to' +} diff --git a/test/test-config/getssl-etc-template.cfg b/test/test-config/getssl-etc-template.cfg new file mode 100644 index 0000000..6bfc8fd --- /dev/null +++ b/test/test-config/getssl-etc-template.cfg @@ -0,0 +1,45 @@ +# vim: filetype=sh +# +# This file is read first and is common to all domains +# +# Uncomment and modify any variables you need +# see https://github.com/srvrco/getssl/wiki/Config-variables for details +# +# The staging server is best for testing (hence set as default) +CA="https://acme-staging-v02.api.letsencrypt.org" +# This server issues full certificates, however has rate limits +#CA="https://acme-v02.api.letsencrypt.org" + +# The agreement that must be signed with the CA, if not defined the default agreement will be used +#AGREEMENT="" + +# Set an email address associated with your account - generally set at account level rather than domain. +#ACCOUNT_EMAIL="me@example.com" +ACCOUNT_KEY_LENGTH=4096 +ACCOUNT_KEY="/etc/getssl/account.key" + +# Account key and private key types - can be rsa, prime256v1, secp384r1 or secp521r1 +#ACCOUNT_KEY_TYPE="rsa" +PRIVATE_KEY_ALG="rsa" +#REUSE_PRIVATE_KEY="true" + +# The command needed to reload apache / nginx or whatever you use +#RELOAD_CMD="" + +# The time period within which you want to allow renewal of a certificate +# this prevents hitting some of the rate limits. +# Creating a file called FORCE_RENEWAL in the domain directory allows one-off overrides +# of this setting +RENEW_ALLOW="30" + +# Define the server type. This can be https, ftp, ftpi, imap, imaps, pop3, pop3s, smtp, +# smtps_deprecated, smtps, smtp_submission, xmpp, xmpps, ldaps or a port number which +# will be checked for certificate expiry and also will be checked after +# an update to confirm correct certificate is running (if CHECK_REMOTE) is set to true +SERVER_TYPE="https" +CHECK_REMOTE="true" + +# Use the following 3 variables if you want to validate via DNS +#VALIDATE_VIA_DNS="true" +#DNS_ADD_COMMAND= +#DNS_DEL_COMMAND= From 857ad87b4f21ca7a8f2c78fe727f620c9ad8c7f1 Mon Sep 17 00:00:00 2001 From: Tim Kimber Date: Sat, 18 Apr 2020 14:37:33 +0100 Subject: [PATCH 093/110] Change bats-assert and bats-support to use bats-core repo --- test/Dockerfile-alpine | 4 ++-- test/Dockerfile-centos6 | 4 ++-- test/Dockerfile-centos7 | 4 ++-- test/Dockerfile-centos7-staging | 4 ++-- test/Dockerfile-debian | 4 ++-- test/Dockerfile-ubuntu | 4 ++-- test/Dockerfile-ubuntu-staging | 4 ++-- test/Dockerfile-ubuntu16 | 4 ++-- test/Dockerfile-ubuntu18 | 4 ++-- 9 files changed, 18 insertions(+), 18 deletions(-) diff --git a/test/Dockerfile-alpine b/test/Dockerfile-alpine index 0c166cb..caad22a 100644 --- a/test/Dockerfile-alpine +++ b/test/Dockerfile-alpine @@ -13,8 +13,8 @@ RUN mkdir /etc/nginx/pki/private # BATS (Bash Automated Testings) RUN git clone https://github.com/bats-core/bats-core.git /bats-core -RUN git clone https://github.com/jasonkarns/bats-support /bats-support -RUN git clone https://github.com/jasonkarns/bats-assert-1 /bats-assert +RUN git clone https://github.com/bats-core/bats-support /bats-support +RUN git clone https://github.com/bats-core/bats-assert /bats-assert RUN /bats-core/install.sh /usr/local # Use supervisord to run nginx in the background diff --git a/test/Dockerfile-centos6 b/test/Dockerfile-centos6 index 9149dad..61c8b6b 100644 --- a/test/Dockerfile-centos6 +++ b/test/Dockerfile-centos6 @@ -14,8 +14,8 @@ COPY ./test/test-config/nginx-ubuntu-no-ssl /etc/nginx/conf.d/default.conf # BATS (Bash Automated Testings) RUN git clone https://github.com/bats-core/bats-core.git /bats-core -RUN git clone https://github.com/jasonkarns/bats-support /bats-support -RUN git clone https://github.com/jasonkarns/bats-assert-1 /bats-assert +RUN git clone https://github.com/bats-core/bats-support /bats-support +RUN git clone https://github.com/bats-core/bats-assert /bats-assert RUN /bats-core/install.sh /usr/local EXPOSE 80 443 diff --git a/test/Dockerfile-centos7 b/test/Dockerfile-centos7 index 8a34bc5..02fbcb7 100644 --- a/test/Dockerfile-centos7 +++ b/test/Dockerfile-centos7 @@ -15,6 +15,6 @@ COPY ./test/test-config/nginx-centos7.conf /etc/nginx/nginx.conf # BATS (Bash Automated Testings) RUN git clone https://github.com/bats-core/bats-core.git /bats-core -RUN git clone https://github.com/jasonkarns/bats-support /bats-support -RUN git clone https://github.com/jasonkarns/bats-assert-1 /bats-assert +RUN git clone https://github.com/bats-core/bats-support /bats-support +RUN git clone https://github.com/bats-core/bats-assert /bats-assert RUN /bats-core/install.sh /usr/local diff --git a/test/Dockerfile-centos7-staging b/test/Dockerfile-centos7-staging index 839ff76..899bf9b 100644 --- a/test/Dockerfile-centos7-staging +++ b/test/Dockerfile-centos7-staging @@ -18,8 +18,8 @@ COPY ./test/test-config/nginx-centos7.conf /etc/nginx/nginx.conf # BATS (Bash Automated Testings) RUN git clone https://github.com/bats-core/bats-core.git /bats-core -RUN git clone https://github.com/jasonkarns/bats-support /bats-support -RUN git clone https://github.com/jasonkarns/bats-assert-1 /bats-assert +RUN git clone https://github.com/bats-core/bats-support /bats-support +RUN git clone https://github.com/bats-core/bats-assert /bats-assert RUN /bats-core/install.sh /usr/local EXPOSE 80 443 diff --git a/test/Dockerfile-debian b/test/Dockerfile-debian index 95ebbac..b5da5dd 100644 --- a/test/Dockerfile-debian +++ b/test/Dockerfile-debian @@ -12,8 +12,8 @@ RUN mkdir /etc/nginx/pki/private # BATS (Bash Automated Testings) RUN git clone https://github.com/bats-core/bats-core.git /bats-core -RUN git clone https://github.com/jasonkarns/bats-support /bats-support -RUN git clone https://github.com/jasonkarns/bats-assert-1 /bats-assert +RUN git clone https://github.com/bats-core/bats-support /bats-support +RUN git clone https://github.com/bats-core/bats-assert /bats-assert RUN /bats-core/install.sh /usr/local # Run eternal loop - for testing diff --git a/test/Dockerfile-ubuntu b/test/Dockerfile-ubuntu index 290100d..720f0b0 100644 --- a/test/Dockerfile-ubuntu +++ b/test/Dockerfile-ubuntu @@ -15,8 +15,8 @@ RUN touch /root/.rnd # BATS (Bash Automated Testings) RUN git clone https://github.com/bats-core/bats-core.git /bats-core -RUN git clone https://github.com/jasonkarns/bats-support /bats-support -RUN git clone https://github.com/jasonkarns/bats-assert-1 /bats-assert +RUN git clone https://github.com/bats-core/bats-support /bats-support +RUN git clone https://github.com/bats-core/bats-assert /bats-assert RUN /bats-core/install.sh /usr/local # Run eternal loop - for testing diff --git a/test/Dockerfile-ubuntu-staging b/test/Dockerfile-ubuntu-staging index 0bdc1f8..84022ca 100644 --- a/test/Dockerfile-ubuntu-staging +++ b/test/Dockerfile-ubuntu-staging @@ -17,8 +17,8 @@ RUN touch /root/.rnd # BATS (Bash Automated Testings) RUN git clone https://github.com/bats-core/bats-core.git /bats-core -RUN git clone https://github.com/jasonkarns/bats-support /bats-support -RUN git clone https://github.com/jasonkarns/bats-assert-1 /bats-assert +RUN git clone https://github.com/bats-core/bats-assert /bats-assert +RUN git clone https://github.com/bats-core/bats-assert /bats-assert RUN /bats-core/install.sh /usr/local # Run eternal loop - for testing diff --git a/test/Dockerfile-ubuntu16 b/test/Dockerfile-ubuntu16 index 958bb6f..030d03a 100644 --- a/test/Dockerfile-ubuntu16 +++ b/test/Dockerfile-ubuntu16 @@ -17,8 +17,8 @@ COPY ./test/test-config/nginx-ubuntu-no-ssl /etc/nginx/sites-enabled/default # BATS (Bash Automated Testings) RUN git clone https://github.com/bats-core/bats-core.git /bats-core -RUN git clone https://github.com/jasonkarns/bats-support /bats-support -RUN git clone https://github.com/jasonkarns/bats-assert-1 /bats-assert +RUN git clone https://github.com/bats-core/bats-support /bats-support +RUN git clone https://github.com/bats-core/bats-assert /bats-assert RUN /bats-core/install.sh /usr/local # Run eternal loop - for testing diff --git a/test/Dockerfile-ubuntu18 b/test/Dockerfile-ubuntu18 index ebe7607..1d68cd3 100644 --- a/test/Dockerfile-ubuntu18 +++ b/test/Dockerfile-ubuntu18 @@ -17,8 +17,8 @@ RUN touch /root/.rnd # BATS (Bash Automated Testings) RUN git clone https://github.com/bats-core/bats-core.git /bats-core -RUN git clone https://github.com/jasonkarns/bats-support /bats-support -RUN git clone https://github.com/jasonkarns/bats-assert-1 /bats-assert +RUN git clone https://github.com/bats-core/bats-support /bats-support +RUN git clone https://github.com/bats-core/bats-assert /bats-assert RUN /bats-core/install.sh /usr/local EXPOSE 80 443 From ac5ef3301107a6e42ebfc105dac405d8bfaf7a52 Mon Sep 17 00:00:00 2001 From: Tim Kimber Date: Sat, 18 Apr 2020 15:12:40 +0100 Subject: [PATCH 094/110] Cleanup after test succeeds --- test/11-test--install.bats | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/test/11-test--install.bats b/test/11-test--install.bats index 0b5bbc7..6949d25 100644 --- a/test/11-test--install.bats +++ b/test/11-test--install.bats @@ -18,8 +18,10 @@ setup() { CONFIG_FILE="getssl-http01.cfg" setup_environment + # Fail if not running in docker and /etc/getssl already exists + refute [ -d /etc/getssl ] + # Create /etc/getssl/$DOMAIN - rm -rf /etc/getssl mkdir -p /etc/getssl/${GETSSL_CMD_HOST} # Copy the config file to /etc/getssl @@ -40,19 +42,12 @@ setup() { @test "Check that --install doesn't call the ACME server" { + # NOTE that this test depends on the previous test! if [ -n "$STAGING" ]; then skip "Using staging server, skipping internal test" fi CONFIG_FILE="getssl-http01.cfg" - #setup_environment - - # Create /etc/getssl/$DOMAIN - #mkdir -p /etc/getssl/${GETSSL_CMD_HOST} - - # Copy the config file to /etc/getssl - #cp "${CODE_DIR}/test/test-config/${CONFIG_FILE}" "/etc/getssl/${GETSSL_CMD_HOST}/getssl.cfg" - #cp "${CODE_DIR}/test/test-config/getssl-etc-template.cfg" "/etc/getssl/getssl.cfg" # Run getssl run ${CODE_DIR}/getssl --install "$GETSSL_CMD_HOST" @@ -66,4 +61,7 @@ setup() { assert_line --partial 'copying domain certificate to' assert_line --partial 'copying private key to' assert_line --partial 'copying CA certificate to' + + # Cleanup previous test + rm -rf /etc/getssl } From 43b82e4fc39f024d05a30abe36df62a9c10a430d Mon Sep 17 00:00:00 2001 From: Tim Kimber Date: Sat, 18 Apr 2020 15:31:48 +0100 Subject: [PATCH 095/110] Fix cut&paste error --- test/Dockerfile-ubuntu-staging | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Dockerfile-ubuntu-staging b/test/Dockerfile-ubuntu-staging index 84022ca..552f096 100644 --- a/test/Dockerfile-ubuntu-staging +++ b/test/Dockerfile-ubuntu-staging @@ -17,7 +17,7 @@ RUN touch /root/.rnd # BATS (Bash Automated Testings) RUN git clone https://github.com/bats-core/bats-core.git /bats-core -RUN git clone https://github.com/bats-core/bats-assert /bats-assert +RUN git clone https://github.com/bats-core/bats-support /bats-support RUN git clone https://github.com/bats-core/bats-assert /bats-assert RUN /bats-core/install.sh /usr/local From d8bf2fa14936912071b6069705daf0d02066aabc Mon Sep 17 00:00:00 2001 From: Tim Kimber Date: Sun, 19 Apr 2020 14:13:29 +0100 Subject: [PATCH 096/110] Remove dependency on seq, ensure clean_up doesn't try to delete /tmp --- getssl | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/getssl b/getssl index bcaa37f..0fd4431 100755 --- a/getssl +++ b/getssl @@ -222,11 +222,12 @@ # 2020-03-30 Fix problems if domain name isn't in lowercase (2.22) # 2020-04-16 Add alternative working dirs '/etc/getssl/' '${PROGDIR}/conf' '${PROGDIR}/.getssl' # 2020-04-16 Add -i|--install command line option (2.23) +# 2020-04-19 Remove dependency on seq, ensure clean_up doesn't try to delete /tmp (2.24) # ---------------------------------------------------------------------------------------- PROGNAME=${0##*/} PROGDIR="$(cd "$(dirname "$0")" || exit; pwd -P;)" -VERSION="2.23" +VERSION="2.24" # defaults ACCOUNT_KEY_LENGTH=4096 @@ -633,7 +634,11 @@ clean_up() { # Perform pre-exit housekeeping shopt -u nullglob fi if [[ -n "$DOMAIN_DIR" ]]; then - rm -rf "${TEMP_DIR:?}" + if [ "${TEMP_DIR}" -ef "/tmp" ]; then + info "Not going to delete TEMP_DIR ${TEMP_DIR} as it appears to be /tmp" + else + rm -rf "${TEMP_DIR:?}" + fi fi if [[ -n "$TEMP_UPGRADE_FILE" ]] && [[ -f "$TEMP_UPGRADE_FILE" ]]; then rm -f "$TEMP_UPGRADE_FILE" @@ -2299,10 +2304,8 @@ fi # Test working directory candidates if unset. Last candidate defaults (~/getssl/) if [[ -z "${WORKING_DIR}" ]] then - for WDCC in $(seq 0 $((${#WORKING_DIR_CANDIDATES[@]}-1)) ) + for WORKING_DIR in "${WORKING_DIR_CANDIDATES[@]}" do - WORKING_DIR="$(eval echo "${WORKING_DIR_CANDIDATES[$WDCC]}")" - debug "Testing working dir location '${WORKING_DIR}'" if [[ -s "$WORKING_DIR/getssl.cfg" ]] then From 462573c8ba17d82730ac69de9b201b377eb7f96d Mon Sep 17 00:00:00 2001 From: Tim Kimber Date: Sun, 19 Apr 2020 14:13:55 +0100 Subject: [PATCH 097/110] Test if DOMAIN_STORAGE is "/" clean_up doesn't delete /tmp --- test/11-test-no-domain-storage.bats | 19 ++++++++++++ .../getssl-http01-no-domain-storage.cfg | 31 +++++++++++++++++++ 2 files changed, 50 insertions(+) create mode 100644 test/11-test-no-domain-storage.bats create mode 100644 test/test-config/getssl-http01-no-domain-storage.cfg diff --git a/test/11-test-no-domain-storage.bats b/test/11-test-no-domain-storage.bats new file mode 100644 index 0000000..cefac3f --- /dev/null +++ b/test/11-test-no-domain-storage.bats @@ -0,0 +1,19 @@ +#! /usr/bin/env bats + +load '/bats-support/load.bash' +load '/bats-assert/load.bash' +load '/getssl/test/test_helper.bash' + + +@test "Check that if domain storage isn't set getssl doesn't try to delete /tmp" { + if [ -n "$STAGING" ]; then + skip "Using staging server, skipping internal test" + fi + CONFIG_FILE="getssl-http01-no-domain-storage.cfg" + setup_environment + mkdir ${INSTALL_DIR}/.getssl + cp "${CODE_DIR}/test/test-config/${CONFIG_FILE}" "${INSTALL_DIR}/.getssl/getssl.cfg" + run ${CODE_DIR}/getssl -a + assert_success + assert_line 'Not going to delete TEMP_DIR ///tmp as it appears to be /tmp' +} diff --git a/test/test-config/getssl-http01-no-domain-storage.cfg b/test/test-config/getssl-http01-no-domain-storage.cfg new file mode 100644 index 0000000..efa5318 --- /dev/null +++ b/test/test-config/getssl-http01-no-domain-storage.cfg @@ -0,0 +1,31 @@ +# Uncomment and modify any variables you need +# see https://github.com/srvrco/getssl/wiki/Config-variables for details +# see https://github.com/srvrco/getssl/wiki/Example-config-files for example configs +# +CA="https://pebble:14000/dir" + +# Additional domains - this could be multiple domains / subdomains in a comma separated list +SANS="" + +# Acme Challenge Location. +ACL=('/var/www/html/.well-known/acme-challenge') + +#Set USE_SINGLE_ACL="true" to use a single ACL for all checks +USE_SINGLE_ACL="false" + +# Location for all your certs, these can either be on the server (full path name) +# or using ssh /sftp as for the ACL +DOMAIN_CERT_LOCATION="/etc/nginx/pki/server.crt" +DOMAIN_KEY_LOCATION="/etc/nginx/pki/private/server.key" +CA_CERT_LOCATION="/etc/nginx/pki/chain.crt" +DOMAIN_CHAIN_LOCATION="" # this is the domain cert and CA cert +DOMAIN_PEM_LOCATION="" # this is the domain_key, domain cert and CA cert + +# The command needed to reload apache / nginx or whatever you use +RELOAD_CMD="cp /getssl/test/test-config/nginx-ubuntu-ssl ${NGINX_CONFIG} && /getssl/test/restart-nginx" + +# Define the server type and confirm correct certificate is installed +SERVER_TYPE="https" +CHECK_REMOTE="true" + +DOMAIN_STORAGE="/" From f7c196602d2067e5da7171b6f9be2bef01456f39 Mon Sep 17 00:00:00 2001 From: Tim Kimber Date: Mon, 20 Apr 2020 21:26:04 +0100 Subject: [PATCH 098/110] Update revision history and version --- getssl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/getssl b/getssl index 2c0c794..35cfbe2 100755 --- a/getssl +++ b/getssl @@ -223,11 +223,12 @@ # 2020-04-16 Add alternative working dirs '/etc/getssl/' '${PROGDIR}/conf' '${PROGDIR}/.getssl' # 2020-04-16 Add -i|--install command line option (2.23) # 2020-04-19 Remove dependency on seq, ensure clean_up doesn't try to delete /tmp (2.24) +# 2020-04-20 Check for domain using all DNS utilities (2.25) # ---------------------------------------------------------------------------------------- PROGNAME=${0##*/} PROGDIR="$(cd "$(dirname "$0")" || exit; pwd -P;)" -VERSION="2.24" +VERSION="2.25" # defaults ACCOUNT_KEY_LENGTH=4096 From a0c6ddf0797334e302405a0ef55deaa3a82ac64a Mon Sep 17 00:00:00 2001 From: Robert Wolf Date: Wed, 22 Apr 2020 09:12:38 +0200 Subject: [PATCH 099/110] fix HAS_HOST and HAS_NSLOOKUP condition --- getssl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/getssl b/getssl index 35cfbe2..a6fc1dc 100755 --- a/getssl +++ b/getssl @@ -532,14 +532,14 @@ check_config() { # check the config files for all obvious errors fi fi - if [[ -n "$HAS_HOST" ]]; then + if [[ "$HAS_HOST" == true ]]; then debug "DNS lookup using host ${d}" if [[ "$(host "${d}" |grep -c -i "^${d}")" -ge 1 ]]; then found_ip=true fi fi - if [[ -n "$HAS_NSLOOKUP" ]]; then + if [[ "$HAS_NSLOOKUP" == true ]]; then debug "DNS lookup using nslookup -query AAAA ${d}" if [[ "$(nslookup -query=AAAA "${d}"|grep -c -i "^${d}.*has AAAA address")" -ge 1 ]]; then debug "found IPv6 record for ${d}" From 23d4d6ae65574a83e9cc9369260c80d4c556045f Mon Sep 17 00:00:00 2001 From: Tim Kimber Date: Wed, 22 Apr 2020 17:55:25 +0100 Subject: [PATCH 100/110] Move test for failed,warning,error to test_helper, add test for "not found" --- test/1-simple-http01.bats | 8 +--- test/10-mixed-case-staging.bats | 4 +- test/10-mixed-case.bats | 8 +--- test/11-test--install.bats | 8 +--- test/11-test-no-domain-storage.bats | 1 + test/2-simple-dns01-dig.bats | 8 +--- test/2-simple-dns01-nslookup.bats | 5 +-- test/3-dual-rsa-ecdsa.bats | 4 ++ test/4-more-than-10-hosts.bats | 8 +--- test/5-secp384-http01.bats | 4 ++ test/6-dual-rsa-ecdsa-copy-2-locations.bats | 1 + test/7-staging-dns01-dig.bats | 8 +--- test/7-staging-dns01-nslookup.bats | 8 +--- test/8-staging-ecdsa.bats | 16 ++------ test/9-multiple-domains-dns01.bats | 12 ++---- test/9-test--all.bats | 4 +- test/test_helper.bash | 41 +++++++++++++-------- 17 files changed, 60 insertions(+), 88 deletions(-) diff --git a/test/1-simple-http01.bats b/test/1-simple-http01.bats index fd96a8a..6b37f86 100644 --- a/test/1-simple-http01.bats +++ b/test/1-simple-http01.bats @@ -20,9 +20,7 @@ setup() { init_getssl create_certificate assert_success - refute_output --regexp '[Ff][Aa][Ii][Ll][Ee][Dd]' - refute_output --regexp '[Ee][Rr][Rr][Oo][Rr]' - refute_output --regexp '[Ww][Aa][Rr][Nn][Ii][Nn][Gg]' + check_output_for_errors } @@ -32,8 +30,6 @@ setup() { fi run ${CODE_DIR}/getssl -f $GETSSL_HOST assert_success - refute_output --regexp '[Ff][Aa][Ii][Ll][Ee][Dd]' - refute_output --regexp '[Ee][Rr][Rr][Oo][Rr]' - refute_output --regexp '[Ww][Aa][Rr][Nn][Ii][Nn][Gg]' + check_output_for_errors cleanup_environment } diff --git a/test/10-mixed-case-staging.bats b/test/10-mixed-case-staging.bats index c1bac0d..ea622f7 100644 --- a/test/10-mixed-case-staging.bats +++ b/test/10-mixed-case-staging.bats @@ -18,7 +18,5 @@ load '/getssl/test/test_helper.bash' create_certificate assert_success - refute_output --regexp '[Ff][Aa][Ii][Ll][Ee][Dd]' - refute_output --regexp '[Ee][Rr][Rr][Oo][Rr]' - refute_output --regexp '[Ww][Aa][Rr][Nn][Ii][Nn][Gg]' + check_output_for_errors } diff --git a/test/10-mixed-case.bats b/test/10-mixed-case.bats index 2a4d6f3..b1d8f07 100644 --- a/test/10-mixed-case.bats +++ b/test/10-mixed-case.bats @@ -23,9 +23,7 @@ setup() { create_certificate assert_success - refute_output --regexp '[Ff][Aa][Ii][Ll][Ee][Dd]' - refute_output --regexp '[Ee][Rr][Rr][Oo][Rr]' - refute_output --regexp '[Ww][Aa][Rr][Nn][Ii][Nn][Gg]' + check_output_for_errors } @test "Check that DNS-01 verification works if the domain is not lowercase" { @@ -39,7 +37,5 @@ setup() { init_getssl create_certificate assert_success - refute_output --regexp '[Ff][Aa][Ii][Ll][Ee][Dd]' - refute_output --regexp '[Ee][Rr][Rr][Oo][Rr]' - refute_output --regexp '[Ww][Aa][Rr][Nn][Ii][Nn][Gg]' + check_output_for_errors } diff --git a/test/11-test--install.bats b/test/11-test--install.bats index 6949d25..e034326 100644 --- a/test/11-test--install.bats +++ b/test/11-test--install.bats @@ -32,9 +32,7 @@ setup() { run ${CODE_DIR}/getssl "$GETSSL_CMD_HOST" assert_success - refute_output --regexp '[Ff][Aa][Ii][Ll][Ee][Dd]' - refute_output --regexp '[Ee][Rr][Rr][Oo][Rr]' - refute_output --regexp '[Ww][Aa][Rr][Nn][Ii][Nn][Gg]' + check_output_for_errors assert_line 'Verification completed, obtaining certificate.' assert_line 'Requesting certificate' refute [ -d '$HOME/.getssl' ] @@ -53,9 +51,7 @@ setup() { run ${CODE_DIR}/getssl --install "$GETSSL_CMD_HOST" assert_success - refute_output --regexp '[Ff][Aa][Ii][Ll][Ee][Dd]' - refute_output --regexp '[Ee][Rr][Rr][Oo][Rr]' - refute_output --regexp '[Ww][Aa][Rr][Nn][Ii][Nn][Gg]' + check_output_for_errors refute_line 'Verification completed, obtaining certificate.' refute_line 'Requesting certificate' assert_line --partial 'copying domain certificate to' diff --git a/test/11-test-no-domain-storage.bats b/test/11-test-no-domain-storage.bats index cefac3f..3be0be5 100644 --- a/test/11-test-no-domain-storage.bats +++ b/test/11-test-no-domain-storage.bats @@ -15,5 +15,6 @@ load '/getssl/test/test_helper.bash' cp "${CODE_DIR}/test/test-config/${CONFIG_FILE}" "${INSTALL_DIR}/.getssl/getssl.cfg" run ${CODE_DIR}/getssl -a assert_success + check_output_for_errors assert_line 'Not going to delete TEMP_DIR ///tmp as it appears to be /tmp' } diff --git a/test/2-simple-dns01-dig.bats b/test/2-simple-dns01-dig.bats index cbac598..6803f15 100644 --- a/test/2-simple-dns01-dig.bats +++ b/test/2-simple-dns01-dig.bats @@ -22,9 +22,7 @@ setup() { create_certificate -d assert_success assert_output --partial "dig" - refute_output --regexp '[Ff][Aa][Ii][Ll][Ee][Dd]' - refute_output --regexp '[^:][Ee][Rr][Rr][Oo][Rr][^:]' # don't fail for :error:badNonce - refute_output --regexp '[Ww][Aa][Rr][Nn][Ii][Nn][Gg]' + check_output_for_errors "debug" } @@ -35,8 +33,6 @@ setup() { run ${CODE_DIR}/getssl -d -f $GETSSL_HOST assert_success assert_output --partial "dig" - refute_output --regexp '[Ff][Aa][Ii][Ll][Ee][Dd]' - refute_output --regexp '[^:][Ee][Rr][Rr][Oo][Rr][^:]' # don't fail for :error:badNonce - refute_output --regexp '[Ww][Aa][Rr][Nn][Ii][Nn][Gg]' + check_output_for_errors "debug" cleanup_environment } diff --git a/test/2-simple-dns01-nslookup.bats b/test/2-simple-dns01-nslookup.bats index 482be2a..7e675a8 100644 --- a/test/2-simple-dns01-nslookup.bats +++ b/test/2-simple-dns01-nslookup.bats @@ -32,8 +32,5 @@ teardown() { create_certificate -d assert_success assert_output --partial "nslookup" - refute_output --regexp '[Ff][Aa][Ii][Ll][Ee][Dd]' - refute_output --regexp '[^:][Ee][Rr][Rr][Oo][Rr][^:]' # don't fail for :error:badNonce - # don't check for "Warnings:" as there might be a warning message if nslookup doesn't support -debug (alpine/ubuntu) - refute_output --regexp '[Ww][Aa][Rr][Nn][Ii][Nn][Gg][^:]' + check_output_for_errors "debug" } diff --git a/test/3-dual-rsa-ecdsa.bats b/test/3-dual-rsa-ecdsa.bats index 7820a96..d5c58f7 100644 --- a/test/3-dual-rsa-ecdsa.bats +++ b/test/3-dual-rsa-ecdsa.bats @@ -20,6 +20,7 @@ setup() { init_getssl create_certificate assert_success + check_output_for_errors } @@ -29,6 +30,7 @@ setup() { fi run ${CODE_DIR}/getssl -f $GETSSL_HOST assert_success + check_output_for_errors } @test "Create dual certificates using DNS-01 verification" { @@ -40,6 +42,7 @@ setup() { init_getssl create_certificate assert_success + check_output_for_errors } @@ -49,5 +52,6 @@ setup() { fi run ${CODE_DIR}/getssl -f $GETSSL_HOST assert_success + check_output_for_errors cleanup_environment } diff --git a/test/4-more-than-10-hosts.bats b/test/4-more-than-10-hosts.bats index bd93adc..f4eb95c 100644 --- a/test/4-more-than-10-hosts.bats +++ b/test/4-more-than-10-hosts.bats @@ -26,9 +26,7 @@ setup() { init_getssl create_certificate assert_success - refute_output --regexp '[Ff][Aa][Ii][Ll][Ee][Dd]' - refute_output --regexp '[Ee][Rr][Rr][Oo][Rr]' - refute_output --regexp '[Ww][Aa][Rr][Nn][Ii][Nn][Gg]' + check_output_for_errors } @@ -38,9 +36,7 @@ setup() { fi run ${CODE_DIR}/getssl -f $GETSSL_HOST assert_success - refute_output --regexp '[Ff][Aa][Ii][Ll][Ee][Dd]' - refute_output --regexp '[Ee][Rr][Rr][Oo][Rr]' - refute_output --regexp '[Ww][Aa][Rr][Nn][Ii][Nn][Gg]' + check_output_for_errors # Remove all the dns aliases cleanup_environment for prefix in a b c d e f g h i j k; do diff --git a/test/5-secp384-http01.bats b/test/5-secp384-http01.bats index 29da2da..3d05159 100644 --- a/test/5-secp384-http01.bats +++ b/test/5-secp384-http01.bats @@ -20,6 +20,7 @@ setup() { init_getssl create_certificate assert_success + check_output_for_errors } @@ -29,6 +30,7 @@ setup() { fi run ${CODE_DIR}/getssl -f $GETSSL_HOST assert_success + check_output_for_errors } @@ -41,6 +43,7 @@ setup() { init_getssl create_certificate assert_success + check_output_for_errors } @@ -50,4 +53,5 @@ setup() { fi run ${CODE_DIR}/getssl -f $GETSSL_HOST assert_success + check_output_for_errors } diff --git a/test/6-dual-rsa-ecdsa-copy-2-locations.bats b/test/6-dual-rsa-ecdsa-copy-2-locations.bats index 73363ec..394e8d6 100644 --- a/test/6-dual-rsa-ecdsa-copy-2-locations.bats +++ b/test/6-dual-rsa-ecdsa-copy-2-locations.bats @@ -32,6 +32,7 @@ teardown() { init_getssl create_certificate assert_success + check_output_for_errors # Check that the RSA chain and key have been copied to both locations assert [ -e "/etc/nginx/pki/domain-chain.crt" ] diff --git a/test/7-staging-dns01-dig.bats b/test/7-staging-dns01-dig.bats index 8c0d7f1..7e21124 100644 --- a/test/7-staging-dns01-dig.bats +++ b/test/7-staging-dns01-dig.bats @@ -16,9 +16,7 @@ load '/getssl/test/test_helper.bash' init_getssl create_certificate assert_success - refute_output --regexp '[Ff][Aa][Ii][Ll][Ee][Dd]' - refute_output --regexp '[Ee][Rr][Rr][Oo][Rr]' - refute_output --regexp '[Ww][Aa][Rr][Nn][Ii][Nn][Gg]' + check_output_for_errors } @test "Force renewal of certificate using staging server, dig and DuckDNS" { @@ -27,8 +25,6 @@ load '/getssl/test/test_helper.bash' fi run ${CODE_DIR}/getssl -f $GETSSL_HOST assert_success - refute_output --regexp '[Ff][Aa][Ii][Ll][Ee][Dd]' - refute_output --regexp '[Ee][Rr][Rr][Oo][Rr]' - refute_output --regexp '[Ww][Aa][Rr][Nn][Ii][Nn][Gg]' + check_output_for_errors cleanup_environment } diff --git a/test/7-staging-dns01-nslookup.bats b/test/7-staging-dns01-nslookup.bats index 027a210..bd8d9da 100644 --- a/test/7-staging-dns01-nslookup.bats +++ b/test/7-staging-dns01-nslookup.bats @@ -30,9 +30,7 @@ teardown() { init_getssl create_certificate assert_success - refute_output --regexp '[Ff][Aa][Ii][Ll][Ee][Dd]' - refute_output --regexp '[Ee][Rr][Rr][Oo][Rr]' - refute_output --regexp '[Ww][Aa][Rr][Nn][Ii][Nn][Gg][^:]' # ignore nslookup warnings + check_output_for_errors "debug" } @@ -42,8 +40,6 @@ teardown() { fi run ${CODE_DIR}/getssl -f $GETSSL_HOST assert_success - refute_output --regexp '[Ff][Aa][Ii][Ll][Ee][Dd]' - refute_output --regexp '[Ee][Rr][Rr][Oo][Rr]' - refute_output --regexp '[Ww][Aa][Rr][Nn][Ii][Nn][Gg][^:]' # ignore nslookup warnings + check_output_for_errors "debug" cleanup_environment } diff --git a/test/8-staging-ecdsa.bats b/test/8-staging-ecdsa.bats index 92c694a..127e989 100644 --- a/test/8-staging-ecdsa.bats +++ b/test/8-staging-ecdsa.bats @@ -18,9 +18,7 @@ load '/getssl/test/test_helper.bash' sed -e 's/rsa/prime256v1/g' < "${CODE_DIR}/test/test-config/${CONFIG_FILE}" > "${INSTALL_DIR}/.getssl/${GETSSL_HOST}/getssl.cfg" run ${CODE_DIR}/getssl -d "$GETSSL_HOST" assert_success - refute_output --regexp '[Ff][Aa][Ii][Ll][Ee][Dd]' - refute_output --regexp '[^:][Ee][Rr][Rr][Oo][Rr][^:]' - refute_output --regexp '[Ww][Aa][Rr][Nn][Ii][Nn][Gg][^:]' # ignore nslookup warnings + check_output_for_errors "debug" } @@ -30,9 +28,7 @@ load '/getssl/test/test_helper.bash' fi run ${CODE_DIR}/getssl -d -f $GETSSL_HOST assert_success - refute_output --regexp '[Ff][Aa][Ii][Ll][Ee][Dd]' - refute_output --regexp '[^:][Ee][Rr][Rr][Oo][Rr][^:]' - refute_output --regexp '[Ww][Aa][Rr][Nn][Ii][Nn][Gg][^:]' # ignore nslookup warnings + check_output_for_errors "debug" cleanup_environment } @@ -48,9 +44,7 @@ load '/getssl/test/test_helper.bash' sed -e 's/rsa/secp384r1/g' < "${CODE_DIR}/test/test-config/${CONFIG_FILE}" > "${INSTALL_DIR}/.getssl/${GETSSL_HOST}/getssl.cfg" run ${CODE_DIR}/getssl -d "$GETSSL_HOST" assert_success - refute_output --regexp '[Ff][Aa][Ii][Ll][Ee][Dd]' - refute_output --regexp '[^:][Ee][Rr][Rr][Oo][Rr][^:]' - refute_output --regexp '[Ww][Aa][Rr][Nn][Ii][Nn][Gg][^:]' + check_output_for_errors "debug" } @@ -60,9 +54,7 @@ load '/getssl/test/test_helper.bash' fi run ${CODE_DIR}/getssl -d -f $GETSSL_HOST assert_success - refute_output --regexp '[Ff][Aa][Ii][Ll][Ee][Dd]' - refute_output --regexp '[^:][Ee][Rr][Rr][Oo][Rr][^:]' - refute_output --regexp '[Ww][Aa][Rr][Nn][Ii][Nn][Gg][^:]' + check_output_for_errors "debug" cleanup_environment } diff --git a/test/9-multiple-domains-dns01.bats b/test/9-multiple-domains-dns01.bats index 2a9344f..c1de91c 100644 --- a/test/9-multiple-domains-dns01.bats +++ b/test/9-multiple-domains-dns01.bats @@ -25,9 +25,7 @@ setup() { init_getssl create_certificate assert_success - refute_output --regexp '[Ff][Aa][Ii][Ll][Ee][Dd]' - refute_output --regexp '[Ee][Rr][Rr][Oo][Rr]' - refute_output --regexp '[Ww][Aa][Rr][Nn][Ii][Nn][Gg]' + check_output_for_errors } @@ -38,9 +36,7 @@ setup() { fi run ${CODE_DIR}/getssl -f $GETSSL_HOST assert_success - refute_output --regexp '[Ff][Aa][Ii][Ll][Ee][Dd]' - refute_output --regexp '[Ee][Rr][Rr][Oo][Rr]' - refute_output --regexp '[Ww][Aa][Rr][Nn][Ii][Nn][Gg]' + check_output_for_errors # Remove all the dns aliases cleanup_environment curl --silent -X POST -d '{"host":"getssl.tst"}' http://10.30.50.3:8055/clear-a @@ -60,7 +56,5 @@ setup() { init_getssl create_certificate assert_success - refute_output --regexp '[Ff][Aa][Ii][Ll][Ee][Dd]' - refute_output --regexp '[Ee][Rr][Rr][Oo][Rr]' - refute_output --regexp '[Ww][Aa][Rr][Nn][Ii][Nn][Gg]' + check_output_for_errors } diff --git a/test/9-test--all.bats b/test/9-test--all.bats index 94a2c28..cb043a5 100644 --- a/test/9-test--all.bats +++ b/test/9-test--all.bats @@ -28,7 +28,5 @@ setup() { # Check success conditions assert_success - refute_output --regexp '[Ff][Aa][Ii][Ll][Ee][Dd]' - refute_output --regexp '[Ee][Rr][Rr][Oo][Rr]' - refute_output --regexp '[Ww][Aa][Rr][Nn][Ii][Nn][Gg]' + check_output_for_errors } diff --git a/test/test_helper.bash b/test/test_helper.bash index f4c62af..4af19dd 100644 --- a/test/test_helper.bash +++ b/test/test_helper.bash @@ -1,23 +1,31 @@ INSTALL_DIR=/root CODE_DIR=/getssl - -setup_environment() { - # One-off test setup - if [[ -d ${INSTALL_DIR}/.getssl ]]; then - rm -r ${INSTALL_DIR}/.getssl +check_output_for_errors() { + refute_output --regexp '[Ff][Aa][Ii][Ll][Ee][Dd]' + # less strict tests if running with debug output + if [ -n "$1" ]; then + # don't fail for :error:badNonce + refute_output --regexp '[^:][Ee][Rr][Rr][Oo][Rr][^:]' + # don't check for "Warnings:" as there might be a warning message if nslookup doesn't support -debug (alpine/ubuntu) + refute_output --regexp '[Ww][Aa][Rr][Nn][Ii][Nn][Gg][^:]' + else + refute_output --regexp '[Ee][Rr][Rr][Oo][Rr]' + refute_output --regexp '[Ww][Aa][Rr][Nn][Ii][Nn][Gg]' fi - - curl --silent -X POST -d '{"host":"'"$GETSSL_HOST"'", "addresses":["'"$GETSSL_IP"'"]}' http://10.30.50.3:8055/add-a - cp ${CODE_DIR}/test/test-config/nginx-ubuntu-no-ssl "${NGINX_CONFIG}" - /getssl/test/restart-nginx + refute_output --partial 'not found' } - cleanup_environment() { curl --silent -X POST -d '{"host":"'"$GETSSL_HOST"'"}' http://10.30.50.3:8055/clear-a } +create_certificate() { + # Create certificate + cp "${CODE_DIR}/test/test-config/${CONFIG_FILE}" "${INSTALL_DIR}/.getssl/${GETSSL_CMD_HOST}/getssl.cfg" + # shellcheck disable=SC2086 + run ${CODE_DIR}/getssl $1 "$GETSSL_CMD_HOST" +} init_getssl() { # Run initialisation (create account key, etc) @@ -26,12 +34,15 @@ init_getssl() { [ -d "$INSTALL_DIR/.getssl" ] } +setup_environment() { + # One-off test setup + if [[ -d ${INSTALL_DIR}/.getssl ]]; then + rm -r ${INSTALL_DIR}/.getssl + fi -create_certificate() { - # Create certificate - cp "${CODE_DIR}/test/test-config/${CONFIG_FILE}" "${INSTALL_DIR}/.getssl/${GETSSL_CMD_HOST}/getssl.cfg" - # shellcheck disable=SC2086 - run ${CODE_DIR}/getssl $1 "$GETSSL_CMD_HOST" + curl --silent -X POST -d '{"host":"'"$GETSSL_HOST"'", "addresses":["'"$GETSSL_IP"'"]}' http://10.30.50.3:8055/add-a + cp ${CODE_DIR}/test/test-config/nginx-ubuntu-no-ssl "${NGINX_CONFIG}" + /getssl/test/restart-nginx } # start nginx in background on alpine via supervisord From 065356bcc8670df944d79855046b13d8532caaa9 Mon Sep 17 00:00:00 2001 From: Tim Kimber Date: Wed, 22 Apr 2020 20:25:05 +0100 Subject: [PATCH 101/110] Tweak the 'not found' check --- test/test_helper.bash | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_helper.bash b/test/test_helper.bash index 4af19dd..0aafa93 100644 --- a/test/test_helper.bash +++ b/test/test_helper.bash @@ -13,7 +13,7 @@ check_output_for_errors() { refute_output --regexp '[Ee][Rr][Rr][Oo][Rr]' refute_output --regexp '[Ww][Aa][Rr][Nn][Ii][Nn][Gg]' fi - refute_output --partial 'not found' + refute_line --partial 'command not found' } cleanup_environment() { From 135fcece9253466f297bda627ace79042d8464d9 Mon Sep 17 00:00:00 2001 From: Tim Kimber Date: Wed, 22 Apr 2020 20:29:31 +0100 Subject: [PATCH 102/110] Fix domain case conversion for different locales --- getssl | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/getssl b/getssl index a6fc1dc..f877ed5 100755 --- a/getssl +++ b/getssl @@ -224,11 +224,13 @@ # 2020-04-16 Add -i|--install command line option (2.23) # 2020-04-19 Remove dependency on seq, ensure clean_up doesn't try to delete /tmp (2.24) # 2020-04-20 Check for domain using all DNS utilities (2.25) +# 2020-04-22 Fix HAS_HOST and HAS_NSLOOKUP checks - wolfaba +# 2020-04-22 Fix domain case conversion for different locales (2.26) - glynge # ---------------------------------------------------------------------------------------- PROGNAME=${0##*/} PROGDIR="$(cd "$(dirname "$0")" || exit; pwd -P;)" -VERSION="2.25" +VERSION="2.26" # defaults ACCOUNT_KEY_LENGTH=4096 @@ -532,14 +534,14 @@ check_config() { # check the config files for all obvious errors fi fi - if [[ "$HAS_HOST" == true ]]; then + if [[ "$HAS_HOST" == "true" ]]; then debug "DNS lookup using host ${d}" if [[ "$(host "${d}" |grep -c -i "^${d}")" -ge 1 ]]; then found_ip=true fi fi - if [[ "$HAS_NSLOOKUP" == true ]]; then + if [[ "$HAS_NSLOOKUP" == "true" ]]; then debug "DNS lookup using nslookup -query AAAA ${d}" if [[ "$(nslookup -query=AAAA "${d}"|grep -c -i "^${d}.*has AAAA address")" -ge 1 ]]; then debug "found IPv6 record for ${d}" @@ -863,8 +865,7 @@ create_order() { dn=0 for d in $alldomains; do # Convert domain to lowercase as response from server will be in lowercase - # shellcheck disable=SC2018,SC2019 - d=$(echo "$d" | tr A-Z a-z) + d=$(echo "$d" | tr "[:upper:]" "[:lower:]") if [ "$d" == "$authdomain" ]; then debug "Saving authorization response for $authdomain for domain alldomains[$dn]" AuthLinkResponse[$dn]=$response @@ -1260,7 +1261,7 @@ get_auth_dns() { # get the authoritative dns server for a domain (sets primary_n fi fi - if [[ "$HAS_HOST" == true ]]; then + if [[ "$HAS_HOST" == "true" ]]; then gad_d="$orig_gad_d" debug Using "host -t NS" to find primary name server for "$gad_d" if [[ -z "$gad_s" ]]; then @@ -1279,7 +1280,7 @@ get_auth_dns() { # get the authoritative dns server for a domain (sets primary_n fi fi - if [[ "$HAS_NSLOOKUP" == true ]]; then + if [[ "$HAS_NSLOOKUP" == "true" ]]; then gad_d="$orig_gad_d" debug Using "nslookup -debug -type=soa -type=ns $gad_d $gad_s" to find primary name server res=$(nslookup -debug -type=soa -type=ns "$gad_d" ${gad_s}) From 472ea2312fdbe6766c8b7953d42a7c0ceb70a979 Mon Sep 17 00:00:00 2001 From: Tim Kimber Date: Sat, 25 Apr 2020 12:37:24 +0100 Subject: [PATCH 103/110] Set noninteractive so tests don't hang --- test/Dockerfile-ubuntu | 3 +++ test/Dockerfile-ubuntu-staging | 5 +++++ 2 files changed, 8 insertions(+) diff --git a/test/Dockerfile-ubuntu b/test/Dockerfile-ubuntu index 720f0b0..66d7a35 100644 --- a/test/Dockerfile-ubuntu +++ b/test/Dockerfile-ubuntu @@ -2,6 +2,9 @@ FROM ubuntu:latest # Note this image uses mawk1.3 +# Set noninteractive otherwise tzdata hangs +ENV DEBIAN_FRONTEND noninteractive + # Update and install required software RUN apt-get update --fix-missing RUN apt-get install -y git curl dnsutils wget nginx-light diff --git a/test/Dockerfile-ubuntu-staging b/test/Dockerfile-ubuntu-staging index 552f096..58762d0 100644 --- a/test/Dockerfile-ubuntu-staging +++ b/test/Dockerfile-ubuntu-staging @@ -2,8 +2,13 @@ FROM ubuntu:latest # Note this image uses mawk1.3 +# Set noninteractive otherwise tzdata hangs +ENV DEBIAN_FRONTEND noninteractive + +# Ensure tests in this image use the staging server ENV staging "true" ENV DUCKDNS_TOKEN 1d616aa9-b8e4-4bb4-b312-3289de82badb + # Update and install required software RUN apt-get update --fix-missing RUN apt-get install -y git curl dnsutils wget nginx-light From 62e7cfce0fc6b28ad62675d0b916832c41b0a866 Mon Sep 17 00:00:00 2001 From: rhinoduck Date: Sun, 26 Apr 2020 09:57:41 +0000 Subject: [PATCH 104/110] Fix changelog formatting --- getssl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/getssl b/getssl index f877ed5..54c95b1 100755 --- a/getssl +++ b/getssl @@ -225,7 +225,7 @@ # 2020-04-19 Remove dependency on seq, ensure clean_up doesn't try to delete /tmp (2.24) # 2020-04-20 Check for domain using all DNS utilities (2.25) # 2020-04-22 Fix HAS_HOST and HAS_NSLOOKUP checks - wolfaba -# 2020-04-22 Fix domain case conversion for different locales (2.26) - glynge +# 2020-04-22 Fix domain case conversion for different locales - glynge (2.26) # ---------------------------------------------------------------------------------------- PROGNAME=${0##*/} From 7ec1e8648c2561263b2781cc8902cc0b67866f05 Mon Sep 17 00:00:00 2001 From: Harald Heigl Date: Sun, 26 Apr 2020 15:01:14 +0200 Subject: [PATCH 105/110] Fixed ipv4 confirmation with nslookup --- getssl | 1 + 1 file changed, 1 insertion(+) diff --git a/getssl b/getssl index 54c95b1..86943ee 100755 --- a/getssl +++ b/getssl @@ -548,6 +548,7 @@ check_config() { # check the config files for all obvious errors found_ip=true elif [[ "$(nslookup "${d}"| grep -c ^Name)" -ge 1 ]]; then debug "found IPv4 record for ${d}" + found_ip=true fi fi From 61fe4647ad2379b2eef6befc1fb4172253e2144c Mon Sep 17 00:00:00 2001 From: Tim Kimber Date: Sun, 26 Apr 2020 20:21:45 +0100 Subject: [PATCH 106/110] Add tests for #553 --- test/1-simple-http01-dig.bats | 40 ++++++++++++++++++++++++++++++ test/1-simple-http01-nslookup.bats | 40 ++++++++++++++++++++++++++++++ test/1-simple-http01.bats | 4 +-- test/2-simple-dns01-dig.bats | 17 ++++++++++++- test/2-simple-dns01-nslookup.bats | 6 +++++ 5 files changed, 104 insertions(+), 3 deletions(-) create mode 100644 test/1-simple-http01-dig.bats create mode 100644 test/1-simple-http01-nslookup.bats diff --git a/test/1-simple-http01-dig.bats b/test/1-simple-http01-dig.bats new file mode 100644 index 0000000..be01f27 --- /dev/null +++ b/test/1-simple-http01-dig.bats @@ -0,0 +1,40 @@ +#! /usr/bin/env bats + +load '/bats-support/load.bash' +load '/bats-assert/load.bash' +load '/getssl/test/test_helper.bash' + + +# This is run for every test +setup() { + export CURL_CA_BUNDLE=/root/pebble-ca-bundle.crt + if [ -f /usr/bin/host ]; then + mv /usr/bin/host /usr/bin/host.getssl.bak + fi + if [ -f /usr/bin/nslookup ]; then + mv /usr/bin/nslookup /usr/bin/nslookup.getssl.bak + fi +} + + +teardown() { + if [ -f /usr/bin/host.getssl.bak ]; then + mv /usr/bin/host.getssl.bak /usr/bin/host + fi + if [ -f /usr/bin/nslookup.getssl.bak ]; then + mv /usr/bin/nslookup.getssl.bak /usr/bin/nslookup + fi +} + + +@test "Create new certificate using HTTP-01 verification (dig)" { + if [ -n "$STAGING" ]; then + skip "Using staging server, skipping internal test" + fi + CONFIG_FILE="getssl-http01.cfg" + setup_environment + init_getssl + create_certificate + assert_success + check_output_for_errors +} diff --git a/test/1-simple-http01-nslookup.bats b/test/1-simple-http01-nslookup.bats new file mode 100644 index 0000000..78d175a --- /dev/null +++ b/test/1-simple-http01-nslookup.bats @@ -0,0 +1,40 @@ +#! /usr/bin/env bats + +load '/bats-support/load.bash' +load '/bats-assert/load.bash' +load '/getssl/test/test_helper.bash' + + +# This is run for every test +setup() { + export CURL_CA_BUNDLE=/root/pebble-ca-bundle.crt + if [ -f /usr/bin/dig ]; then + mv /usr/bin/dig /usr/bin/dig.getssl.bak + fi + if [ -f /usr/bin/host ]; then + mv /usr/bin/host /usr/bin/host.getssl.bak + fi +} + + +teardown() { + if [ -f /usr/bin/dig.getssl.bak ]; then + mv /usr/bin/dig.getssl.bak /usr/bin/dig + fi + if [ -f /usr/bin/host.getssl.bak ]; then + mv /usr/bin/host.getssl.bak /usr/bin/host + fi +} + + +@test "Create new certificate using HTTP-01 verification (nslookup)" { + if [ -n "$STAGING" ]; then + skip "Using staging server, skipping internal test" + fi + CONFIG_FILE="getssl-http01.cfg" + setup_environment + init_getssl + create_certificate + assert_success + check_output_for_errors +} diff --git a/test/1-simple-http01.bats b/test/1-simple-http01.bats index 6b37f86..a57010b 100644 --- a/test/1-simple-http01.bats +++ b/test/1-simple-http01.bats @@ -11,7 +11,7 @@ setup() { } -@test "Create new certificate using HTTP-01 verification" { +@test "Create new certificate using HTTP-01 verification (any dns tool)" { if [ -n "$STAGING" ]; then skip "Using staging server, skipping internal test" fi @@ -24,7 +24,7 @@ setup() { } -@test "Force renewal of certificate using HTTP-01" { +@test "Force renewal of certificate using HTTP-01 (any dns tool)" { if [ -n "$STAGING" ]; then skip "Using staging server, skipping internal test" fi diff --git a/test/2-simple-dns01-dig.bats b/test/2-simple-dns01-dig.bats index 6803f15..0a54684 100644 --- a/test/2-simple-dns01-dig.bats +++ b/test/2-simple-dns01-dig.bats @@ -5,9 +5,24 @@ load '/bats-assert/load.bash' load '/getssl/test/test_helper.bash' -# This is run for every test setup() { export CURL_CA_BUNDLE=/root/pebble-ca-bundle.crt + if [ -f /usr/bin/host ]; then + mv /usr/bin/host /usr/bin/host.getssl.bak + fi + if [ -f /usr/bin/nslookup ]; then + mv /usr/bin/nslookup /usr/bin/nslookup.getssl.bak + fi +} + + +teardown() { + if [ -f /usr/bin/host.getssl.bak ]; then + mv /usr/bin/host.getssl.bak /usr/bin/host + fi + if [ -f /usr/bin/nslookup.getssl.bak ]; then + mv /usr/bin/nslookup.getssl.bak /usr/bin/nslookup + fi } diff --git a/test/2-simple-dns01-nslookup.bats b/test/2-simple-dns01-nslookup.bats index 7e675a8..dc6f2f5 100644 --- a/test/2-simple-dns01-nslookup.bats +++ b/test/2-simple-dns01-nslookup.bats @@ -11,6 +11,9 @@ setup() { if [ -f /usr/bin/dig ]; then mv /usr/bin/dig /usr/bin/dig.getssl.bak fi + if [ -f /usr/bin/host ]; then + mv /usr/bin/host /usr/bin/host.getssl.bak + fi } @@ -18,6 +21,9 @@ teardown() { if [ -f /usr/bin/dig.getssl.bak ]; then mv /usr/bin/dig.getssl.bak /usr/bin/dig fi + if [ -f /usr/bin/host.getssl.bak ]; then + mv /usr/bin/host.getssl.bak /usr/bin/host + fi } From 39ccc6bb0e9790a0264cd181644ed59da4ae3c42 Mon Sep 17 00:00:00 2001 From: Tim Kimber Date: Wed, 29 Apr 2020 19:53:37 +0100 Subject: [PATCH 107/110] Fix ftp/sftp problems if challenge starts with a dash --- getssl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/getssl b/getssl index 86943ee..109d96d 100755 --- a/getssl +++ b/getssl @@ -700,7 +700,7 @@ copy_file_to_location() { # copies a file, using scp, sftp or ftp if required. user $ftpuser $ftppass cd $ftpdirn lcd $fromdir - put $fromfile + put ./$fromfile _EOF elif [[ "${to:0:5}" == "sftp:" ]] ; then debug "using sftp to copy the file from $from" @@ -718,7 +718,7 @@ copy_file_to_location() { # copies a file, using scp, sftp or ftp if required. sshpass -p "$ftppass" sftp $SFTP_OPTS "$ftpuser@$ftphost" <<- _EOF cd $ftpdirn lcd $fromdir - put $fromfile + put ./$fromfile _EOF elif [[ "${to:0:5}" == "davs:" ]] ; then debug "using davs to copy the file from $from" From b5a9a4280ee6d758668877bfc576ac085a46c8af Mon Sep 17 00:00:00 2001 From: Tim Kimber Date: Wed, 29 Apr 2020 20:56:40 +0100 Subject: [PATCH 108/110] Update revision history --- getssl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/getssl b/getssl index 109d96d..5c044d4 100755 --- a/getssl +++ b/getssl @@ -226,6 +226,8 @@ # 2020-04-20 Check for domain using all DNS utilities (2.25) # 2020-04-22 Fix HAS_HOST and HAS_NSLOOKUP checks - wolfaba # 2020-04-22 Fix domain case conversion for different locales - glynge (2.26) +# 2020-04-26 Fixed ipv4 confirmation with nslookup - Cyber1000 +# 2020-04-29 Fix ftp/sftp problems if challenge starts with a dash # ---------------------------------------------------------------------------------------- PROGNAME=${0##*/} From 50a9865e716ec433f878836d6fca30c80cbae0c8 Mon Sep 17 00:00:00 2001 From: Scott Gustafson Date: Sat, 2 May 2020 23:39:50 -0600 Subject: [PATCH 109/110] Fix route53 script for python 3. --- dns_scripts/dns_route53.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dns_scripts/dns_route53.py b/dns_scripts/dns_route53.py index a972dfa..f2011bf 100755 --- a/dns_scripts/dns_route53.py +++ b/dns_scripts/dns_route53.py @@ -31,7 +31,7 @@ for zone in response['HostedZones']: if not zone['Config']['PrivateZone']: zone_list[zone['Name']] = zone['Id'] -for key in sorted(zone_list.iterkeys(), key=len, reverse=True): +for key in sorted(zone_list.keys(), key=len, reverse=True): if ".{z}".format(z=key) in ".{z}.".format(z=fqdn): zone_id = zone_list[key] From 919aed2cdb26b148cf1daeeb7d0a4871b4cb1a09 Mon Sep 17 00:00:00 2001 From: Tim Kimber Date: Wed, 6 May 2020 16:04:11 +0100 Subject: [PATCH 110/110] Fix missing fullchain.ec.crt when creating dual certificates --- getssl | 18 ++++++++++-------- test/3-dual-rsa-ecdsa.bats | 8 ++++++++ test/test_helper.bash | 7 +++++++ 3 files changed, 25 insertions(+), 8 deletions(-) diff --git a/getssl b/getssl index 5c044d4..a92f09b 100755 --- a/getssl +++ b/getssl @@ -1342,6 +1342,7 @@ get_certificate() { # get certificate for csr, if all domains validated. gc_csr=$1 # the csr file gc_certfile=$2 # The filename for the certificate gc_cafile=$3 # The filename for the CA certificate + gc_fullchain=$4 # The filename for the fullchain der=$(openssl req -in "$gc_csr" -outform DER | urlbase64) if [[ $API -eq 1 ]]; then @@ -1388,9 +1389,9 @@ get_certificate() { # get certificate for csr, if all domains validated. done info "Requesting certificate" CertData=$(json_get "$response" "certificate") - send_signed_request "$CertData" "" "" "$FULL_CHAIN" - info "Full certificate saved in $FULL_CHAIN" - awk -v CERT_FILE="$gc_certfile" -v CA_CERT="$gc_cafile" 'BEGIN {outfile=CERT_FILE} split_after==1 {outfile=CA_CERT;split_after=0} /-----END CERTIFICATE-----/ {split_after=1} {print > outfile}' "$FULL_CHAIN" + send_signed_request "$CertData" "" "" "$gc_fullchain" + info "Full certificate saved in $gc_fullchain" + awk -v CERT_FILE="$gc_certfile" -v CA_CERT="$gc_cafile" 'BEGIN {outfile=CERT_FILE} split_after==1 {outfile=CA_CERT;split_after=0} /-----END CERTIFICATE-----/ {split_after=1} {print > outfile}' "$gc_fullchain" info "Certificate saved in $gc_certfile" fi } @@ -1892,7 +1893,7 @@ send_signed_request() { # Sends a request to the ACME server, signed with your p # get nonce from ACME server if [[ $API -eq 1 ]]; then nonceurl="$CA/directory" - nonce=$($CURL -I $nonceurl | grep "^Replay-Nonce:" | awk '{print $2}' | tr -d '\r\n ') + nonce=$($CURL -I "$nonceurl" | grep "^Replay-Nonce:" | awk '{print $2}' | tr -d '\r\n ') else # APIv2 nonce=$($CURL -I "$URL_newNonce" | grep "^Replay-Nonce:" | awk '{print $2}' | tr -d '\r\n ') fi @@ -2458,8 +2459,7 @@ find_dns_utils check_config # if -i|--install install certs, reload and exit -if [ "0${_CERT_INSTALL}" -eq 1 ] -then +if [ "0${_CERT_INSTALL}" -eq 1 ]; then cert_install reload_service graceful_exit @@ -2724,7 +2724,8 @@ info "Verification completed, obtaining certificate." #obtain the certificate. get_certificate "$DOMAIN_DIR/${DOMAIN}.csr" \ "$CERT_FILE" \ - "$CA_CERT" + "$CA_CERT" \ + "$FULL_CHAIN" if [[ "$DUAL_RSA_ECDSA" == "true" ]]; then info "Creating order for EC certificate" if [[ $API -eq 2 ]]; then @@ -2734,7 +2735,8 @@ if [[ "$DUAL_RSA_ECDSA" == "true" ]]; then info "obtaining EC certificate." get_certificate "$DOMAIN_DIR/${DOMAIN}.ec.csr" \ "${CERT_FILE%.*}.ec.crt" \ - "${CA_CERT%.*}.ec.crt" + "${CA_CERT%.*}.ec.crt" \ + "${FULL_CHAIN%.*}.ec.crt" fi # create Archive of new certs and keys. diff --git a/test/3-dual-rsa-ecdsa.bats b/test/3-dual-rsa-ecdsa.bats index d5c58f7..486fa13 100644 --- a/test/3-dual-rsa-ecdsa.bats +++ b/test/3-dual-rsa-ecdsa.bats @@ -21,6 +21,10 @@ setup() { create_certificate assert_success check_output_for_errors + check_certificates + assert [ -e "${INSTALL_DIR}/.getssl/${GETSSL_CMD_HOST}/chain.ec.crt" ] + assert [ -e "${INSTALL_DIR}/.getssl/${GETSSL_CMD_HOST}/fullchain.ec.crt" ] + assert [ -e "${INSTALL_DIR}/.getssl/${GETSSL_CMD_HOST}/${GETSSL_CMD_HOST}.ec.crt" ] } @@ -43,6 +47,10 @@ setup() { create_certificate assert_success check_output_for_errors + check_certificates + assert [ -e "${INSTALL_DIR}/.getssl/${GETSSL_CMD_HOST}/chain.ec.crt" ] + assert [ -e "${INSTALL_DIR}/.getssl/${GETSSL_CMD_HOST}/fullchain.ec.crt" ] + assert [ -e "${INSTALL_DIR}/.getssl/${GETSSL_CMD_HOST}/${GETSSL_CMD_HOST}.ec.crt" ] } diff --git a/test/test_helper.bash b/test/test_helper.bash index 0aafa93..1e65189 100644 --- a/test/test_helper.bash +++ b/test/test_helper.bash @@ -1,6 +1,13 @@ INSTALL_DIR=/root CODE_DIR=/getssl +check_certificates() +{ + assert [ -e "${INSTALL_DIR}/.getssl/${GETSSL_CMD_HOST}/chain.crt" ] + assert [ -e "${INSTALL_DIR}/.getssl/${GETSSL_CMD_HOST}/fullchain.crt" ] + assert [ -e "${INSTALL_DIR}/.getssl/${GETSSL_CMD_HOST}/${GETSSL_CMD_HOST}.crt" ] +} + check_output_for_errors() { refute_output --regexp '[Ff][Aa][Ii][Ll][Ee][Dd]' # less strict tests if running with debug output