4 #AWS_ACCESS_KEY_ID="sdfsdfsdfljlbjkljlkjsdfoiwje"
6 #AWS_SECRET_ACCESS_KEY="xxxxxxx"
8 #This is the Amazon Route53 api wrapper for acme.sh
9 #All `_sleep` commands are included to avoid Route53 throttling, see
10 #https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/DNSLimitations.html#limits-api-requests
12 AWS_HOST
="route53.amazonaws.com"
13 AWS_URL
="https://$AWS_HOST"
15 AWS_WIKI
="https://github.com/acmesh-official/acme.sh/wiki/How-to-use-Amazon-Route53-API"
17 ######## Public functions #####################
19 #Usage: dns_myapi_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
24 AWS_ACCESS_KEY_ID
="${AWS_ACCESS_KEY_ID:-$(_readaccountconf_mutable AWS_ACCESS_KEY_ID)}"
25 AWS_SECRET_ACCESS_KEY
="${AWS_SECRET_ACCESS_KEY:-$(_readaccountconf_mutable AWS_SECRET_ACCESS_KEY)}"
26 AWS_DNS_SLOWRATE
="${AWS_DNS_SLOWRATE:-$(_readaccountconf_mutable AWS_DNS_SLOWRATE)}"
28 if [ -z "$AWS_ACCESS_KEY_ID" ] ||
[ -z "$AWS_SECRET_ACCESS_KEY" ]; then
29 _use_container_role || _use_instance_role
32 if [ -z "$AWS_ACCESS_KEY_ID" ] ||
[ -z "$AWS_SECRET_ACCESS_KEY" ]; then
34 AWS_SECRET_ACCESS_KEY
=""
35 _err
"You haven't specified the aws route53 api key id and and api key secret yet."
36 _err
"Please create your key and try again. see $(__green $AWS_WIKI)"
40 #save for future use, unless using a role which will be fetched as needed
41 if [ -z "$_using_role" ]; then
42 _saveaccountconf_mutable AWS_ACCESS_KEY_ID
"$AWS_ACCESS_KEY_ID"
43 _saveaccountconf_mutable AWS_SECRET_ACCESS_KEY
"$AWS_SECRET_ACCESS_KEY"
44 _saveaccountconf_mutable AWS_DNS_SLOWRATE
"$AWS_DNS_SLOWRATE"
47 _debug
"First detect the root zone"
48 if ! _get_root
"$fulldomain"; then
53 _debug _domain_id
"$_domain_id"
54 _debug _sub_domain
"$_sub_domain"
55 _debug _domain
"$_domain"
57 _info
"Getting existing records for $fulldomain"
58 if ! aws_rest GET
"2013-04-01$_domain_id/rrset" "name=$fulldomain&type=TXT"; then
63 if _contains
"$response" "<Name>$fulldomain.</Name>"; then
64 _resource_record
="$(echo "$response" | sed 's/<ResourceRecordSet>/"/g
' | tr '"' "\n" | grep "<Name
>$fulldomain.
</Name
>" | _egrep_o "<ResourceRecords.
*</ResourceRecords
>" | sed "s
/<ResourceRecords
>//" | sed "s
#</ResourceRecords>##")"
65 _debug
"_resource_record" "$_resource_record"
67 _debug
"single new add"
70 if [ "$_resource_record" ] && _contains
"$response" "$txtvalue"; then
71 _info
"The TXT record already exists. Skipping."
76 _debug
"Adding records"
78 _aws_tmpl_xml
="<ChangeResourceRecordSetsRequest xmlns=\"https://route53.amazonaws.com/doc/2013-04-01/\"><ChangeBatch><Changes><Change><Action>UPSERT</Action><ResourceRecordSet><Name>$fulldomain</Name><Type>TXT</Type><TTL>300</TTL><ResourceRecords>$_resource_record<ResourceRecord><Value>\"$txtvalue\"</Value></ResourceRecord></ResourceRecords></ResourceRecordSet></Change></Changes></ChangeBatch></ChangeResourceRecordSetsRequest>"
80 if aws_rest POST
"2013-04-01$_domain_id/rrset/" "" "$_aws_tmpl_xml" && _contains
"$response" "ChangeResourceRecordSetsResponse"; then
81 _info
"TXT record updated successfully."
82 if [ -n "$AWS_DNS_SLOWRATE" ]; then
83 _info
"Slow rate activated: sleeping for $AWS_DNS_SLOWRATE seconds"
84 _sleep
"$AWS_DNS_SLOWRATE"
100 AWS_ACCESS_KEY_ID
="${AWS_ACCESS_KEY_ID:-$(_readaccountconf_mutable AWS_ACCESS_KEY_ID)}"
101 AWS_SECRET_ACCESS_KEY
="${AWS_SECRET_ACCESS_KEY:-$(_readaccountconf_mutable AWS_SECRET_ACCESS_KEY)}"
102 AWS_DNS_SLOWRATE
="${AWS_DNS_SLOWRATE:-$(_readaccountconf_mutable AWS_DNS_SLOWRATE)}"
104 if [ -z "$AWS_ACCESS_KEY_ID" ] ||
[ -z "$AWS_SECRET_ACCESS_KEY" ]; then
105 _use_container_role || _use_instance_role
108 _debug
"First detect the root zone"
109 if ! _get_root
"$fulldomain"; then
110 _err
"invalid domain"
114 _debug _domain_id
"$_domain_id"
115 _debug _sub_domain
"$_sub_domain"
116 _debug _domain
"$_domain"
118 _info
"Getting existing records for $fulldomain"
119 if ! aws_rest GET
"2013-04-01$_domain_id/rrset" "name=$fulldomain&type=TXT"; then
124 if _contains
"$response" "<Name>$fulldomain.</Name>"; then
125 _resource_record
="$(echo "$response" | sed 's/<ResourceRecordSet>/"/g
' | tr '"' "\n" | grep "<Name
>$fulldomain.
</Name
>" | _egrep_o "<ResourceRecords.
*</ResourceRecords
>" | sed "s
/<ResourceRecords
>//" | sed "s
#</ResourceRecords>##")"
126 _debug
"_resource_record" "$_resource_record"
128 _debug
"no records exist, skip"
133 _aws_tmpl_xml
="<ChangeResourceRecordSetsRequest xmlns=\"https://route53.amazonaws.com/doc/2013-04-01/\"><ChangeBatch><Changes><Change><Action>DELETE</Action><ResourceRecordSet><ResourceRecords>$_resource_record</ResourceRecords><Name>$fulldomain.</Name><Type>TXT</Type><TTL>300</TTL></ResourceRecordSet></Change></Changes></ChangeBatch></ChangeResourceRecordSetsRequest>"
135 if aws_rest POST
"2013-04-01$_domain_id/rrset/" "" "$_aws_tmpl_xml" && _contains
"$response" "ChangeResourceRecordSetsResponse"; then
136 _info
"TXT record deleted successfully."
137 if [ -n "$AWS_DNS_SLOWRATE" ]; then
138 _info
"Slow rate activated: sleeping for $AWS_DNS_SLOWRATE seconds"
139 _sleep
"$AWS_DNS_SLOWRATE"
151 #################### Private functions below ##################################
158 # iterate over names (a.b.c.d -> b.c.d -> c.d -> d)
160 h
=$
(printf "%s" "$domain" | cut
-d .
-f $i-100)
161 _debug
"Checking domain: $h"
163 _error
"invalid domain"
167 # iterate over paginated result for list_hosted_zones
168 aws_rest GET
"2013-04-01/hostedzone"
170 if _contains
"$response" "<Name>$h.</Name>"; then
171 hostedzone
="$(echo "$response" | tr -d '\n' | sed 's/<HostedZone>/#&/g' | tr '#' '\n' | _egrep_o "<HostedZone
><Id
>[^
<]*<.Id
><Name
>$h.
<.Name
>.
*<PrivateZone
>false
<.PrivateZone
>.
*<.HostedZone
>")"
172 _debug hostedzone
"$hostedzone"
173 if [ "$hostedzone" ]; then
174 _domain_id
=$
(printf "%s\n" "$hostedzone" | _egrep_o
"<Id>.*<.Id>" |
head -n 1 | _egrep_o
">.*<" |
tr -d "<>")
175 if [ "$_domain_id" ]; then
176 _sub_domain
=$
(printf "%s" "$domain" | cut
-d .
-f 1-$p)
180 _err
"Can't find domain with id: $h"
184 if _contains
"$response" "<IsTruncated>true</IsTruncated>" && _contains
"$response" "<NextMarker>"; then
186 _nextMarker
="$(echo "$response" | _egrep_o "<NextMarker
>.
*</NextMarker
>" | cut -d '>' -f 2 | cut -d '<' -f 1)"
187 _debug
"NextMarker" "$_nextMarker"
191 _debug
"Checking domain: $h - Next Page "
192 aws_rest GET
"2013-04-01/hostedzone" "marker=$_nextMarker"
200 _use_container_role
() {
201 # automatically set if running inside ECS
202 if [ -z "$AWS_CONTAINER_CREDENTIALS_RELATIVE_URI" ]; then
203 _debug
"No ECS environment variable detected"
206 _use_metadata
"169.254.170.2$AWS_CONTAINER_CREDENTIALS_RELATIVE_URI"
209 _use_instance_role
() {
210 _url
="http://169.254.169.254/latest/meta-data/iam/security-credentials/"
211 _debug
"_url" "$_url"
212 if ! _get
"$_url" true
1 | _head_n
1 |
grep -Fq 200; then
213 _debug
"Unable to fetch IAM role from instance metadata"
216 _aws_role
=$
(_get
"$_url" "" 1)
217 _debug
"_aws_role" "$_aws_role"
218 _use_metadata
"$_url$_aws_role"
226 while read -r _line; do
227 _key="$
(echo "${_line%%:*}" |
tr -d '"')"
229 _debug3 "_key
" "$_key"
230 _secure_debug3 "_value
" "$_value"
232 AccessKeyId) echo "AWS_ACCESS_KEY_ID
=$_value" ;;
233 SecretAccessKey) echo "AWS_SECRET_ACCESS_KEY
=$_value" ;;
234 Token) echo "AWS_SESSION_TOKEN
=$_value" ;;
239 _secure_debug
"_aws_creds" "$_aws_creds"
241 if [ -z "$_aws_creds" ]; then
249 #method uri qstr data
262 _debug2 CanonicalURI
"$CanonicalURI"
264 CanonicalQueryString
="$qsr"
265 _debug2 CanonicalQueryString
"$CanonicalQueryString"
267 RequestDate
="$(date -u +"%Y
%m
%dT
%H
%M
%SZ
")"
268 _debug2 RequestDate
"$RequestDate"
270 #RequestDate="20161120T141056Z" ##############
272 export _H1
="x-amz-date: $RequestDate"
275 CanonicalHeaders
="host:$aws_host\nx-amz-date:$RequestDate\n"
276 SignedHeaders
="host;x-amz-date"
277 if [ -n "$AWS_SESSION_TOKEN" ]; then
278 export _H3
="x-amz-security-token: $AWS_SESSION_TOKEN"
279 CanonicalHeaders
="${CanonicalHeaders}x-amz-security-token:$AWS_SESSION_TOKEN\n"
280 SignedHeaders
="${SignedHeaders};x-amz-security-token"
282 _debug2 CanonicalHeaders
"$CanonicalHeaders"
283 _debug2 SignedHeaders
"$SignedHeaders"
285 RequestPayload
="$data"
286 _debug2 RequestPayload
"$RequestPayload"
290 CanonicalRequest
="$mtd\n$CanonicalURI\n$CanonicalQueryString\n$CanonicalHeaders\n$SignedHeaders\n$(printf "%s
" "$RequestPayload" | _digest "$Hash" hex)"
291 _debug2 CanonicalRequest
"$CanonicalRequest"
293 HashedCanonicalRequest
="$(printf "$CanonicalRequest%s
" | _digest "$Hash" hex)"
294 _debug2 HashedCanonicalRequest
"$HashedCanonicalRequest"
296 Algorithm
="AWS4-HMAC-SHA256"
297 _debug2 Algorithm
"$Algorithm"
299 RequestDateOnly
="$(echo "$RequestDate" | cut -c 1-8)"
300 _debug2 RequestDateOnly
"$RequestDateOnly"
305 CredentialScope
="$RequestDateOnly/$Region/$Service/aws4_request"
306 _debug2 CredentialScope
"$CredentialScope"
308 StringToSign
="$Algorithm\n$RequestDate\n$CredentialScope\n$HashedCanonicalRequest"
310 _debug2 StringToSign
"$StringToSign"
312 kSecret
="AWS4$AWS_SECRET_ACCESS_KEY"
314 #kSecret="wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY" ############################
316 _secure_debug2 kSecret
"$kSecret"
318 kSecretH
="$(printf "%s
" "$kSecret" | _hex_dump | tr -d " ")"
319 _secure_debug2 kSecretH
"$kSecretH"
321 kDateH
="$(printf "$RequestDateOnly%s
" | _hmac "$Hash" "$kSecretH" hex)"
322 _debug2 kDateH
"$kDateH"
324 kRegionH
="$(printf "$Region%s
" | _hmac "$Hash" "$kDateH" hex)"
325 _debug2 kRegionH
"$kRegionH"
327 kServiceH
="$(printf "$Service%s
" | _hmac "$Hash" "$kRegionH" hex)"
328 _debug2 kServiceH
"$kServiceH"
330 kSigningH
="$(printf "%s
" "aws4_request
" | _hmac "$Hash" "$kServiceH" hex)"
331 _debug2 kSigningH
"$kSigningH"
333 signature
="$(printf "$StringToSign%s
" | _hmac "$Hash" "$kSigningH" hex)"
334 _debug2 signature
"$signature"
336 Authorization
="$Algorithm Credential=$AWS_ACCESS_KEY_ID/$CredentialScope, SignedHeaders=$SignedHeaders, Signature=$signature"
337 _debug2 Authorization
"$Authorization"
339 _H2
="Authorization: $Authorization"
344 url
="$AWS_URL/$ep?$qsr"
347 if [ "$mtd" = "GET" ]; then
348 response
="$(_get "$url")"
350 response
="$(_post "$data" "$url")"
354 _debug2 response
"$response"
355 if [ "$_ret" = "0" ]; then
356 if _contains
"$response" "<ErrorResponse"; then
357 _err
"Response error:$response"