From e5702045b055c3ba9aba440a0769549db52ab41b Mon Sep 17 00:00:00 2001 From: Timothe Litt Date: Tue, 16 May 2017 21:01:11 -0400 Subject: [PATCH] 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