From f2a4f6bfa8b573cb280ff8312cc4f4fa4aa02066 Mon Sep 17 00:00:00 2001 From: Timothe Litt Date: Mon, 15 May 2017 13:57:22 -0400 Subject: [PATCH 01/43] Initial version of GoDaddy DNS validation support. Uses the GoDaddy "Developer" API to add and remove the ACME challenge records. --- .gitattributes | 3 + .gitignore | 9 + dns_scripts/00GoDaddy-README.txt | 68 ++++++ dns_scripts/dns_add_godaddy | 39 +++ dns_scripts/dns_del_godaddy | 37 +++ dns_scripts/dns_godaddy | 393 +++++++++++++++++++++++++++++++ 6 files changed, 549 insertions(+) create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 dns_scripts/00GoDaddy-README.txt create mode 100755 dns_scripts/dns_add_godaddy create mode 100755 dns_scripts/dns_del_godaddy create mode 100755 dns_scripts/dns_godaddy diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..21cd744 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,3 @@ +# Files not to include in .zip/.tar.gz archives +# +.git* export-ignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8317ebf --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +*~ +*# +*.swp +*.tmp +*.bak +*.tdy +*.tar.gz +*.orig +JSON.sh diff --git a/dns_scripts/00GoDaddy-README.txt b/dns_scripts/00GoDaddy-README.txt new file mode 100644 index 0000000..f53ab03 --- /dev/null +++ b/dns_scripts/00GoDaddy-README.txt @@ -0,0 +1,68 @@ +Using GoDaddy DNS for LetsEncrypt domain validation. + +Quick guide to setting up getssl for domain validation of +GoDaddy DNS domains. + +There are two prerequisites to using getssl with GoDaddy DNS: + +1) Obtain an API access key from developer.godaddy.com + At first sign-up, you will be required to take a "test" key. + This is NOT what you need. Accept it, then get a "Production" + key. At this writing, there is no charge - but you must have + a GoDaddy customer account. + + You must get the API key for the account which owns the domain + that you want to get certificates for. If the domains that you + manage are owned by more than one account, get a key for each. + + The access key consists of a "Key" and a "Secret". You need + both. + +2) Obtain JSON.sh - https://github.com/dominictarr/JSON.sh + +With those in hand, the installation procedure is: + +1) Create a "myscripts" directory under ~/.getssl/ + +2) Put JSON.sh in "myscripts" + +3) Copy (or softlink from the distribution directory) the + following files to "myscripts": + dns_godaddy dns_add_godaddy dns_del_godaddy + None of these files need to be customized. + +4) Open your config file (the global file in ~/.getssl/getssl.cfg + or the per-account file in ~/.getssl/example.net/getssl.cfg + +5) Set the following options: + VALIDATE_VIA_DNS="true" + DNS_ADD_COMMAND="/path/to/myscripts/dns_add_godaddy" + DNS_DEL_COMMAND="/path/to/myscripts/dns_del_godaddy" + # The API key for your account/this domain + export GODADDY_KEY="..." GODADDY_SECRET="..." + + Note that ~user/ probably won't work in the path. + +6) Set any other options that you wish (per the standard + directions.) Use the test CA to make sure that + everything is setup correctly. + +That's it. getssl example.net will now validate with DNS. + +To trace record additions and removals, run getssl as +GODADDY_TRACE=Y getssl example.net + +There are additional options, which are documented in the +*godaddy" files and dns_godaddy -h. + +Copyright (2017) Timothe Litt litt at acm _dot org + +This sofware may be freely used providing this notice is included with +all copies. The name of the author may not be used to endorse +any other product or derivative work. No warranty is provided +and the user assumes all responsibility for use of this software. + +Report any issues to https://github.com/tlhackque/getssl/issues. + +Enjoy. + diff --git a/dns_scripts/dns_add_godaddy b/dns_scripts/dns_add_godaddy new file mode 100755 index 0000000..4983ea9 --- /dev/null +++ b/dns_scripts/dns_add_godaddy @@ -0,0 +1,39 @@ +#!/bin/bash + +# Copyright (2017) Timothe Litt litt at acm _dot org + +# Add token to GoDaddy dns using dns_godaddy + +# You do not have to customize this script. +# +# Obtain the Key and Secret from https://developer.godaddy.com/getstarted +# You must obtain a "Production" key - NOT the "Test" key you're required +# to get first. +# +# Obtain JSON.sh from https://github.com/dominictarr/JSON.sh +# Place it in (or softlink it to) the same directory as $GODADDY_SCRIPT, +# or specify its location with GODADDY_JSON +# +# Define GODADDY_KEY and GO_DADDY_SECRET in your account or domain getssl.cfg +# +# See GoDaddy-README.txt for complete instructions. + +fulldomain="$1" +token="$2" + +[ -z "$GODADDY_SCRIPT" ] && GODADDY_SCRIPT="~/.getssl/myscripts/dns_godaddy" +[[ "$GODADDY_SCRIPT" =~ ^~ ]] && \ + eval 'GODADDY_SCRIPT=`readlink -nf ' $GODADDY_SCRIPT '`' + +if [ ! -x "$GODADDY_SCRIPT" ]; then + echo "$GODADDY_SCRIPT: not found. Please install, softlink or set GODADDY_SCRIPT to its full path" + echo "See GoDaddy-README.txt for complete instructions." + exit 3 +fi + +# JSON.sh is not (currently) used by add + +export GODADDY_KEY +export GODADDY_SECRET + +$GODADDY_SCRIPT -q add ${fulldomain} "_acme-challenge.${fulldomain}." "${token}" diff --git a/dns_scripts/dns_del_godaddy b/dns_scripts/dns_del_godaddy new file mode 100755 index 0000000..c261998 --- /dev/null +++ b/dns_scripts/dns_del_godaddy @@ -0,0 +1,37 @@ +#!/bin/bash + +# Copyright (2017) Timothe Litt litt at acm _dot org + +# Remove token from GoDaddy dns using dns_godaddy + +# You do not have to customize this script. +# +# Obtain the Key and Secret from https://developer.godaddy.com/getstarted +# You must obtain a "Production" key - NOT the "Test" key you're required +# to get first. +# +# Obtain JSON.sh from https://github.com/dominictarr/JSON.sh +# Place it in (or softlink it to) the same directory as $GODADDY_SCRIPT, +# or specify its location with GODADDY_JSON +# +# Define GODADDY_KEY and GO_DADDY_SECRET in your account or domain getssl.cfg +# +# See GoDaddy-README.txt for complete instructions. + +fulldomain="$1" +token="$2" + +[ -z "$GODADDY_SCRIPT" ] && GODADDY_SCRIPT="~/.getssl/myscripts/dns_godaddy" +[[ "$GODADDY_SCRIPT" =~ ^~ ]] && \ + eval 'GODADDY_SCRIPT=`readlink -nf ' $GODADDY_SCRIPT '`' + +if ! [ -x "$GODADDY_SCRIPT" ]; then + echo "$GODADDY_SCRIPT: not found. Please install, softlink or set GODADDY_SCRIPT to its full path" + echo "See GoDaddy-README.txt for complete instructions." + exit 3 +fi + +export GODADDY_KEY +export GODADDY_SECRET + +$GODADDY_SCRIPT -q del ${fulldomain} "_acme-challenge.${fulldomain}." "${token}" diff --git a/dns_scripts/dns_godaddy b/dns_scripts/dns_godaddy new file mode 100755 index 0000000..536fdd1 --- /dev/null +++ b/dns_scripts/dns_godaddy @@ -0,0 +1,393 @@ +#!/bin/bash + +# Copyright (2017) Timothe Litt litt at acm _dot org + +VERSION="1.0.0" + +# This script is used to update TXT records in GoDaddy DNS server +# It depends on JSON.sh from https://github.com/dominictarr/JSON.sh +# Place it in (or softlink it to) the same directory as this script, +# or specify its location with GODADDY_JSON +# +# See the usage text below, 00GoDaddy-README.txt, dns_add_godaddy +# and dns_del_godaddy for additional information. +# +# It may be freely used providing this notice is included with +# all copies. The name of the author may not be used to endorse +# any other product or derivative work. No warranty is provided +# and the user assumes all responsibility for use of this software. +# +# Bug reports are welcome at https://github.com/tlhackque/getssl/issues. + +API='https://api.godaddy.com/v1/domains' +APISIGNUP='https://developer.godaddy.com/getstarted' +GETJSON='https://github.com/dominictarr/JSON.sh' + +VERB="y" +DEBUG="$GODADDY_DEBUG" +[ -z "$JSON" ] && JSON="$GODADDY_JSON" +[ -z "$JSON" ] && JSON="`dirname $0`/JSON.sh" + +while getopts 'dhj:k:s:qv' opt; do + case $opt in + d) DEBUG="Y" ;; + j) JSON="$OPTARG" ;; + k) GODADDY_KEY="$OPTARG" ;; + s) GODADDY_SECRET="$OPTARG" ;; + q) VERB= ;; + v) echo "dns_godaddy version $VERSION"; exit 0 ;; + *) + cat <&2 +$0: requires JSON.sh as "$JSON" + +The full path to JSON.sh can be specified with -j, or the +GODADDY_JSON environment variable. + +You can obtain a copy from $GETJSON + +Then place or softlink it to $JSON or set GODADDY_JSON. +EOF + exit 2 +fi + +if [ -z "$GODADDY_KEY" ] || [ -z "$GODADDY_SECRET" ]; then + echo "GODADDY_KEY and GODADDY secret must be defined" >&2 + exit 3 +fi + +[ -n "$DEBUG" ] && VERB="y" +[ -n "$GODADDY_TRACE" ] && VERB="Y" + +# Get parameters & validate + +op="$1" +if ! [[ "$op" =~ ^(add|del)$ ]]; then + echo "Operation must be \"add\" or \"del\"" >&2 + exit 3 +fi +domain="$2" +domain="${domain%'.'}" +if [ -z "$domain" ]; then + echo "'domain' parameter is required, see -h" >&2 + exit 3 +fi +name="$3" +if [ -z "$name" ]; then + echo "'name' parameter is required, see -h" >&2 + exit 3 +fi +! [[ "$name" =~ [.]$ ]] && name="${name}.${domain}." +data="$4" +if [ -z "$data" ]; then + echo "'data' parameter is required, see -h" >&2 + exit 3 +fi + +if [ "$op" = 'del' ]; then + ttl= +elif [ -z "$5" ]; then + ttl="600" # GoDaddy minimum TTL is 600 +elif ! [[ "$5" =~ ^[0-9]+$ ]]; then + echo "TTL $5 is not numeric" >&2 + exit 3 +elif [ $5 -lt 600 ]; then + [ -n "$VERB" ] && \ + echo "$5 is less than GoDaddy minimum of 600; increased to 600" >&2 + ttl="600" +else + ttl="$5" +fi + +# --- Done with parameters + +[ -n "$DEBUG" ] && \ + echo "`basename $0`: $op $domain $name \"$data\" $ttl" >&2 + +# Authorization header has secret and key +# N.B. These will appear in a 'ps' listing since curl only allows +# headers to be provided on the command line. + +authhdr="Authorization: sso-key $GODADDY_KEY:$GODADDY_SECRET" + +[ -n "$DEBUG" ] && echo "$authhdr" >&2 + +if [ "$op" = "add" ]; then + # May need to retry due to zone cuts + + while [[ "$domain" =~ [^.]+\.[^.]+ ]]; do + + url="$API/$domain/records/TXT/$name" + + request='{"data":"'$data'","ttl":'$ttl'}' + [ -n "$DEBUG" ] && cat >&2 <&2 <&2 + exit $sts + fi + if ! echo "$result" | grep -q '^HTTP/.* 200 '; then + code="`echo "$result" | grep '"code":' | sed -e's/^.*"code":"//; s/\".*$//'`" + msg="`echo "$result" | grep '"message":' | sed -e's/^.*"message":"//; s/\".*$//'`" + if [ "$code" = "DUPLICATE_RECORD" ]; then + if [ -n "$VERB" ]; then + echo "$msg in $domain" >&2 + fi + exit 0 # Duplicate record is still success + fi + if [ "$code" = 'UNKNOWN_DOMAIN' ]; then + if [[ "$domain" =~ ^([^.]+)\.([^.]+\.[^.]+.*) ]]; then + [ -n "$DEBUG" ] && \ + echo "$domain unknown, trying ${BASH_REMATCH[2]}" >&2 + domain="${BASH_REMATCH[2]}" + continue; + fi + fi + echo "Request failed $msg" >&2 + exit 1 + fi + [ -n "$VERB" ] && echo "$domain: added $name $ttl TXT \"$data\"" >&2 + exit 0 + done +fi + + +# ----- Delete + +# There is no delete API +# But, it is possible to replace all TXT records. +# +# So, first query for all TXT records + +# May need to retry due to zone cuts + +while [[ "$domain" =~ [^.]+\.[^.]+ ]]; do + + url="$API/$domain/records/TXT" + [ -n "$DEBUG" ] && echo "Query for TXT records to: $url" >&2 + + current="$(curl -i -s -X GET -H "$authhdr" "$url")" + sts=$? + if [ $sts -ne 0 ]; then + echo "curl error $sts for query" >&2 + exit $sts + fi + [ -n "$DEBUG" ] && cat >&2 <&2 + domain="${BASH_REMATCH[2]}" + continue; + fi + fi + echo "Request failed $msg" >&2 + exit 1 + fi + # Remove headers + + current="$(echo "$current" | sed -e'0,/^\r*$/d')" + break +done + + # The zone cut is known, so the replace can't fail due to UNKNOWN domain + +if [ "$current" = '[]' ]; then # No TXT records in zone + [ -n "$VERB" ] && echo "$domain: $name TXT \"$data\" does not exist" >&2 + [ -n "$DEBUG" ] && echo "No TXT records in $domain" >&2 + exit 1 # Intent was to change, so error status +fi + +[ -n "$DEBUG" ] && echo "Response is valid" + +# Prepare request to replace TXT RRSET + +# Parse JSON and select only the record structures, which are [index] { ...} + +current="$(echo "$current" | $JSON | sed -n -e'/^\[[0-9][0-9]*\]/{ s/^\[[0-9][0-9]*\]//; p}')" +base="$current" + +[ -n "$DEBUG" ] && cat >&2 <&2 + exit 1 # Intent was to change DNS, so this is an error +fi + +# Remove whitespace and insert needed commmas +# +fmtnew="$new" +new=$(echo "$new" | sed -e"s/}/},/g; \$s/},/}/;" | tr -d '\t\n') + +if [ -z "$new" ]; then + [ -n "$VERB" ] && echo "Replacing last TXT record with a dummy (see -h)" >&2 + new='{"type":"TXT","name":"_dummy.record_","data":"_This record is not used_","ttl":601}' + dummy="t" + TAB=$'\t' + fmtnew="${TAB}$new" + if [ "$fmtnew" = "$base" ]; then + [ -n "$VERB" ] && echo "This tool can't delete a placeholder when it is the only TXT record" >&2 + exit 0 # Not really success, but retrying won't help. + fi +fi + +request="[$new]" + +[ -n "$DEBUG" ] && cat >&2 <&2 <&2 <&2 + exit $sts +fi +if ! echo "$result" | grep -q '^HTTP/.* 200 '; then + result="$(echo "$result" | sed -e'0,/^\r*$/d')" + code="`echo "$result" | grep '"code":' | sed -e's/^.*"code":"//; s/\".*$//'`" + msg="`echo "$result" | grep '"message":' | sed -e's/^.*"message":"//; s/\".*$//'`" + echo "Request failed $msg" >&2 + exit 1 +fi + +if [ -n "$VERB" ]; then + if [ -n "$dummy" ]; then + echo "$domain: replaced $name TXT \"$data\" with a placeholder" >&2 + else + echo "$domain: deleted $name TXT \"$data\"" >&2 + fi +fi +exit 0 + From 49d837de532e2cb3d21497248d1f834909035602 Mon Sep 17 00:00:00 2001 From: Timothe Litt Date: Mon, 15 May 2017 14:40:18 -0400 Subject: [PATCH 02/43] Improve message from curl error code 60 Issue #288 --- getssl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/getssl b/getssl index 7f3713e..5bce35c 100755 --- a/getssl +++ b/getssl @@ -429,7 +429,8 @@ check_getssl_upgrade() { # check if a more recent version of code is available a curl --silent "$CODE_LOCATION" --output "$TEMP_UPGRADE_FILE" errcode=$? if [[ $errcode -eq 60 ]]; then - error_exit "curl needs updating, your version does not support SNI (multiple SSL domains on a single IP)" + longmsg=$'Can not authenticate SSL peer. Your ca_bundle.crt and/or curl may need\nupdating. ca_bundle.crt can be updates with mk-ca-bundle. Curl should\nsupport SNI (multiple SSL domains on a single IP)' + error_exit "$longmsg" elif [[ $errcode -gt 0 ]]; then error_exit "curl error : $errcode" fi From 134456b9685f5c9f0d69807a8d0db7eb5c9c9abe Mon Sep 17 00:00:00 2001 From: Timothe Litt Date: Mon, 15 May 2017 14:49:15 -0400 Subject: [PATCH 03/43] Fix misleading error message saving certificates to file. --- getssl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/getssl b/getssl index 5bce35c..8b0b7c2 100755 --- a/getssl +++ b/getssl @@ -814,7 +814,7 @@ get_certificate() { # get certificate for csr, if all domains validated. echo -----BEGIN CERTIFICATE----- > "$gc_certfile" curl --silent "$CertData" | openssl base64 -e >> "$gc_certfile" echo -----END CERTIFICATE----- >> "$gc_certfile" - info "Certificate saved in $CERT_FILE" + info "Certificate saved in $gc_certfile" fi # If certificate wasn't a valid certificate, error exit. From 568e3373fcd6a8b92576f9fc059e3a6c08c313b4 Mon Sep 17 00:00:00 2001 From: Timothe Litt Date: Mon, 15 May 2017 14:53:07 -0400 Subject: [PATCH 04/43] Add delimiter to INVALID PRIVATE_KEY_ALG message --- getssl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/getssl b/getssl index 8b0b7c2..aadb809 100755 --- a/getssl +++ b/getssl @@ -336,7 +336,7 @@ check_config() { # check the config files for all obvious errors rsa|prime256v1|secp384r1|secp521r1) debug "checked PRIVATE_KEY_ALG " ;; *) - info "${DOMAIN}: invalid PRIVATE_KEY_ALG - $PRIVATE_KEY_ALG" + info "${DOMAIN}: invalid PRIVATE_KEY_ALG - '$PRIVATE_KEY_ALG'" config_errors=true ;; esac if [[ "$DUAL_RSA_ECDSA" == "true" ]] && [[ "$PRIVATE_KEY_ALG" == "rsa" ]]; then From c80f3c86925f328e5696fa090e57a5f9d3e5a4a3 Mon Sep 17 00:00:00 2001 From: Timothe Litt Date: Mon, 15 May 2017 17:21:33 -0400 Subject: [PATCH 05/43] Replace dns_*_nsupdate scripts Old versions did not return correct exit status. Did not provide for pre/post processing hooks (e.g. to mount a disk) Required custom edits/copies for each instance. These fix all those issues. --- dns_scripts/dns_add_nsupdate | 35 +++++++++++++++++++++++++++++------ dns_scripts/dns_del_nsupdate | 36 ++++++++++++++++++++++++++++++------ 2 files changed, 59 insertions(+), 12 deletions(-) diff --git a/dns_scripts/dns_add_nsupdate b/dns_scripts/dns_add_nsupdate index 9e8ebfe..03f73d4 100755 --- a/dns_scripts/dns_add_nsupdate +++ b/dns_scripts/dns_add_nsupdate @@ -2,15 +2,38 @@ # example of script to add token to local dns using nsupdate -dnskeyfile="path/to/bla.key" - fulldomain="$1" token="$2" -updatefile=$(mktemp) +# VARIABLES: +# +# DNS_NSUPDATE_KEYFILE - path to a TSIG key file, if required +# DNS_NSUPDATE_GETKEY - command to execute if access to the key file requires +# some special action: mounting a disk, decrypting a file.. +# Called with the operation 'add' and action 'open" / 'close' + + +if [ -n "${DNS_NSUPDATE_KEYFILE}" ]; then + if [ -n "${DNS_NSUPDATE_KEY_HOOK}" ] && ! ${DNS_NSUPDATE_KEY_HOOK} 'add' 'open' ; then + exit $(( $? + 128 )) + fi + + options="-k ${DNS_NSUPDATE_KEYFILE}" +fi + +# Note that blank line is a "send" command to nsupdate + +nsupdate ${options} -v < "${updatefile}" +sts=$? -nsupdate -k "${dnskeyfile}" -v "${updatefile}" +if [ -n "${DNS_NSUPDATE_KEYFILE}" ]; then + if [ -n "${DNS_NSUPDATE_KEY_HOOK}" ] && ! ${DNS_NSUPDATE_KEY_HOOK} 'add' 'close' ; then + exit $(( ${sts} + ( $? * 10 ) )) + fi +fi -rm -f "${updatefile}" +exit ${sts} \ No newline at end of file diff --git a/dns_scripts/dns_del_nsupdate b/dns_scripts/dns_del_nsupdate index 62291b7..ab9ca28 100755 --- a/dns_scripts/dns_del_nsupdate +++ b/dns_scripts/dns_del_nsupdate @@ -1,15 +1,39 @@ #!/bin/bash -# example of script to add token to local dns using nsupdate +# example of script to remove token from local dns using nsupdate -dnskeyfile="path/to/bla.key" fulldomain="$1" token="$2" -updatefile=$(mktemp) +# VARIABLES: +# +# DNS_NSUPDATE_KEYFILE - path to a TSIG key file, if required +# DNS_NSUPDATE_GETKEY - command to execute if access to the key file requires +# some special action: dismounting a disk, encrypting a +# file... Called with the operation 'del' and action +# 'open" / 'close' -printf "update delete _acme-challenge.%s. 300 in TXT \"%s\"\n\n" "${fulldomain}" "${token}" > "${updatefile}" +if [ -n "${DNS_NSUPDATE_KEYFILE}" ]; then + if [ -n "${DNS_NSUPDATE_KEY_HOOK}" ] && ! ${DNS_NSUPDATE_KEY_HOOK} 'del' 'open'; then + exit $(( $? + 128 )) + fi -nsupdate -k "${dnskeyfile}" -v "${updatefile}" + options="-k ${DNS_NSUPDATE_KEYFILE}" +fi -rm -f "${updatefile}" +# Note that blank line is a "send" command to nsupdate + +nsupdate ${options} -v < Date: Mon, 15 May 2017 17:38:24 -0400 Subject: [PATCH 06/43] Include domain name in dns_nsupdate hooks --- dns_scripts/dns_add_nsupdate | 4 ++-- dns_scripts/dns_del_nsupdate | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/dns_scripts/dns_add_nsupdate b/dns_scripts/dns_add_nsupdate index 03f73d4..55178db 100755 --- a/dns_scripts/dns_add_nsupdate +++ b/dns_scripts/dns_add_nsupdate @@ -14,7 +14,7 @@ token="$2" if [ -n "${DNS_NSUPDATE_KEYFILE}" ]; then - if [ -n "${DNS_NSUPDATE_KEY_HOOK}" ] && ! ${DNS_NSUPDATE_KEY_HOOK} 'add' 'open' ; then + if [ -n "${DNS_NSUPDATE_KEY_HOOK}" ] && ! ${DNS_NSUPDATE_KEY_HOOK} 'add' 'open' ${fulldomain} ; then exit $(( $? + 128 )) fi @@ -31,7 +31,7 @@ EOF sts=$? if [ -n "${DNS_NSUPDATE_KEYFILE}" ]; then - if [ -n "${DNS_NSUPDATE_KEY_HOOK}" ] && ! ${DNS_NSUPDATE_KEY_HOOK} 'add' 'close' ; then + if [ -n "${DNS_NSUPDATE_KEY_HOOK}" ] && ! ${DNS_NSUPDATE_KEY_HOOK} 'add' 'close' ${fulldomain}; then exit $(( ${sts} + ( $? * 10 ) )) fi fi diff --git a/dns_scripts/dns_del_nsupdate b/dns_scripts/dns_del_nsupdate index ab9ca28..b3a553e 100755 --- a/dns_scripts/dns_del_nsupdate +++ b/dns_scripts/dns_del_nsupdate @@ -14,7 +14,7 @@ token="$2" # 'open" / 'close' if [ -n "${DNS_NSUPDATE_KEYFILE}" ]; then - if [ -n "${DNS_NSUPDATE_KEY_HOOK}" ] && ! ${DNS_NSUPDATE_KEY_HOOK} 'del' 'open'; then + if [ -n "${DNS_NSUPDATE_KEY_HOOK}" ] && ! ${DNS_NSUPDATE_KEY_HOOK} 'del' 'open' ${fulldomain} ; then exit $(( $? + 128 )) fi @@ -31,7 +31,7 @@ EOF sts=$? if [ -n "${DNS_NSUPDATE_KEYFILE}" ]; then - if [ -n "${DNS_NSUPDATE_KEY_HOOK}" ] && ! ${DNS_NSUPDATE_KEY_HOOK} 'del' 'close' ; then + if [ -n "${DNS_NSUPDATE_KEY_HOOK}" ] && ! ${DNS_NSUPDATE_KEY_HOOK} 'del' 'close' ${fulldomain} ; then exit $(( ${sts} + ( $? * 10 ) )) fi fi From b579af8552e6d5e329777710511721cf631603ea Mon Sep 17 00:00:00 2001 From: Timothe Litt Date: Mon, 15 May 2017 21:04:52 -0400 Subject: [PATCH 07/43] Move GoDaddy scripts to installation directory; use install -v for all files Simplifies installation; private directory no longer required since the scripts don't need to be customized. -v makes sure that the installer knows what is happening. --- Makefile | 7 +++---- dns_scripts/00GoDaddy-README.txt | 22 +++++++--------------- dns_scripts/dns_add_godaddy | 5 +++-- dns_scripts/dns_del_godaddy | 5 +++-- 4 files changed, 16 insertions(+), 23 deletions(-) diff --git a/Makefile b/Makefile index 4f16126..d88e22c 100644 --- a/Makefile +++ b/Makefile @@ -19,10 +19,9 @@ ifneq ($(strip $(DESTDIR)),) mkdir -p $(DESTDIR) endif - install -Dm755 getssl $(DESTDIR)/usr/bin/getssl - - install -dm755 $(DESTDIR)/usr/share/getssl - cp -r *_scripts $(DESTDIR)/usr/share/getssl + install -Dvm755 getssl $(DESTDIR)/usr/bin/getssl + install -dvm755 $(DESTDIR)/usr/share/getssl + for dir in *_scripts; do install -dv $(DESTDIR)/usr/share/getssl/$$dir; install -pv $$dir/* $(DESTDIR)/usr/share/getssl/$$dir/; done .PHONY: install diff --git a/dns_scripts/00GoDaddy-README.txt b/dns_scripts/00GoDaddy-README.txt index f53ab03..d58ba73 100644 --- a/dns_scripts/00GoDaddy-README.txt +++ b/dns_scripts/00GoDaddy-README.txt @@ -22,28 +22,20 @@ There are two prerequisites to using getssl with GoDaddy DNS: With those in hand, the installation procedure is: -1) Create a "myscripts" directory under ~/.getssl/ +1) Put JSON.sh in the getssl DNS scripts directory + Default: /usr/share/getssl/dns_scripts -2) Put JSON.sh in "myscripts" - -3) Copy (or softlink from the distribution directory) the - following files to "myscripts": - dns_godaddy dns_add_godaddy dns_del_godaddy - None of these files need to be customized. - -4) Open your config file (the global file in ~/.getssl/getssl.cfg +2) Open your config file (the global file in ~/.getssl/getssl.cfg or the per-account file in ~/.getssl/example.net/getssl.cfg -5) Set the following options: +3) Set the following options: VALIDATE_VIA_DNS="true" - DNS_ADD_COMMAND="/path/to/myscripts/dns_add_godaddy" - DNS_DEL_COMMAND="/path/to/myscripts/dns_del_godaddy" + DNS_ADD_COMMAND="/usr/share/getssl/dns_scripts/dns_add_godaddy" + DNS_DEL_COMMAND="/usr/share/getssl/dns_scripts/dns_del_godaddy" # The API key for your account/this domain export GODADDY_KEY="..." GODADDY_SECRET="..." - Note that ~user/ probably won't work in the path. - -6) Set any other options that you wish (per the standard + 4) Set any other options that you wish (per the standard directions.) Use the test CA to make sure that everything is setup correctly. diff --git a/dns_scripts/dns_add_godaddy b/dns_scripts/dns_add_godaddy index 4983ea9..f7a871c 100755 --- a/dns_scripts/dns_add_godaddy +++ b/dns_scripts/dns_add_godaddy @@ -12,7 +12,8 @@ # # Obtain JSON.sh from https://github.com/dominictarr/JSON.sh # Place it in (or softlink it to) the same directory as $GODADDY_SCRIPT, -# or specify its location with GODADDY_JSON +# or specify its location with GODADDY_JSON The default is +# /usr/share/getssl/dns_scripts/ # # Define GODADDY_KEY and GO_DADDY_SECRET in your account or domain getssl.cfg # @@ -21,7 +22,7 @@ fulldomain="$1" token="$2" -[ -z "$GODADDY_SCRIPT" ] && GODADDY_SCRIPT="~/.getssl/myscripts/dns_godaddy" +[ -z "$GODADDY_SCRIPT" ] && GODADDY_SCRIPT="/usr/share/getssl/dns_scripts/dns_godaddy" [[ "$GODADDY_SCRIPT" =~ ^~ ]] && \ eval 'GODADDY_SCRIPT=`readlink -nf ' $GODADDY_SCRIPT '`' diff --git a/dns_scripts/dns_del_godaddy b/dns_scripts/dns_del_godaddy index c261998..7ca0da9 100755 --- a/dns_scripts/dns_del_godaddy +++ b/dns_scripts/dns_del_godaddy @@ -12,7 +12,8 @@ # # Obtain JSON.sh from https://github.com/dominictarr/JSON.sh # Place it in (or softlink it to) the same directory as $GODADDY_SCRIPT, -# or specify its location with GODADDY_JSON +# or specify its location with GODADDY_JSON The default is +# /usr/share/getssl/dns_scripts/ # # Define GODADDY_KEY and GO_DADDY_SECRET in your account or domain getssl.cfg # @@ -21,7 +22,7 @@ fulldomain="$1" token="$2" -[ -z "$GODADDY_SCRIPT" ] && GODADDY_SCRIPT="~/.getssl/myscripts/dns_godaddy" +[ -z "$GODADDY_SCRIPT" ] && GODADDY_SCRIPT="/usr/share/getssl/dns_scripts/dns_godaddy" [[ "$GODADDY_SCRIPT" =~ ^~ ]] && \ eval 'GODADDY_SCRIPT=`readlink -nf ' $GODADDY_SCRIPT '`' From 820dec6d3f7b5c272c9ee43de48a6b94a61b9db5 Mon Sep 17 00:00:00 2001 From: Timothe Litt Date: Mon, 15 May 2017 22:44:24 -0400 Subject: [PATCH 08/43] Have curl read Authrorizaton header from stdin rather than the command line. Prevents GoDaddy secret and key from exposure via ps. There is still an issue if environment variables can be read. --- dns_scripts/dns_godaddy | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/dns_scripts/dns_godaddy b/dns_scripts/dns_godaddy index 536fdd1..0e6454c 100755 --- a/dns_scripts/dns_godaddy +++ b/dns_scripts/dns_godaddy @@ -178,8 +178,6 @@ fi echo "`basename $0`: $op $domain $name \"$data\" $ttl" >&2 # Authorization header has secret and key -# N.B. These will appear in a 'ps' listing since curl only allows -# headers to be provided on the command line. authhdr="Authorization: sso-key $GODADDY_KEY:$GODADDY_SECRET" @@ -199,9 +197,12 @@ Add request to: $url $request" -------- EOF - result="$(curl -i -s -X PUT -H "$authhdr" \ - -H "Content-Type: application/json" \ - -d "$request" "$url")" + + result="$(curl -i -s -X PUT -d "$request" --config - "$url" <&2 <&2 - current="$(curl -i -s -X GET -H "$authhdr" "$url")" + current="$(curl -i -s -X GET --config - "$url" <&2 @@ -357,9 +361,11 @@ $request -------- EOF -result="$(curl -i -s -X PUT -H "$authhdr" \ - -H "Content-Type: application/json" \ - -d "$request" "$url")" +result="$(curl -i -s -X PUT -d "$request" --config - "$url" <&2 < Date: Tue, 16 May 2017 08:05:30 -0400 Subject: [PATCH 09/43] Add option for full protocol trace -t FILE (or GODADDY_TFILE=FILE) will append command arguments and all wire traffic to a file. This provides all the information needed for debugging future issues. --- dns_scripts/dns_godaddy | 30 ++++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/dns_scripts/dns_godaddy b/dns_scripts/dns_godaddy index 0e6454c..ff25449 100755 --- a/dns_scripts/dns_godaddy +++ b/dns_scripts/dns_godaddy @@ -2,7 +2,8 @@ # Copyright (2017) Timothe Litt litt at acm _dot org -VERSION="1.0.0" +VERSION="1.0.1" +PROG="`basename $0`" # This script is used to update TXT records in GoDaddy DNS server # It depends on JSON.sh from https://github.com/dominictarr/JSON.sh @@ -28,19 +29,20 @@ DEBUG="$GODADDY_DEBUG" [ -z "$JSON" ] && JSON="$GODADDY_JSON" [ -z "$JSON" ] && JSON="`dirname $0`/JSON.sh" -while getopts 'dhj:k:s:qv' opt; do +while getopts 'dhj:k:s:t:qv' opt; do case $opt in d) DEBUG="Y" ;; j) JSON="$OPTARG" ;; k) GODADDY_KEY="$OPTARG" ;; s) GODADDY_SECRET="$OPTARG" ;; + t) TRACE="$OPTARG" ;; q) VERB= ;; v) echo "dns_godaddy version $VERSION"; exit 0 ;; *) cat <&2 + echo "$PROG: $op $domain $name \"$data\" $ttl" >&2 # Authorization header has secret and key authhdr="Authorization: sso-key $GODADDY_KEY:$GODADDY_SECRET" +if [ -n "$TRACE" ]; then + function timestamp { local tm="`date '+%T:%S%N'`" + local class="$1"; shift + echo "${tm:0:15} ** ${class}: $*" >>"$TRACE" + } + timestamp 'Info' "$PROG" "V$VERSION" 'Starting new protocol trace' + timestamp 'Args' "$@" + function curl { + command curl --trace-time --trace-ascii % "$@" 2>>"$TRACE" + } + [ -n "$VERB" ] && echo "Appending protocol trace to $TRACE" +fi + [ -n "$DEBUG" ] && echo "$authhdr" >&2 if [ "$op" = "add" ]; then From e5702045b055c3ba9aba440a0769549db52ab41b Mon Sep 17 00:00:00 2001 From: Timothe Litt Date: Tue, 16 May 2017 21:01:11 -0400 Subject: [PATCH 10/43] Support views. Also, avoid hangs interrogating remote. rdbath suggested how to implement a timeout on s_client probes. Unfortunately, wait -n is a bash 4.3 feature. So this requires bash 4.3. CHECK_CERT_TIMEOUT can be used to override the default, which is 4 seconds. Fallback is provided for older versions. Views may require the 'nslookup' process to do somthing special, usually provide a TSIG key or bind to a specific local address. Add a hook for that - export the VARIABLE DNS_CHECK_FUNC_OPTIONS with the desired options._Set DNS_CHECK_FUNC to the desired command, which must be one of the supported ones: 'dig', 'drill', 'host' or 'nslookup'. However, this turned up the fact that the dig/drill code had the domain and record type arguments in the wrong order on the command line. (The domain comes first, see the man page.) Fixed. In some cases defining the previously undocumented PUBLIC_DNS_SERVER may work. This commit adds it to the template file, and exports it for the benefit of DNS_UPDATE scripts. Also AUTH_DNS_SERVER. Squashed awk complaints about curl.header in some cases with debugging on. Support older curl (--trace-time is somewhat recent) --- dns_scripts/dns_godaddy | 3 +- getssl | 103 +++++++++++++++++++++++++++++++--------- 2 files changed, 82 insertions(+), 24 deletions(-) diff --git a/dns_scripts/dns_godaddy b/dns_scripts/dns_godaddy index ff25449..5e9aa0b 100755 --- a/dns_scripts/dns_godaddy +++ b/dns_scripts/dns_godaddy @@ -193,8 +193,9 @@ if [ -n "$TRACE" ]; then } timestamp 'Info' "$PROG" "V$VERSION" 'Starting new protocol trace' timestamp 'Args' "$@" + curl --help | grep -q -- --trace-time && CURL_TFLAGS="--trace-time" # 7.14.0 function curl { - command curl --trace-time --trace-ascii % "$@" 2>>"$TRACE" + command curl ${CURL_TFLAGS} --trace-ascii % "$@" 2>>"$TRACE" } [ -n "$VERB" ] && echo "Appending protocol trace to $TRACE" fi diff --git a/getssl b/getssl index aadb809..da7f386 100755 --- a/getssl +++ b/getssl @@ -192,16 +192,20 @@ VERSION="2.10" # defaults ACCOUNT_KEY_LENGTH=4096 ACCOUNT_KEY_TYPE="rsa" +export AUTH_DNS_SERVER="" CA="https://acme-staging.api.letsencrypt.org" CA_CERT_LOCATION="" CHALLENGE_CHECK_TYPE="http" CHECK_ALL_AUTH_DNS="false" +CHECK_CERT_TIMEOUT="4" CHECK_REMOTE="true" CHECK_REMOTE_WAIT=0 CODE_LOCATION="https://raw.githubusercontent.com/srvrco/getssl/master/getssl" CSR_SUBJECT="/" DEACTIVATE_AUTH="false" DEFAULT_REVOKE_CA="https://acme-v01.api.letsencrypt.org" +DNS_CHECK_FUNC="" +DNS_CHECK_OPTIONS="" DNS_EXTRA_WAIT="" DNS_WAIT=10 DOMAIN_KEY_LENGTH=4096 @@ -212,7 +216,7 @@ IGNORE_DIRECTORY_DOMAIN="false" ORIG_UMASK=$(umask) PREVIOUSLY_VALIDATED="true" PRIVATE_KEY_ALG="rsa" -PUBLIC_DNS_SERVER="" +export PUBLIC_DNS_SERVER="" RELOAD_CMD="" RENEW_ALLOW="30" REUSE_PRIVATE_KEY="true" @@ -389,14 +393,14 @@ 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 ]] || [[ "$DNS_CHECK_FUNC" =~ ^dig ]]; 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" == "host" ]]; then + elif [[ "$DNS_CHECK_FUNC" =~ ^host ]]; then if [[ "$($DNS_CHECK_FUNC "${d}" |grep -c "^${d}")" -ge 1 ]]; then debug "found IP for ${d}" else @@ -716,20 +720,19 @@ get_auth_dns() { # get the authoritative dns server for a domain (sets primary_n primary_ns="$all_auth_dns_servers" return fi - - if [[ "$DNS_CHECK_FUNC" == "drill" ]] || [[ "$DNS_CHECK_FUNC" == "dig" ]]; then + 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") + res=$($DNS_CHECK_FUNC "$gad_d" CNAME| grep "^$gad_d") else - res=$($DNS_CHECK_FUNC CNAME "$gad_d" "@$gad_s"| grep "^$gad_d") + res=$($DNS_CHECK_FUNC "$gad_d" CNAME "@$gad_s"| grep "^$gad_d") fi if [[ ! -z "$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") + res=$($DNS_CHECK_FUNC "$gad_d" NS| grep "^$gad_d") else - res=$($DNS_CHECK_FUNC NS "$gad_d" "@$gad_s"| grep "^$gad_d") + res=$($DNS_CHECK_FUNC "$gad_d" NS "@$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" @@ -744,7 +747,7 @@ get_auth_dns() { # get the authoritative dns server for a domain (sets primary_n return fi - if [[ "$DNS_CHECK_FUNC" == "host" ]]; then + if [[ "$DNS_CHECK_FUNC" =~ ^host ]]; then if [[ -z "$gad_s" ]]; then res=$($DNS_CHECK_FUNC -t NS "$gad_d"| grep "name server") else @@ -1178,6 +1181,7 @@ send_signed_request() { # Sends a request to the ACME server, signed with your p response=$($CURL -X POST --data "$body" "$url") fi + touch "$CURL_HEADER" responseHeaders=$(cat "$CURL_HEADER") debug responseHeaders "$responseHeaders" debug response "$response" @@ -1325,6 +1329,21 @@ write_domain_template() { # write out a template file for a domain. # an update to confirm correct certificate is running (if CHECK_REMOTE) is set to true #SERVER_TYPE="https" #CHECK_REMOTE="true" + + # Unusual configurations (especially split views) may require these. + # If these (or any variable) apply to all your domains, put them in + # the per-domain getssl.cfg. + # + # If you must use an external DNS Server (e.g. due to split views) + # Specify it here. Otherwise, the default is to find the zone master. + # The default will usually work. + # PUBLIC_DNS_SERVER="8.8.8.8" + + # If getssl is unable to determine the authoritative nameserver for a domain + # it will as you to enter AUTH_DNS_SERVER. This is the primary server that + # getssl will use to check for the acme tokens. It must be visible externally + # as well as internally. It need not be "authoritiative" in the RFC1035 sense. + # AUTH_DNS_SERVER="8.8.8.8" _EOF_domain_ } @@ -1364,6 +1383,19 @@ write_getssl_template() { # write out the main template file #VALIDATE_VIA_DNS="true" #DNS_ADD_COMMAND= #DNS_DEL_COMMAND= + + # Unusual configurations (especially split views) may require these. + # If you have a mixture, these can go in the per-domain getssl.cfg. + # + # If you must use an external DNS Server (e.g. due to split views) + # Specify it here. Otherwise, the default is to find the zone master. + # The default will usually work. + # PUBLIC_DNS_SERVER="8.8.8.8" + + # If getssl is unable to determine the authoritative nameserver for a domain + # it will as you to enter AUTH_DNS_SERVER. This is a server that + # can answer queries for the zone - a master or a slave, not a recursive server. + # AUTH_DNS_SERVER="10.0.0.14" _EOF_getssl_ } @@ -1446,7 +1478,6 @@ get_os requires which requires openssl requires curl -requires nslookup drill dig host DNS_CHECK_FUNC requires awk requires tr requires date @@ -1497,6 +1528,12 @@ if [[ -s "$WORKING_DIR/getssl.cfg" ]]; then . "$WORKING_DIR/getssl.cfg" fi +if [[ -n "$DNS_CHECK_FUNC" ]]; then + requires "${DNS_CHECK_FUNC}" +else + requires nslookup drill dig host DNS_CHECK_FUNC +fi + # Define defaults for variables not set in the main config. ACCOUNT_KEY="${ACCOUNT_KEY:=$WORKING_DIR/account.key}" DOMAIN_STORAGE="${DOMAIN_STORAGE:=$WORKING_DIR}" @@ -1569,11 +1606,17 @@ if [[ ${_CREATE_CONFIG} -eq 1 ]]; then 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) + info "Contacting ${DOMAIN} to inspect current certificate" + EX_CERT=$( { + if [[ "${BASH_VERSINFO[0]}${BASH_VERSINFO[1]}" -ge 43 ]]; then + openssl s_client -servername "$DOMAIN" -connect "$DOMAIN":443 /dev/null & PID=$! + sleep ${CHECK_CERT_TIMEOUT} & PIDW=$! + wait -n # Requires bash 4.3+ + kill -9 "$PID" "$PIDW" 2>/dev/null + else + openssl s_client -servername "$DOMAIN" -connect "$DOMAIN":443 /dev/null + fi + } | openssl x509 2>/dev/null) EX_SANS="www.${DOMAIN}" if [[ ! -z "${EX_CERT}" ]]; then EX_SANS=$(echo "$EX_CERT" \ @@ -1582,6 +1625,7 @@ if [[ ${_CREATE_CONFIG} -eq 1 ]]; then EX_SANS=${EX_SANS//$'\n'/','} fi write_domain_template "$DOMAIN_DIR/getssl.cfg" + info "created domain config file in $DOMAIN_DIR/getssl.cfg" fi TEMP_DIR="$DOMAIN_DIR/tmp" # end of "-c|--create" option, so exit @@ -1609,6 +1653,11 @@ if [[ -s "$DOMAIN_DIR/getssl.cfg" ]]; then . "$DOMAIN_DIR/getssl.cfg" fi +# In case special options are needed for DNS_CHECK_FUNC, add them +# to the command. E.G. if a TSIG key or bound local IP is required... + +DNS_CHECK_FUNC="${DNS_CHECK_FUNC} ${DNS_CHECK_OPTIONS}" + # from SERVER_TYPE set REMOTE_PORT and REMOTE_EXTRA set_server_type @@ -1629,11 +1678,19 @@ URL_new_cert=$(echo "$ca_all_loc" | grep "new-cert" | awk -F'"' '{print $4}') # 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" + info "Contacting $DOMAIN on port ${REMOTE_PORT} to inspect current certificate" # 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) + EX_CERT=$( { + if [[ "${BASH_VERSINFO[0]}${BASH_VERSINFO[1]}" -ge 43 ]]; then + echo | openssl s_client -servername "${DOMAIN}" -connect "${DOMAIN}:${REMOTE_PORT}" ${REMOTE_EXTRA} 2>/dev/null & PID=$! + sleep ${CHECK_CERT_TIMEOUT} & PIDW=$! + wait -n # Requires bash 4.3+ + kill -9 "$PID" "$PIDW" 2>/dev/null + else + echo | openssl s_client -servername "${DOMAIN}" -connect "${DOMAIN}:${REMOTE_PORT}" ${REMOTE_EXTRA} 2>/dev/null + fi + } | openssl x509 2>/dev/null ) + if [[ ! -z "$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) @@ -1980,10 +2037,10 @@ if [[ $VALIDATE_VIA_DNS == "true" ]]; 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}" \ + elif [[ "$DNS_CHECK_FUNC" =~ ^drill ]] || [[ "$DNS_CHECK_FUNC" =~ ^dig ]]; then + check_result=$($DNS_CHECK_FUNC "_acme-challenge.${d}" TXT "@${ns}" \ | grep ^_acme|awk -F'"' '{ print $2}') - elif [[ "$DNS_CHECK_FUNC" == "host" ]]; then + elif [[ "$DNS_CHECK_FUNC" =~ ^host ]]; then check_result=$($DNS_CHECK_FUNC -t TXT "_acme-challenge.${d}" "${ns}" \ | grep ^_acme|awk -F'"' '{ print $2}') else From 676e2cc72d499a1243371ca93872b4600db92b3d Mon Sep 17 00:00:00 2001 From: Timothe Litt Date: Thu, 18 May 2017 23:31:20 -0400 Subject: [PATCH 11/43] Correct timestamp in protocol debug. Inadvertently duplicated seconds & truncated fraction. --- dns_scripts/dns_godaddy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dns_scripts/dns_godaddy b/dns_scripts/dns_godaddy index 5e9aa0b..4443bd4 100755 --- a/dns_scripts/dns_godaddy +++ b/dns_scripts/dns_godaddy @@ -187,7 +187,7 @@ fi authhdr="Authorization: sso-key $GODADDY_KEY:$GODADDY_SECRET" if [ -n "$TRACE" ]; then - function timestamp { local tm="`date '+%T:%S%N'`" + function timestamp { local tm="`LC_TIME=C date '+%T.%N'`" local class="$1"; shift echo "${tm:0:15} ** ${class}: $*" >>"$TRACE" } From 1c7c0276488990d5464e4a218ce39450adbfe15e Mon Sep 17 00:00:00 2001 From: Timothe Litt Date: Sat, 5 Aug 2017 10:02:47 -0400 Subject: [PATCH 12/43] When combining .key files with certs, set umask to 077 to protect key --- getssl | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/getssl b/getssl index da7f386..e869b86 100755 --- a/getssl +++ b/getssl @@ -1736,11 +1736,15 @@ if [[ "${CHECK_REMOTE}" == "true" ]] && [[ $_FORCE_RENEW -eq 0 ]]; then copy_file_to_location "full pem" \ "$TEMP_DIR/${DOMAIN}_chain.pem" \ "$DOMAIN_CHAIN_LOCATION" + umask 077 cat "$DOMAIN_DIR/${DOMAIN}.key" "$CERT_FILE" > "$TEMP_DIR/${DOMAIN}_K_C.pem" + umask "$ORIG_UMASK" copy_file_to_location "private key and domain cert pem" \ "$TEMP_DIR/${DOMAIN}_K_C.pem" \ "$DOMAIN_KEY_CERT_LOCATION" + umask 077 cat "$DOMAIN_DIR/${DOMAIN}.key" "$CERT_FILE" "$CA_CERT" > "$TEMP_DIR/${DOMAIN}.pem" + umask "$ORIG_UMASK" copy_file_to_location "full pem" \ "$TEMP_DIR/${DOMAIN}.pem" \ "$DOMAIN_PEM_LOCATION" @@ -2154,12 +2158,14 @@ if [[ ! -z "$DOMAIN_KEY_CERT_LOCATION" ]]; then else to_location="${DOMAIN_KEY_CERT_LOCATION}" fi + umask 077 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::-4}.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 + umask "$ORIG_UMASK" fi # if DOMAIN_PEM_LOCATION is not blank, then create and copy file. if [[ ! -z "$DOMAIN_PEM_LOCATION" ]]; then @@ -2168,12 +2174,14 @@ if [[ ! -z "$DOMAIN_PEM_LOCATION" ]]; then else to_location="${DOMAIN_PEM_LOCATION}" fi + umask 077 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::-4}.ec.crt" "${CA_CERT::-4}.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 + umask "$ORIG_UMASK" fi # end of copying certs. From b57cad33451889edcdba78aa41a4ee31a03b33ea Mon Sep 17 00:00:00 2001 From: Timothe Litt Date: Mon, 14 May 2018 06:41:29 -0400 Subject: [PATCH 13/43] GoDaddy API change requires JSON array to add TXT records The GoDaddy API now strictly requires any array when adding a single TXT record. --- dns_scripts/dns_godaddy | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dns_scripts/dns_godaddy b/dns_scripts/dns_godaddy index 4443bd4..9ebe6dd 100755 --- a/dns_scripts/dns_godaddy +++ b/dns_scripts/dns_godaddy @@ -1,8 +1,8 @@ #!/bin/bash -# Copyright (2017) Timothe Litt litt at acm _dot org +# Copyright (C) 2017,2018) Timothe Litt litt at acm _dot org -VERSION="1.0.1" +VERSION="1.0.2" PROG="`basename $0`" # This script is used to update TXT records in GoDaddy DNS server @@ -209,7 +209,7 @@ if [ "$op" = "add" ]; then url="$API/$domain/records/TXT/$name" - request='{"data":"'$data'","ttl":'$ttl'}' + request='[{"data":"'$data'","ttl":'$ttl'}]' [ -n "$DEBUG" ] && cat >&2 < Date: Wed, 16 May 2018 10:44:16 -0400 Subject: [PATCH 14/43] More GoDaddy API changes PUT now requires a relative domain name. GET changed the order of name and data in the JSON. Note: Due to the API changes, you may need to add GODADDY_BASE to getssl.cfg. This is a space-separated list of base domain names (zones) in which the challenge responses are entered. There doesn't seem to be a way around this that works in all cases, as the GoDaddy API requires that the zone name be known. --- dns_scripts/00GoDaddy-README.txt | 5 ++++- dns_scripts/dns_add_godaddy | 3 ++- dns_scripts/dns_del_godaddy | 3 ++- dns_scripts/dns_godaddy | 36 ++++++++++++++++++++++++++------ 4 files changed, 38 insertions(+), 9 deletions(-) diff --git a/dns_scripts/00GoDaddy-README.txt b/dns_scripts/00GoDaddy-README.txt index d58ba73..9973556 100644 --- a/dns_scripts/00GoDaddy-README.txt +++ b/dns_scripts/00GoDaddy-README.txt @@ -34,6 +34,9 @@ With those in hand, the installation procedure is: DNS_DEL_COMMAND="/usr/share/getssl/dns_scripts/dns_del_godaddy" # The API key for your account/this domain export GODADDY_KEY="..." GODADDY_SECRET="..." + # The base domain name(s) in which the challege records are stored + # E.g. if www.example.net is in the example.net zone: + export GODADDY_BASE="example.com example.net" 4) Set any other options that you wish (per the standard directions.) Use the test CA to make sure that @@ -47,7 +50,7 @@ GODADDY_TRACE=Y getssl example.net There are additional options, which are documented in the *godaddy" files and dns_godaddy -h. -Copyright (2017) Timothe Litt litt at acm _dot org +Copyright (C) 2017, 2018 Timothe Litt litt at acm _dot org This sofware may be freely used providing this notice is included with all copies. The name of the author may not be used to endorse diff --git a/dns_scripts/dns_add_godaddy b/dns_scripts/dns_add_godaddy index f7a871c..fc08f09 100755 --- a/dns_scripts/dns_add_godaddy +++ b/dns_scripts/dns_add_godaddy @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright (2017) Timothe Litt litt at acm _dot org +# Copyright (C) 2017, 2018 Timothe Litt litt at acm _dot org # Add token to GoDaddy dns using dns_godaddy @@ -36,5 +36,6 @@ fi export GODADDY_KEY export GODADDY_SECRET +export GODADDY_BASE $GODADDY_SCRIPT -q add ${fulldomain} "_acme-challenge.${fulldomain}." "${token}" diff --git a/dns_scripts/dns_del_godaddy b/dns_scripts/dns_del_godaddy index 7ca0da9..120430f 100755 --- a/dns_scripts/dns_del_godaddy +++ b/dns_scripts/dns_del_godaddy @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright (2017) Timothe Litt litt at acm _dot org +# Copyright (C) 2017,2018 Timothe Litt litt at acm _dot org # Remove token from GoDaddy dns using dns_godaddy @@ -34,5 +34,6 @@ fi export GODADDY_KEY export GODADDY_SECRET +export GODADDY_BASE $GODADDY_SCRIPT -q del ${fulldomain} "_acme-challenge.${fulldomain}." "${token}" diff --git a/dns_scripts/dns_godaddy b/dns_scripts/dns_godaddy index 9ebe6dd..a89a855 100755 --- a/dns_scripts/dns_godaddy +++ b/dns_scripts/dns_godaddy @@ -1,8 +1,8 @@ #!/bin/bash -# Copyright (C) 2017,2018) Timothe Litt litt at acm _dot org +# Copyright (C) 2017,2018 Timothe Litt litt at acm _dot org -VERSION="1.0.2" +VERSION="1.0.3" PROG="`basename $0`" # This script is used to update TXT records in GoDaddy DNS server @@ -31,6 +31,7 @@ DEBUG="$GODADDY_DEBUG" while getopts 'dhj:k:s:t:qv' opt; do case $opt in + b) GODADDY_BASE="$OPTARG" ;; d) DEBUG="Y" ;; j) JSON="$OPTARG" ;; k) GODADDY_KEY="$OPTARG" ;; @@ -72,6 +73,9 @@ Arguments: For minimal trace output (to override -q), define GODADDY_TRACE="y". Options + -b Domain name(s) in which challenge records are stored + E.g. often, www.example.net is stored in example.net. + Default from GODADDY_BASE -d Provide debugging output - all requests and responses -h This help. -j: Location of JSON.sh Default `dirname $0`/JSON.sh, or @@ -84,6 +88,7 @@ Options All output, except for this help text, is to stderr. Environment variables + GODADDY_BASE Domain name(s) in which challenge records are stored GODADDY_JSON location of the JSOH.sh script GODADDY_KEY default API key GODADDY_SCRIPT location of this script, default location of JSON.sh @@ -92,7 +97,7 @@ Environment variables GODADDY_TFILE appends protocol trace to file. Overrides -t BUGS - Due to a limitation of the gOdADDY API, deleting the last TXT record + Due to a limitation of the GoDaddy API, deleting the last TXT record would be too risky for my taste. So in that case, I replace it with _dummy.record_.domain. TXT "Ihis record is not used". This record is not automatically deleted by this script, though it's perfectly OK to @@ -207,7 +212,23 @@ if [ "$op" = "add" ]; then while [[ "$domain" =~ [^.]+\.[^.]+ ]]; do - url="$API/$domain/records/TXT/$name" + reqname="$name" + # The API doesn't trim the base domain from the name (it used to) + # If specified, remove any listed base. + if [ -n "$GODADDY_BASE" ]; then + for GDB in $GODADDY_BASE; do + gdb="`echo "$GDB" | sed -e's/\\.$//;s/\\./\\\\./g;'`" + gdb="^(.+)\\.$gdb\\.?$" + if [[ "$name" =~ $gdb ]]; then + reqname="${BASH_REMATCH[1]}" + break; + fi + done + else + eval 'reqname="$''{name%'"'.$domain.'}"'"' + fi + + url="$API/$domain/records/TXT/$reqname" request='[{"data":"'$data'","ttl":'$ttl'}]' [ -n "$DEBUG" ] && cat >&2 <&2 exit 1 # Intent was to change DNS, so this is an error From 6a1f4320005fd90d421664b650956ba9565e8208 Mon Sep 17 00:00:00 2001 From: Timothe Litt Date: Fri, 27 Sep 2019 11:58:04 -0400 Subject: [PATCH 15/43] work-around for http2 syntax differences Newer curl uses http2 by default, results in getssl: Error registering account ... JWS has no anti-replay nonce Force http1.1 as a work-around. Also add debugging info for "error in EC signing".issue opened --- getssl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/getssl b/getssl index e869b86..1453870 100755 --- a/getssl +++ b/getssl @@ -1139,7 +1139,7 @@ send_signed_request() { # Sends a request to the ACME server, signed with your p CURL_HEADER="$TEMP_DIR/curl.header" dp="$TEMP_DIR/curl.dump" - CURL="curl --silent --dump-header $CURL_HEADER " + CURL="curl --http1.1 --silent --dump-header $CURL_HEADER " if [[ ${_USE_DEBUG} -eq 1 ]]; then CURL="$CURL --trace-ascii $dp " fi @@ -1232,7 +1232,7 @@ sign_string() { # sign a string with a given key and algorithm and return urlbas 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" + error_exit "error in EC signing couldn't get R from $signed ($signalg using $key)" fi debug "R $R" From 76c9ffadc4a529a91bfe074867c8cecacf3c1464 Mon Sep 17 00:00:00 2001 From: Peter Dave Hello Date: Fri, 27 Nov 2020 14:01:41 +0800 Subject: [PATCH 16/43] Reduce git clone depth in the Dockerfile for test This change will drop the history of the repository when cloning, which will save bandwidth and disk space, speed up the clone and checkout process during the test and Docker build process. --- test/Dockerfile-alpine | 6 +++--- test/Dockerfile-centos6 | 6 +++--- test/Dockerfile-centos7 | 6 +++--- test/Dockerfile-centos7-staging | 6 +++--- test/Dockerfile-centos8 | 6 +++--- test/Dockerfile-debian | 6 +++--- test/Dockerfile-ubuntu | 6 +++--- test/Dockerfile-ubuntu-staging | 6 +++--- test/Dockerfile-ubuntu16 | 6 +++--- test/Dockerfile-ubuntu18 | 6 +++--- 10 files changed, 30 insertions(+), 30 deletions(-) diff --git a/test/Dockerfile-alpine b/test/Dockerfile-alpine index b609938..7728c8c 100644 --- a/test/Dockerfile-alpine +++ b/test/Dockerfile-alpine @@ -12,9 +12,9 @@ RUN mkdir /etc/nginx/pki RUN mkdir /etc/nginx/pki/private # BATS (Bash Automated Testings) -RUN git clone https://github.com/bats-core/bats-core.git /bats-core --branch v1.2.1 -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 git clone --depth 1 https://github.com/bats-core/bats-core.git /bats-core --branch v1.2.1 +RUN git clone --depth 1 https://github.com/bats-core/bats-support /bats-support +RUN git clone --depth 1 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 d578f4b..6db7a74 100644 --- a/test/Dockerfile-centos6 +++ b/test/Dockerfile-centos6 @@ -16,9 +16,9 @@ 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 --branch v1.2.1 -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 git clone --depth 1 https://github.com/bats-core/bats-core.git /bats-core --branch v1.2.1 +RUN git clone --depth 1 https://github.com/bats-core/bats-support /bats-support +RUN git clone --depth 1 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 e86f521..620a9c6 100644 --- a/test/Dockerfile-centos7 +++ b/test/Dockerfile-centos7 @@ -12,7 +12,7 @@ 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 --branch v1.2.1 -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 git clone --depth 1 https://github.com/bats-core/bats-core.git /bats-core --branch v1.2.1 +RUN git clone --depth 1 https://github.com/bats-core/bats-support /bats-support +RUN git clone --depth 1 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 0b2ff08..9fdb29d 100644 --- a/test/Dockerfile-centos7-staging +++ b/test/Dockerfile-centos7-staging @@ -17,9 +17,9 @@ 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 --branch v1.2.1 -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 git clone --depth 1 https://github.com/bats-core/bats-core.git /bats-core --branch v1.2.1 +RUN git clone --depth 1 https://github.com/bats-core/bats-support /bats-support +RUN git clone --depth 1 https://github.com/bats-core/bats-assert /bats-assert RUN /bats-core/install.sh /usr/local EXPOSE 80 443 diff --git a/test/Dockerfile-centos8 b/test/Dockerfile-centos8 index 4ccb817..9c144d3 100644 --- a/test/Dockerfile-centos8 +++ b/test/Dockerfile-centos8 @@ -14,7 +14,7 @@ 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 --branch v1.2.1 -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 git clone --depth 1 https://github.com/bats-core/bats-core.git /bats-core --branch v1.2.1 +RUN git clone --depth 1 https://github.com/bats-core/bats-support /bats-support +RUN git clone --depth 1 https://github.com/bats-core/bats-assert /bats-assert RUN /bats-core/install.sh /usr/local diff --git a/test/Dockerfile-debian b/test/Dockerfile-debian index b39f915..2cf919d 100644 --- a/test/Dockerfile-debian +++ b/test/Dockerfile-debian @@ -11,9 +11,9 @@ RUN mkdir /etc/nginx/pki RUN mkdir /etc/nginx/pki/private # BATS (Bash Automated Testings) -RUN git clone https://github.com/bats-core/bats-core.git /bats-core --branch v1.2.1 -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 git clone --depth 1 https://github.com/bats-core/bats-core.git /bats-core --branch v1.2.1 +RUN git clone --depth 1 https://github.com/bats-core/bats-support /bats-support +RUN git clone --depth 1 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 3849e55..21169ea 100644 --- a/test/Dockerfile-ubuntu +++ b/test/Dockerfile-ubuntu @@ -16,9 +16,9 @@ WORKDIR /root RUN touch /root/.rnd # BATS (Bash Automated Testings) -RUN git clone https://github.com/bats-core/bats-core.git /bats-core --branch v1.2.1 -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 git clone --depth 1 https://github.com/bats-core/bats-core.git /bats-core --branch v1.2.1 +RUN git clone --depth 1 https://github.com/bats-core/bats-support /bats-support +RUN git clone --depth 1 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 1ee3f83..15d5a59 100644 --- a/test/Dockerfile-ubuntu-staging +++ b/test/Dockerfile-ubuntu-staging @@ -20,9 +20,9 @@ WORKDIR /root RUN touch /root/.rnd # BATS (Bash Automated Testings) -RUN git clone https://github.com/bats-core/bats-core.git /bats-core --branch v1.2.1 -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 git clone --depth 1 https://github.com/bats-core/bats-core.git /bats-core --branch v1.2.1 +RUN git clone --depth 1 https://github.com/bats-core/bats-support /bats-support +RUN git clone --depth 1 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 41be837..038fd79 100644 --- a/test/Dockerfile-ubuntu16 +++ b/test/Dockerfile-ubuntu16 @@ -13,9 +13,9 @@ 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 /bats-core --branch v1.2.1 -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 git clone --depth 1 https://github.com/bats-core/bats-core.git /bats-core --branch v1.2.1 +RUN git clone --depth 1 https://github.com/bats-core/bats-support /bats-support +RUN git clone --depth 1 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 5e4c574..01d33d0 100644 --- a/test/Dockerfile-ubuntu18 +++ b/test/Dockerfile-ubuntu18 @@ -16,9 +16,9 @@ COPY ./test/test-config/nginx-ubuntu-no-ssl /etc/nginx/sites-enabled/default RUN touch /root/.rnd # BATS (Bash Automated Testings) -RUN git clone https://github.com/bats-core/bats-core.git /bats-core --branch v1.2.1 -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 git clone --depth 1 https://github.com/bats-core/bats-core.git /bats-core --branch v1.2.1 +RUN git clone --depth 1 https://github.com/bats-core/bats-support /bats-support +RUN git clone --depth 1 https://github.com/bats-core/bats-assert /bats-assert RUN /bats-core/install.sh /usr/local EXPOSE 80 443 From 6a6851e185f0336c95aeb4b5d46b567fbc187e32 Mon Sep 17 00:00:00 2001 From: Peter Dave Hello Date: Thu, 26 Nov 2020 12:04:48 +0800 Subject: [PATCH 17/43] Reduce Docker image layer by merging mkdir operation --- test/Dockerfile-alpine | 3 +-- test/Dockerfile-centos6 | 3 +-- test/Dockerfile-centos7 | 3 +-- test/Dockerfile-centos7-staging | 3 +-- test/Dockerfile-centos8 | 3 +-- test/Dockerfile-debian | 3 +-- test/Dockerfile-ubuntu16 | 3 +-- test/Dockerfile-ubuntu18 | 3 +-- 8 files changed, 8 insertions(+), 16 deletions(-) diff --git a/test/Dockerfile-alpine b/test/Dockerfile-alpine index 7728c8c..5b1dbf3 100644 --- a/test/Dockerfile-alpine +++ b/test/Dockerfile-alpine @@ -8,8 +8,7 @@ WORKDIR /root # Create nginx directories in standard places RUN mkdir /run/nginx -RUN mkdir /etc/nginx/pki -RUN mkdir /etc/nginx/pki/private +RUN mkdir -p /etc/nginx/pki/private # BATS (Bash Automated Testings) RUN git clone --depth 1 https://github.com/bats-core/bats-core.git /bats-core --branch v1.2.1 diff --git a/test/Dockerfile-centos6 b/test/Dockerfile-centos6 index 6db7a74..108add0 100644 --- a/test/Dockerfile-centos6 +++ b/test/Dockerfile-centos6 @@ -11,8 +11,7 @@ RUN yum -y install epel-release RUN yum -y install git curl dnsutils ldns wget nginx WORKDIR /root -RUN mkdir /etc/nginx/pki -RUN mkdir /etc/nginx/pki/private +RUN mkdir -p /etc/nginx/pki/private COPY ./test/test-config/nginx-ubuntu-no-ssl /etc/nginx/conf.d/default.conf # BATS (Bash Automated Testings) diff --git a/test/Dockerfile-centos7 b/test/Dockerfile-centos7 index 620a9c6..382a703 100644 --- a/test/Dockerfile-centos7 +++ b/test/Dockerfile-centos7 @@ -6,8 +6,7 @@ RUN yum -y install epel-release RUN yum -y install git curl ldns bind-utils wget which nginx WORKDIR /root -RUN mkdir /etc/nginx/pki -RUN mkdir /etc/nginx/pki/private +RUN mkdir -p /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 diff --git a/test/Dockerfile-centos7-staging b/test/Dockerfile-centos7-staging index 9fdb29d..abb697c 100644 --- a/test/Dockerfile-centos7-staging +++ b/test/Dockerfile-centos7-staging @@ -11,8 +11,7 @@ ENV staging "true" ENV DUCKDNS_TOKEN 1d616aa9-b8e4-4bb4-b312-3289de82badb WORKDIR /root -RUN mkdir /etc/nginx/pki -RUN mkdir /etc/nginx/pki/private +RUN mkdir -p /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 diff --git a/test/Dockerfile-centos8 b/test/Dockerfile-centos8 index 9c144d3..2b20d8f 100644 --- a/test/Dockerfile-centos8 +++ b/test/Dockerfile-centos8 @@ -8,8 +8,7 @@ RUN yum -y install epel-release RUN yum -y install git curl bind-utils wget which nginx WORKDIR /root -RUN mkdir /etc/nginx/pki -RUN mkdir /etc/nginx/pki/private +RUN mkdir -p /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 diff --git a/test/Dockerfile-debian b/test/Dockerfile-debian index 2cf919d..6da08d5 100644 --- a/test/Dockerfile-debian +++ b/test/Dockerfile-debian @@ -7,8 +7,7 @@ RUN apt-get update --fix-missing RUN apt-get install -y git curl dnsutils ldnsutils wget nginx-light WORKDIR /root -RUN mkdir /etc/nginx/pki -RUN mkdir /etc/nginx/pki/private +RUN mkdir -p /etc/nginx/pki/private # BATS (Bash Automated Testings) RUN git clone --depth 1 https://github.com/bats-core/bats-core.git /bats-core --branch v1.2.1 diff --git a/test/Dockerfile-ubuntu16 b/test/Dockerfile-ubuntu16 index 038fd79..6b13f68 100644 --- a/test/Dockerfile-ubuntu16 +++ b/test/Dockerfile-ubuntu16 @@ -8,8 +8,7 @@ RUN apt-get update --fix-missing RUN apt-get install -y git curl dnsutils ldnsutils wget nginx-light WORKDIR /root -RUN mkdir /etc/nginx/pki -RUN mkdir /etc/nginx/pki/private +RUN mkdir -p /etc/nginx/pki/private COPY ./test/test-config/nginx-ubuntu-no-ssl /etc/nginx/sites-enabled/default # BATS (Bash Automated Testings) diff --git a/test/Dockerfile-ubuntu18 b/test/Dockerfile-ubuntu18 index 01d33d0..0979cab 100644 --- a/test/Dockerfile-ubuntu18 +++ b/test/Dockerfile-ubuntu18 @@ -8,8 +8,7 @@ RUN apt-get update --fix-missing RUN apt-get install -y git curl dnsutils ldnsutils wget gawk nginx-light WORKDIR /root -RUN mkdir /etc/nginx/pki -RUN mkdir /etc/nginx/pki/private +RUN mkdir -p /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 From fb7b3ee145203257ee59c6a81fb1a9995f29084c Mon Sep 17 00:00:00 2001 From: Peter Dave Hello Date: Thu, 26 Nov 2020 20:08:44 +0800 Subject: [PATCH 18/43] Use JSON notation for Dockerfile CMD arguments MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reference: - https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#cmd > The `CMD` instruction should be used to run the software contained in your image, along with any arguments. `CMD` should almost always be used in the form of `CMD ["executable", "param1", "param2"…]` --- test/Dockerfile-alpine | 2 +- test/Dockerfile-centos6 | 2 +- test/Dockerfile-centos7-staging | 2 +- test/Dockerfile-debian | 2 +- test/Dockerfile-ubuntu | 2 +- test/Dockerfile-ubuntu-staging | 2 +- test/Dockerfile-ubuntu16 | 2 +- test/Dockerfile-ubuntu18 | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/test/Dockerfile-alpine b/test/Dockerfile-alpine index 5b1dbf3..399e10f 100644 --- a/test/Dockerfile-alpine +++ b/test/Dockerfile-alpine @@ -18,4 +18,4 @@ RUN /bats-core/install.sh /usr/local # Use supervisord to run nginx in the background COPY ./test/test-config/alpine-supervisord.conf /etc/supervisord.conf -CMD tail -f /dev/null +CMD [ "tail", "-f", "/dev/null" ] diff --git a/test/Dockerfile-centos6 b/test/Dockerfile-centos6 index 108add0..66cc468 100644 --- a/test/Dockerfile-centos6 +++ b/test/Dockerfile-centos6 @@ -23,4 +23,4 @@ RUN /bats-core/install.sh /usr/local EXPOSE 80 443 # Run eternal loop - for testing -CMD tail -f /dev/null +CMD [ "tail", "-f", "/dev/null" ] diff --git a/test/Dockerfile-centos7-staging b/test/Dockerfile-centos7-staging index abb697c..f3d985b 100644 --- a/test/Dockerfile-centos7-staging +++ b/test/Dockerfile-centos7-staging @@ -24,4 +24,4 @@ RUN /bats-core/install.sh /usr/local EXPOSE 80 443 # Run eternal loop - for testing -CMD tail -f /dev/null +CMD [ "tail", "-f", "/dev/null" ] diff --git a/test/Dockerfile-debian b/test/Dockerfile-debian index 6da08d5..640f069 100644 --- a/test/Dockerfile-debian +++ b/test/Dockerfile-debian @@ -16,4 +16,4 @@ RUN git clone --depth 1 https://github.com/bats-core/bats-assert /bats-assert RUN /bats-core/install.sh /usr/local # Run eternal loop - for testing -CMD tail -f /dev/null +CMD [ "tail", "-f", "/dev/null" ] diff --git a/test/Dockerfile-ubuntu b/test/Dockerfile-ubuntu index 21169ea..953c3bf 100644 --- a/test/Dockerfile-ubuntu +++ b/test/Dockerfile-ubuntu @@ -22,4 +22,4 @@ RUN git clone --depth 1 https://github.com/bats-core/bats-assert /bats-assert RUN /bats-core/install.sh /usr/local # Run eternal loop - for testing -CMD tail -f /dev/null +CMD [ "tail", "-f", "/dev/null" ] diff --git a/test/Dockerfile-ubuntu-staging b/test/Dockerfile-ubuntu-staging index 15d5a59..8fc3455 100644 --- a/test/Dockerfile-ubuntu-staging +++ b/test/Dockerfile-ubuntu-staging @@ -26,4 +26,4 @@ RUN git clone --depth 1 https://github.com/bats-core/bats-assert /bats-assert RUN /bats-core/install.sh /usr/local # Run eternal loop - for testing -CMD tail -f /dev/null +CMD [ "tail", "-f", "/dev/null" ] diff --git a/test/Dockerfile-ubuntu16 b/test/Dockerfile-ubuntu16 index 6b13f68..bb521ff 100644 --- a/test/Dockerfile-ubuntu16 +++ b/test/Dockerfile-ubuntu16 @@ -18,4 +18,4 @@ RUN git clone --depth 1 https://github.com/bats-core/bats-assert /bats-assert RUN /bats-core/install.sh /usr/local # Run eternal loop - for testing -CMD tail -f /dev/null +CMD [ "tail", "-f", "/dev/null" ] diff --git a/test/Dockerfile-ubuntu18 b/test/Dockerfile-ubuntu18 index 0979cab..98b71af 100644 --- a/test/Dockerfile-ubuntu18 +++ b/test/Dockerfile-ubuntu18 @@ -23,4 +23,4 @@ RUN /bats-core/install.sh /usr/local EXPOSE 80 443 # Run eternal loop - for testing -CMD tail -f /dev/null +CMD [ "tail", "-f", "/dev/null" ] From 522918f023d2be3093583c834efc48db74627ffb Mon Sep 17 00:00:00 2001 From: Tim Kimber Date: Thu, 18 Feb 2021 17:06:13 +0000 Subject: [PATCH 19/43] Add FULL_CHAIN_INCLUDE_ROOT Fixes #594 #272 #564 --- getssl | 30 +++++++++++- test/36-full-chain-inc-root.bats | 81 ++++++++++++++++++++++++++++++++ 2 files changed, 110 insertions(+), 1 deletion(-) create mode 100644 test/36-full-chain-inc-root.bats diff --git a/getssl b/getssl index 066a83c..80a3fae 100755 --- a/getssl +++ b/getssl @@ -256,7 +256,8 @@ # 2021-02-07 Allow -u --upgrade without any domain, so that one can only update the script (Benno-K)(2.34) # 2021-02-09 Prevent listing the complete file if version tag missing (#637)(softins) # 2021-02-12 Add PREFERRED_CHAIN -# 2021-02-15 ADD ftp explicit SSL with curl for upload the challenge +# 2021-02-15 ADD ftp explicit SSL with curl for upload the challenge (CoolMischa) +# 2021-02-18 Add FULL_CHAIN_INCLUDE_ROOT # ---------------------------------------------------------------------------------------- case :$SHELLOPTS: in @@ -283,6 +284,7 @@ DEFAULT_REVOKE_CA="https://acme-v02.api.letsencrypt.org" DOMAIN_KEY_LENGTH=4096 DUAL_RSA_ECDSA="false" FTP_OPTIONS="" +FULL_CHAIN_INCLUDE_ROOT="false" GETSSL_IGNORE_CP_PRESERVE="false" HTTP_TOKEN_CHECK_WAIT=0 IGNORE_DIRECTORY_DOMAIN="false" @@ -1598,7 +1600,27 @@ get_certificate() { # get certificate for csr, if all domains validated. # tidy up rm -f "$cert_to_check" fi + 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" + if [[ "$FULL_CHAIN_INCLUDE_ROOT" = "true" ]]; then + # Some of the code below was copied from zakjan/cert-chain-resolver + + # Download the certificate for the issuer using the "CA Issuers" attribute from the AIA x509 extension + issuer_url=$(openssl x509 -inform pem -noout -text -in "$gc_certfile" | awk 'BEGIN {FS="CA Issuers - URI:"} NF==2 {print $2; exit}') + debug Issuer for "$gc_certfile" is "$issuer_url" + + # Keep downloading issuer certficates until we find the root certificate (which doesn't have a "CA Issuers" attribure) + cp "$gc_certfile" "$gc_fullchain" + while [[ -n "$issuer_url" ]]; do + debug Fetching certificate issuer from "$issuer_url" + issuer_cert=$(curl --user-agent "$CURL_USERAGENT" --silent "$issuer_url" | openssl x509 -inform der -outform pem) + debug Fetched issuer certificate "$(echo "$issuer_cert" | openssl x509 -inform pem -noout -text | awk 'BEGIN {FS="Subject: "} NF==2 {print $2; exit}')" + echo "$issuer_cert" >> "$gc_fullchain" + + # get issuer for the certificate that's just been downloaded + issuer_url=$(echo "$issuer_cert" | openssl x509 -inform pem -noout -text | awk 'BEGIN {FS="CA Issuers - URI:"} NF==2 {print $2; exit}') + done + fi info "Certificate saved in $gc_certfile" fi } @@ -2382,6 +2404,9 @@ write_domain_template() { # write out a template file for a domain. # Production options are: "ISRG Root X1" and "ISRG Root X2" #PREFERRED_CHAIN="" + # Uncomment this if you need the full chain file to include the root certificate (Java keystores, Nutanix Prism) + #FULL_CHAIN_INCLUDE_ROOT="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/ssl/${DOMAIN}.crt" # this is domain cert @@ -2441,6 +2466,9 @@ write_getssl_template() { # write out the main template file # Production options are: "ISRG Root X1" and "ISRG Root X2" #PREFERRED_CHAIN="" + # Uncomment this if you need the full chain file to include the root certificate (Java keystores, Nutanix Prism) + #FULL_CHAIN_INCLUDE_ROOT="true" + # The command needed to reload apache / nginx or whatever you use. # Several (ssh) commands may be given using a bash array: # RELOAD_CMD=('ssh:sshuserid@server5:systemctl reload httpd' 'logger getssl for server5 efficient.') diff --git a/test/36-full-chain-inc-root.bats b/test/36-full-chain-inc-root.bats new file mode 100644 index 0000000..5932ea7 --- /dev/null +++ b/test/36-full-chain-inc-root.bats @@ -0,0 +1,81 @@ +#! /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() { + if [ -z "$STAGING" ]; then + export CURL_CA_BUNDLE=/root/pebble-ca-bundle.crt + fi +} + + +@test "Use FULL_CHAIN_INCLUDE_ROOT to include the root certificate in the fullchain" { + CONFIG_FILE="getssl-dns01.cfg" + setup_environment + init_getssl + + cat <<- EOF > ${INSTALL_DIR}/.getssl/${GETSSL_CMD_HOST}/getssl_test_specific.cfg +FULL_CHAIN_INCLUDE_ROOT="true" +EOF + + create_certificate + assert_success + check_output_for_errors + + if [ -n "$STAGING" ]; then + PREFERRED_CHAIN="Fake LE Root X1" + else + # pebble doesn't support CA Issuers so the fullchain.crt will just contain the certificate (code path means it won't contain the intermediate cert in this case) + # This is testing that requesting FULL_CHAIN_INCLUDE_ROOT doesn't fail if there is no CA Issuers in the certificate + PREFERRED_CHAIN=$(openssl crl2pkcs7 -nocrl -certfile "${INSTALL_DIR}/.getssl/${GETSSL_CMD_HOST}/${GETSSL_CMD_HOST}.crt" | openssl pkcs7 -print_certs -text -noout | grep Subject: | tail -1 | cut -d= -f2) + fi + + final_issuer=$(openssl crl2pkcs7 -nocrl -certfile "${INSTALL_DIR}/.getssl/${GETSSL_CMD_HOST}/fullchain.crt" | openssl pkcs7 -print_certs -text -noout | grep Subject: | tail -1 | cut -d= -f2) + # verify certificate includes the chain root + [ "$PREFERRED_CHAIN" = "$final_issuer" ] +} + + +@test "Use FULL_CHAIN_INCLUDE_ROOT with dual certificates" { + if [ -n "$STAGING" ]; then + PREFERRED_CHAIN="Fake LE Root X1" + fi + + CONFIG_FILE="getssl-dns01.cfg" + setup_environment + init_getssl + + cat <<- EOF > ${INSTALL_DIR}/.getssl/${GETSSL_CMD_HOST}/getssl_test_specific.cfg +FULL_CHAIN_INCLUDE_ROOT="true" +DUAL_RSA_ECDSA="true" +ACCOUNT_KEY_TYPE="prime256v1" +PRIVATE_KEY_ALG="prime256v1" +CHECK_REMOTE="false" +EOF + + 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" ] + + if [ -n "$STAGING" ]; then + PREFERRED_CHAIN="Fake LE Root X1" + else + # pebble doesn't support CA Issuers so the fullchain.crt will just contain the certificate (code path means it won't contain the intermediate cert in this case) + # This is testing that requesting FULL_CHAIN_INCLUDE_ROOT doesn't fail if there is no CA Issuers in the certificate + PREFERRED_CHAIN=$(openssl crl2pkcs7 -nocrl -certfile "${INSTALL_DIR}/.getssl/${GETSSL_CMD_HOST}/${GETSSL_CMD_HOST}.crt" | openssl pkcs7 -print_certs -text -noout | grep Subject: | tail -1 | cut -d= -f2) + fi + + # verify both rsa and ecdsa certificates include the chain root + final_issuer=$(openssl crl2pkcs7 -nocrl -certfile "${INSTALL_DIR}/.getssl/${GETSSL_CMD_HOST}/fullchain.crt" | openssl pkcs7 -print_certs -text -noout | grep Subject: | tail -1 | cut -d= -f2) + [ "$PREFERRED_CHAIN" = "$final_issuer" ] + ecdsa_final_issuer=$(openssl crl2pkcs7 -nocrl -certfile "${INSTALL_DIR}/.getssl/${GETSSL_CMD_HOST}/fullchain.ec.crt" | openssl pkcs7 -print_certs -text -noout | grep Subject: | tail -1 | cut -d= -f2) + [ "$PREFERRED_CHAIN" = "$ecdsa_final_issuer" ] +} From e19afe87af0863c95f570997c537e8bbad3d006a Mon Sep 17 00:00:00 2001 From: Tim Kimber Date: Thu, 18 Feb 2021 17:14:44 +0000 Subject: [PATCH 20/43] Fixes double slash reported in #585 --- getssl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/getssl b/getssl index 80a3fae..88b4d9f 100755 --- a/getssl +++ b/getssl @@ -302,7 +302,7 @@ OCSP_MUST_STAPLE="false" TEMP_UPGRADE_FILE="" TOKEN_USER_ID="" USE_SINGLE_ACL="false" -WORKING_DIR_CANDIDATES=("/etc/getssl/" "${PROGDIR}/conf" "${PROGDIR}/.getssl" "${HOME}/.getssl") +WORKING_DIR_CANDIDATES=("/etc/getssl" "${PROGDIR}/conf" "${PROGDIR}/.getssl" "${HOME}/.getssl") # Variables used when validating using a DNS entry VALIDATE_VIA_DNS="" # Set this to "true" to enable DNS validation From 27af6d9f2f8043e4df47e1df2a23c2bf0c9de7ee Mon Sep 17 00:00:00 2001 From: Tim Kimber Date: Fri, 19 Feb 2021 15:51:56 +0000 Subject: [PATCH 21/43] Fix test scripts to add to dns once and remove once --- test/17-test-spaces-in-sans-dns01.bats | 19 +++++-------------- test/17-test-spaces-in-sans-http01.bats | 19 +++++-------------- 2 files changed, 10 insertions(+), 28 deletions(-) diff --git a/test/17-test-spaces-in-sans-dns01.bats b/test/17-test-spaces-in-sans-dns01.bats index 9f3b3dc..9d425af 100644 --- a/test/17-test-spaces-in-sans-dns01.bats +++ b/test/17-test-spaces-in-sans-dns01.bats @@ -50,11 +50,6 @@ setup() { CONFIG_FILE="getssl-dns01-spaces-sans-and-ignore-dir-domain.cfg" setup_environment - # Add hosts to DNS (also need to be added as aliases in docker-compose.yml) - for prefix in a b c; do - curl --silent -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 @@ -70,10 +65,6 @@ setup() { assert_success check_output_for_errors cleanup_environment - - for prefix in a b c; do - curl --silent -X POST -d '{"host":"'$prefix.$GETSSL_HOST'"}' http://10.30.50.3:8055/clear-a - done } @@ -84,13 +75,13 @@ setup() { CONFIG_FILE="getssl-dns01-spaces-and-commas-sans.cfg" setup_environment - # Add hosts to DNS (also need to be added as aliases in docker-compose.yml) - for prefix in a b c; do - curl --silent -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 check_output_for_errors + cleanup_environment + + for prefix in a b c; do + curl --silent -X POST -d '{"host":"'$prefix.$GETSSL_HOST'"}' http://10.30.50.3:8055/clear-a + done } diff --git a/test/17-test-spaces-in-sans-http01.bats b/test/17-test-spaces-in-sans-http01.bats index fab530f..1730e99 100644 --- a/test/17-test-spaces-in-sans-http01.bats +++ b/test/17-test-spaces-in-sans-http01.bats @@ -50,11 +50,6 @@ setup() { CONFIG_FILE="getssl-http01-spaces-sans-and-ignore-dir-domain.cfg" setup_environment - # Add hosts to DNS (also need to be added as aliases in docker-compose.yml) - for prefix in a b c; do - curl --silent -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 @@ -70,10 +65,6 @@ setup() { assert_success check_output_for_errors cleanup_environment - - for prefix in a b c; do - curl --silent -X POST -d '{"host":"'$prefix.$GETSSL_HOST'"}' http://10.30.50.3:8055/clear-a - done } @@ -84,13 +75,13 @@ setup() { CONFIG_FILE="getssl-http01-spaces-and-commas-sans.cfg" setup_environment - # Add hosts to DNS (also need to be added as aliases in docker-compose.yml) - for prefix in a b c; do - curl --silent -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 check_output_for_errors + cleanup_environment + + for prefix in a b c; do + curl --silent -X POST -d '{"host":"'$prefix.$GETSSL_HOST'"}' http://10.30.50.3:8055/clear-a + done } From 2863e9e52d0f2c65536a41891fe7be79ede80253 Mon Sep 17 00:00:00 2001 From: Tim Kimber Date: Fri, 19 Feb 2021 15:52:24 +0000 Subject: [PATCH 22/43] Clear previous dns entries before adding a new entry --- test/test_helper.bash | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/test_helper.bash b/test/test_helper.bash index 11cdf44..48a8d4b 100644 --- a/test/test_helper.bash +++ b/test/test_helper.bash @@ -64,6 +64,8 @@ setup_environment() { fi if [ -z "$STAGING" ]; then + # Make sure that we have cleared any previous entries, otherwise get random dns failures + curl --silent -X POST -d '{"host":"'"$GETSSL_HOST"'"}' http://10.30.50.3:8055/clear-a curl --silent -X POST -d '{"host":"'"$GETSSL_HOST"'", "addresses":["'"$GETSSL_IP"'"]}' http://10.30.50.3:8055/add-a fi cp ${CODE_DIR}/test/test-config/nginx-ubuntu-no-ssl "${NGINX_CONFIG}" From 4f5b518038116405aef6ed554fab6bd54b392e8d Mon Sep 17 00:00:00 2001 From: Tim Kimber Date: Sat, 20 Feb 2021 11:55:20 +0000 Subject: [PATCH 23/43] Update staging certificate names --- getssl | 12 +++++---- test/35-preferred-chain.bats | 45 +++++++++++++++++++++----------- test/36-full-chain-inc-root.bats | 35 +++++++++++++++++-------- 3 files changed, 61 insertions(+), 31 deletions(-) diff --git a/getssl b/getssl index 88b4d9f..d73faea 100755 --- a/getssl +++ b/getssl @@ -1582,7 +1582,7 @@ get_certificate() { # get certificate for csr, if all domains validated. cp "$gc_fullchain" "$cert_to_check" i=0 while [[ $i -le ${#alternate_links[@]} ]]; do - cert_issuer=$(openssl crl2pkcs7 -nocrl -certfile "$cert_to_check" | openssl pkcs7 -print_certs -text -noout | grep 'Issuer:' | tail -1 | cut -d= -f2) + cert_issuer=$(openssl crl2pkcs7 -nocrl -certfile "$cert_to_check" | openssl pkcs7 -print_certs -text -noout | grep 'Issuer:' | tail -1 | awk -F"CN=" '{ print $2 }') debug Certificate issued by "$cert_issuer" if [[ $cert_issuer = *${PREFERRED_CHAIN}* ]]; then debug "Found required certificate" @@ -2400,9 +2400,10 @@ write_domain_template() { # write out a template file for a domain. #USE_SINGLE_ACL="false" # Preferred Chain - use an different certificate root from the default - # Staging options are: "Fake LE Root X1" and "Fake LE Root X2" + # This uses wildcard matching so requesting "X1" returns the correct certificate - may need to escape characters + # Staging options are: "(STAGING) Doctored Durian Root CA X3" and "(STAGING) Pretend Pear X1" # Production options are: "ISRG Root X1" and "ISRG Root X2" - #PREFERRED_CHAIN="" + #PREFERRED_CHAIN="\(STAGING\) Pretend Pear X1" # Uncomment this if you need the full chain file to include the root certificate (Java keystores, Nutanix Prism) #FULL_CHAIN_INCLUDE_ROOT="true" @@ -2462,9 +2463,10 @@ write_getssl_template() { # write out the main template file #REUSE_PRIVATE_KEY="true" # Preferred Chain - use an different certificate root from the default - # Staging options are: "Fake LE Root X1" and "Fake LE Root X2" + # This uses wildcard matching so requesting "X1" returns the correct certificate - may need to escape characters + # Staging options are: "(STAGING) Doctored Durian Root CA X3" and "(STAGING) Pretend Pear X1" # Production options are: "ISRG Root X1" and "ISRG Root X2" - #PREFERRED_CHAIN="" + #PREFERRED_CHAIN="\(STAGING\) Pretend Pear X1" # Uncomment this if you need the full chain file to include the root certificate (Java keystores, Nutanix Prism) #FULL_CHAIN_INCLUDE_ROOT="true" diff --git a/test/35-preferred-chain.bats b/test/35-preferred-chain.bats index 4389d3b..0a5821c 100644 --- a/test/35-preferred-chain.bats +++ b/test/35-preferred-chain.bats @@ -15,10 +15,12 @@ setup() { @test "Use PREFERRED_CHAIN to select an alternate root" { if [ -n "$STAGING" ]; then - PREFERRED_CHAIN="Fake LE Root X2" + PREFERRED_CHAIN="\(STAGING\) Pretend Pear X1" + CHECK_CHAIN="(STAGING) Pretend Pear X1" else - PREFERRED_CHAIN=$(curl --silent https://pebble:15000/roots/2 | openssl x509 -text -noout | grep "Issuer:" | cut -d= -f2) + PREFERRED_CHAIN=$(curl --silent https://pebble:15000/roots/2 | openssl x509 -text -noout | grep "Issuer:" | awk -F"CN=" '{ print $2 }') PREFERRED_CHAIN="${PREFERRED_CHAIN# }" # remove leading whitespace + CHECK_CHAIN=$PREFERRED_CHAIN fi CONFIG_FILE="getssl-dns01.cfg" @@ -29,21 +31,27 @@ setup() { PREFERRED_CHAIN="${PREFERRED_CHAIN}" EOF - create_certificate + create_certificate -d assert_success check_output_for_errors - issuer=$(openssl crl2pkcs7 -nocrl -certfile "${INSTALL_DIR}/.getssl/${GETSSL_CMD_HOST}/fullchain.crt" | openssl pkcs7 -print_certs -text -noout | grep Issuer: | tail -1 | cut -d= -f2) + issuer=$(openssl crl2pkcs7 -nocrl -certfile "${INSTALL_DIR}/.getssl/${GETSSL_CMD_HOST}/fullchain.crt" | openssl pkcs7 -print_certs -text -noout | grep Issuer: | tail -1 | awk -F"CN=" '{ print $2 }') # verify certificate is issued by preferred chain root - [ "$PREFERRED_CHAIN" = "$issuer" ] + if [[ "${CHECK_CHAIN}" != "$issuer" ]]; then + echo "# PREFERRED_CHAIN=$PREFERRED_CHAIN" + echo "# issuer=$issuer" + fi + + [ "${CHECK_CHAIN}" = "$issuer" ] } @test "Use PREFERRED_CHAIN to select the default root" { if [ -n "$STAGING" ]; then - PREFERRED_CHAIN="Fake LE Root X1" + PREFERRED_CHAIN="\(STAGING\) Doctored Durian Root CA X3" + CHECK_CHAIN="(STAGING) Doctored Durian Root CA X3" else - PREFERRED_CHAIN=$(curl --silent https://pebble:15000/roots/0 | openssl x509 -text -noout | grep Issuer: | cut -d= -f2 ) + PREFERRED_CHAIN=$(curl --silent https://pebble:15000/roots/0 | openssl x509 -text -noout | grep Issuer: | awk -F"CN=" '{ print $2 }') PREFERRED_CHAIN="${PREFERRED_CHAIN# }" # remove leading whitespace fi @@ -59,17 +67,21 @@ EOF assert_success check_output_for_errors - issuer=$(openssl crl2pkcs7 -nocrl -certfile "${INSTALL_DIR}/.getssl/${GETSSL_CMD_HOST}/fullchain.crt" | openssl pkcs7 -print_certs -text -noout | grep Issuer: | tail -1 | cut -d= -f2) + issuer=$(openssl crl2pkcs7 -nocrl -certfile "${INSTALL_DIR}/.getssl/${GETSSL_CMD_HOST}/fullchain.crt" | openssl pkcs7 -print_certs -text -noout | grep Issuer: | tail -1 | awk -F"CN=" '{ print $2 }') # verify certificate is issued by preferred chain root - [ "$PREFERRED_CHAIN" = "$issuer" ] + if [[ "${CHECK_CHAIN}" != "$issuer" ]]; then + echo "# PREFERRED_CHAIN=$PREFERRED_CHAIN" + echo "# issuer=$issuer" + fi + [ "${CHECK_CHAIN}" = "$issuer" ] } @test "Use PREFERRED_CHAIN to select an alternate root by suffix" { if [ -n "$STAGING" ]; then - FULL_PREFERRED_CHAIN="Fake LE Root X2" + FULL_PREFERRED_CHAIN="(STAGING) Pretend Pear X1" else - FULL_PREFERRED_CHAIN=$(curl --silent https://pebble:15000/roots/2 | openssl x509 -text -noout | grep "Issuer:" | cut -d= -f2) + FULL_PREFERRED_CHAIN=$(curl --silent https://pebble:15000/roots/2 | openssl x509 -text -noout | grep "Issuer:" | awk -F"CN=" '{ print $2 }') FULL_PREFERRED_CHAIN="${FULL_PREFERRED_CHAIN# }" # remove leading whitespace fi @@ -87,9 +99,12 @@ EOF assert_success check_output_for_errors - issuer=$(openssl crl2pkcs7 -nocrl -certfile "${INSTALL_DIR}/.getssl/${GETSSL_CMD_HOST}/fullchain.crt" | openssl pkcs7 -print_certs -text -noout | grep Issuer: | tail -1 | cut -d= -f2) + issuer=$(openssl crl2pkcs7 -nocrl -certfile "${INSTALL_DIR}/.getssl/${GETSSL_CMD_HOST}/fullchain.crt" | openssl pkcs7 -print_certs -text -noout | grep Issuer: | tail -1 | awk -F"CN=" '{ print $2 }') # verify certificate is issued by preferred chain root - echo "# ${issuer}" - echo "# ${FULL_PREFERRED_CHAIN}" - [ "$FULL_PREFERRED_CHAIN" = "$issuer" ] + if [[ "${FULL_PREFERRED_CHAIN}" != "$issuer" ]]; then + echo "# PREFERRED_CHAIN=$PREFERRED_CHAIN" + echo "# FULL_PREFERRED_CHAIN=$FULL_PREFERRED_CHAIN" + echo "# issuer=$issuer" + fi + [ "${FULL_PREFERRED_CHAIN}" = "$issuer" ] } diff --git a/test/36-full-chain-inc-root.bats b/test/36-full-chain-inc-root.bats index 5932ea7..5b29d0b 100644 --- a/test/36-full-chain-inc-root.bats +++ b/test/36-full-chain-inc-root.bats @@ -27,22 +27,27 @@ EOF check_output_for_errors if [ -n "$STAGING" ]; then - PREFERRED_CHAIN="Fake LE Root X1" + PREFERRED_CHAIN="(STAGING) Doctored Durian Root CA X3" else # pebble doesn't support CA Issuers so the fullchain.crt will just contain the certificate (code path means it won't contain the intermediate cert in this case) # This is testing that requesting FULL_CHAIN_INCLUDE_ROOT doesn't fail if there is no CA Issuers in the certificate - PREFERRED_CHAIN=$(openssl crl2pkcs7 -nocrl -certfile "${INSTALL_DIR}/.getssl/${GETSSL_CMD_HOST}/${GETSSL_CMD_HOST}.crt" | openssl pkcs7 -print_certs -text -noout | grep Subject: | tail -1 | cut -d= -f2) + PREFERRED_CHAIN=$(openssl crl2pkcs7 -nocrl -certfile "${INSTALL_DIR}/.getssl/${GETSSL_CMD_HOST}/${GETSSL_CMD_HOST}.crt" | openssl pkcs7 -print_certs -text -noout | grep Subject: | tail -1 | awk -F"CN=" '{ print $2 }') fi - final_issuer=$(openssl crl2pkcs7 -nocrl -certfile "${INSTALL_DIR}/.getssl/${GETSSL_CMD_HOST}/fullchain.crt" | openssl pkcs7 -print_certs -text -noout | grep Subject: | tail -1 | cut -d= -f2) + final_issuer=$(openssl crl2pkcs7 -nocrl -certfile "${INSTALL_DIR}/.getssl/${GETSSL_CMD_HOST}/fullchain.crt" | openssl pkcs7 -print_certs -text -noout | grep Subject: | tail -1 | awk -F"CN=" '{ print $2 }') + # verify certificate includes the chain root - [ "$PREFERRED_CHAIN" = "$final_issuer" ] + if [[ "${PREFERRED_CHAIN}" != "$final_issuer" ]]; then + echo "# PREFERRED_CHAIN=$PREFERRED_CHAIN" + echo "# final_issuer=$final_issuer" + fi + [ "${PREFERRED_CHAIN}" = "$final_issuer" ] } @test "Use FULL_CHAIN_INCLUDE_ROOT with dual certificates" { if [ -n "$STAGING" ]; then - PREFERRED_CHAIN="Fake LE Root X1" + PREFERRED_CHAIN="(STAGING) Doctored Durian Root CA X3" fi CONFIG_FILE="getssl-dns01.cfg" @@ -66,16 +71,24 @@ EOF assert [ -e "${INSTALL_DIR}/.getssl/${GETSSL_CMD_HOST}/${GETSSL_CMD_HOST}.ec.crt" ] if [ -n "$STAGING" ]; then - PREFERRED_CHAIN="Fake LE Root X1" + PREFERRED_CHAIN="(STAGING) Doctored Durian Root CA X3" else # pebble doesn't support CA Issuers so the fullchain.crt will just contain the certificate (code path means it won't contain the intermediate cert in this case) # This is testing that requesting FULL_CHAIN_INCLUDE_ROOT doesn't fail if there is no CA Issuers in the certificate - PREFERRED_CHAIN=$(openssl crl2pkcs7 -nocrl -certfile "${INSTALL_DIR}/.getssl/${GETSSL_CMD_HOST}/${GETSSL_CMD_HOST}.crt" | openssl pkcs7 -print_certs -text -noout | grep Subject: | tail -1 | cut -d= -f2) + PREFERRED_CHAIN=$(openssl crl2pkcs7 -nocrl -certfile "${INSTALL_DIR}/.getssl/${GETSSL_CMD_HOST}/${GETSSL_CMD_HOST}.crt" | openssl pkcs7 -print_certs -text -noout | grep Subject: | tail -1 | awk -F"CN=" '{ print $2 }') fi # verify both rsa and ecdsa certificates include the chain root - final_issuer=$(openssl crl2pkcs7 -nocrl -certfile "${INSTALL_DIR}/.getssl/${GETSSL_CMD_HOST}/fullchain.crt" | openssl pkcs7 -print_certs -text -noout | grep Subject: | tail -1 | cut -d= -f2) - [ "$PREFERRED_CHAIN" = "$final_issuer" ] - ecdsa_final_issuer=$(openssl crl2pkcs7 -nocrl -certfile "${INSTALL_DIR}/.getssl/${GETSSL_CMD_HOST}/fullchain.ec.crt" | openssl pkcs7 -print_certs -text -noout | grep Subject: | tail -1 | cut -d= -f2) - [ "$PREFERRED_CHAIN" = "$ecdsa_final_issuer" ] + final_issuer=$(openssl crl2pkcs7 -nocrl -certfile "${INSTALL_DIR}/.getssl/${GETSSL_CMD_HOST}/fullchain.crt" | openssl pkcs7 -print_certs -text -noout | grep Subject: | tail -1 | awk -F"CN=" '{ print $2 }') + if [[ "${PREFERRED_CHAIN}" != "$final_issuer" ]]; then + echo "# PREFERRED_CHAIN=$PREFERRED_CHAIN" + echo "# final_issuer=$final_issuer" + fi + [ "${PREFERRED_CHAIN}" = "$final_issuer" ] + ecdsa_final_issuer=$(openssl crl2pkcs7 -nocrl -certfile "${INSTALL_DIR}/.getssl/${GETSSL_CMD_HOST}/fullchain.ec.crt" | openssl pkcs7 -print_certs -text -noout | grep Subject: | tail -1 | awk -F"CN=" '{ print $2 }') + if [[ "$PREFERRED_CHAIN" != "$ecdsa_final_issuer" ]]; then + echo "# PREFERRED_CHAIN=$PREFERRED_CHAIN" + echo "# ecdsa_final_issuer=$ecdsa_final_issuer" + fi + [ "${PREFERRED_CHAIN}" = "$ecdsa_final_issuer" ] } From da63cf3ac42b43c18af8e42a982bdc236c80e668 Mon Sep 17 00:00:00 2001 From: Tim Kimber Date: Sat, 20 Feb 2021 15:07:58 +0000 Subject: [PATCH 24/43] Fix awk command for pebble --- test/35-preferred-chain.bats | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/test/35-preferred-chain.bats b/test/35-preferred-chain.bats index 0a5821c..b16e77a 100644 --- a/test/35-preferred-chain.bats +++ b/test/35-preferred-chain.bats @@ -18,7 +18,7 @@ setup() { PREFERRED_CHAIN="\(STAGING\) Pretend Pear X1" CHECK_CHAIN="(STAGING) Pretend Pear X1" else - PREFERRED_CHAIN=$(curl --silent https://pebble:15000/roots/2 | openssl x509 -text -noout | grep "Issuer:" | awk -F"CN=" '{ print $2 }') + PREFERRED_CHAIN=$(curl --silent https://pebble:15000/roots/2 | openssl x509 -text -noout | grep "Issuer:" | awk -F"CN *= *" '{ print $2 }') PREFERRED_CHAIN="${PREFERRED_CHAIN# }" # remove leading whitespace CHECK_CHAIN=$PREFERRED_CHAIN fi @@ -51,8 +51,9 @@ EOF PREFERRED_CHAIN="\(STAGING\) Doctored Durian Root CA X3" CHECK_CHAIN="(STAGING) Doctored Durian Root CA X3" else - PREFERRED_CHAIN=$(curl --silent https://pebble:15000/roots/0 | openssl x509 -text -noout | grep Issuer: | awk -F"CN=" '{ print $2 }') + PREFERRED_CHAIN=$(curl --silent https://pebble:15000/roots/0 | openssl x509 -text -noout | grep Issuer: | awk -F"CN *= *" '{ print $2 }') PREFERRED_CHAIN="${PREFERRED_CHAIN# }" # remove leading whitespace + CHECK_CHAIN=$PREFERRED_CHAIN fi CONFIG_FILE="getssl-dns01.cfg" @@ -81,7 +82,7 @@ EOF if [ -n "$STAGING" ]; then FULL_PREFERRED_CHAIN="(STAGING) Pretend Pear X1" else - FULL_PREFERRED_CHAIN=$(curl --silent https://pebble:15000/roots/2 | openssl x509 -text -noout | grep "Issuer:" | awk -F"CN=" '{ print $2 }') + FULL_PREFERRED_CHAIN=$(curl --silent https://pebble:15000/roots/2 | openssl x509 -text -noout | grep "Issuer:" | awk -F"CN *= *" '{ print $2 }') FULL_PREFERRED_CHAIN="${FULL_PREFERRED_CHAIN# }" # remove leading whitespace fi From 8bb211417457bf20e0a964d703a86acb9bf83023 Mon Sep 17 00:00:00 2001 From: Tim Kimber Date: Sat, 20 Feb 2021 15:49:37 +0000 Subject: [PATCH 25/43] Remove debug --- test/35-preferred-chain.bats | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/35-preferred-chain.bats b/test/35-preferred-chain.bats index b16e77a..9c3fc6b 100644 --- a/test/35-preferred-chain.bats +++ b/test/35-preferred-chain.bats @@ -31,7 +31,7 @@ setup() { PREFERRED_CHAIN="${PREFERRED_CHAIN}" EOF - create_certificate -d + create_certificate assert_success check_output_for_errors From 39ddb2da7a116d6499b5aa06ea304e5339a2dd97 Mon Sep 17 00:00:00 2001 From: Tim Kimber Date: Sat, 13 Mar 2021 20:27:07 +0000 Subject: [PATCH 26/43] Tweaked code in requires() so it doesn't break function auto complete in vscode --- getssl | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/getssl b/getssl index d73faea..1f1ca7c 100755 --- a/getssl +++ b/getssl @@ -2073,16 +2073,18 @@ revoke_certificate() { # revoke a certificate } requires() { # check if required function is available + args=("${@}") + lastarg=${args[${#args[@]}-1]} 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 + if [[ "$i" == "$lastarg" ]]; 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" + debug "function $i found at $res - setting ${lastarg} to $i" + eval "${lastarg}=\$i" return fi done From 761e61fdeda1c0fc768c0327e455c5c701ea145e Mon Sep 17 00:00:00 2001 From: Tim Kimber Date: Sat, 13 Mar 2021 20:27:42 +0000 Subject: [PATCH 27/43] Add dns scripts for cpanel --- dns_scripts/dns_add_cpanel | 76 ++++++++++++++++++++++++++++++++++++++ dns_scripts/dns_del_cpanel | 69 ++++++++++++++++++++++++++++++++++ 2 files changed, 145 insertions(+) create mode 100644 dns_scripts/dns_add_cpanel create mode 100644 dns_scripts/dns_del_cpanel diff --git a/dns_scripts/dns_add_cpanel b/dns_scripts/dns_add_cpanel new file mode 100644 index 0000000..24a1ca8 --- /dev/null +++ b/dns_scripts/dns_add_cpanel @@ -0,0 +1,76 @@ +#!/usr/bin/env bash + +# Need to add your email address and API key to cpanel below or set as env variables +user=${CPANEL_USERNAME:-''} +password=${CPANEL_PASSWORD:-''} +url=${CPANEL_URL:-''} # e.g. https://www.cpanel-host.test:2083 +apitoken=${CPANEL_APITOKEN:-''} + +fulldomain="${1}" +token="${2}" + +# 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 "$user" ]]; then + echo "CPANEL_USERNAME (username) parameter not set" + exit 1 +fi +if [[ -z "$apitoken" ]] && [[ -z "$password" ]]; then + echo "Must set either CPANEL_APITOKEN or CPANEL_PASSWORD in dns script, environment variable or getssl.cfg" + exit 1 +fi +if [[ -z "$url" ]]; then + echo "CPANEL_URL (url) parameter not set" + exit 1 +fi + +# Setup +request_func="${url}/json-api/cpanel?cpanel_jsonapi_apiversion=2&cpanel_jsonapi_module=ZoneEdit" +if [[ -n $apitoken ]]; then + curl_params=( -H "Authorization: cpanel $user:$apitoken" ) +else + auth_string=$(echo -ne "$user:$password" | base64 --wrap 0) + curl_params=( -H "Authorization: Basic $auth_string" ) +fi + +# Check if domain is a CNAME +res=$(dig CNAME "$fulldomain") +domain=$(echo "$res"| awk '$4 ~ "CNAME" {print $5}' |sed 's/\.$//g') +if [[ -n "$domain" ]]; then + name=".${fulldomain%.$domain}" +else + domain=$fulldomain + name="" +fi + +# Check to see if challenge dns entry already exists (update or delete?) +request_params="&cpanel_jsonapi_func=fetchzone_records&domain=${domain}&type=TXT&name=_acme-challenge.${fulldomain}." +resp=$(curl --silent "${curl_params[@]}" "$request_func$request_params") +if [[ "$resp" = *\"error\":* ]]; then + echo -n "cpanel fetchzone records failed: " + echo "$resp" | awk -F"error" '{ print $2 }' | awk -F\" '{ print $3 }' + exit 1 +fi + +# If no existing record, create a new TXT record, otherwise edit the existing record +if [[ "$resp" == *\"data\":[]* ]]; then + request_params="&cpanel_jsonapi_func=add_zone_record&domain=$domain&type=TXT&name=_acme-challenge$name&txtdata=$token" +else + # shellcheck disable=SC2001 + line=$(echo "$resp" | sed -e 's/.*line":\([0-9]*\),.*/\1/') + request_params="&cpanel_jsonapi_func=edit_zone_record&domain=$domain&type=TXT&name=_acme-challenge$name&txtdata=${token}&line=${line}" +fi +resp=$(curl --silent "${curl_params[@]}" "$request_func$request_params") + +if [[ "$resp" = *\"status\":0* ]]; then + echo -n "cpanel edit zone record failed: " + echo "$resp" | awk -F"statusmsg" '{ print $2 }' | awk -F\" '{ print $3 }' + exit 1 +fi diff --git a/dns_scripts/dns_del_cpanel b/dns_scripts/dns_del_cpanel new file mode 100644 index 0000000..922151a --- /dev/null +++ b/dns_scripts/dns_del_cpanel @@ -0,0 +1,69 @@ +#!/usr/bin/env bash + +# Need to add your email address and API key to cpanel below or set as env variables +user=${CPANEL_USERNAME:-''} +password=${CPANEL_PASSWORD:-''} +url=${CPANEL_URL:-''} # e.g. https://www.cpanel-host.test:2083 +apitoken=${CPANEL_APITOKEN:-''} + +fulldomain="${1}" + +# Check initial parameters +if [[ -z "$fulldomain" ]]; then + echo "DNS script requires full domain name as first parameter" + exit 1 +fi +if [[ -z "$user" ]]; then + echo "CPANEL_USERNAME (username) parameter not set" + exit 1 +fi +if [[ -z "$apitoken" ]] && [[ -z "$password" ]]; then + echo "Must set either CPANEL_APITOKEN or CPANEL_PASSWORD in dns script, environment variable or getssl.cfg" + exit 1 +fi +if [[ -z "$url" ]]; then + echo "CPANEL_URL (url) parameter not set" + exit 1 +fi + +# Setup +request_func="${url}/json-api/cpanel?cpanel_jsonapi_apiversion=2&cpanel_jsonapi_module=ZoneEdit" +if [[ -n $apitoken ]]; then + curl_params=( -H "Authorization: cpanel $user:$apitoken" ) +else + auth_string=$(echo -ne "$user:$password" | base64 --wrap 0) + curl_params=( -H "Authorization: Basic $auth_string" ) +fi + +# Check if domain is a CNAME +res=$(dig CNAME "$fulldomain") +domain=$(echo "$res"| awk '$4 ~ "CNAME" {print $5}' |sed 's/\.$//g') +if [[ -n "$domain" ]]; then + name=".${fulldomain%.$domain}" +else + domain=$fulldomain + name="" +fi + +# Find line number of existing record +request_params="&cpanel_jsonapi_func=fetchzone_records&domain=${domain}&type=TXT&name=_acme-challenge.${fulldomain}." +resp=$(curl --silent "${curl_params[@]}" "$request_func$request_params") +if [[ "$resp" = *\"error\":* ]]; then + echo -n "cpanel fetchzone records failed: " + echo "$resp" | awk -F"error" '{ print $2 }' | awk -F\" '{ print $3 }' + exit 1 +fi + +# shellcheck disable=SC2001 +line=$(echo "$resp" | sed -e 's/.*line":\([0-9]*\),.*/\1/') +if [[ "$line" != "" ]]; then + # Delete the challenge token + request_params="&cpanel_jsonapi_func=remove_zone_record&domain=$domain&type=TXT&name=_acme-challenge$name&line=$line" + resp=$(curl --silent "${curl_params[@]}" "$request_func$request_params") +fi + +if [[ "$resp" = *\"status\":0* ]]; then + echo -n "cpanel remove zone record failed: " + echo "$resp" | awk -F"statusmsg" '{ print $2 }' | awk -F\" '{ print $3 }' + exit 1 +fi From 349210e2d924c445b39f0e7fef8cf9d509e9afff Mon Sep 17 00:00:00 2001 From: Tim Kimber Date: Sat, 13 Mar 2021 20:29:52 +0000 Subject: [PATCH 28/43] Document wildcards, PREFERRED_CHAIN and FULL_CHAIN_INCLUDE_ROOT --- README.md | 64 +++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 60 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index cad2775..cb226e3 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,23 @@ -# getssl +# getssl ![Run all tests](https://github.com/srvrco/getssl/workflows/Run%20all%20tests/badge.svg) ![shellcheck](https://github.com/srvrco/getssl/workflows/shellcheck/badge.svg) Obtain SSL certificates from the letsencrypt.org ACME server. Suitable for automating the process on remote servers. -## Table of Contents - +## Table of Contents - [Features](#features) - [Installation](#installation) - [Overview](#overview) - [Getting started](#getting-started) +- [Wildcard certificates](#wildcard-certificates) - [Automating updates](#automating-updates) - [Structure](#structure) - [Server-Types](#server-types) - [Revoke a certificate](#revoke-a-certificate) - [Elliptic curve keys](#elliptic-curve-keys) +- [Preferred Chain](#preferred-chain) +- [Full chain](#full-chain) - [Issues / problems / help](#issues--problems--help) ## Features @@ -163,9 +165,35 @@ Change the server in your config file to get a fully valid certificate. dns. The certificate can be used (and checked with getssl) on alternate ports. +## Wildcard certificates + +`getssl` supports creating wildcard certificates, i.e. _*.example.com_ which allows a single certificate to be used for any domain under *example.com*, e.g. *www.example.com*, *mail.example.com*. These must be validated using the dns-01 method. + +A *partial* example `getssl.cfg` file is: + +```sh +VALIDATE_VIA_DNS=true +export CPANEL_USERNAME='' +export CPANEL_URL='https://www.cpanel.host:2083' +export CPANEL_APITOKEN='1ABC2DEF3GHI4JKL5MNO6PQR7STU8VWX9YZA' +DNS_ADD_COMMAND=/home/root/getssl/dns_scripts/dns_add_cpanel +DNS_DEL_COMMAND=/home/root/getssl/dns_scripts/dns_del_cpanel +``` + +Create the wildcard certificate (need to use quotes to prevent globbing): + +```sh +getssl "*.example.domain" +``` + +You can renew the certificate using `getssl -a` to renew all configured certificates. + +You can also specify additional domains in the `SANS` line, e.g. `SANS="www.test.example.com"`. +This cannot contain any of the domains which would be covered by the wildcard certificate. + ## Automating updates -I use the following cron +I use the following **cron** job ```cron 23 5 * * * /root/scripts/getssl -u -a -q @@ -353,6 +381,34 @@ key (different of course, don't use the same key for both). prime256v1 secp521r1 (NIST P-521) is included in the code, but not currently supported by Let's Encrypt). +## Preferred Chain + +If a CA offers multiple chains then it is possible to select which chain +is used by using the `PREFERRED_CHAIN` variable in `getssl.cfg` or specifying + `--preferred-chain` in the call to `getssl` + +This uses wildcard matching so requesting "X1" returns the first certificate +returned by the CA which contains the text "X1", Note you may need to escape +any characters which special characters, e.g. +` PREFERRED_CHAIN="\(STAGING\) Doctored Durian Root CA X3"` + +* Staging options are: "(STAGING) Doctored Durian Root CA X3" and "(STAGING) Pretend Pear X1" +* Production options are: "ISRG Root X1" and "ISRG Root X2" + +## Full chain + +Some servers, including those that use Java keystores, will not accept a server certificate if it cannot valid the full chain of signers. + +Specifically, Nutanix Prism (Element and Central) will not accept the `fullchain.crt` until the root CA's certificate has been appended to it manually. + +If your application requires the full chain, i.e. including the +root certificate of the CA, then this can be included in the `fullchain.crt` file by +adding the following line to `getssl.cfg` + +```sh +FULL_CHAIN_INCLUDE_ROOT="true" +``` + ## Issues / problems / help If you have any issues, please log them at From 2dd9e9acd41cd68ef5ab637b369b3dce3738211e Mon Sep 17 00:00:00 2001 From: Tim Kimber Date: Tue, 16 Mar 2021 12:02:03 +0000 Subject: [PATCH 29/43] Tweak full chain heading --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index cb226e3..def44c5 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ for automating the process on remote servers. - [Revoke a certificate](#revoke-a-certificate) - [Elliptic curve keys](#elliptic-curve-keys) - [Preferred Chain](#preferred-chain) -- [Full chain](#full-chain) +- [Include Root certificate in full chain](#include-root-certificate-in-full-chain) - [Issues / problems / help](#issues--problems--help) ## Features @@ -395,7 +395,7 @@ any characters which special characters, e.g. * Staging options are: "(STAGING) Doctored Durian Root CA X3" and "(STAGING) Pretend Pear X1" * Production options are: "ISRG Root X1" and "ISRG Root X2" -## Full chain +## Include Root certificate in full chain Some servers, including those that use Java keystores, will not accept a server certificate if it cannot valid the full chain of signers. From 073b33c7f617ded56e3533a9e4b240825c1d0e4b Mon Sep 17 00:00:00 2001 From: Tim Kimber Date: Tue, 16 Mar 2021 12:03:14 +0000 Subject: [PATCH 30/43] Add more info to message for "Certificate on remote domain does not match" --- getssl | 36 ++++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/getssl b/getssl index 1f1ca7c..cebd7f7 100755 --- a/getssl +++ b/getssl @@ -2401,14 +2401,14 @@ write_domain_template() { # write out a template file for a domain. # Set USE_SINGLE_ACL="true" to use a single ACL for all checks #USE_SINGLE_ACL="false" - # Preferred Chain - use an different certificate root from the default - # This uses wildcard matching so requesting "X1" returns the correct certificate - may need to escape characters - # Staging options are: "(STAGING) Doctored Durian Root CA X3" and "(STAGING) Pretend Pear X1" - # Production options are: "ISRG Root X1" and "ISRG Root X2" - #PREFERRED_CHAIN="\(STAGING\) Pretend Pear X1" + # Preferred Chain - use an different certificate root from the default + # This uses wildcard matching so requesting "X1" returns the correct certificate - may need to escape characters + # Staging options are: "(STAGING) Doctored Durian Root CA X3" and "(STAGING) Pretend Pear X1" + # Production options are: "ISRG Root X1" and "ISRG Root X2" + #PREFERRED_CHAIN="\(STAGING\) Pretend Pear X1" - # Uncomment this if you need the full chain file to include the root certificate (Java keystores, Nutanix Prism) - #FULL_CHAIN_INCLUDE_ROOT="true" + # Uncomment this if you need the full chain file to include the root certificate (Java keystores, Nutanix Prism) + #FULL_CHAIN_INCLUDE_ROOT="true" # Location for all your certs, these can either be on the server (full path name) # or using ssh /sftp as for the ACL @@ -2464,14 +2464,14 @@ write_getssl_template() { # write out the main template file PRIVATE_KEY_ALG="rsa" #REUSE_PRIVATE_KEY="true" - # Preferred Chain - use an different certificate root from the default - # This uses wildcard matching so requesting "X1" returns the correct certificate - may need to escape characters - # Staging options are: "(STAGING) Doctored Durian Root CA X3" and "(STAGING) Pretend Pear X1" - # Production options are: "ISRG Root X1" and "ISRG Root X2" - #PREFERRED_CHAIN="\(STAGING\) Pretend Pear X1" + # Preferred Chain - use an different certificate root from the default + # This uses wildcard matching so requesting "X1" returns the correct certificate - may need to escape characters + # Staging options are: "(STAGING) Doctored Durian Root CA X3" and "(STAGING) Pretend Pear X1" + # Production options are: "ISRG Root X1" and "ISRG Root X2" + #PREFERRED_CHAIN="\(STAGING\) Pretend Pear X1" - # Uncomment this if you need the full chain file to include the root certificate (Java keystores, Nutanix Prism) - #FULL_CHAIN_INCLUDE_ROOT="true" + # Uncomment this if you need the full chain file to include the root certificate (Java keystores, Nutanix Prism) + #FULL_CHAIN_INCLUDE_ROOT="true" # The command needed to reload apache / nginx or whatever you use. # Several (ssh) commands may be given using a bash array: @@ -2857,7 +2857,7 @@ if [[ "${CHECK_REMOTE}" == "true" ]] && [[ $_FORCE_RENEW -eq 0 ]]; then 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; }' \ + | 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) @@ -2901,7 +2901,11 @@ if [[ "${CHECK_REMOTE}" == "true" ]] && [[ $_FORCE_RENEW -eq 0 ]]; then reload_service fi else - info "${DOMAIN}: Certificate on remote domain does not match, ignoring remote certificate" + # Get the domain from the existing certificate for the error message + 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 | head -1) + info "${DOMAIN}: Certificate on remote domain does not match, ignoring remote certificate ($EX_CERT_DOMAIN != $real_d)" fi fi else From 8d24863cb940ae523491cf25dd5b5f483fc4912c Mon Sep 17 00:00:00 2001 From: Maciej Modzelewski Date: Fri, 19 Mar 2021 06:21:36 +0100 Subject: [PATCH 31/43] Update GoDaddy DNS scripts --- dns_scripts/dns_add_godaddy | 2 +- dns_scripts/dns_godaddy | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/dns_scripts/dns_add_godaddy b/dns_scripts/dns_add_godaddy index dfd3b3b..835bb22 100755 --- a/dns_scripts/dns_add_godaddy +++ b/dns_scripts/dns_add_godaddy @@ -37,4 +37,4 @@ fi export GODADDY_KEY export GODADDY_SECRET -$GODADDY_SCRIPT -q add "${fulldomain}" "_acme-challenge.${fulldomain}." "${token}" +$GODADDY_SCRIPT -q add "${fulldomain}" "_acme-challenge" "${token}" diff --git a/dns_scripts/dns_godaddy b/dns_scripts/dns_godaddy index 4443bd4..7c36d19 100644 --- a/dns_scripts/dns_godaddy +++ b/dns_scripts/dns_godaddy @@ -155,7 +155,6 @@ if [ -z "$name" ]; then echo "'name' parameter is required, see -h" >&2 exit 3 fi -! [[ "$name" =~ [.]$ ]] && name="${name}.${domain}." data="$4" if [ -z "$data" ]; then echo "'data' parameter is required, see -h" >&2 @@ -209,7 +208,7 @@ if [ "$op" = "add" ]; then url="$API/$domain/records/TXT/$name" - request='{"data":"'$data'","ttl":'$ttl'}' + request='[{"data":"'$data'","ttl":'$ttl'}]' [ -n "$DEBUG" ] && cat >&2 < Date: Tue, 23 Mar 2021 12:29:16 +0800 Subject: [PATCH 32/43] Create Aliyun DNS Scripts Founded in 2009, Aliyun is a leading cloud computing and artificial intelligence technology company in China --- dns_scripts/dns_add_del_aliyun.sh | 182 ++++++++++++++++++++++++++++++ 1 file changed, 182 insertions(+) create mode 100644 dns_scripts/dns_add_del_aliyun.sh diff --git a/dns_scripts/dns_add_del_aliyun.sh b/dns_scripts/dns_add_del_aliyun.sh new file mode 100644 index 0000000..de324e0 --- /dev/null +++ b/dns_scripts/dns_add_del_aliyun.sh @@ -0,0 +1,182 @@ +#!/bin/bash +#https://blog.aymar.cn +#https://protocol.aymar.cn +PROGNAME=${0##*/} +VERSION="2021年3月22日 16:07:05" +Ali_API="https://dns.aliyuncs.com/" +_timestamp=$(date -u +"%Y-%m-%dT%H%%3A%M%%3A%SZ") +__debug="0" +__delete="0" + +#Wildcard certificates +#A partial example getssl.cfg file is: +#VALIDATE_VIA_DNS=true +#DNS_ADD_COMMAND=/root/.getssl/dns_add_del_aliyun.sh +#DNS_DEL_COMMAND=/root/.getssl/dns_add_del_aliyun.sh + +# either configure KeyId & KeySecret here or export environment variables in getssl.cfg +AccessKeyId=${ALI_KeyId:-''} +AccessKeySecret=${ALI_KeySecret:-''} + +usage() { # print out the program usage + echo "Usage: $PROGNAME [-a|--add ] [-d|--delete ] [-s|--search ] [-h|--help] [-t|--type] "\ + "[-q|--quiet] [-c|--check] [-S|--status] [-l|--lock #] [-T|--ttl] [-u|--update] [-w|--weight] [-L|--Line]" +} + +help_message() { # print out the help message + cat <<- _EOF_ + $PROGNAME Version. $VERSION + $(usage) + + Options: + -a, --add Add Domain Record 域名 ip (默认类型TXT) + -d, --delete Delete Domain Record 域名 (默认类型TXT) + -s, --search Search Domain Record 域名 + -t, --type Record Type 类型(A、MX、CNAME、TXT、REDIRECT_URL、FORWORD_URL、NS、AAAA、SRV) + _EOF_ +} + +_arg_check(){ + [ -z "$1" ] || _arg_count=$1 + shift + [ ${#} -lt $_arg_count ] && help_message && exit 1 || (echo $2 | grep "^-") && help_message && exit 1 + #If the number of arguments <$_ARG_COUNT print help and exit, and if the second argument begins with “-” print help and exit + return 0 +} + +#[ ${#} -lt 2 ] && help_message && exit 1 #Same as below +#[ -z "$2" ] && help_message && exit 1 #Same as below +_arg_check 2 $@ + +_debug (){ + if [ "$__debug" -eq 1 ]; then + echo -e "\033[1;31m # debug: $(date "+%m %d %T") | Func: ${FUNCNAME[@]} | Line:${BASH_LINENO[@]} \033[0m" "\n $@ " #"Current FUNCNAME ${FUNCNAME} #$LINENO " #"$(($RANDOM%10))" + fi + return 0 +} + +_requires() { + _cmds='' # Check if the commands exists + if [[ "$#" -gt 0 ]]; then + for i in "$@"; do + if eval type type >/dev/null 2>&1; then + eval type "$i" >/dev/null 2>&1 + elif command >/dev/null 2>&1; then + command -v "$i" >/dev/null 2>&1 + else + which "$i" >/dev/null 2>&1 + fi + #[ "$?" -eq 0 ] && _debug "checking for $i exists = ok" || _cmds=$_cmds"$i: " + if [ "$?" -eq 0 ]; then + #_debug "checking for $i exists = ok" + continue + else + _cmds=$_cmds"$i: " + fi + done + else + echo "Usage: _requires [command] " + return 1 + fi + [ -n "$_cmds" ] && { echo -e "\033[1;31m $_cmds command not found \033[0m" && return 1 ;} || return 0 +} + +_requires openssl + +_hex_dump() { #ascii hex + local _str='' + [ $# -gt 0 ] && _str=$@ || read _str + local _str_len=${#_str} + local i=1 + while [ "$i" -le "$_str_len" ]; do + local _str_c="$(printf "%s" "$_str" | cut -c "$i")" + printf " %02x" "'$_str_c" + i=$(($i + 1)) + done + #printf "%s" " 0a" +} + +_urlencode() { + local length="${#1}" + local i='' + for i in $(awk "BEGIN { for ( i=0; i<$length; i++ ) print i }") + do + #local _strc="$(printf "%s" "$1" | cut -c "$i")" #i=1; i<=$length; i++ + local _strc="${1:$i:1}" + case $_strc in [a-zA-Z0-9.~_-]) printf "%s" "$_strc" ;; *) printf "%%%02X" "'$_strc" ;; + esac + done +} + +_signature(){ + signature='' + _hexkey=$(printf "%s" "$AccessKeySecret&" | _hex_dump |sed 's/ //g') + #signature=$(printf "%s" "GET&%2F&$(_urlencode "$query")" | openssl dgst -sha1 -hmac $(printf "%s" "$AccessKeySecret&" | _hex_dump |sed 's/ //g'| xxd -r -p ) -binary | openssl base64 -e) + signature=$(printf "%s" "GET&%2F&$(_urlencode "$query")" | openssl dgst -sha1 -mac HMAC -macopt "hexkey:$_hexkey" -binary | openssl base64 -e) + signature=$(_urlencode "$signature") + } + +_query() { + [ -n "$__type" ] && { [[ "$_Action" = "AddDomainRecord" ]] && _Type="$__type" || { [ "$_Action" = "DescribeDomainRecords" ] && _TypeKeyWord="$__type"; } ; } + query='' + [ -n $AccessKeyId ] && query=$query'AccessKeyId='$AccessKeyId + query=$query'&Action='"$1" + [ -z $_DomainNames ] || query=$query'&DomainName='$_DomainNames + query=$query'&Format=json' + [ -z $_RR ] || query=$query'&RR='$_RR + [ -z $_RRKeyWord ] || query=$query'&RRKeyWord='$_RRKeyWord + [ -z $_RecordId ] || query=$query'&RecordId='$_RecordId + query=$query'&SignatureMethod=HMAC-SHA1' + query=$query"&SignatureNonce=$(date +"%s%N")" + query=$query'&SignatureVersion=1.0' + query=$query'&Timestamp='$_timestamp + [ -z $_Type ] || query=$query'&Type='$_Type + [ -z $_TypeKeyWord ] || query=$query'&TypeKeyWord='$_TypeKeyWord + [ -z $_Value ] || query=$query'&Value='$_Value + [ -z $_ValueKeyWord ] || query=$query'&ValueKeyWord='$_ValueKeyWord + query=$query'&Version=2015-01-09' + #_debug "$query" + _signature + return 0 +} + +_Get_RecordIds(){ + _Action="DescribeDomainRecords" + _query $_Action $_DomainNames + url="${Ali_API}?${query}&Signature=${signature}" + _debug $url + _RecordIds=$(curl -k -s $url | grep -Po 'RecordId[": "]+\K[^"]+') && __delete="1" #RecordId requisite + _debug $_RecordIds + return 0 +} + +__type='TXT' +_DomainNames=$(printf "%s" $1| awk -F"." '{if(NF>=2){print $(NF-1)"."$NF}}') #awk -F\. '{print $(NF-1) FS $NF}') #requisite +_RRKeyWord="_acme-challenge" +#_ValueKeyWord=$2 +_Get_RecordIds +_RRKeyWord='' +_TypeKeyWord='' +_ValueKeyWord='' + +if [ "$__delete" = "1" ];then + _Action="DeleteDomainRecord" #Action requisite + _DomainNames='' + for _RecordId in ${_RecordIds[@]} #Delete multiple txt domain record + do + _debug "_RecordId" $_RecordId + _query $_Action $_RecordId + url="${Ali_API}?${query}&Signature=${signature}" + _debug $url + curl -k -s $url && ( echo -e "\n\033[1;32m Aliyun DNS record _acme-challenge.$1 has been deleted \033[0m") + done +else +_Action="AddDomainRecord" #requisite +_RR=$(printf "_acme-challenge.%s" $1| awk -F'.' '{if(NF>2){gsub("."$(NF-1)"."$NF,"");print}}') #requisite +_Value=$2 #requisite +_query $_Action $_DomainNames +url="${Ali_API}?${query}&Signature=${signature}" +_debug $url +curl -k -s $url && (echo -e "\n\033[1;32m Start Checking aliyun DNS record _acme-challenge.$1 \033[0m") +exit 0 +fi \ No newline at end of file From efbe5ce7d366d50ed310186f02aa32eab8200849 Mon Sep 17 00:00:00 2001 From: Tim Kimber Date: Tue, 23 Mar 2021 20:30:16 +0000 Subject: [PATCH 33/43] ignore some shellcheck warnings indentation / whitespace changes --- dns_scripts/dns_add_del_aliyun.sh | 125 +++++++++++++++--------------- 1 file changed, 64 insertions(+), 61 deletions(-) diff --git a/dns_scripts/dns_add_del_aliyun.sh b/dns_scripts/dns_add_del_aliyun.sh index de324e0..d6d9461 100644 --- a/dns_scripts/dns_add_del_aliyun.sh +++ b/dns_scripts/dns_add_del_aliyun.sh @@ -8,7 +8,7 @@ _timestamp=$(date -u +"%Y-%m-%dT%H%%3A%M%%3A%SZ") __debug="0" __delete="0" -#Wildcard certificates +#Wildcard certificates #A partial example getssl.cfg file is: #VALIDATE_VIA_DNS=true #DNS_ADD_COMMAND=/root/.getssl/dns_add_del_aliyun.sh @@ -20,7 +20,7 @@ AccessKeySecret=${ALI_KeySecret:-''} usage() { # print out the program usage echo "Usage: $PROGNAME [-a|--add ] [-d|--delete ] [-s|--search ] [-h|--help] [-t|--type] "\ - "[-q|--quiet] [-c|--check] [-S|--status] [-l|--lock #] [-T|--ttl] [-u|--update] [-w|--weight] [-L|--Line]" + "[-q|--quiet] [-c|--check] [-S|--status] [-l|--lock #] [-T|--ttl] [-u|--update] [-w|--weight] [-L|--Line]" } help_message() { # print out the help message @@ -38,7 +38,7 @@ help_message() { # print out the help message _arg_check(){ [ -z "$1" ] || _arg_count=$1 - shift + shift [ ${#} -lt $_arg_count ] && help_message && exit 1 || (echo $2 | grep "^-") && help_message && exit 1 #If the number of arguments <$_ARG_COUNT print help and exit, and if the second argument begins with “-” print help and exit return 0 @@ -50,39 +50,41 @@ _arg_check 2 $@ _debug (){ if [ "$__debug" -eq 1 ]; then - echo -e "\033[1;31m # debug: $(date "+%m %d %T") | Func: ${FUNCNAME[@]} | Line:${BASH_LINENO[@]} \033[0m" "\n $@ " #"Current FUNCNAME ${FUNCNAME} #$LINENO " #"$(($RANDOM%10))" + echo -e "\033[1;31m # debug: $(date "+%m %d %T") | Func: ${FUNCNAME[@]} | Line:${BASH_LINENO[@]} \033[0m" "\n $@ " #"Current FUNCNAME ${FUNCNAME} #$LINENO " #"$(($RANDOM%10))" fi return 0 } -_requires() { +_requires() { _cmds='' # Check if the commands exists if [[ "$#" -gt 0 ]]; then for i in "$@"; do - if eval type type >/dev/null 2>&1; then - eval type "$i" >/dev/null 2>&1 - elif command >/dev/null 2>&1; then - command -v "$i" >/dev/null 2>&1 - else - which "$i" >/dev/null 2>&1 - fi - #[ "$?" -eq 0 ] && _debug "checking for $i exists = ok" || _cmds=$_cmds"$i: " - if [ "$?" -eq 0 ]; then - #_debug "checking for $i exists = ok" - continue - else - _cmds=$_cmds"$i: " - fi + if eval type type >/dev/null 2>&1; then + eval type "$i" >/dev/null 2>&1 + elif command >/dev/null 2>&1; then + command -v "$i" >/dev/null 2>&1 + else + which "$i" >/dev/null 2>&1 + fi + #[ "$?" -eq 0 ] && _debug "checking for $i exists = ok" || _cmds=$_cmds"$i: " + #shellcheck disable=SC2181 + if [ "$?" -eq 0 ]; then + #_debug "checking for $i exists = ok" + continue + else + _cmds=$_cmds"$i: " + fi done - else - echo "Usage: _requires [command] " - return 1 + else + echo "Usage: _requires [command] " + return 1 fi - [ -n "$_cmds" ] && { echo -e "\033[1;31m $_cmds command not found \033[0m" && return 1 ;} || return 0 + [ -n "$_cmds" ] && { echo -e "\033[1;31m $_cmds command not found \033[0m" && return 1 ;} || return 0 } _requires openssl +#shellcheck disable=SC2120 _hex_dump() { #ascii hex local _str='' [ $# -gt 0 ] && _str=$@ || read _str @@ -99,12 +101,12 @@ _hex_dump() { #ascii hex _urlencode() { local length="${#1}" local i='' - for i in $(awk "BEGIN { for ( i=0; i<$length; i++ ) print i }") - do - #local _strc="$(printf "%s" "$1" | cut -c "$i")" #i=1; i<=$length; i++ - local _strc="${1:$i:1}" - case $_strc in [a-zA-Z0-9.~_-]) printf "%s" "$_strc" ;; *) printf "%%%02X" "'$_strc" ;; - esac + for i in $(awk "BEGIN { for ( i=0; i<$length; i++ ) print i }") + do + #local _strc="$(printf "%s" "$1" | cut -c "$i")" #i=1; i<=$length; i++ + local _strc="${1:$i:1}" + case $_strc in [a-zA-Z0-9.~_-]) printf "%s" "$_strc" ;; *) printf "%%%02X" "'$_strc" ;; + esac done } @@ -114,7 +116,7 @@ _signature(){ #signature=$(printf "%s" "GET&%2F&$(_urlencode "$query")" | openssl dgst -sha1 -hmac $(printf "%s" "$AccessKeySecret&" | _hex_dump |sed 's/ //g'| xxd -r -p ) -binary | openssl base64 -e) signature=$(printf "%s" "GET&%2F&$(_urlencode "$query")" | openssl dgst -sha1 -mac HMAC -macopt "hexkey:$_hexkey" -binary | openssl base64 -e) signature=$(_urlencode "$signature") - } +} _query() { [ -n "$__type" ] && { [[ "$_Action" = "AddDomainRecord" ]] && _Type="$__type" || { [ "$_Action" = "DescribeDomainRecords" ] && _TypeKeyWord="$__type"; } ; } @@ -138,45 +140,46 @@ _query() { #_debug "$query" _signature return 0 -} - -_Get_RecordIds(){ - _Action="DescribeDomainRecords" - _query $_Action $_DomainNames - url="${Ali_API}?${query}&Signature=${signature}" - _debug $url - _RecordIds=$(curl -k -s $url | grep -Po 'RecordId[": "]+\K[^"]+') && __delete="1" #RecordId requisite - _debug $_RecordIds - return 0 +} + +_Get_RecordIds(){ + _Action="DescribeDomainRecords" + _query $_Action $_DomainNames + url="${Ali_API}?${query}&Signature=${signature}" + _debug $url + _RecordIds=$(curl -k -s $url | grep -Po 'RecordId[": "]+\K[^"]+') && __delete="1" #RecordId requisite + _debug $_RecordIds + return 0 } __type='TXT' _DomainNames=$(printf "%s" $1| awk -F"." '{if(NF>=2){print $(NF-1)"."$NF}}') #awk -F\. '{print $(NF-1) FS $NF}') #requisite _RRKeyWord="_acme-challenge" -#_ValueKeyWord=$2 + _Get_RecordIds + _RRKeyWord='' -_TypeKeyWord='' +_TypeKeyWord='' _ValueKeyWord='' if [ "$__delete" = "1" ];then - _Action="DeleteDomainRecord" #Action requisite - _DomainNames='' - for _RecordId in ${_RecordIds[@]} #Delete multiple txt domain record - do - _debug "_RecordId" $_RecordId - _query $_Action $_RecordId - url="${Ali_API}?${query}&Signature=${signature}" - _debug $url - curl -k -s $url && ( echo -e "\n\033[1;32m Aliyun DNS record _acme-challenge.$1 has been deleted \033[0m") - done + _Action="DeleteDomainRecord" #Action requisite + _DomainNames='' + for _RecordId in ${_RecordIds[@]} #Delete multiple txt domain record + do + _debug "_RecordId" $_RecordId + _query $_Action $_RecordId + url="${Ali_API}?${query}&Signature=${signature}" + _debug $url + curl -k -s $url && ( echo -e "\n\033[1;32m Aliyun DNS record _acme-challenge.$1 has been deleted \033[0m") + done else -_Action="AddDomainRecord" #requisite -_RR=$(printf "_acme-challenge.%s" $1| awk -F'.' '{if(NF>2){gsub("."$(NF-1)"."$NF,"");print}}') #requisite -_Value=$2 #requisite -_query $_Action $_DomainNames -url="${Ali_API}?${query}&Signature=${signature}" -_debug $url -curl -k -s $url && (echo -e "\n\033[1;32m Start Checking aliyun DNS record _acme-challenge.$1 \033[0m") -exit 0 -fi \ No newline at end of file + _Action="AddDomainRecord" #requisite + _RR=$(printf "_acme-challenge.%s" $1| awk -F'.' '{if(NF>2){gsub("."$(NF-1)"."$NF,"");print}}') #requisite + _Value=$2 #requisite + _query $_Action $_DomainNames + url="${Ali_API}?${query}&Signature=${signature}" + _debug $url + curl -k -s $url && (echo -e "\n\033[1;32m Start Checking aliyun DNS record _acme-challenge.$1 \033[0m") + exit 0 +fi From 3eb7ba35406c9759d9467e8d662327adfb34d565 Mon Sep 17 00:00:00 2001 From: Dennis Camera Date: Thu, 25 Mar 2021 17:23:48 +0100 Subject: [PATCH 34/43] Split DNS add and remove into separate functions --- getssl | 49 ++++++++++++++++++++++++++++++------------------- 1 file changed, 30 insertions(+), 19 deletions(-) diff --git a/getssl b/getssl index cebd7f7..ff97639 100755 --- a/getssl +++ b/getssl @@ -573,20 +573,18 @@ check_challenge_completion_dns() { # perform validation via DNS challenge ntries=$(( ntries + 1 )) if [[ $DNS_WAIT_RETRY_ADD == "true" && $(( ntries % 10 )) == 0 ]]; then - debug "Retrying adding dns via command: $DNS_ADD_COMMAND $lower_d $auth_key" - test_output "Retrying adding dns via command: $DNS_ADD_COMMAND" - eval "$DNS_DEL_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 - + test_output "Deleting DNS RR via command: ${DNS_DEL_COMMAND}" + del_dns_rr "${lower_d}" "${auth_key}" + test_output "Retrying adding DNS via command: ${DNS_ADD_COMMAND}" + add_dns_rr "${lower_d}" "${auth_key}" \ + || error_exit "DNS_ADD_COMMAND failed for domain ${d}" fi info "checking DNS at ${ns} for ${lower_d}. Attempt $ntries/${DNS_WAIT_COUNT} gave wrong result, "\ "waiting $DNS_WAIT secs before checking again" sleep $DNS_WAIT else debug "dns check failed - removing existing value" - eval "$DNS_DEL_COMMAND" "$lower_d" "$auth_key" + del_dns_rr "${lower_d}" "${auth_key}" error_exit "checking _acme-challenge.${lower_d} gave $check_result not $auth_key" fi @@ -601,10 +599,7 @@ check_challenge_completion_dns() { # perform validation via DNS challenge check_challenge_completion "$uri" "$d" "$keyauthorization" - debug "remove DNS entry" - # shellcheck disable=SC2018,SC2019 - lower_d=$(echo "${d##\*.}" | tr A-Z a-z) - eval "$DNS_DEL_COMMAND" "$lower_d" "$auth_key" + del_dns_rr "${d}" "${auth_key}" } # end of ... perform validation if via DNS challenge @@ -807,7 +802,7 @@ clean_up() { # Perform pre-exit housekeeping # shellcheck source=/dev/null . "$dnsfile" debug "attempting to clean up DNS entry for $d" - eval "$DNS_DEL_COMMAND" "${d##\*.}" "$auth_key" + del_dns_rr "${d}" "${auth_key}" done shopt -u nullglob fi @@ -1171,6 +1166,26 @@ find_ftp_command() { } +add_dns_rr() { + d=${1} + auth_key=${2} + + # shellcheck disable=SC2018,SC2019 + lower_d=$(printf '%s' "${d#\*.}" | tr 'A-Z' 'a-z') + debug "adding DNS RR via command: ${DNS_ADD_COMMAND} ${lower_d} ${auth_key}" + eval "${DNS_ADD_COMMAND}" "${lower_d}" "${auth_key}" +} + +del_dns_rr() { + d=${1} + auth_key=${2} + + # shellcheck disable=SC2018,SC2019 + lower_d=$(printf '%s' "${d#\*.}" | tr 'A-Z' 'a-z') + debug "removing DNS RR via command: ${DNS_DEL_COMMAND} ${lower_d} ${auth_key}" + eval "${DNS_DEL_COMMAND}" "${lower_d}" "${auth_key}" +} + fulfill_challenges() { dn=0 for d in "${alldomains[@]}"; do @@ -1236,12 +1251,8 @@ for d in "${alldomains[@]}"; do | sed -e 's:=*$::g' -e 'y:+/:-_:') debug auth_key "$auth_key" - # 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 + add_dns_rr "${d}" "${auth_key}" \ + || error_exit "DNS_ADD_COMMAND failed for domain $d" # find a primary / authoritative DNS server for the domain if [[ -z "$AUTH_DNS_SERVER" ]]; then From 87ae2500d970cc6dd60b22903b6169760909f47d Mon Sep 17 00:00:00 2001 From: Dennis Camera Date: Thu, 25 Mar 2021 17:44:01 +0100 Subject: [PATCH 35/43] Fix DNS challenge completion check if CNAMEs on different NS are used --- getssl | 65 +++++++++++++++++++++++++++++----------------------------- 1 file changed, 33 insertions(+), 32 deletions(-) diff --git a/getssl b/getssl index ff97639..e485737 100755 --- a/getssl +++ b/getssl @@ -518,48 +518,42 @@ check_challenge_completion() { # checks with the ACME server if our challenge is } check_challenge_completion_dns() { # perform validation via DNS challenge - token=$1 - uri=$2 - keyauthorization=$3 - d=$4 - primary_ns=$5 - auth_key=$6 - - # Always use lowercase domain name when querying DNS servers - # shellcheck disable=SC2018,SC2019 - lower_d=$(echo "${d##\*.}" | tr A-Z a-z) + d=${1} + rr=${2} + primary_ns=${3} + auth_key=${4} # check for token at public dns server, waiting for a valid response. for ns in $primary_ns; do - info "checking dns at $ns" + info "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.${lower_d}" "${ns}" \ + check_result=$(nslookup -type=txt "${rr}" "${ns}" \ | 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.${lower_d}" "@${ns}" - check_result=$($DNS_CHECK_FUNC TXT "_acme-challenge.${lower_d}" "@${ns}" \ - | grep -i "^_acme-challenge.${lower_d}" \ + debug "$DNS_CHECK_FUNC" TXT "${rr}" "@${ns}" + check_result=$($DNS_CHECK_FUNC TXT "${rr}" "@${ns}" \ + | grep -i "^${rr}" \ | grep 'IN\WTXT'|awk -F'"' '{ print $2}') debug "check_result=$check_result" if [[ -z "$check_result" ]]; then - debug "$DNS_CHECK_FUNC" ANY "_acme-challenge.${lower_d}" "@${ns}" - check_result=$($DNS_CHECK_FUNC ANY "_acme-challenge.${lower_d}" "@${ns}" \ - | grep -i "^_acme-challenge.${lower_d}" \ + debug "$DNS_CHECK_FUNC" ANY "${rr}" "@${ns}" + check_result=$($DNS_CHECK_FUNC ANY "${rr}" "@${ns}" \ + | grep -i "^${rr}" \ | grep 'IN\WTXT'|awk -F'"' '{ print $2}') debug "check_result=$check_result" fi elif [[ "$DNS_CHECK_FUNC" == "host" ]]; then - check_result=$($DNS_CHECK_FUNC -t TXT "_acme-challenge.${lower_d}" "${ns}" \ + check_result=$($DNS_CHECK_FUNC -t TXT "${rr}" "${ns}" \ | grep 'descriptive text'|awk -F'"' '{ print $2}') else - check_result=$(nslookup -type=txt "_acme-challenge.${lower_d}" "${ns}" \ + check_result=$(nslookup -type=txt "${rr}" "${ns}" \ | grep 'text ='|awk -F'"' '{ print $2}') if [[ -z "$check_result" ]]; then - check_result=$(nslookup -type=any "_acme-challenge.${lower_d}" "${ns}" \ + check_result=$(nslookup -type=any "${rr}" "${ns}" \ | grep 'text ='|awk -F'"' '{ print $2}') fi fi @@ -574,19 +568,19 @@ check_challenge_completion_dns() { # perform validation via DNS challenge if [[ $DNS_WAIT_RETRY_ADD == "true" && $(( ntries % 10 )) == 0 ]]; then test_output "Deleting DNS RR via command: ${DNS_DEL_COMMAND}" - del_dns_rr "${lower_d}" "${auth_key}" + del_dns_rr "${d}" "${auth_key}" test_output "Retrying adding DNS via command: ${DNS_ADD_COMMAND}" - add_dns_rr "${lower_d}" "${auth_key}" \ + add_dns_rr "${d}" "${auth_key}" \ || error_exit "DNS_ADD_COMMAND failed for domain ${d}" fi - info "checking DNS at ${ns} for ${lower_d}. Attempt $ntries/${DNS_WAIT_COUNT} gave wrong result, "\ + info "checking DNS at ${ns} for ${rr}. Attempt $ntries/${DNS_WAIT_COUNT} gave wrong result, "\ "waiting $DNS_WAIT secs before checking again" sleep $DNS_WAIT else debug "dns check failed - removing existing value" - del_dns_rr "${lower_d}" "${auth_key}" + del_dns_rr "${d}" "${auth_key}" - error_exit "checking _acme-challenge.${lower_d} gave $check_result not $auth_key" + error_exit "checking ${rr} gave $check_result not $auth_key" fi fi done @@ -596,10 +590,6 @@ check_challenge_completion_dns() { # perform validation via DNS challenge info "sleeping $DNS_EXTRA_WAIT seconds before asking the ACME server to check the dns" sleep "$DNS_EXTRA_WAIT" fi - - check_challenge_completion "$uri" "$d" "$keyauthorization" - - del_dns_rr "${d}" "${auth_key}" } # end of ... perform validation if via DNS challenge @@ -1256,7 +1246,12 @@ for d in "${alldomains[@]}"; do # find a primary / authoritative DNS server for the domain if [[ -z "$AUTH_DNS_SERVER" ]]; then - get_auth_dns "$d" + # shellcheck disable=SC2018,SC2019 + rr="_acme-challenge.$(printf '%s' "${d#\*.}" | tr 'A-Z' 'a-z')" + get_auth_dns "${rr}" + if test -n "${cname}"; then + rr=${cname} + fi elif [[ "$CHECK_PUBLIC_DNS_SERVER" == "true" ]]; then primary_ns="$AUTH_DNS_SERVER $PUBLIC_DNS_SERVER" else @@ -1264,7 +1259,13 @@ for d in "${alldomains[@]}"; do fi debug set primary_ns = "$primary_ns" - check_challenge_completion_dns "${token}" "${uri}" "${keyauthorization}" "${d}" "${primary_ns}" "${auth_key}" + # internal check + check_challenge_completion_dns "${d}" "${rr}" "${primary_ns}" "${auth_key}" + + # let Let's Encrypt check + check_challenge_completion "${uri}" "${d}" "${keyauthorization}" + + del_dns_rr "${d}" "${auth_key}" else # set up the correct http token for verification if [[ $API -eq 1 ]]; then # get the token from the http component From 20fc8affa6e926a82d1eceb1750ab0a810344370 Mon Sep 17 00:00:00 2001 From: Dennis Camera Date: Thu, 25 Mar 2021 18:53:19 +0100 Subject: [PATCH 36/43] Resolve CNAMEs before searching for authoritative DNS server --- getssl | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/getssl b/getssl index e485737..8de8353 100755 --- a/getssl +++ b/getssl @@ -1378,6 +1378,27 @@ get_auth_dns() { # get the authoritative dns server for a domain (sets primary_n gad_s="@$gad_s" fi + # Check if domain is a CNAME, first + test_output "Using $HAS_DIG_OR_DRILL CNAME" + + # Two options here; either dig CNAME will return the CNAME and the NS or just the CNAME + debug Checking for CNAME using "$HAS_DIG_OR_DRILL CNAME $gad_d $gad_s" + res=$($HAS_DIG_OR_DRILL CNAME "$gad_d" $gad_s| grep "^$gad_d") + cname=$(echo "$res"| awk '$4 ~ "CNAME" {print $5}' |sed 's/\.$//g') + + if [[ $_TEST_SKIP_CNAME_CALL == 0 ]]; then + debug Checking if CNAME result contains NS records + res=$($HAS_DIG_OR_DRILL CNAME "$gad_d" $gad_s| grep -E "IN\W(NS|SOA)\W") + else + res= + fi + + if [[ -n "${cname}" ]]; then + # domain is a CNAME: resolve it and continue with that + debug Domain is a CNAME, actual domain is "$cname" + gad_d=${cname} + fi + # Use SOA +trace to find the name server if [[ $_TEST_SKIP_SOA_CALL == 0 ]]; then if [[ "$HAS_DIG_OR_DRILL" == "drill" ]]; then @@ -1391,27 +1412,6 @@ get_auth_dns() { # get the authoritative dns server for a domain (sets primary_n fi fi - # Check if domain is a CNAME - if [[ -z "$res" ]]; then - test_output "Using $HAS_DIG_OR_DRILL CNAME" - - # Two options here; either dig CNAME will return the CNAME and the NS or just the CNAME - debug Checking for CNAME using "$HAS_DIG_OR_DRILL CNAME $gad_d $gad_s" - res=$($HAS_DIG_OR_DRILL CNAME "$gad_d" $gad_s| grep "^$gad_d") - cname=$(echo "$res"| awk '$4 ~ "CNAME" {print $5}' |sed 's/\.$//g') - - if [[ $_TEST_SKIP_CNAME_CALL == 0 ]]; then - debug Checking if CNAME result contains NS records - res=$($HAS_DIG_OR_DRILL CNAME "$gad_d" $gad_s| grep -E "IN\W(NS|SOA)\W") - else - res="" - fi - - if [[ -n "$cname" ]]; then # domain is a CNAME so get main domain - debug Domain is a CNAME, actual domain is "$cname" - fi - fi - # Query for NS records if [[ -z "$res" ]]; then test_output "Using $HAS_DIG_OR_DRILL NS" From 4c3be42e97b288697c7e041cac22b1ab46fdc681 Mon Sep 17 00:00:00 2001 From: mokaton Date: Fri, 26 Mar 2021 22:12:42 +0300 Subject: [PATCH 37/43] DeprecationWarning: please use dns.resolver.Resolver.resolve() Fixed issue with deprecated function query for dns.resolver.Resolver --- 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 f2011bf..6b88b37 100755 --- a/dns_scripts/dns_route53.py +++ b/dns_scripts/dns_route53.py @@ -70,7 +70,7 @@ if action == 'UPSERT': try: my_resolver = dns.resolver.Resolver(configure=False) my_resolver.nameservers = ['8.8.8.8', '8.8.4.4'] - results = my_resolver.query(challenge_fqdn, 'TXT') + results = my_resolver.resolve(challenge_fqdn, 'TXT') data = str(results.response.answer[0][0]).strip('\"') if data == challenge: print("found {f} entry".format(f=challenge_fqdn)) From 4846512269497a166aa9648b4e30bc277b8cdf9b Mon Sep 17 00:00:00 2001 From: Tim Kimber Date: Mon, 29 Mar 2021 12:56:48 +0100 Subject: [PATCH 38/43] Tweaks so non-cname domains work, fix broken retry dns add test --- getssl | 13 ++++++++++--- test/18-retry-dns-add.bats | 5 +++-- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/getssl b/getssl index 8de8353..1cf1454 100755 --- a/getssl +++ b/getssl @@ -1244,14 +1244,21 @@ for d in "${alldomains[@]}"; do add_dns_rr "${d}" "${auth_key}" \ || error_exit "DNS_ADD_COMMAND failed for domain $d" + # shellcheck disable=SC2018,SC2019 + rr="_acme-challenge.$(printf '%s' "${d#\*.}" | tr 'A-Z' 'a-z')" + # find a primary / authoritative DNS server for the domain if [[ -z "$AUTH_DNS_SERVER" ]]; then - # shellcheck disable=SC2018,SC2019 - rr="_acme-challenge.$(printf '%s' "${d#\*.}" | tr 'A-Z' 'a-z')" + # Find authorative dns server for _acme-challenge.{domain} (for CNAMES/acme-dns) get_auth_dns "${rr}" if test -n "${cname}"; then rr=${cname} fi + + # If no authorative dns server found, try again for {domain} + if [[ -z "$primary_ns" ]]; then + get_auth_dns "$d" + fi elif [[ "$CHECK_PUBLIC_DNS_SERVER" == "true" ]]; then primary_ns="$AUTH_DNS_SERVER $PUBLIC_DNS_SERVER" else @@ -1400,7 +1407,7 @@ get_auth_dns() { # get the authoritative dns server for a domain (sets primary_n fi # Use SOA +trace to find the name server - if [[ $_TEST_SKIP_SOA_CALL == 0 ]]; then + if [[ -z "$res" ]] && [[ $_TEST_SKIP_SOA_CALL == 0 ]]; then if [[ "$HAS_DIG_OR_DRILL" == "drill" ]]; then debug Using "$HAS_DIG_OR_DRILL -T $gad_d $gad_s" to find primary nameserver test_output "Using $HAS_DIG_OR_DRILL SOA" diff --git a/test/18-retry-dns-add.bats b/test/18-retry-dns-add.bats index 3a79880..25318de 100644 --- a/test/18-retry-dns-add.bats +++ b/test/18-retry-dns-add.bats @@ -30,8 +30,9 @@ DNS_EXTRA_WAIT=0 CHECK_ALL_AUTH_DNS="false" CHECK_PUBLIC_DNS_SERVER="false" DNS_WAIT_RETRY_ADD="true" +_RUNNING_TEST=1 EOF - create_certificate -d + create_certificate assert_failure - assert_line --partial "Retrying adding dns via command" + assert_line --partial "Retrying adding DNS via command" } From 8625e0774b80c9a1c9a8e02f668a55bfc3b52f22 Mon Sep 17 00:00:00 2001 From: Tim Kimber Date: Mon, 29 Mar 2021 20:54:38 +0100 Subject: [PATCH 39/43] Update change log and version --- getssl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/getssl b/getssl index 1cf1454..bbc7afd 100755 --- a/getssl +++ b/getssl @@ -258,6 +258,7 @@ # 2021-02-12 Add PREFERRED_CHAIN # 2021-02-15 ADD ftp explicit SSL with curl for upload the challenge (CoolMischa) # 2021-02-18 Add FULL_CHAIN_INCLUDE_ROOT +# 2021-03-25 Fix DNS challenge completion check if CNAMEs on different NS are used (sideeffect42)(2.35) # ---------------------------------------------------------------------------------------- case :$SHELLOPTS: in @@ -266,7 +267,7 @@ esac PROGNAME=${0##*/} PROGDIR="$(cd "$(dirname "$0")" || exit; pwd -P;)" -VERSION="2.34" +VERSION="2.35" # defaults ACCOUNT_KEY_LENGTH=4096 From d8006d6585979b2cde6f02ee93b585db9811b577 Mon Sep 17 00:00:00 2001 From: Tim Kimber Date: Thu, 1 Apr 2021 11:57:53 +0100 Subject: [PATCH 40/43] Update README.md --- README.md | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index def44c5..7ff8b2e 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,7 @@ for automating the process on remote servers. - [Installation](#installation) - [Overview](#overview) - [Getting started](#getting-started) +- [Detailed guide to getting started with more examples](#detailed-guide-to-getting-started-with-more-examples) - [Wildcard certificates](#wildcard-certificates) - [Automating updates](#automating-updates) - [Structure](#structure) @@ -87,25 +88,27 @@ 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 +```getssl -h +getssl ver. 2.35 Obtain SSL certificates from the letsencrypt.org ACME server -Usage: getssl [-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 +Usage: getssl [-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] [--preferred-chain chain] domain Options: -a, --all Check all certificates - -d, --debug Outputs debug information + -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 + -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 mutes notification about successful upgrade + -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 - can be used with or without domain(s) - -k, --keep "#" Maximum amount of old getssl versions to keep when upgrading + -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" + --preferred-chain "chain" Use an alternate chain for the certificate ``` ## Getting started @@ -165,6 +168,10 @@ Change the server in your config file to get a fully valid certificate. dns. The certificate can be used (and checked with getssl) on alternate ports. +## Detailed guide to getting started with more examples + +[Guide to getting a certificate for example.com and www.example.com](https://github.com/srvrco/getssl/wiki/Guide-to-getting-a-certificate-for-example.com-and-www.example.com) + ## Wildcard certificates `getssl` supports creating wildcard certificates, i.e. _*.example.com_ which allows a single certificate to be used for any domain under *example.com*, e.g. *www.example.com*, *mail.example.com*. These must be validated using the dns-01 method. From ff2a55622492bb1b73b015c011235f4132662ab5 Mon Sep 17 00:00:00 2001 From: Timothe Litt Date: Sat, 8 May 2021 16:48:45 -0400 Subject: [PATCH 41/43] Update README Add --version Update default endpoint to acme-v02. --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7ff8b2e..454a388 100644 --- a/README.md +++ b/README.md @@ -89,7 +89,7 @@ certificates to a remote server ( providing you have a ssh with key, sftp or ftp access to the remote server). ```getssl -h -getssl ver. 2.35 +getssl ver. 2.36 Obtain SSL certificates from the letsencrypt.org ACME server Usage: getssl [-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] [--preferred-chain chain] domain @@ -107,6 +107,7 @@ Options: -u, --upgrade Upgrade getssl if a more recent version is available - can be used with or without domain(s) -k, --keep "#" Maximum number of old getssl versions to keep when upgrading -U, --nocheck Do not check if a more recent version is available + -v --version Display current version of getssl -w working_dir "Working directory" --preferred-chain "chain" Use an alternate chain for the certificate ``` @@ -376,7 +377,7 @@ 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 ("") as +defaults to Let's Encrypt ("") as that is currently the only Certificate Authority using the ACME protocol. From e315d870e4d0ca46e0a0da4ec8c7d8a6a68268b1 Mon Sep 17 00:00:00 2001 From: Timothe Litt Date: Sat, 8 May 2021 17:51:11 -0400 Subject: [PATCH 42/43] Fix lint from merge --- dns_scripts/dns_add_godaddy | 2 +- dns_scripts/dns_del_godaddy | 2 +- dns_scripts/dns_godaddy | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/dns_scripts/dns_add_godaddy b/dns_scripts/dns_add_godaddy index fc08f09..f9be745 100755 --- a/dns_scripts/dns_add_godaddy +++ b/dns_scripts/dns_add_godaddy @@ -38,4 +38,4 @@ export GODADDY_KEY export GODADDY_SECRET export GODADDY_BASE -$GODADDY_SCRIPT -q add ${fulldomain} "_acme-challenge.${fulldomain}." "${token}" +$GODADDY_SCRIPT -q add "${fulldomain}" "_acme-challenge.${fulldomain}." "${token}" diff --git a/dns_scripts/dns_del_godaddy b/dns_scripts/dns_del_godaddy index 120430f..4a3228f 100755 --- a/dns_scripts/dns_del_godaddy +++ b/dns_scripts/dns_del_godaddy @@ -36,4 +36,4 @@ export GODADDY_KEY export GODADDY_SECRET export GODADDY_BASE -$GODADDY_SCRIPT -q del ${fulldomain} "_acme-challenge.${fulldomain}." "${token}" +$GODADDY_SCRIPT -q del "${fulldomain}" "_acme-challenge.${fulldomain}." "${token}" diff --git a/dns_scripts/dns_godaddy b/dns_scripts/dns_godaddy index a89a855..0d41e6a 100755 --- a/dns_scripts/dns_godaddy +++ b/dns_scripts/dns_godaddy @@ -237,6 +237,7 @@ Add request to: $url $request" -------- EOF + result="$(curl -i -s -X PUT -d "$request" --config - "$url" < Date: Sat, 8 May 2021 19:05:50 -0400 Subject: [PATCH 43/43] Fix test errors caused by DNS_CHECK_OPTIONS Use a regexp rather than a partial match to skip any DNS_CHECK_OPTIONS, which in the current tests create whitespace, but could be anything. Catch a missing inclusion of DNS_CHECK_OPTIONS for dig CNAME --- getssl | 4 ++-- test/u1-test-get_auth_dns-dig.bats | 14 +++++++------- test/u2-test-get_auth_dns-drill.bats | 14 +++++++------- test/u5-test-get_auth_dns-no-root-servers.bats | 4 ++-- 4 files changed, 18 insertions(+), 18 deletions(-) diff --git a/getssl b/getssl index 4f5fa34..6ba6523 100755 --- a/getssl +++ b/getssl @@ -674,7 +674,7 @@ check_config() { # check the config files for all obvious errors # check domain exists using all DNS utilities. DNS_CHECK_OPTIONS may bind IP address or provide TSIG found_ip=false if [[ -n "$HAS_DIG_OR_DRILL" ]]; then - debug "DNS lookup using $HAS_DIG_OR_DRILL ${d}" + debug "DNS lookup using $HAS_DIG_OR_DRILL $DNS_CHECK_OPTIONS ${d}" if [[ "$($HAS_DIG_OR_DRILL $DNS_CHECK_OPTIONS -t SOA "${d}" |grep -c -i "^${d}")" -ge 1 ]]; then found_ip=true elif [[ "$($HAS_DIG_OR_DRILL $DNS_CHECK_OPTIONS -t A "${d}"|grep -c -i "^${d}")" -ge 1 ]]; then @@ -1389,7 +1389,7 @@ get_auth_dns() { # get the authoritative dns server for a domain (sets primary_n fi # Check if domain is a CNAME, first - test_output "Using $HAS_DIG_OR_DRILL CNAME" + test_output "Using $HAS_DIG_OR_DRILL $DNS_CHECK_OPTIONS CNAME" # Two options here; either dig CNAME will return the CNAME and the NS or just the CNAME debug Checking for CNAME using "$HAS_DIG_OR_DRILL $DNS_CHECK_OPTIONS CNAME $gad_d $gad_s" diff --git a/test/u1-test-get_auth_dns-dig.bats b/test/u1-test-get_auth_dns-dig.bats index 6e64e68..471732f 100644 --- a/test/u1-test-get_auth_dns-dig.bats +++ b/test/u1-test-get_auth_dns-dig.bats @@ -61,7 +61,7 @@ teardown() { # Assert that we've found the primary_ns server assert_output --regexp 'set primary_ns = ns[1-4]+\.duckdns\.org' # Assert that we had to use dig NS - assert_line --partial 'Using dig NS' + assert_line --regexp 'Using dig.* NS' # Check all Authoritive DNS servers are returned if requested CHECK_ALL_AUTH_DNS=true @@ -89,8 +89,8 @@ teardown() { assert_output --regexp 'set primary_ns = ns[1-4]+\.duckdns\.org' # Assert that we had to use dig NS - assert_line --partial 'Using dig SOA' - refute_line --partial 'Using dig NS' + assert_line --regexp 'Using dig.* SOA' + refute_line --regexp 'Using dig.* NS' # Check all Authoritive DNS servers are returned if requested CHECK_ALL_AUTH_DNS=true @@ -125,8 +125,8 @@ teardown() { assert_output --regexp 'set primary_ns = ns.*\.awsdns.*\.com' # Assert that we found a CNAME and use dig NS - assert_line --partial 'Using dig CNAME' - assert_line --partial 'Using dig NS' + assert_line --regexp 'Using dig.* CNAME' + assert_line --regexp 'Using dig.* NS' # Check all Authoritive DNS servers are returned if requested CHECK_ALL_AUTH_DNS=true @@ -168,8 +168,8 @@ teardown() { assert_output --regexp 'set primary_ns = ns[1-4]+\.duckdns\.org' # Assert that we found a CNAME but didn't use dig NS - assert_line --partial 'Using dig CNAME' - refute_line --partial 'Using dig NS' + assert_line --regexp 'Using dig.* CNAME' + refute_line --regexp 'Using dig.* NS' # Check all Authoritive DNS servers are returned if requested CHECK_ALL_AUTH_DNS=true diff --git a/test/u2-test-get_auth_dns-drill.bats b/test/u2-test-get_auth_dns-drill.bats index 33b2277..434a9b5 100644 --- a/test/u2-test-get_auth_dns-drill.bats +++ b/test/u2-test-get_auth_dns-drill.bats @@ -67,7 +67,7 @@ teardown() { # Assert that we've found the primary_ns server assert_output --regexp 'set primary_ns = ns[1-4]+\.duckdns\.org' # Assert that we had to use drill NS - assert_line --partial 'Using drill NS' + assert_line --regexp 'Using drill.* NS' # Check all Authoritive DNS servers are returned if requested CHECK_ALL_AUTH_DNS=true @@ -100,8 +100,8 @@ teardown() { assert_output --regexp 'set primary_ns = ns[1-4]+\.duckdns\.org' # Assert that we had to use drill NS - assert_line --partial 'Using drill SOA' - refute_line --partial 'Using drill NS' + assert_line --regexp 'Using drill.* SOA' + refute_line --regexp 'Using drill.* NS' # Check all Authoritive DNS servers are returned if requested CHECK_ALL_AUTH_DNS=true @@ -141,8 +141,8 @@ teardown() { assert_output --regexp 'set primary_ns = ns.*\.awsdns.*\.com' # Assert that we found a CNAME and use drill NS - assert_line --partial 'Using drill CNAME' - assert_line --partial 'Using drill NS' + assert_line --regexp 'Using drill.* CNAME' + assert_line --regexp 'Using drill.* NS' # Check all Authoritive DNS servers are returned if requested CHECK_ALL_AUTH_DNS=true @@ -192,8 +192,8 @@ teardown() { assert_output --regexp 'set primary_ns = ns[1-4]+\.duckdns\.org' # Assert that we found a CNAME but didn't use drill NS - assert_line --partial 'Using drill CNAME' - refute_line --partial 'Using drill NS' + assert_line --regexp 'Using drill.* CNAME' + refute_line --regexp 'Using drill.* NS' # Check all Authoritive DNS servers are returned if requested CHECK_ALL_AUTH_DNS=true diff --git a/test/u5-test-get_auth_dns-no-root-servers.bats b/test/u5-test-get_auth_dns-no-root-servers.bats index b88fd41..d218eb9 100644 --- a/test/u5-test-get_auth_dns-no-root-servers.bats +++ b/test/u5-test-get_auth_dns-no-root-servers.bats @@ -60,7 +60,7 @@ teardown() { # Assert that we've found the primary_ns server assert_output --regexp 'set primary_ns = ' # Assert that we had to use dig NS - assert_line --partial 'Using dig NS' + assert_line --regexp 'Using dig.* NS' # Check we didn't include any root servers refute_line --partial 'root-servers.net' @@ -89,7 +89,7 @@ teardown() { # Assert that we've found the primary_ns server assert_output --regexp 'set primary_ns = ' # Assert that we had to use dig SOA - assert_line --partial 'Using dig SOA' + assert_line --regexp 'Using dig.* SOA' # Check we didn't include any root servers refute_line --partial 'root-servers.net'