From 945dde0077f96153ab87e1d5692a9db2fa4fe2e3 Mon Sep 17 00:00:00 2001 From: Steve Kennedy <skennedy@xyide.com> Date: Tue, 14 Dec 2021 10:16:19 -0500 Subject: [PATCH 1/2] GoDaddy Updates - Support for domains longer than 3 segments (ex. 1.www.mydomain.com). - No need for GODADDY_BASE. Domain will be automatically determined from full RR name. - Update to delete challenge records. --- dns_scripts/00GoDaddy-README.txt | 63 ------- dns_scripts/GoDaddy-README.txt | 2 + dns_scripts/dns_add_godaddy | 2 +- dns_scripts/dns_del_godaddy | 2 +- dns_scripts/dns_godaddy | 312 ++++++++++++------------------- 5 files changed, 119 insertions(+), 262 deletions(-) delete mode 100644 dns_scripts/00GoDaddy-README.txt diff --git a/dns_scripts/00GoDaddy-README.txt b/dns_scripts/00GoDaddy-README.txt deleted file mode 100644 index 9973556e..00000000 --- a/dns_scripts/00GoDaddy-README.txt +++ /dev/null @@ -1,63 +0,0 @@ -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) Put JSON.sh in the getssl DNS scripts directory - Default: /usr/share/getssl/dns_scripts - -2) Open your config file (the global file in ~/.getssl/getssl.cfg - or the per-account file in ~/.getssl/example.net/getssl.cfg - -3) Set the following options: - VALIDATE_VIA_DNS="true" - 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="..." - # 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 - 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 (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 -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/GoDaddy-README.txt b/dns_scripts/GoDaddy-README.txt index d58ba737..54db3b8e 100644 --- a/dns_scripts/GoDaddy-README.txt +++ b/dns_scripts/GoDaddy-README.txt @@ -34,6 +34,8 @@ 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="..." + # If you have been using GODADDY_BASE previously, then it is no + longer necessary. The base domain will automatically be determined. 4) Set any other options that you wish (per the standard directions.) Use the test CA to make sure that diff --git a/dns_scripts/dns_add_godaddy b/dns_scripts/dns_add_godaddy index f9be7453..0baf3126 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 "_acme-challenge.${fulldomain}." "${token}" diff --git a/dns_scripts/dns_del_godaddy b/dns_scripts/dns_del_godaddy index 4a3228f7..692dff89 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 "_acme-challenge.${fulldomain}." "${token}" diff --git a/dns_scripts/dns_godaddy b/dns_scripts/dns_godaddy index 0d41e6a1..cdcfed31 100755 --- a/dns_scripts/dns_godaddy +++ b/dns_scripts/dns_godaddy @@ -2,7 +2,7 @@ # Copyright (C) 2017,2018 Timothe Litt litt at acm _dot org -VERSION="1.0.3" +VERSION="2.0" PROG="`basename $0`" # This script is used to update TXT records in GoDaddy DNS server @@ -42,8 +42,8 @@ while getopts 'dhj:k:s:t:qv' opt; do *) cat <<EOF Usage - $PROG [-dt -h -j JSON -k:KEY -s:SECRET -q] add domain name data [ttl] - $PROG [-dt -h -j JSON -k:KEY -s:SECRET -q] del domain name data + $PROG [-dt -h -j JSON -k:KEY -s:SECRET -q] add name data [ttl] + $PROG [-dt -h -j JSON -k:KEY -s:SECRET -q] del name data Add or delete TXT records from GoDaddy DNS @@ -59,8 +59,6 @@ Arguments: del - remove the specified record from the domain - domain is the domain name, e.g. example.org - name is the DNS record name to add, e.g. _acme-challenge.example.org. Note that trailing '.' is significant in DNS. @@ -73,9 +71,6 @@ 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 @@ -88,7 +83,6 @@ 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 @@ -149,19 +143,13 @@ 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" +name="$2" if [ -z "$name" ]; then echo "'name' parameter is required, see -h" >&2 exit 3 fi ! [[ "$name" =~ [.]$ ]] && name="${name}.${domain}." -data="$4" +data="$3" if [ -z "$data" ]; then echo "'data' parameter is required, see -h" >&2 exit 3 @@ -207,28 +195,92 @@ fi [ -n "$DEBUG" ] && echo "$authhdr" >&2 -if [ "$op" = "add" ]; then - # May need to retry due to zone cuts - - while [[ "$domain" =~ [^.]+\.[^.]+ ]]; do - - 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.'}"'"' +#strip off the last period +if [[ "$name" =~ ^.+\. ]]; then + name=${name%?} +fi + +reqdomain= +reqname= + +# GoDaddy REST API URL is in the format /v1/domains/{domain}/records/{type}/{name} +# for adding/updating (PUT) or deleting (DELETE) a record. The API will support up +# to three segments in domain names (ex. mydomain.com and www.mydomain.com) +# in order to determine which domain the API call will affect (both mydomain.com and +# www.mydomain.com will result in the modification of the mydomain.com domain. Any +# more than three segments (ex. sub.something.mydomain.com will result in +# the API throwing a MISMATCH_FORMAT error. +# +# Examples +# 1. If mydomain.com was provided to this script as the domain parameter, and +# _acme-challengemydomain.com was provided as the name, then the URL +# /v1/domains/mydomain.com/records/TXT/_acme-challenge will be used which +# +# 2. If www.mydomain.com was provided to this script as the domain parameter, +# and _acme-challenge.www.mydomain.com was provided as the name, then the +# URL /v1/domains/mydomain.com/records/TXT/_acme-challenge.www will be used. + +# Determine the domain and the name to use for the API the URL +# The name parameter given to us is in the format challenge.domain. +# (ex _acme-challenge.mydomain.com. - note the trailing period). We will just +# use the name given us to determine the domain + +while [[ "$name" =~ ^([^.]+)\.([^.]+.*) ]]; do + if [ -n "${reqname}" ]; then reqname="${reqname}."; fi + reqname="${reqname}${BASH_REMATCH[1]}" + testdomain="${BASH_REMATCH[2]}" + name=$testdomain + if [[ ! "$name" =~ [^.]+\.[^.]+ ]]; then + exit 1 + fi + + url="$API/$testdomain" + + [ -n "$DEBUG" ] && echo "Looking for domain ${testdomain}" + + response="$(curl -i -s -X GET --config - "${url}" <<EOF + header = "Content-Type: application/json" + header = "$authhdr" +EOF +)" + sts=$? + + [ -n "$DEBUG" ] && cat >&2 <<EOF + +Response: +curl status = $sts +-------- +$response +-------- +EOF + if [ $sts -ne 0 ]; then + echo "curl error $sts querying domain" >&2 + exit $sts fi - url="$API/$domain/records/TXT/$reqname" + if echo "$response" | grep -q '^HTTP/.* 200 '; then + [ -n "$DEBUG" ] && echo "Found domain ${testdomain}" + reqdomain=${testdomain} + break + fi + + code="`echo "$response" | grep '"code":' | sed -e's/^.*"code":"//; s/\".*$//'`" + if [ "$code" = 'NOT_FOUND' ]; then + continue + fi +done + + +if [ -z "$reqdomain" ] || [ -z "$reqname" ]; then + echo "Unable to determine domain or RR name" >&2 + exit 3 +fi + + + +if [ "$op" = "add" ]; then + + url="$API/$reqdomain/records/TXT/$reqname" request='[{"data":"'$data'","ttl":'$ttl'}]' [ -n "$DEBUG" ] && cat >&2 <<EOF @@ -252,54 +304,38 @@ curl status = $sts $result -------- EOF - if [ $sts -ne 0 ]; then - echo "curl error $sts adding record" >&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 + if [ $sts -ne 0 ]; then + echo "curl error $sts adding record" >&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 - echo "Request failed $msg" >&2 - exit 1 + exit 0 # Duplicate record is still success fi - [ -n "$VERB" ] && echo "$domain: added $name $ttl TXT \"$data\"" >&2 - exit 0 - done + echo "Request failed $msg" >&2 + exit 1 + fi + [ -n "$VERB" ] && echo "$reqdomain: added $reqname $ttl TXT \"$data\"" >&2 + exit 0 + 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 +if [ "$op" = "del" ]; then + url="$API/$reqdomain/records/TXT/$reqname" + [ -n "$DEBUG" ] && echo "Deleting challenge TXT records at: $url" >&2 - current="$(curl -i -s -X GET --config - "$url" <<EOF - header = "$authhdr" + current="$(curl -i -s -X DELETE --config - "$url" <<EOF + header = "$authhdr" EOF )" + sts=$? if [ $sts -ne 0 ]; then echo "curl error $sts for query" >&2 @@ -312,132 +348,14 @@ Response $current -------- EOF + if ! echo "$current" | grep -q '^HTTP/.* 200 '; then code="`echo "$current" | grep '"code":' | sed -e's/^.*"code":"//; s/\".*$//'`" msg="`echo "$current" | grep '"message":' | sed -e's/^.*"message":"//; s/\".*$//'`" - 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 - # 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 <<EOF -Old TXT RRSET: -$current -EOF - -# Remove the desired record. The name must be relative. Order varies. - -eval 'name="$''{name%'"'.$domain.'}"'"' - -match="$(printf '"name":"%s","data":"%s","ttl":' "$name" "$data")" -cmd="$(printf 'echo %s%s%s | grep -v %s%s%s' "'" "$current" "'" "'" "$match" "'")" -eval 'new="$('"$cmd"')"' - -match="$(printf '"data":"%s","name":"%s","ttl":' "$data" "$name")" -cmd="$(printf 'echo %s%s%s | grep -v %s%s%s' "'" "$current" "'" "'" "$match" "'")" -eval 'new="$('"$cmd"')"' - -if [ "$new" = "$base" ]; then - [ -n "$VERB" ] && echo "$domain: $name TXT \"$data\" does not exist" >&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 + [ -n "$VERB" ] && echo "$reqdomain: deleted $reqname TXT \"$data\"" >&2 + exit 0 fi - -request="[$new]" - -[ -n "$DEBUG" ] && cat >&2 <<EOF -New TXT RRSET will be -$fmtnew - -EOF - -url="$API/$domain/records/TXT" - -[ -n "$DEBUG" ] && cat >&2 <<EOF -Replace (delete) request to: $url --------- -$request --------- -EOF - -result="$(curl -i -s -X PUT -d "$request" --config - "$url" <<EOF - header = "Content-Type: application/json" - header = "$authhdr" -EOF -)" -sts=$? -[ -n "$DEBUG" ] && cat >&2 <<EOF - -Result: -curl status = $sts --------- -$result --------- -EOF - -if [ $sts -ne 0 ]; then - echo "curl error $sts deleting record" >&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 cc6a3d054cc87046a02ac08036b33038166ba7f1 Mon Sep 17 00:00:00 2001 From: Steve Kennedy <skennedy@xyide.com> Date: Mon, 20 May 2024 00:46:11 -0400 Subject: [PATCH 2/2] Add BASH implementation of Route 53 scripts --- dns_scripts/Route53-README.md | 37 +++++++ dns_scripts/dns_add_route53 | 18 +++ dns_scripts/dns_del_route53 | 18 +++ dns_scripts/dns_route53 | 199 ++++++++++++++++++++++++++++++++++ 4 files changed, 272 insertions(+) create mode 100644 dns_scripts/Route53-README.md create mode 100755 dns_scripts/dns_add_route53 create mode 100755 dns_scripts/dns_del_route53 create mode 100644 dns_scripts/dns_route53 diff --git a/dns_scripts/Route53-README.md b/dns_scripts/Route53-README.md new file mode 100644 index 00000000..b078a782 --- /dev/null +++ b/dns_scripts/Route53-README.md @@ -0,0 +1,37 @@ +# Using Route53 BASH scripts for LetsEncrypt domain validation. + +## Quick guide to setting up getssl for domain validation of Route53 DNS domains. + +There a few prerequisites to using getssl with Route53 DNS: + +1. You will need to set up an IAM user with the necessary permissions to modify resource records in the hosted zone. + + - route53:ListHostedZones + - route53:ChangeResourceRecordSets + +1. You will need the AWS CLI Client installed on your machine. + +1. You will need to configure the client for the IAM user that has permission to modify the resource records. + +With those in hand, the installation procedure is: + +1. Open your config file (the global file in ~/.getssl/getssl.cfg + or the per-account file in ~/.getssl/example.net/getssl.cfg) + +1. Set the following options: + + - VALIDATE_VIA_DNS="true" + - DNS_ADD_COMMAND="/usr/share/getssl/dns_scripts/dns_add_route53" + - DNS_DEL_COMMAND="/usr/share/getssl/dns_scripts/dns_del_route53" + + The AWS CLI profile to use (will use _default_ if not specified) + + - export AWS*CLI_PROFILE="\_profile name*" + +1. 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. + +There are additional options, which are documented in `dns_route53 -h` diff --git a/dns_scripts/dns_add_route53 b/dns_scripts/dns_add_route53 new file mode 100755 index 00000000..88e3f0b5 --- /dev/null +++ b/dns_scripts/dns_add_route53 @@ -0,0 +1,18 @@ +#!/bin/bash + +# Add token to Route53 dns using dns_route53 bash version + +fulldomain="$1" +token="$2" + +[ -z "$ROUTE53_SCRIPT" ] && ROUTE53_SCRIPT="/usr/share/getssl/dns_scripts/dns_route53" +[[ "$ROUTE53_SCRIPT" =~ ^~ ]] && \ + eval 'ROUTE53_SCRIPT=`readlink -nf ' $ROUTE53_SCRIPT '`' + +if [ ! -x "$ROUTE53_SCRIPT" ]; then + echo "$ROUTE53_SCRIPT: not found. Please install, softlink or set ROUTE53_SCRIPT to its full path" + echo "See ROUTE53-README.txt for complete instructions." + exit 3 +fi + +$ROUTE53_SCRIPT -q add "${fulldomain}." "${token}" diff --git a/dns_scripts/dns_del_route53 b/dns_scripts/dns_del_route53 new file mode 100755 index 00000000..79a05700 --- /dev/null +++ b/dns_scripts/dns_del_route53 @@ -0,0 +1,18 @@ +#!/bin/bash + +# Delete token from Route53 dns using dns_route53 bash version + +fulldomain="$1" +token="$2" + +[ -z "$ROUTE53_SCRIPT" ] && ROUTE53_SCRIPT="/usr/share/getssl/dns_scripts/dns_route53" +[[ "$ROUTE53_SCRIPT" =~ ^~ ]] && \ + eval 'ROUTE53_SCRIPT=`readlink -nf ' $ROUTE53_SCRIPT '`' + +if [ ! -x "$ROUTE53_SCRIPT" ]; then + echo "$ROUTE53_SCRIPT: not found. Please install, softlink or set ROUTE53_SCRIPT to its full path" + echo "See ROUTE53-README.txt for complete instructions." + exit 3 +fi + +$ROUTE53_SCRIPT -q del "${fulldomain}." "${token}" diff --git a/dns_scripts/dns_route53 b/dns_scripts/dns_route53 new file mode 100644 index 00000000..5f1aaab7 --- /dev/null +++ b/dns_scripts/dns_route53 @@ -0,0 +1,199 @@ +#!/usr/bin/env bash +VERSION="1.0" +PROG="$(basename "$0")" + +QUIET=n + +while getopts 'dhp:t:z:i:qv' opt; do + case $opt in + d) DEBUG="Y" ;; + p) AWS_CLI_PROFILE="$OPTARG" ;; + q) QUIET= ;; + v) echo "dns_route53 version $VERSION"; exit 0 ;; + z) ROUTE53_HOSTED_ZONE_NAME="$OPTARG" ;; + i) ROUTE53_HOSTED_ZONE_ID="$OPTARG" ;; + *) + cat <<EOF +Usage + $PROG [-dt -q] add name data [ttl] + $PROG [-dt -h -p "aws-profile-name" -q] del name data + +Add or delete TXT records from Route53 Hosted Zone + +You must have the AWS CLI installed and a profile configured for this script to work. + The IAM user that the profile uses requires the following action permissions in AWS: + - route53:ListHostedZones - Not necessary if zone ID is available to this script + - route53:ChangeResourceRecordSets + +With getssl, this script is called from the dns_add_route53 and +dns_del_route53 wrapper scripts. + +Arguments: + add - add the specified record to the domain + + del - remove the specified record from the domain + + name is the fully qualified record name to create the challenge for e.g. www.example.org. Note that trailing '.' is necessary. Also note that _acme-challenge. will automatically be prepended by this script + + data is the record data, e.g. "myverificationtoken" + + ttl is optional and will default to 120 if not specified + + If it is necessary to turn on debugging externally, define + ROUTE53_DEBUG="y" (any non-null string will do). + For minimal trace output (to override -q), define ROUTE53_TRACE="y". + +Options + -d Provide debugging output - all requests and responses + -h This help. + -i: The hosted zone ID + -p: The AWS CLI profile to use. Will use default if not specified + -q: Quiet - omit normal success messages + -z: The hosted zone name. Will be used to determine the zone ID if ID was not provided + + All output, except for this help text, is to stderr. + +Environment variables + ROUTE53_SCRIPT location of this script + ROUTE53_HOSTED_ZONE_NAME The name of the hosted zone name. If not specified, then the name will be determined from the record name provided to this script + ROUTE53_HOSTED_ZONE_ID The id of the hosted zone to be used instead of trying to automatically determine the ID + AWS_CLI_PROFILE the aws cli profile to use if not using default + +BUGS + Report any issues to https://github.com/xyide/getssl/issues +EOF + exit 0 + ;; + esac +done +shift $((OPTIND-1)) + +if [ -z "$AWS_CLI_PROFILE" ]; then + echo "AWS_CLI_PROFILE not defined. Using default" >&2 + AWS_CLI_PROFILE=default +fi + +op="$1" +if ! [[ "$op" =~ ^(add|del)$ ]]; then + echo "Operation must be \"add\" or \"del\"" >&2 + exit 3 +fi +name="$2" +if [ -z "$name" ]; then + echo "'name' parameter is required, see -h" >&2 + exit 3 +fi +data="$3" +if [ -z "$data" ]; then + echo "'data' parameter is required, see -h" >&2 + exit 3 +fi + +if [ "$op" = 'del' ]; then + ttl=120 +elif [ -z "$5" ]; then + ttl="120" +elif ! [[ "$5" =~ ^[0-9]+$ ]]; then + echo "TTL $5 is not numeric" >&2 + exit 3 +elif [ "$5" -lt 120 ]; then + [ -n "$VERB" ] && \ + echo "$5 is too small. Using TTL of 120 instead" >&2 + ttl="120" +else + ttl="$5" +fi + +# end processing parameters + +[ -n "$DEBUG" ] && \ + echo "$PROG: $op $name \"$data\" $ttl" >&2 + +# Determine what actual hosted zone to use. + +HOSTED_ZONE_NAME=$ROUTE53_HOSTED_ZONE_NAME +HOSTED_ZONE_ID=$ROUTE53_HOSTED_ZONE_ID +RR_NAME="_acme-challenge.${name}" +RR_VALUE="${data}" + +# Function to parse through the segments in the supplied name +# to determine the zone and its id +function determine_hosted_zone_name_and_id() { + TMP_NAME=$name + TMP_RR_NAME= + while [[ "$TMP_NAME" =~ ^([^.]+)\.([^.]+.*) ]]; do + if [ -n "${TMP_RR_NAME}" ]; then + TMP_RR_NAME="${TMP_RR_NAME}."; + fi + TMP_RR_NAME="${TMP_RR_NAME}${BASH_REMATCH[1]}" + testdomain="${BASH_REMATCH[2]}" + [ -n "$DEBUG" ] && echo "Testing hosted zone ${testdomain}" + TMP_NAME=$testdomain + if [[ ! "$TMP_NAME" =~ [^.]+\.[^.]+ ]]; then + [ -n "$DEBUG" ] && echo "No segments left" + exit 1 + fi + + TMP_ZONE_ID=$(aws --profile=${AWS_CLI_PROFILE} route53 list-hosted-zones --query "HostedZones[?Name=='${testdomain}'].Id | [0]" | sed -e 's/^"//' -e 's/"$//') + + + if [ "${TMP_ZONE_ID}" != "null" ]; then + [ -n "$DEBUG" ] && echo "Found hosted zone ${testdomain}" + HOSTED_ZONE_NAME=${testdomain} + HOSTED_ZONE_ID=$TMP_ZONE_ID + break + fi + done +} + +# If zone ID is specified, then use it to determine the hosted zone name +if [ -n "${HOSTED_ZONE_ID}" ]; then + HOSTED_ZONE_NAME=$(aws --profile=${AWS_CLI_PROFILE} route53 list-hosted-zones --query "HostedZones[?Id=='${ZONE_ID}'].Name | [0]" | sed -e 's/^"//' -e 's/"$//') +# If zone name is specified, then use it to get the zone id +elif [ -n "${HOSTED_ZONE_NAME}" ]; then + HOSTED_ZONE_ID=$(aws --profile=${AWS_CLI_PROFILE} route53 list-hosted-zones --query "HostedZones[?Name=='${HOSTED_ZONE_NAME}'].Id | [0]" | sed -e 's/^"//' -e 's/"$//') +else + determine_hosted_zone_name_and_id +fi + + + if [ -z "${HOSTED_ZONE_ID}" ]; then + echo "Hosted zone id not specified or determined" >&2 + exit 3 +fi + +if [ "$op" = "add" ]; then + ACTION="UPSERT" +elif [ "$op" = "del" ]; then + ACTION="DELETE" +else + echo "Unsupported Operation: $op" >&2 +fi + +CHANGE_BATCH=' +{ + "Comment": "GetSSL LetsEncrypt DNS Challenge", + "Changes": [{ + "Action" : "'"$ACTION"'", + "ResourceRecordSet" : { + "Name" : "'"$RR_NAME"'", + "Type" : "TXT", + "TTL" : '${ttl}', + "ResourceRecords" : [{ + "Value" : "\"'$RR_VALUE'\"" + }] + } + }] + } +' + + +[ -n "$DEBUG" ] && echo "${CHANGE_BATCH}" >&2 + +aws \ + --profile=${AWS_CLI_PROFILE} \ + route53 \ + change-resource-record-sets \ + --hosted-zone-id=${HOSTED_ZONE_ID} \ + --change-batch "${CHANGE_BATCH}" +exit $? \ No newline at end of file