acme.sh/dnsapi/dns_ali.sh
neil 447dc3c7e7 dns_ali: convert IDN domains to punycode
The Aliyun API only accepts punycode domain names, and a raw UTF-8
domain also breaks the request signature. Same _idn pattern as
dns_namecom.

https://github.com/acmesh-official/acme.sh/issues/4733
2026-07-12 17:21:19 +08:00

237 lines
6.5 KiB
Bash
Executable file

#!/usr/bin/env sh
# shellcheck disable=SC2034
dns_ali_info='AlibabaCloud.com
Domains: Aliyun.com
Site: AlibabaCloud.com
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_ali
Options:
Ali_Key API Key
Ali_Secret API Secret
'
# NOTICE:
# This file is referenced by Alibaba Cloud Services deploy hooks
# https://github.com/acmesh-official/acme.sh/pull/5205#issuecomment-2357867276
# Be careful when modifying this file, especially when making breaking changes for common functions
Ali_DNS_API="https://alidns.aliyuncs.com/"
#Usage: dns_ali_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
dns_ali_add() {
# the API only accepts punycode for IDN domains, and a raw UTF-8 domain
# also breaks the request signature (issue 4733)
fulldomain=$(_idn "$1")
txtvalue=$2
_prepare_ali_credentials || return 1
_debug "First detect the root zone"
if ! _get_root "$fulldomain"; then
return 1
fi
_debug "Add record"
_add_record_query "$_domain" "$_sub_domain" "$txtvalue" && _ali_rest "Add record"
}
dns_ali_rm() {
fulldomain=$(_idn "$1")
txtvalue=$2
Ali_Key="${Ali_Key:-$(_readaccountconf_mutable Ali_Key)}"
Ali_Secret="${Ali_Secret:-$(_readaccountconf_mutable Ali_Secret)}"
_debug "First detect the root zone"
if ! _get_root "$fulldomain"; then
return 1
fi
_clean
}
#################### Alibaba Cloud common functions below ####################
_prepare_ali_credentials() {
Ali_Key="${Ali_Key:-$(_readaccountconf_mutable Ali_Key)}"
Ali_Secret="${Ali_Secret:-$(_readaccountconf_mutable Ali_Secret)}"
if [ -z "$Ali_Key" ] || [ -z "$Ali_Secret" ]; then
Ali_Key=""
Ali_Secret=""
_err "You don't specify aliyun api key and secret yet."
return 1
fi
#save the api key and secret to the account conf file.
_saveaccountconf_mutable Ali_Key "$Ali_Key"
_saveaccountconf_mutable Ali_Secret "$Ali_Secret"
}
# act ign mtd
_ali_rest() {
act="$1"
ign="$2"
mtd="${3:-GET}"
signature=$(printf "%s" "$mtd&%2F&$(printf "%s" "$query" | _ali_urlencode_upper)" | _hmac "sha1" "$(printf "%s" "$Ali_Secret&" | _hex_dump | tr -d " ")" | _base64)
signature=$(printf "%s" "$signature" | _ali_urlencode_upper)
url="$endpoint?Signature=$signature"
if [ "$mtd" = "GET" ]; then
url="$url&$query"
response="$(_get "$url")"
else
response="$(_post "$query" "$url" "" "$mtd" "application/x-www-form-urlencoded")"
fi
_ret="$?"
_debug2 response "$response"
if [ "$_ret" != "0" ]; then
_err "Error <$act>"
return 1
fi
if [ -z "$ign" ]; then
message="$(echo "$response" | _egrep_o "\"Message\":\"[^\"]*\"" | cut -d : -f 2 | tr -d \")"
if [ "$message" ]; then
_err "$message"
return 1
fi
fi
}
# stdin stdout
# The Aliyun signature requires percent-encoding with upper-case hex.
# Do not use "_url_encode upper-hex" here: this file is also bundled by
# third parties (e.g. Proxmox VE proxmox-acme) whose older copies of the
# acme.sh function library ignore the upper-hex argument and output
# lower-case hex, which invalidates the signature.
# https://github.com/acmesh-official/acme.sh/issues/6272
_ali_urlencode_upper() {
{
_url_encode
echo
} | sed 's/%a/%A/g;s/%b/%B/g;s/%c/%C/g;s/%d/%D/g;s/%e/%E/g;s/%f/%F/g;s/%\(.\)a/%\1A/g;s/%\(.\)b/%\1B/g;s/%\(.\)c/%\1C/g;s/%\(.\)d/%\1D/g;s/%\(.\)e/%\1E/g;s/%\(.\)f/%\1F/g'
}
_ali_nonce() {
if [ "$ACME_OPENSSL_BIN" ]; then
"$ACME_OPENSSL_BIN" rand -hex 16 2>/dev/null && return 0
fi
printf "%s" "$(date +%s)$$$(date +%N)" | _digest sha256 hex | cut -c 1-32
}
_ali_timestamp() {
date -u +"%Y-%m-%dT%H%%3A%M%%3A%SZ"
}
#################### Private functions below ####################
_get_root() {
domain=$1
i=1
p=1
while true; do
h=$(printf "%s" "$domain" | cut -d . -f "$i"-100)
if [ -z "$h" ]; then
#not valid
return 1
fi
_describe_records_query "$h"
if ! _ali_rest "Get root" "ignore"; then
return 1
fi
if _contains "$response" "PageNumber"; then
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p")
_debug _sub_domain "$_sub_domain"
_domain="$h"
_debug _domain "$_domain"
return 0
fi
p="$i"
i=$(_math "$i" + 1)
done
return 1
}
_check_exist_query() {
_qdomain="$1"
_qsubdomain="$2"
endpoint=$Ali_DNS_API
query=''
query=$query'AccessKeyId='$Ali_Key
query=$query'&Action=DescribeDomainRecords'
query=$query'&DomainName='$_qdomain
query=$query'&Format=json'
query=$query'&RRKeyWord='$_qsubdomain
query=$query'&SignatureMethod=HMAC-SHA1'
query=$query"&SignatureNonce=$(_ali_nonce)"
query=$query'&SignatureVersion=1.0'
query=$query'&Timestamp='$(_ali_timestamp)
query=$query'&TypeKeyWord=TXT'
query=$query'&Version=2015-01-09'
}
_add_record_query() {
endpoint=$Ali_DNS_API
query=''
query=$query'AccessKeyId='$Ali_Key
query=$query'&Action=AddDomainRecord'
query=$query'&DomainName='$1
query=$query'&Format=json'
query=$query'&RR='$2
query=$query'&SignatureMethod=HMAC-SHA1'
query=$query"&SignatureNonce=$(_ali_nonce)"
query=$query'&SignatureVersion=1.0'
query=$query'&Timestamp='$(_ali_timestamp)
query=$query'&Type=TXT'
query=$query'&Value='$3
query=$query'&Version=2015-01-09'
}
_delete_record_query() {
endpoint=$Ali_DNS_API
query=''
query=$query'AccessKeyId='$Ali_Key
query=$query'&Action=DeleteDomainRecord'
query=$query'&Format=json'
query=$query'&RecordId='$1
query=$query'&SignatureMethod=HMAC-SHA1'
query=$query"&SignatureNonce=$(_ali_nonce)"
query=$query'&SignatureVersion=1.0'
query=$query'&Timestamp='$(_ali_timestamp)
query=$query'&Version=2015-01-09'
}
_describe_records_query() {
endpoint=$Ali_DNS_API
query=''
query=$query'AccessKeyId='$Ali_Key
query=$query'&Action=DescribeDomainRecords'
query=$query'&DomainName='$1
query=$query'&Format=json'
query=$query'&SignatureMethod=HMAC-SHA1'
query=$query"&SignatureNonce=$(_ali_nonce)"
query=$query'&SignatureVersion=1.0'
query=$query'&Timestamp='$(_ali_timestamp)
query=$query'&Version=2015-01-09'
}
_clean() {
_check_exist_query "$_domain" "$_sub_domain"
# do not correct grammar here
if ! _ali_rest "Check exist records" "ignore"; then
return 1
fi
record_id="$(echo "$response" | tr '{' "\n" | grep "$_sub_domain" | grep -- "$txtvalue" | tr "," "\n" | grep RecordId | cut -d '"' -f 4)"
_debug2 record_id "$record_id"
if [ -z "$record_id" ]; then
_debug "record not found, skip"
else
_delete_record_query "$record_id"
_ali_rest "Delete record $record_id" "ignore"
fi
}