Browse Source

dns_yc: fix CI failures and correct TXT handling

- Do not fail when TXT recordset does not yet exist (YC returns 404)
- Properly append TXT values for wildcard certificates
- Remove only the specific TXT value during cleanup
- Delete recordset only when last TXT value is removed
- Ensure POSIX sh compatibility
- Improve credential loading and renew support
- Prevent duplicate TXT entries
pull/6784/head
root 5 days ago
parent
commit
7470b0baba
  1. 198
      dnsapi/dns_yc.sh

198
dnsapi/dns_yc.sh

@ -34,116 +34,182 @@ dns_yc_add() {
existing="$(_yc_extract_data_array)"
newdata="$(_yc_array_add "$existing" "$txtvalue")"
_info "Adding TXT record"
_yc_rest POST "zones/${_domain_id}:upsertRecordSets" \
"{\"merges\":[{\"name\":\"$_sub_domain\",\"type\":\"TXT\",\"ttl\":\"60\",\"data\":$newdata}]}" \
|| return 1
#!/usr/bin/env sh
# shellcheck disable=SC2034
_contains "$response" "\"done\": true" || return 1
dns_yc_info='Yandex Cloud DNS
Site: Cloud.Yandex.com
Docs: https://github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_yc
Options:
YC_Zone_ID DNS Zone ID
YC_Folder_ID YC Folder ID
YC_SA_ID Service Account ID
YC_SA_Key_ID Service Account IAM Key ID
YC_SA_Key_File_Path Private key file path
YC_SA_Key_File_PEM_b64 Base64 content of private key file
'
return 0
}
YC_Api="https://dns.api.cloud.yandex.net/dns/v1"
########################
# rm
########################
############################
# Public functions
############################
dns_yc_rm() {
fulldomain="$(echo "$1" | _lower_case)"
dns_yc_add() {
fulldomain="$(echo "$1." | _lower_case)"
txtvalue="$2"
_yc_init || return 1
_yc_load_credentials || return 1
_get_root "$fulldomain" || return 1
_debug "Detect root zone"
if ! _get_root "$fulldomain"; then
_err "invalid domain"
return 1
fi
_yc_rest GET "zones/${_domain_id}:getRecordSet?type=TXT&name=$_sub_domain" || return 1
_debug "_domain_id=$_domain_id"
_debug "_sub_domain=$_sub_domain"
existing="$(_yc_extract_data_array)"
# Try get existing recordset (may not exist!)
_yc_rest GET "zones/${_domain_id}:getRecordSet?type=TXT&name=$_sub_domain" || true
_existing="$(_yc_extract_txt_data_array)"
if [ -z "$_existing" ]; then
_new="[\"$txtvalue\"]"
else
if _contains "$_existing" "\"$txtvalue\""; then
_info "TXT already exists."
return 0
fi
_new=$(printf "%s" "$_existing" | sed "s/]$/,\"$txtvalue\"]/")
fi
[ -z "$existing" ] && return 0
_info "Adding TXT record"
if _yc_rest POST "zones/$_domain_id:upsertRecordSets" \
"{\"merges\": [{\"name\":\"$_sub_domain\",\"type\":\"TXT\",\"ttl\":\"120\",\"data\":$_new}]}"; then
if _contains "$response" "\"done\": true"; then
_info "Added"
return 0
fi
fi
_err "Add failed"
return 1
}
newdata="$(_yc_array_remove "$existing" "$txtvalue")"
dns_yc_rm() {
fulldomain="$(echo "$1." | _lower_case)"
txtvalue="$2"
if [ "$newdata" = "$existing" ]; then
_yc_load_credentials || return 1
if ! _get_root "$fulldomain"; then
_err "invalid domain"
return 1
fi
_yc_rest GET "zones/${_domain_id}:getRecordSet?type=TXT&name=$_sub_domain" || return 0
_existing="$(_yc_extract_txt_data_array)"
[ -z "$_existing" ] && return 0
if ! _contains "$_existing" "\"$txtvalue\""; then
_info "TXT not present, skip"
return 0
fi
if [ "$newdata" = "[]" ]; then
_yc_rest POST "zones/${_domain_id}:updateRecordSets" \
"{\"deletions\":[{\"name\":\"$_sub_domain\",\"type\":\"TXT\",\"ttl\":\"60\",\"data\":$existing}]}" \
|| return 1
_new=$(printf "%s" "$_existing" | sed \
-e "s/\"$txtvalue\",//" \
-e "s/,\"$txtvalue\"//" \
-e "s/\"$txtvalue\"//" \
-e 's/\[,/[/' \
-e 's/,\]/]/')
if [ "$_new" = "[]" ] || [ -z "$_new" ]; then
_info "Deleting full recordset"
if _yc_rest POST "zones/$_domain_id:updateRecordSets" \
"{\"deletions\": [{\"name\":\"$_sub_domain\",\"type\":\"TXT\",\"ttl\":\"120\",\"data\":$_existing}]}"; then
return 0
fi
else
_yc_rest POST "zones/${_domain_id}:upsertRecordSets" \
"{\"merges\":[{\"name\":\"$_sub_domain\",\"type\":\"TXT\",\"ttl\":\"60\",\"data\":$newdata}]}" \
|| return 1
_info "Updating recordset"
if _yc_rest POST "zones/$_domain_id:upsertRecordSets" \
"{\"merges\": [{\"name\":\"$_sub_domain\",\"type\":\"TXT\",\"ttl\":\"120\",\"data\":$_new}]}"; then
return 0
fi
fi
_contains "$response" "\"done\": true" || return 1
return 0
_err "Delete failed"
return 1
}
########################
# internal helpers
########################
_yc_init() {
############################
# Helpers
############################
_yc_load_credentials() {
YC_SA_ID="${YC_SA_ID:-$(_readaccountconf_mutable YC_SA_ID)}"
YC_SA_Key_ID="${YC_SA_Key_ID:-$(_readaccountconf_mutable YC_SA_Key_ID)}"
YC_Zone_ID="${YC_Zone_ID:-$(_readaccountconf_mutable YC_Zone_ID)}"
YC_Folder_ID="${YC_Folder_ID:-$(_readaccountconf_mutable YC_Folder_ID)}"
YC_SA_Key_File_Path="${YC_SA_Key_File_Path:-$(_readaccountconf_mutable YC_SA_Key_File_Path)}"
YC_SA_Key_File_PEM_b64="${YC_SA_Key_File_PEM_b64:-$(_readaccountconf_mutable YC_SA_Key_File_PEM_b64)}"
YC_Zone_ID="${YC_Zone_ID:-$(_readaccountconf_mutable YC_Zone_ID)}"
YC_Folder_ID="${YC_Folder_ID:-$(_readaccountconf_mutable YC_Folder_ID)}"
[ -z "$YC_SA_ID" ] && return 1
[ -z "$YC_SA_Key_ID" ] && return 1
if [ "$YC_SA_Key_File_PEM_b64" ]; then
tmpkey="$(_mktemp)"
echo "$YC_SA_Key_File_PEM_b64" | _dbase64 > "$tmpkey"
chmod 600 "$tmpkey"
YC_SA_Key_File="$tmpkey"
YC_SA_Key_File="$(_mktemp)"
echo "$YC_SA_Key_File_PEM_b64" | _dbase64 > "$YC_SA_Key_File"
else
YC_SA_Key_File="$YC_SA_Key_File_Path"
fi
[ ! -f "$YC_SA_Key_File" ] && return 1
[ -f "$YC_SA_Key_File" ] || return 1
_saveaccountconf_mutable YC_SA_ID "$YC_SA_ID"
_saveaccountconf_mutable YC_SA_Key_ID "$YC_SA_Key_ID"
return 0
}
_yc_extract_data_array() {
echo "$response" | _normalizeJson | _egrep_o "\"data\":\\[[^\\]]*\\]" | _egrep_o "\\[[^\\]]*\\]"
_yc_extract_txt_data_array() {
echo "$response" | _normalizeJson | _egrep_o "\"data\":\[[^]]*\]" | _egrep_o "\[[^]]*\]"
}
_yc_array_add() {
arr="$1"
val="$2"
_yc_rest() {
m="$1"
ep="$2"
data="$3"
[ -z "$arr" ] && { printf "[\"%s\"]" "$val"; return; }
if printf "%s" "$arr" | _contains "\"$val\""; then
printf "%s" "$arr"
return
if [ ! "$YC_Token" ]; then
_yc_login || return 1
fi
printf "%s" "$arr" | sed "s/]$/,\"$val\"]/"
export _H1="Content-Type: application/json"
export _H2="Authorization: Bearer $YC_Token"
}
if [ "$m" = "GET" ]; then
response="$(_get "$YC_Api/$ep")"
else
response="$(_post "$data" "$YC_Api/$ep" "" "$m")"
fi
_yc_array_remove() {
arr="$1"
val="$2"
printf "%s" "$arr" | sed \
-e "s/\"$val\",//g" \
-e "s/,\"$val\"//g" \
-e "s/\"$val\"//g" \
-e 's/\[,/[/' \
-e 's/,\]/]/' \
-e 's/,,/,/g'
return 0
}
_yc_login() {
header=$(printf '{"typ":"JWT","alg":"PS256","kid":"%s"}' "$YC_SA_Key_ID" | _normalizeJson | _base64 | _url_replace)
now=$(_time)
exp=$(_math "$now" + 1200)
payload=$(printf '{"iss":"%s","aud":"https://iam.api.cloud.yandex.net/iam/v1/tokens","iat":%s,"exp":%s}' "$YC_SA_ID" "$now" "$exp" | _normalizeJson | _base64 | _url_replace)
sig=$(printf "%s.%s" "$header" "$payload" | _sign "$YC_SA_Key_File" "sha256 -sigopt rsa_padding_mode:pss -sigopt rsa_pss_saltlen:-1" | _url_replace)
jwt=$(printf '{"jwt":"%s.%s.%s"}' "$header" "$payload" "$sig")
iam="$(_post "$jwt" "https://iam.api.cloud.yandex.net/iam/v1/tokens" "" "POST")"
YC_Token="$(echo "$iam" | _normalizeJson | _egrep_o "\"iamToken\"[^,]*" | _egrep_o "[^:]*$" | tr -d '"')"
[ -n "$YC_Token" ] || return 1
return 0
}
Loading…
Cancel
Save