From 078a431a432507146002b5122295f87ae2d0b5ec Mon Sep 17 00:00:00 2001 From: Roger Lehrke <35877543+rlehrke@users.noreply.github.com> Date: Mon, 17 Aug 2020 13:09:38 -0500 Subject: [PATCH] Update dns_aws.sh Use `hostedzonesbyname` Route 53 API endpoint instead of `hostedzones` endpoint. The `hostedzones` endpoint returns all hosted zones for a given Route 53 account in groups of 100. For AWS Route 53 accounts with many domains, this could mean a large number of requests to the `hostedzones` endpoint as it progresses through each page of 100 results. This will often result in a "Rate exceeded" API error from Route 53. Instead of using `hostedzones` endpoint, we can use `hostedzonesbyname` and then filter by the specific domain we are looking for and ask for a `max-items` of 1. The while loop in _get_root() starts with a given domain and removes parts from the front of the given domain if no match is found. For example, when requesting a certificate for `test.www.domain.co.uk`, the while loop will check for Route 53 hosted zones for: 1st: test.www.domain.co.uk 2nd: www.domain.co.uk 3rd: domain.co.uk 4th: co.uk 5th: uk The first two checks will result in no matches, while the third check should be successful (if, of course, domain.co.uk is actually a hosted zone in the given AWS account). Now imagine that the given AWS account owns 2500 domains and, therefore, has 2500 hosted zones. Using the `hostedzones` endpoint would result in: 1st: 25 GET requests to the Route 53 API looking for a match to test.www.domain.co.uk 2nd: 25 GET requests to the Route 53 API looking for a match to www.domain.co.uk 3rd: 25 GET requests to the Route 53 API looking for a match to domain.co.uk 4th: 25 GET requests to the Route 53 API looking for a match to co.uk 5th: 25 GET requests to the Route 53 API looking for a match to uk This would far exceed the Route 53 limit of five requests per second. Using `hostedzonesbyname` results in a dramatic reduction in Route 53 API GET requests for AWS accounts with large numbers of hosted zones. --- dnsapi/dns_aws.sh | 96 +++++++++++++++++++++++++---------------------- 1 file changed, 52 insertions(+), 44 deletions(-) diff --git a/dnsapi/dns_aws.sh b/dnsapi/dns_aws.sh index 068c337c..cb288e1d 100755 --- a/dnsapi/dns_aws.sh +++ b/dnsapi/dns_aws.sh @@ -12,7 +12,7 @@ AWS_HOST="route53.amazonaws.com" AWS_URL="https://$AWS_HOST" -AWS_WIKI="https://github.com/acmesh-official/acme.sh/wiki/How-to-use-Amazon-Route53-API" +AWS_WIKI="https://github.com/Neilpang/acme.sh/wiki/How-to-use-Amazon-Route53-API" ######## Public functions ##################### @@ -152,50 +152,58 @@ dns_aws_rm() { _get_root() { domain=$1 - i=2 - p=1 - - if aws_rest GET "2013-04-01/hostedzone"; then - while true; do - h=$(printf "%s" "$domain" | cut -d . -f $i-100) - _debug2 "Checking domain: $h" - if [ -z "$h" ]; then - if _contains "$response" "true" && _contains "$response" ""; then - _debug "IsTruncated" - _nextMarker="$(echo "$response" | _egrep_o ".*" | cut -d '>' -f 2 | cut -d '<' -f 1)" - _debug "NextMarker" "$_nextMarker" - if aws_rest GET "2013-04-01/hostedzone" "marker=$_nextMarker"; then - _debug "Truncated request OK" - i=2 - p=1 - continue - else - _err "Truncated request error." - fi - fi - #not valid - _err "Invalid domain" - return 1 - fi + # Start with field 2 since each domain starts with _acme-challenge + # Example: _acme-challenge.www.domain.com + field=2 + subdomain_part=1 + + while true; do + hostname=$(printf "%s" "$domain" | cut -d . -f $field-100) + + if [ -z "$hostname" ] + then + _debug "There are no more fields in the hostname to check" + _err "No matching Route 53 hosted zones for: $domain" + return 1 + fi - if _contains "$response" "$h."; then - hostedzone="$(echo "$response" | sed 's//#&/g' | tr '#' '\n' | _egrep_o "[^<]*<.Id>$h.<.Name>.*false<.PrivateZone>.*<.HostedZone>")" + _debug "Checking domain: $hostname" + + if aws_rest GET "2013-04-01/hostedzonesbyname" "dnsname=$hostname&maxitems=1"; then + if _contains "$response" "$hostname."; then + hostedzone="$(echo "$response" | sed 's//#&/g' | tr '#' '\n' | _egrep_o "[^<]*<.Id>$hostname.<.Name>.*false<.PrivateZone>.*<.HostedZone>")" _debug hostedzone "$hostedzone" if [ "$hostedzone" ]; then _domain_id=$(printf "%s\n" "$hostedzone" | _egrep_o ".*<.Id>" | head -n 1 | _egrep_o ">.*<" | tr -d "<>") if [ "$_domain_id" ]; then - _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) - _domain=$h + _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$subdomain_part) + _domain=$hostname return 0 fi - _err "Can't find domain with id: $h" + _err "Can't find domain with id: $hostname" return 1 fi + else + _debug "Route 53 does not have a hosted zone for: $hostname." + _debug "Moving on to next part of the hostname" fi - p=$i - i=$(_math "$i" + 1) - done - fi + else + _err "Getting Route 53 hosted zones by name failed for: $domain" + return 1 + fi + + subdomain_part=$field + field=$(_math "$field" + 1) + + if [ -n "$AWS_DNS_SLOWRATE" ]; then + _info "Slow rate activated: sleeping for $AWS_DNS_SLOWRATE seconds" + _sleep "$AWS_DNS_SLOWRATE" + else + _sleep 1 + fi + done + + _err "_get_root failed for domain: $domain" return 1 } @@ -222,21 +230,21 @@ _use_instance_role() { _use_metadata() { _aws_creds="$( - _get "$1" "" 1 | - _normalizeJson | - tr '{,}' '\n' | - while read -r _line; do + _get "$1" "" 1 \ + | _normalizeJson \ + | tr '{,}' '\n' \ + | while read -r _line; do _key="$(echo "${_line%%:*}" | tr -d '"')" _value="${_line#*:}" _debug3 "_key" "$_key" _secure_debug3 "_value" "$_value" case "$_key" in - AccessKeyId) echo "AWS_ACCESS_KEY_ID=$_value" ;; - SecretAccessKey) echo "AWS_SECRET_ACCESS_KEY=$_value" ;; - Token) echo "AWS_SESSION_TOKEN=$_value" ;; + AccessKeyId) echo "AWS_ACCESS_KEY_ID=$_value" ;; + SecretAccessKey) echo "AWS_SECRET_ACCESS_KEY=$_value" ;; + Token) echo "AWS_SESSION_TOKEN=$_value" ;; esac - done | - paste -sd' ' - + done \ + | paste -sd' ' - )" _secure_debug "_aws_creds" "$_aws_creds"