From ec53b27dfe7bcc030c4e4e6613a7bb902eac018f Mon Sep 17 00:00:00 2001 From: Viktor Sokhan Date: Wed, 24 Aug 2022 13:48:10 +0700 Subject: [PATCH] Add dns_yc.sh --- dnsapi/dns_yc.sh | 259 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 259 insertions(+) create mode 100644 dnsapi/dns_yc.sh diff --git a/dnsapi/dns_yc.sh b/dnsapi/dns_yc.sh new file mode 100644 index 00000000..74a605b5 --- /dev/null +++ b/dnsapi/dns_yc.sh @@ -0,0 +1,259 @@ +#!/usr/bin/env sh + +#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="/path/to/private.key" # Path to private.key use instead of PEM +#YC_SA_Key_File_PEM="" # Content of private.key use instead of Path +YC_Api="https://dns.api.cloud.yandex.net/dns/v1" + +######## Public functions ##################### + +#Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" +dns_yc_add() { + fulldomain="$(echo "$1". | _lower_case)" # Add dot at end of domain name + txtvalue=$2 + + if ["$YC_SA_Key_File_PEM"]; then + YC_SA_Key_File="<(echo '$YC_SA_Key_File_PEM')" + else + YC_SA_Key_File=$YC_SA_Key_File_Path + fi + + YC_Zone_ID="${YC_Zone_ID:-$(_readaccountconf_mutable YC_Zone_ID)}" + YC_Folder_ID="${YC_Folder_ID:-$(_readaccountconf_mutable YC_Folder_ID)}" + 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_SA_Key_File="${YC_SA_Key_File:-$(_readaccountconf_mutable YC_SA_Key_File)}" + + if [ "$YC_SA_ID" ] && [ "$YC_SA_Key_ID" ] && [ "$YC_SA_Key_File" ]; then + if [ -f "$YC_SA_Key_File" ]; then + if _isRSA "$YC_SA_Key_File" >/dev/null 2>&1; then + if [ "$YC_Zone_ID" ]; then + _savedomainconf YC_Zone_ID "$YC_Zone_ID" + _savedomainconf YC_SA_ID "$YC_SA_ID" + _savedomainconf YC_SA_Key_ID "$YC_SA_Key_ID" + _savedomainconf YC_SA_Key_File "$YC_SA_Key_File" + elif [ "$YC_Folder_ID" ]; then + _savedomainconf YC_Folder_ID "$YC_Folder_ID" + _saveaccountconf_mutable YC_SA_ID "$YC_SA_ID" + _saveaccountconf_mutable YC_SA_Key_ID "$YC_SA_Key_ID" + _saveaccountconf_mutable YC_SA_Key_File "$YC_SA_Key_File" + _clearaccountconf_mutable YC_Zone_ID + _clearaccountconf YC_Zone_ID + else + _err "You didn't specify a Yandex Cloud Zone ID or Folder ID yet." + return 1 + fi + else + _err "YC_SA_Key_File not a RSA file(_isRSA function return false)." + return 1 + fi + else + _err "YC_SA_Key_File not found in path $YC_SA_Key_File." + return 1 + fi + else + _clearaccountconf YC_Zone_ID + _clearaccountconf YC_Folder_ID + _clearaccountconf YC_SA_ID + _clearaccountconf YC_SA_Key_ID + _clearaccountconf YC_SA_Key_File + _err "You didn't specify a YC_SA_ID or YC_SA_Key_ID or YC_SA_Key_File." + return 1 + fi + + _debug "First detect the root zone" + if ! _get_root "$fulldomain"; then + _err "invalid domain" + return 1 + fi + _debug _domain_id "$_domain_id" + _debug _sub_domain "$_sub_domain" + _debug _domain "$_domain" + + _debug "Getting txt records" + if ! _yc_rest GET "zones/${_domain_id}:getRecordSet?type=TXT&name=$_sub_domain"; then + _err "Error: $response" + return 1 + fi + + _info "Adding record" + if _yc_rest POST "zones/$_domain_id:upsertRecordSets" "{\"merges\": [ { \"name\":\"$_sub_domain\",\"type\":\"TXT\",\"ttl\":\"120\",\"data\":[\"$txtvalue\"]}]}"; then + if _contains "$response" "\"done\": true"; then + _info "Added, OK" + return 0 + else + _err "Add txt record error." + return 1 + fi + fi + _err "Add txt record error." + return 1 + +} + +#fulldomain txtvalue +dns_yc_rm() { + fulldomain="$(echo "$1". | _lower_case)" # Add dot at end of domain name + txtvalue=$2 + + YC_Zone_ID="${YC_Zone_ID:-$(_readaccountconf_mutable YC_Zone_ID)}" + YC_Folder_ID="${YC_Folder_ID:-$(_readaccountconf_mutable YC_Folder_ID)}" + 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_SA_Key_File="${YC_SA_Key_File:-$(_readaccountconf_mutable YC_SA_Key_File)}" + + _debug "First detect the root zone" + if ! _get_root "$fulldomain"; then + _err "invalid domain" + return 1 + fi + _debug _domain_id "$_domain_id" + _debug _sub_domain "$_sub_domain" + _debug _domain "$_domain" + + _debug "Getting txt records" + if _yc_rest GET "zones/${_domain_id}:getRecordSet?type=TXT&name=$_sub_domain"; then + exists_txtvalue=$(echo "$response" | _normalizeJson | _egrep_o "\"data\".*\][^,]*" | _egrep_o "[^:]*$") + _debug exists_txtvalue "$exists_txtvalue" + else + _err "Error: $response" + return 1 + fi + + if _yc_rest POST "zones/$_domain_id:updateRecordSets" "{\"deletions\": [ { \"name\":\"$_sub_domain\",\"type\":\"TXT\",\"ttl\":\"120\",\"data\":$exists_txtvalue}]}"; then + if _contains "$response" "\"done\": true"; then + _info "Delete, OK" + return 0 + else + _err "Delete record error." + return 1 + fi + fi + _err "Delete record error." + return 1 +} + +#################### Private functions below ################################## +#_acme-challenge.www.domain.com +#returns +# _sub_domain=_acme-challenge.www +# _domain=domain.com +# _domain_id=sdjkglgdfewsdfg +_get_root() { + domain=$1 + i=1 + p=1 + + # Use Zone ID directly if provided + if [ "$YC_Zone_ID" ]; then + if ! _yc_rest GET "zones/$YC_Zone_ID"; then + return 1 + else + if echo "$response" | tr -d " " | grep \"id\":\"$YC_Zone_ID\" >/dev/null; then + _domain=$(echo "$response" | _egrep_o "\"zone\": *\"[^\"]*\"" | cut -d : -f 2 | tr -d \" | _head_n 1 | tr -d " ") + if [ "$_domain" ]; then + _cutlength=$((${#domain} - ${#_domain})) + _sub_domain=$(printf "%s" "$domain" | cut -c "1-$_cutlength") + _domain_id=$YC_Zone_ID + return 0 + else + return 1 + fi + else + return 1 + fi + fi + fi + + while true; do + h=$(printf "%s" "$domain" | cut -d . -f $i-100) + _debug h "$h" + if [ -z "$h" ]; then + #not valid + return 1 + fi + if [ "$YC_Folder_ID" ]; then + if ! _yc_rest GET "zones?folderId=$YC_Folder_ID"; then + return 1 + fi + else + echo "You didn't specify a Yandex Cloud Folder ID." + return 1 + fi + if _contains "$response" "\"zone\": \"$h\""; then + _domain_id=$(echo "$response" | _normalizeJson | _egrep_o "[^{]*\"zone\":\"$h\"[^}]*" | _egrep_o "\"id\"[^,]*" | _egrep_o "[^:]*$" | tr -d '"') + _debug _domain_id "$_domain_id" + if [ "$_domain_id" ]; then + _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) + _domain=$h + return 0 + fi + return 1 + fi + p=$i + i=$(_math "$i" + 1) + done + return 1 +} + +_yc_rest() { + m=$1 + ep="$2" + data="$3" + _debug "$ep" + + if [ ! "$YC_Token" ]; then + _debug "Login" + _yc_login + else + _debug "Token already exists. Skip Login." + fi + + token_trimmed=$(echo "$YC_Token" | tr -d '"') + + export _H1="Content-Type: application/json" + export _H2="Authorization: Bearer $token_trimmed" + + if [ "$m" != "GET" ]; then + _debug data "$data" + response="$(_post "$data" "$YC_Api/$ep" "" "$m")" + else + response="$(_get "$YC_Api/$ep")" + fi + + if [ "$?" != "0" ]; then + _err "error $ep" + return 1 + fi + _debug2 response "$response" + return 0 +} + +_yc_login() { + header=$(echo "{\"typ\":\"JWT\",\"alg\":\"PS256\",\"kid\":\"$YC_SA_Key_ID\"}" | _normalizeJson | _base64 | _url_replace) + _debug header "$header" + + _current_timestamp=$(_time) + _expire_timestamp=$(_math $_current_timestamp + 1200) # 20 minutes + payload=$(echo "{\"iss\":\"$YC_SA_ID\",\"aud\":\"https://iam.api.cloud.yandex.net/iam/v1/tokens\",\"iat\":$_current_timestamp,\"exp\":$_expire_timestamp}" | _normalizeJson | _base64 | _url_replace) + _debug payload "$payload" + + #signature=$(printf "%s.%s" "$header" "$payload" | ${ACME_OPENSSL_BIN:-openssl} dgst -sign "$YC_SA_Key_File -sha256 -sigopt rsa_padding_mode:pss -sigopt rsa_pss_saltlen:-1" | _base64 | _url_replace ) + _signature=$(printf "%s.%s" "$header" "$payload" | _sign "$YC_SA_Key_File" "sha256 -sigopt rsa_padding_mode:pss -sigopt rsa_pss_saltlen:-1" | _url_replace) + _debug2 _signature "$_signature" + + _jwt=$(printf "{\"jwt\": \"%s.%s.%s\"}" "$header" "$payload" "$_signature") + _debug2 _jwt "$_jwt" + + export _H1="Content-Type: application/json" + _iam_response="$(_post "$_jwt" "https://iam.api.cloud.yandex.net/iam/v1/tokens" "" "POST")" + _debug3 _iam_response "$(echo "$_iam_response" | _normalizeJson)" + + YC_Token="$(echo "$_iam_response" | _normalizeJson | _egrep_o "\"iamToken\"[^,]*" | _egrep_o "[^:]*$" | tr -d '"')" + _debug3 YC_Token + + return 0 +}