2016-11-20 15:57:07 +01:00
#!/usr/bin/env sh
2023-11-18 17:57:12 +01:00
# shellcheck disable=SC2034
dns_aws_info = ' Amazon AWS Route53 domain API
Site: docs.aws.amazon.com/route53/
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_aws
Options:
AWS_ACCESS_KEY_ID API Key ID
AWS_SECRET_ACCESS_KEY API Secret
'
# All `_sleep` commands are included to avoid Route53 throttling, see
# https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/DNSLimitations.html#limits-api-requests
2016-11-20 15:57:07 +01:00
AWS_HOST = "route53.amazonaws.com"
AWS_URL = " https:// $AWS_HOST "
2020-01-30 05:06:39 +01:00
AWS_WIKI = "https://github.com/acmesh-official/acme.sh/wiki/How-to-use-Amazon-Route53-API"
2016-11-20 15:57:07 +01:00
######## Public functions #####################
#Usage: dns_myapi_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
dns_aws_add( ) {
fulldomain = $1
txtvalue = $2
2018-02-20 01:34:55 +01:00
AWS_ACCESS_KEY_ID = " ${ AWS_ACCESS_KEY_ID :- $( _readaccountconf_mutable AWS_ACCESS_KEY_ID) } "
AWS_SECRET_ACCESS_KEY = " ${ AWS_SECRET_ACCESS_KEY :- $( _readaccountconf_mutable AWS_SECRET_ACCESS_KEY) } "
2020-02-25 00:14:40 +01:00
AWS_DNS_SLOWRATE = " ${ AWS_DNS_SLOWRATE :- $( _readaccountconf_mutable AWS_DNS_SLOWRATE) } "
2018-02-20 01:34:55 +01:00
if [ -z " $AWS_ACCESS_KEY_ID " ] || [ -z " $AWS_SECRET_ACCESS_KEY " ] ; then
2018-02-20 15:55:05 +01:00
_use_container_role || _use_instance_role
2018-02-19 18:48:54 +01:00
fi
2016-11-20 15:57:07 +01:00
if [ -z " $AWS_ACCESS_KEY_ID " ] || [ -z " $AWS_SECRET_ACCESS_KEY " ] ; then
AWS_ACCESS_KEY_ID = ""
AWS_SECRET_ACCESS_KEY = ""
2021-08-02 19:36:59 +02:00
_err "You haven't specified the aws route53 api key id and and api key secret yet."
2018-01-15 12:48:57 +01:00
_err " Please create your key and try again. see $( __green $AWS_WIKI ) "
2016-11-20 15:57:07 +01:00
return 1
fi
2018-02-20 01:34:55 +01:00
#save for future use, unless using a role which will be fetched as needed
2018-02-20 13:40:24 +01:00
if [ -z " $_using_role " ] ; then
2018-02-19 18:48:54 +01:00
_saveaccountconf_mutable AWS_ACCESS_KEY_ID " $AWS_ACCESS_KEY_ID "
_saveaccountconf_mutable AWS_SECRET_ACCESS_KEY " $AWS_SECRET_ACCESS_KEY "
2020-02-25 00:14:40 +01:00
_saveaccountconf_mutable AWS_DNS_SLOWRATE " $AWS_DNS_SLOWRATE "
2018-02-19 18:48:54 +01:00
fi
2016-11-20 15:57:07 +01:00
_debug "First detect the root zone"
if ! _get_root " $fulldomain " ; then
_err "invalid domain"
2019-10-28 14:32:08 +01:00
_sleep 1
2016-11-20 15:57:07 +01:00
return 1
fi
_debug _domain_id " $_domain_id "
_debug _sub_domain " $_sub_domain "
_debug _domain " $_domain "
2019-05-21 12:21:54 +02:00
_info " Getting existing records for $fulldomain "
2018-02-13 15:17:20 +01:00
if ! aws_rest GET " 2013-04-01 $_domain_id /rrset " " name= $fulldomain &type=TXT " ; then
2019-10-28 14:32:08 +01:00
_sleep 1
2018-02-13 15:17:20 +01:00
return 1
fi
if _contains " $response " " <Name> $fulldomain .</Name> " ; then
2018-02-14 12:39:47 +01:00
_resource_record = " $( echo " $response " | sed 's/<ResourceRecordSet>/"/g' | tr '"' "\n" | grep " <Name> $fulldomain .</Name> " | _egrep_o "<ResourceRecords.*</ResourceRecords>" | sed "s/<ResourceRecords>//" | sed "s#</ResourceRecords>##" ) "
2018-02-13 15:17:20 +01:00
_debug "_resource_record" " $_resource_record "
else
_debug "single new add"
fi
if [ " $_resource_record " ] && _contains " $response " " $txtvalue " ; then
2018-08-07 13:35:08 +02:00
_info "The TXT record already exists. Skipping."
2019-10-28 14:32:08 +01:00
_sleep 1
2018-02-13 15:17:20 +01:00
return 0
fi
_debug "Adding records"
_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> "
2016-11-20 16:04:28 +01:00
2016-11-20 15:57:07 +01:00
if aws_rest POST " 2013-04-01 $_domain_id /rrset/ " "" " $_aws_tmpl_xml " && _contains " $response " "ChangeResourceRecordSetsResponse" ; then
2018-08-07 13:35:08 +02:00
_info "TXT record updated successfully."
2020-02-25 00:14:40 +01:00
if [ -n " $AWS_DNS_SLOWRATE " ] ; then
_info " Slow rate activated: sleeping for $AWS_DNS_SLOWRATE seconds "
_sleep " $AWS_DNS_SLOWRATE "
2019-11-22 11:20:25 +01:00
else
_sleep 1
2019-03-29 15:12:50 +01:00
fi
2019-11-22 11:20:25 +01:00
2016-11-20 15:57:07 +01:00
return 0
fi
2019-10-28 14:32:08 +01:00
_sleep 1
2016-11-20 16:04:28 +01:00
return 1
2016-11-20 15:57:07 +01:00
}
2016-12-06 09:52:02 +01:00
#fulldomain txtvalue
2016-11-20 15:57:07 +01:00
dns_aws_rm( ) {
fulldomain = $1
2016-12-06 09:52:02 +01:00
txtvalue = $2
2018-02-20 01:34:55 +01:00
AWS_ACCESS_KEY_ID = " ${ AWS_ACCESS_KEY_ID :- $( _readaccountconf_mutable AWS_ACCESS_KEY_ID) } "
AWS_SECRET_ACCESS_KEY = " ${ AWS_SECRET_ACCESS_KEY :- $( _readaccountconf_mutable AWS_SECRET_ACCESS_KEY) } "
2020-02-25 00:14:40 +01:00
AWS_DNS_SLOWRATE = " ${ AWS_DNS_SLOWRATE :- $( _readaccountconf_mutable AWS_DNS_SLOWRATE) } "
2018-02-20 01:34:55 +01:00
if [ -z " $AWS_ACCESS_KEY_ID " ] || [ -z " $AWS_SECRET_ACCESS_KEY " ] ; then
2018-02-20 15:55:05 +01:00
_use_container_role || _use_instance_role
2018-02-19 18:48:54 +01:00
fi
2016-12-06 09:52:02 +01:00
_debug "First detect the root zone"
if ! _get_root " $fulldomain " ; then
_err "invalid domain"
2019-10-28 14:32:08 +01:00
_sleep 1
2016-12-06 09:52:02 +01:00
return 1
fi
_debug _domain_id " $_domain_id "
_debug _sub_domain " $_sub_domain "
_debug _domain " $_domain "
2018-08-07 13:35:08 +02:00
_info " Getting existing records for $fulldomain "
2018-02-13 15:17:20 +01:00
if ! aws_rest GET " 2013-04-01 $_domain_id /rrset " " name= $fulldomain &type=TXT " ; then
2019-10-28 14:32:08 +01:00
_sleep 1
2018-02-13 15:17:20 +01:00
return 1
fi
if _contains " $response " " <Name> $fulldomain .</Name> " ; then
2018-02-14 12:39:47 +01:00
_resource_record = " $( echo " $response " | sed 's/<ResourceRecordSet>/"/g' | tr '"' "\n" | grep " <Name> $fulldomain .</Name> " | _egrep_o "<ResourceRecords.*</ResourceRecords>" | sed "s/<ResourceRecords>//" | sed "s#</ResourceRecords>##" ) "
2018-02-13 15:17:20 +01:00
_debug "_resource_record" " $_resource_record "
else
2018-08-07 13:35:08 +02:00
_debug "no records exist, skip"
2019-10-28 14:32:08 +01:00
_sleep 1
2018-02-13 15:17:20 +01:00
return 0
fi
_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> "
2016-12-06 09:52:02 +01:00
if aws_rest POST " 2013-04-01 $_domain_id /rrset/ " "" " $_aws_tmpl_xml " && _contains " $response " "ChangeResourceRecordSetsResponse" ; then
2018-08-07 13:35:08 +02:00
_info "TXT record deleted successfully."
2020-02-25 00:14:40 +01:00
if [ -n " $AWS_DNS_SLOWRATE " ] ; then
_info " Slow rate activated: sleeping for $AWS_DNS_SLOWRATE seconds "
_sleep " $AWS_DNS_SLOWRATE "
2019-11-22 11:20:25 +01:00
else
_sleep 1
2019-03-29 15:39:32 +01:00
fi
2019-11-22 11:20:25 +01:00
2016-12-06 09:52:02 +01:00
return 0
fi
2019-10-28 14:32:08 +01:00
_sleep 1
2016-12-06 09:52:02 +01:00
return 1
2016-11-20 15:57:07 +01:00
}
2016-12-14 21:32:24 +01:00
#################### Private functions below ##################################
2016-11-20 15:57:07 +01:00
_get_root( ) {
2022-05-12 11:36:19 +02:00
domain = $1
2022-05-12 10:51:15 +02:00
i = 1
2016-11-20 15:57:07 +01:00
p = 1
2016-11-20 16:04:28 +01:00
2022-06-05 15:46:42 +02:00
# iterate over names (a.b.c.d -> b.c.d -> c.d -> d)
2022-06-04 20:24:33 +02:00
while true; do
2024-10-13 17:41:22 +02:00
h = $( printf "%s" " $domain " | cut -d . -f " $i " -100 | sed 's/\./\\./g' )
2022-06-04 20:24:33 +02:00
_debug " Checking domain: $h "
if [ -z " $h " ] ; then
_error "invalid domain"
return 1
fi
2016-11-20 15:57:07 +01:00
2022-06-05 15:46:42 +02:00
# iterate over paginated result for list_hosted_zones
2022-06-04 20:24:33 +02:00
aws_rest GET "2013-04-01/hostedzone"
while true; do
2016-11-20 15:57:07 +01:00
if _contains " $response " " <Name> $h .</Name> " ; then
2022-05-12 19:57:32 +02:00
hostedzone = " $( echo " $response " | tr -d '\n' | sed 's/<HostedZone>/#&/g' | tr '#' '\n' | _egrep_o " <HostedZone><Id>[^<]*<.Id><Name> $h .<.Name>.*<PrivateZone>false<.PrivateZone>.*<.HostedZone> " ) "
2016-11-20 15:57:07 +01:00
_debug hostedzone " $hostedzone "
2017-10-11 14:34:56 +02:00
if [ " $hostedzone " ] ; then
_domain_id = $( printf "%s\n" " $hostedzone " | _egrep_o "<Id>.*<.Id>" | head -n 1 | _egrep_o ">.*<" | tr -d "<>" )
if [ " $_domain_id " ] ; then
2024-10-13 17:41:22 +02:00
_sub_domain = $( printf "%s" " $domain " | cut -d . -f 1-" $p " )
2017-10-11 14:34:56 +02:00
_domain = $h
return 0
fi
2018-08-07 13:35:08 +02:00
_err " Can't find domain with id: $h "
2016-11-20 15:57:07 +01:00
return 1
fi
fi
2022-06-04 20:24:33 +02:00
if _contains " $response " "<IsTruncated>true</IsTruncated>" && _contains " $response " "<NextMarker>" ; then
_debug "IsTruncated"
_nextMarker = " $( echo " $response " | _egrep_o "<NextMarker>.*</NextMarker>" | cut -d '>' -f 2 | cut -d '<' -f 1) "
_debug "NextMarker" " $_nextMarker "
else
break
fi
_debug " Checking domain: $h - Next Page "
aws_rest GET "2013-04-01/hostedzone" " marker= $_nextMarker "
2016-11-20 15:57:07 +01:00
done
2022-06-04 20:24:33 +02:00
p = $i
i = $( _math " $i " + 1)
done
2016-11-20 15:57:07 +01:00
return 1
}
2018-02-20 15:55:05 +01:00
_use_container_role( ) {
# automatically set if running inside ECS
if [ -z " $AWS_CONTAINER_CREDENTIALS_RELATIVE_URI " ] ; then
_debug "No ECS environment variable detected"
return 1
fi
_use_metadata " 169.254.170.2 $AWS_CONTAINER_CREDENTIALS_RELATIVE_URI "
}
2018-02-19 18:48:54 +01:00
_use_instance_role( ) {
2024-02-01 00:39:08 +01:00
_instance_role_name_url = "http://169.254.169.254/latest/meta-data/iam/security-credentials/"
2024-02-01 00:52:59 +01:00
2024-02-01 00:39:08 +01:00
if _get " $_instance_role_name_url " true 1 | _head_n 1 | grep -Fq 401; then
_debug "Using IMDSv2"
_token_url = "http://169.254.169.254/latest/api/token"
export _H1 = "X-aws-ec2-metadata-token-ttl-seconds: 21600"
_token = " $( _post "" " $_token_url " "" "PUT" ) "
2024-02-01 00:52:59 +01:00
_secure_debug3 "_token" " $_token "
2024-02-01 00:39:08 +01:00
if [ -z " $_token " ] ; then
_debug "Unable to fetch IMDSv2 token from instance metadata"
return 1
fi
export _H1 = " X-aws-ec2-metadata-token: $_token "
2024-01-31 00:51:55 +01:00
fi
2024-02-01 00:39:08 +01:00
if ! _get " $_instance_role_name_url " true 1 | _head_n 1 | grep -Fq 200; then
_debug "Unable to fetch IAM role from instance metadata"
2024-01-31 00:51:55 +01:00
return 1
fi
2024-02-01 00:52:59 +01:00
2024-02-01 00:39:08 +01:00
_instance_role_name = $( _get " $_instance_role_name_url " "" 1)
2024-01-31 00:51:55 +01:00
_debug "_instance_role_name" " $_instance_role_name "
2024-02-01 00:39:08 +01:00
_use_metadata " $_instance_role_name_url $_instance_role_name " " $_token "
2024-02-01 02:32:56 +01:00
2018-02-20 15:55:05 +01:00
}
2024-02-01 00:39:08 +01:00
_use_metadata( ) {
export _H1 = " X-aws-ec2-metadata-token: $2 "
2018-02-19 18:48:54 +01:00
_aws_creds = " $(
2020-08-17 16:18:20 +02:00
_get " $1 " "" 1 |
_normalizeJson |
tr '{,}' '\n' |
while read -r _line; do
2024-03-11 18:33:14 +01:00
_key = " $( echo " ${ _line %% : * } " | tr -d '\"' ) "
2018-02-19 18:48:54 +01:00
_value = " ${ _line #* : } "
_debug3 "_key" " $_key "
_secure_debug3 "_value" " $_value "
case " $_key " in
2020-08-17 16:18:20 +02:00
AccessKeyId) echo " AWS_ACCESS_KEY_ID= $_value " ; ;
SecretAccessKey) echo " AWS_SECRET_ACCESS_KEY= $_value " ; ;
Token) echo " AWS_SESSION_TOKEN= $_value " ; ;
2018-02-19 18:48:54 +01:00
esac
2020-08-17 16:18:20 +02:00
done |
paste -sd' ' -
2018-02-19 18:48:54 +01:00
) "
_secure_debug "_aws_creds" " $_aws_creds "
2018-02-20 15:55:05 +01:00
if [ -z " $_aws_creds " ] ; then
return 1
fi
2018-02-19 18:48:54 +01:00
eval " $_aws_creds "
2018-02-20 13:40:24 +01:00
_using_role = true
2018-02-19 18:48:54 +01:00
}
2016-11-20 15:57:07 +01:00
#method uri qstr data
aws_rest( ) {
mtd = " $1 "
ep = " $2 "
qsr = " $3 "
data = " $4 "
_debug mtd " $mtd "
_debug ep " $ep "
_debug qsr " $qsr "
_debug data " $data "
CanonicalURI = " / $ep "
_debug2 CanonicalURI " $CanonicalURI "
CanonicalQueryString = " $qsr "
_debug2 CanonicalQueryString " $CanonicalQueryString "
RequestDate = " $( date -u +"%Y%m%dT%H%M%SZ" ) "
_debug2 RequestDate " $RequestDate "
#RequestDate="20161120T141056Z" ##############
2017-01-09 17:04:09 +01:00
export _H1 = " x-amz-date: $RequestDate "
2016-11-20 15:57:07 +01:00
aws_host = " $AWS_HOST "
CanonicalHeaders = " host: $aws_host \nx-amz-date: $RequestDate \n "
SignedHeaders = "host;x-amz-date"
2017-01-06 03:27:55 +01:00
if [ -n " $AWS_SESSION_TOKEN " ] ; then
2017-03-15 15:52:57 +01:00
export _H3 = " x-amz-security-token: $AWS_SESSION_TOKEN "
2017-01-06 03:27:55 +01:00
CanonicalHeaders = " ${ CanonicalHeaders } x-amz-security-token: $AWS_SESSION_TOKEN \n "
SignedHeaders = " ${ SignedHeaders } ;x-amz-security-token "
fi
_debug2 CanonicalHeaders " $CanonicalHeaders "
2016-11-20 15:57:07 +01:00
_debug2 SignedHeaders " $SignedHeaders "
RequestPayload = " $data "
_debug2 RequestPayload " $RequestPayload "
Hash = "sha256"
CanonicalRequest = " $mtd \n $CanonicalURI \n $CanonicalQueryString \n $CanonicalHeaders \n $SignedHeaders \n $( printf "%s" " $RequestPayload " | _digest " $Hash " hex) "
_debug2 CanonicalRequest " $CanonicalRequest "
2016-11-20 16:09:57 +01:00
2016-11-20 16:04:28 +01:00
HashedCanonicalRequest = " $( printf " $CanonicalRequest %s " | _digest " $Hash " hex) "
2016-11-20 15:57:07 +01:00
_debug2 HashedCanonicalRequest " $HashedCanonicalRequest "
Algorithm = "AWS4-HMAC-SHA256"
_debug2 Algorithm " $Algorithm "
2016-11-20 16:04:28 +01:00
RequestDateOnly = " $( echo " $RequestDate " | cut -c 1-8) "
2016-11-20 15:57:07 +01:00
_debug2 RequestDateOnly " $RequestDateOnly "
Region = "us-east-1"
Service = "route53"
CredentialScope = " $RequestDateOnly / $Region / $Service /aws4_request "
_debug2 CredentialScope " $CredentialScope "
StringToSign = " $Algorithm \n $RequestDate \n $CredentialScope \n $HashedCanonicalRequest "
_debug2 StringToSign " $StringToSign "
kSecret = " AWS4 $AWS_SECRET_ACCESS_KEY "
#kSecret="wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY" ############################
2017-02-19 06:24:00 +01:00
_secure_debug2 kSecret " $kSecret "
2016-11-20 15:57:07 +01:00
2017-01-31 07:04:40 +01:00
kSecretH = " $( printf "%s" " $kSecret " | _hex_dump | tr -d " " ) "
2017-02-19 06:24:00 +01:00
_secure_debug2 kSecretH " $kSecretH "
2016-11-20 15:57:07 +01:00
kDateH = " $( printf " $RequestDateOnly %s " | _hmac " $Hash " " $kSecretH " hex) "
_debug2 kDateH " $kDateH "
kRegionH = " $( printf " $Region %s " | _hmac " $Hash " " $kDateH " hex) "
_debug2 kRegionH " $kRegionH "
kServiceH = " $( printf " $Service %s " | _hmac " $Hash " " $kRegionH " hex) "
_debug2 kServiceH " $kServiceH "
2017-07-08 03:20:12 +02:00
kSigningH = " $( printf "%s" "aws4_request" | _hmac " $Hash " " $kServiceH " hex) "
2016-11-20 15:57:07 +01:00
_debug2 kSigningH " $kSigningH "
signature = " $( printf " $StringToSign %s " | _hmac " $Hash " " $kSigningH " hex) "
_debug2 signature " $signature "
Authorization = " $Algorithm Credential= $AWS_ACCESS_KEY_ID / $CredentialScope , SignedHeaders= $SignedHeaders , Signature= $signature "
_debug2 Authorization " $Authorization "
2017-03-15 15:52:57 +01:00
_H2 = " Authorization: $Authorization "
_debug _H2 " $_H2 "
2016-11-20 15:57:07 +01:00
2017-04-04 08:34:23 +02:00
url = " $AWS_URL / $ep "
if [ " $qsr " ] ; then
url = " $AWS_URL / $ep ? $qsr "
fi
2016-11-20 15:57:07 +01:00
if [ " $mtd " = "GET" ] ; then
response = " $( _get " $url " ) "
else
response = " $( _post " $data " " $url " ) "
fi
_ret = " $? "
2018-02-13 15:17:20 +01:00
_debug2 response " $response "
2016-11-20 15:57:07 +01:00
if [ " $_ret " = "0" ] ; then
if _contains " $response " "<ErrorResponse" ; then
_err " Response error: $response "
return 1
fi
fi
2016-11-20 16:09:57 +01:00
2016-11-20 15:57:07 +01:00
return " $_ret "
}