2016-11-20 15:57:07 +01:00
#!/usr/bin/env sh
#
#AWS_ACCESS_KEY_ID="sdfsdfsdfljlbjkljlkjsdfoiwje"
#
#AWS_SECRET_ACCESS_KEY="xxxxxxx"
#This is the Amazon Route53 api wrapper for acme.sh
2019-10-28 14:32:08 +01:00
#All `_sleep` commands are included to avoid Route53 throttling, see
2019-10-25 21:05:15 +02:00
#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) } "
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 = ""
2018-08-07 13:35:08 +02:00
_err "You haven't specifed 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 "
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."
2019-10-28 14:32:08 +01:00
_sleep 1
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) } "
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."
2019-10-28 14:32:08 +01:00
_sleep 1
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( ) {
domain = $1
i = 2
p = 1
2016-11-20 16:04:28 +01:00
2016-11-20 15:57:07 +01:00
if aws_rest GET "2013-04-01/hostedzone" ; then
while true; do
h = $( printf "%s" " $domain " | cut -d . -f $i -100)
2017-10-11 14:34:56 +02:00
_debug2 " Checking domain: $h "
2016-11-20 15:57:07 +01:00
if [ -z " $h " ] ; then
2017-04-04 09:54:45 +02:00
if _contains " $response " "<IsTruncated>true</IsTruncated>" && _contains " $response " "<NextMarker>" ; then
2017-04-03 18:21:39 +02:00
_debug "IsTruncated"
_nextMarker = " $( echo " $response " | _egrep_o "<NextMarker>.*</NextMarker>" | cut -d '>' -f 2 | cut -d '<' -f 1) "
_debug "NextMarker" " $_nextMarker "
2017-04-04 04:02:45 +02:00
if aws_rest GET "2013-04-01/hostedzone" " marker= $_nextMarker " ; then
2017-04-03 18:21:39 +02:00
_debug "Truncated request OK"
i = 2
p = 1
continue
else
_err "Truncated request error."
fi
fi
2016-11-20 15:57:07 +01:00
#not valid
2017-10-11 14:34:56 +02:00
_err "Invalid domain"
2016-11-20 15:57:07 +01:00
return 1
fi
if _contains " $response " " <Name> $h .</Name> " ; then
2017-05-05 13:55:51 +02:00
hostedzone = " $( echo " $response " | 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
_sub_domain = $( printf "%s" " $domain " | cut -d . -f 1-$p )
_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
p = $i
i = $( _math " $i " + 1)
done
fi
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( ) {
2018-02-20 13:40:24 +01:00
_url = "http://169.254.169.254/latest/meta-data/iam/security-credentials/"
_debug "_url" " $_url "
if ! _get " $_url " true 1 | _head_n 1 | grep -Fq 200; then
2018-02-20 15:55:05 +01:00
_debug "Unable to fetch IAM role from instance metadata"
return 1
2018-02-19 18:48:54 +01:00
fi
2018-02-20 13:40:24 +01:00
_aws_role = $( _get " $_url " "" 1)
2018-02-19 18:48:54 +01:00
_debug "_aws_role" " $_aws_role "
2018-02-20 15:55:05 +01:00
_use_metadata " $_url $_aws_role "
}
_use_metadata( ) {
2018-02-19 18:48:54 +01:00
_aws_creds = " $(
2018-02-20 15:55:05 +01:00
_get " $1 " "" 1 \
2018-02-19 18:48:54 +01:00
| _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 " ; ;
esac
done \
| paste -sd' ' -
) "
_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 "
}