From 48eaa0e5bfb7579b6719a640eb6253f92c93c842 Mon Sep 17 00:00:00 2001 From: Mal Graty Date: Mon, 19 Feb 2018 17:48:54 +0000 Subject: [PATCH 1/3] Let AWS DNS API code pull creds from instance role Add option (AWS_USE_INSTANCE_ROLE) to have the AWS DNS API driver pull the necessary credentials from the AWS EC2 instance metadata endpoint when required. This is a non-breaking change as it only takes effect when explicitly turned on via the environment variable, and fails safe back to the normal code path. --- dnsapi/dns_aws.sh | 45 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 43 insertions(+), 2 deletions(-) diff --git a/dnsapi/dns_aws.sh b/dnsapi/dns_aws.sh index 71f969f..c754341 100755 --- a/dnsapi/dns_aws.sh +++ b/dnsapi/dns_aws.sh @@ -9,6 +9,7 @@ AWS_HOST="route53.amazonaws.com" AWS_URL="https://$AWS_HOST" +AWS_METADATA_URL="http://169.254.169.254/latest/meta-data" AWS_WIKI="https://github.com/Neilpang/acme.sh/wiki/How-to-use-Amazon-Route53-API" @@ -19,6 +20,10 @@ dns_aws_add() { fulldomain=$1 txtvalue=$2 + if [ -n "${AWS_USE_INSTANCE_ROLE:=$(_readaccountconf_mutable AWS_USE_INSTANCE_ROLE)}" ]; then + _use_instance_role + fi + 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 @@ -30,8 +35,12 @@ dns_aws_add() { fi #save for future use - _saveaccountconf_mutable AWS_ACCESS_KEY_ID "$AWS_ACCESS_KEY_ID" - _saveaccountconf_mutable AWS_SECRET_ACCESS_KEY "$AWS_SECRET_ACCESS_KEY" + if [ -n "$AWS_USE_INSTANCE_ROLE" ]; then + _saveaccountconf_mutable AWS_USE_INSTANCE_ROLE "$AWS_USE_INSTANCE_ROLE" + else + _saveaccountconf_mutable AWS_ACCESS_KEY_ID "$AWS_ACCESS_KEY_ID" + _saveaccountconf_mutable AWS_SECRET_ACCESS_KEY "$AWS_SECRET_ACCESS_KEY" + fi _debug "First detect the root zone" if ! _get_root "$fulldomain"; then @@ -76,6 +85,10 @@ dns_aws_rm() { fulldomain=$1 txtvalue=$2 + if [ -n "${AWS_USE_INSTANCE_ROLE:=$(_readaccountconf_mutable AWS_USE_INSTANCE_ROLE)}" ]; then + _use_instance_role + fi + 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)}" _debug "First detect the root zone" @@ -162,6 +175,34 @@ _get_root() { return 1 } +_use_instance_role() { + if ! _get "$AWS_METADATA_URL/iam/security-credentials/" true | _head_n 1 | grep -Fq 200; then + _err "Unable to fetch IAM role from AWS instance metadata." + return + fi + _aws_role=$(_get "$AWS_METADATA_URL/iam/security-credentials/") + _debug "_aws_role" "$_aws_role" + _aws_creds="$( + _get "$AWS_METADATA_URL/iam/security-credentials/$_aws_role" \ + | _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" + eval "$_aws_creds" +} + #method uri qstr data aws_rest() { mtd="$1" From 693627a858e22391201ef385388756508da9c070 Mon Sep 17 00:00:00 2001 From: Mal Graty Date: Tue, 20 Feb 2018 00:34:55 +0000 Subject: [PATCH 2/3] Emulate Boto when using role metadata Use the behavior established in the botocore python library to inform how and when instance metadata is fetched in an attempt to acquire valid AWS credentials. - Use it as a fallback when no other credentials are provided - Set the timeout of metadata requests to 1 second --- dnsapi/dns_aws.sh | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/dnsapi/dns_aws.sh b/dnsapi/dns_aws.sh index c754341..05a6200 100755 --- a/dnsapi/dns_aws.sh +++ b/dnsapi/dns_aws.sh @@ -20,12 +20,13 @@ dns_aws_add() { fulldomain=$1 txtvalue=$2 - if [ -n "${AWS_USE_INSTANCE_ROLE:=$(_readaccountconf_mutable AWS_USE_INSTANCE_ROLE)}" ]; then + 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 _use_instance_role fi - 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 AWS_ACCESS_KEY_ID="" AWS_SECRET_ACCESS_KEY="" @@ -34,10 +35,8 @@ dns_aws_add() { return 1 fi - #save for future use - if [ -n "$AWS_USE_INSTANCE_ROLE" ]; then - _saveaccountconf_mutable AWS_USE_INSTANCE_ROLE "$AWS_USE_INSTANCE_ROLE" - else + #save for future use, unless using a role which will be fetched as needed + if [ -z "$_using_instance_role" ]; then _saveaccountconf_mutable AWS_ACCESS_KEY_ID "$AWS_ACCESS_KEY_ID" _saveaccountconf_mutable AWS_SECRET_ACCESS_KEY "$AWS_SECRET_ACCESS_KEY" fi @@ -85,12 +84,13 @@ dns_aws_rm() { fulldomain=$1 txtvalue=$2 - if [ -n "${AWS_USE_INSTANCE_ROLE:=$(_readaccountconf_mutable AWS_USE_INSTANCE_ROLE)}" ]; then + 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 _use_instance_role fi - 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)}" _debug "First detect the root zone" if ! _get_root "$fulldomain"; then _err "invalid domain" @@ -176,14 +176,14 @@ _get_root() { } _use_instance_role() { - if ! _get "$AWS_METADATA_URL/iam/security-credentials/" true | _head_n 1 | grep -Fq 200; then + if ! _get "$AWS_METADATA_URL/iam/security-credentials/" true 1 | _head_n 1 | grep -Fq 200; then _err "Unable to fetch IAM role from AWS instance metadata." return fi - _aws_role=$(_get "$AWS_METADATA_URL/iam/security-credentials/") + _aws_role=$(_get "$AWS_METADATA_URL/iam/security-credentials/" "" 1) _debug "_aws_role" "$_aws_role" _aws_creds="$( - _get "$AWS_METADATA_URL/iam/security-credentials/$_aws_role" \ + _get "$AWS_METADATA_URL/iam/security-credentials/$_aws_role" "" 1 \ | _normalizeJson \ | tr '{,}' '\n' \ | while read -r _line; do @@ -201,6 +201,7 @@ _use_instance_role() { )" _secure_debug "_aws_creds" "$_aws_creds" eval "$_aws_creds" + _using_instance_role=true } #method uri qstr data From 759f4f2c62bd5118495937713134f9e7960bd98e Mon Sep 17 00:00:00 2001 From: Mal Graty Date: Tue, 20 Feb 2018 12:40:24 +0000 Subject: [PATCH 3/3] Make the instance metadata fetcher self-contained This is to provide a clean path to future extension work such as adding a _use_container_role function to offer similar support for ECS containers. The $_using_role flag has also been made generic so that future role providers can also make use of it. --- dnsapi/dns_aws.sh | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/dnsapi/dns_aws.sh b/dnsapi/dns_aws.sh index 05a6200..9c9e931 100755 --- a/dnsapi/dns_aws.sh +++ b/dnsapi/dns_aws.sh @@ -9,7 +9,6 @@ AWS_HOST="route53.amazonaws.com" AWS_URL="https://$AWS_HOST" -AWS_METADATA_URL="http://169.254.169.254/latest/meta-data" AWS_WIKI="https://github.com/Neilpang/acme.sh/wiki/How-to-use-Amazon-Route53-API" @@ -36,7 +35,7 @@ dns_aws_add() { fi #save for future use, unless using a role which will be fetched as needed - if [ -z "$_using_instance_role" ]; then + if [ -z "$_using_role" ]; then _saveaccountconf_mutable AWS_ACCESS_KEY_ID "$AWS_ACCESS_KEY_ID" _saveaccountconf_mutable AWS_SECRET_ACCESS_KEY "$AWS_SECRET_ACCESS_KEY" fi @@ -176,14 +175,16 @@ _get_root() { } _use_instance_role() { - if ! _get "$AWS_METADATA_URL/iam/security-credentials/" true 1 | _head_n 1 | grep -Fq 200; then + _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 _err "Unable to fetch IAM role from AWS instance metadata." return fi - _aws_role=$(_get "$AWS_METADATA_URL/iam/security-credentials/" "" 1) + _aws_role=$(_get "$_url" "" 1) _debug "_aws_role" "$_aws_role" _aws_creds="$( - _get "$AWS_METADATA_URL/iam/security-credentials/$_aws_role" "" 1 \ + _get "$_url$_aws_role" "" 1 \ | _normalizeJson \ | tr '{,}' '\n' \ | while read -r _line; do @@ -201,7 +202,7 @@ _use_instance_role() { )" _secure_debug "_aws_creds" "$_aws_creds" eval "$_aws_creds" - _using_instance_role=true + _using_role=true } #method uri qstr data