diff --git a/acme.sh b/acme.sh index a5ac9e47..748e3f65 100755 --- a/acme.sh +++ b/acme.sh @@ -14,11 +14,12 @@ _WINDOWS_SCHEDULER_NAME="$PROJECT_NAME.cron" _SCRIPT_="$0" +_SUB_FOLDER_COMMON="common" _SUB_FOLDER_NOTIFY="notify" _SUB_FOLDER_DNSAPI="dnsapi" _SUB_FOLDER_DEPLOY="deploy" -_SUB_FOLDERS="$_SUB_FOLDER_DNSAPI $_SUB_FOLDER_DEPLOY $_SUB_FOLDER_NOTIFY" +_SUB_FOLDERS="$_SUB_FOLDER_COMMON $_SUB_FOLDER_DNSAPI $_SUB_FOLDER_DEPLOY $_SUB_FOLDER_NOTIFY" CA_LETSENCRYPT_V1="https://acme-v01.api.letsencrypt.org/directory" diff --git a/common/aws.sh b/common/aws.sh new file mode 100644 index 00000000..2f83ccdf --- /dev/null +++ b/common/aws.sh @@ -0,0 +1,197 @@ +#!/usr/bin/env sh + +# usage: _aws +# +# services: +# +# ACM: _aws acm [json] +# _aws acm ListCertificates us-east-1 '{"MaxItems": 2}' + +_aws() { + _svc="$1" # _args=... + shift + if ! _aws_auth; then + return 255 + fi + n="$(printf '\nn')" n="${n%n}" + "_aws_svc_$_svc" "$@" +} + +# private + +# services + +_aws_svc_acm() { + _rpc="$1" _region="$2" _json="$3" + + _empty='{}' + _rpc="x-amz-target:CertificateManager.$_rpc" + _type='content-type:application/x-amz-json-1.1' + + _aws_wrap '"__type":' \ + POST "acm.$_region.amazonaws.com" '/' '' "$_region/acm" \ + "$_rpc$n$_type" "${_json:-$_empty}" +} + +# core + +_aws_wrap() { + _check="$1" # _args=... + shift + _resp="$(_aws_req4 "$@")" + _ret="$?" + _debug2 _resp "$_resp" + if [ "$_ret" -eq 0 ] && _contains "$_resp" "$_check"; then + _err "Response error: $_resp" + return 1 + fi + printf %s "$_resp" + return "$_ret" +} + +_aws_req4() { + _verb="$1" _host="$2" _path="$3" _query="$4" _svc="$5" _hdrs="$6" _data="$7" + + _debug _verb "$_verb" + _debug _host "$_host" + _debug _path "$_path" + _debug _query "$_query" + _debug _svc "$_svc" + _debug _hdrs "$_hdrs" + _debug _data "$_data" + + _date="$(date -u +%Y%m%dT%H%M%SZ)" + _debug2 _date "$_date" + + _hdrs="host:$_host${n}x-amz-date:$_date$n$_hdrs" + if [ "$AWS_SESSION_TOKEN" ]; then + _hdrs="$_hdrs${n}x-amz-security-token:$AWS_SESSION_TOKEN" + fi + _hdrs="$(printf %s "$_hdrs" | sort | sed '/^$/d')$n" + _debug2 _hdrs "$_hdrs" + + _keys="$( + printf %s "$_hdrs" | while read -r _hdr; do + printf '%s\n' "${_hdr%%:*}" + done | paste -sd ';' + )" + _debug2 _keys "$_keys" + + _scope="$(printf %s "$_date" | cut -c 1-8)/$_svc/aws4_request" + _debug2 _scope "$_scope" + + _hash='sha256' + _debug3 _hash "$_hash" + _algo='AWS4-HMAC-SHA256' + _debug3 _algo "$_algo" + + _bdy="$(printf %s "$_data" | _digest "$_hash" hex)" + _debug2 _bdy "$_bdy" + _req="$_verb$n$_path$n$_query$n$_hdrs$n$_keys$n$_bdy" + _debug2 _req "$_req" + _req="$(printf %s "$_req" | _digest "$_hash" hex)" + _debug2 _req "$_req" + _str="$_algo$n$_date$n$_scope$n$_req" + _debug2 _str "$_str" + + _sig="$(printf %s "AWS4$AWS_SECRET_ACCESS_KEY" | _hex_dump | tr -d ' ')" + _secure_debug2 _sig "$_sig" + for _step in $(printf %s "$_scope" | tr '/' ' ') "$_str"; do + _debug2 _step "$_step" + _sig="$(printf %s "$_step" | _hmac "$_hash" "$_sig" hex)" + _debug2 _sig "$_sig" + done + + _cred="$AWS_ACCESS_KEY_ID/$_scope" + _auth="$_algo Credential=$_cred, SignedHeaders=$_keys, Signature=$_sig" + _debug2 _auth "$_auth" + + _url="https://$_host$_path" + if [ "$_query" ]; then + _url="$_url?$_query" + fi + + unset i + while read -r _line; do + i=$((i + 1)) + eval "_H$i=\"\$_line\"; _debug2 _H$i \"\$_H$i\"" + done <<-END + authorization:$_auth + $_hdrs + END + + case "$(printf %s "$_verb" | tr '[:upper:]' '[:lower:]')" in + get) _get "$_url" ;; + post) _post "$_data" "$_url" ;; + *) _err '_aws only supports get and post' ;; + esac +} + +# credentials + +_aws_auth() { + _aws_auth_environment || _aws_auth_container_role || _aws_auth_instance_role +} + +_aws_auth_environment() { + 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 + unset AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY + return 1 + fi + if [ -z "$_aws_using_role" ]; then + _saveaccountconf_mutable AWS_ACCESS_KEY_ID "$AWS_ACCESS_KEY_ID" + _saveaccountconf_mutable AWS_SECRET_ACCESS_KEY "$AWS_SECRET_ACCESS_KEY" + fi +} + +_aws_auth_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 + _aws_auth_metadata "169.254.170.2$AWS_CONTAINER_CREDENTIALS_RELATIVE_URI" +} + +_aws_auth_instance_role() { + _url='http://169.254.169.254/latest/meta-data/iam/security-credentials/' + _debug _url "$_url" + _aws_role=$(_get "$_url" '' 1) + if [ "$?" -gt 0 ]; then + _debug 'unable to fetch IAM role from instance metadata' + return 1 + fi + _debug _aws_role "$_aws_role" + _aws_auth_metadata "$_url$_aws_role" +} + +_aws_auth_metadata() { + _url="$1" + + _aws_creds="$( + _get "$_url" "" 1 | + _normalizeJson | + tr '{,}' '\n' | + while read -r _line; do + _key="$(printf %s "${_line%%:*}" | tr -d '"')" _value="${_line#*:}" + _debug3 _key "$_key" + _secure_debug3 _value "$_value" + case "$_key" in + AccessKeyId) printf '%s\n' "AWS_ACCESS_KEY_ID=$_value" ;; + SecretAccessKey) printf '%s\n' "AWS_SECRET_ACCESS_KEY=$_value" ;; + Token) printf '%s\n' "AWS_SESSION_TOKEN=$_value" ;; + esac + done | + paste -sd' ' - + )" + _secure_debug _aws_creds "$_aws_creds" + + if [ -z "$_aws_creds" ]; then + return 1 + fi + + eval "$_aws_creds" + _aws_using_role=true +}