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"