From ec53b27dfe7bcc030c4e4e6613a7bb902eac018f Mon Sep 17 00:00:00 2001 From: Viktor Sokhan Date: Wed, 24 Aug 2022 13:48:10 +0700 Subject: [PATCH 1/4] 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 +} From 43503a20e571b354877a9246bc13c3514275cf20 Mon Sep 17 00:00:00 2001 From: Viktor Sokhan Date: Wed, 24 Aug 2022 14:12:57 +0700 Subject: [PATCH 2/4] Fix --- dnsapi/dns_yc.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dnsapi/dns_yc.sh b/dnsapi/dns_yc.sh index 74a605b5..9f9c3e5e 100644 --- a/dnsapi/dns_yc.sh +++ b/dnsapi/dns_yc.sh @@ -5,7 +5,7 @@ #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_SA_Key_File_PEM_b64="" # Base64 content of private.key use instead of Path YC_Api="https://dns.api.cloud.yandex.net/dns/v1" ######## Public functions ##################### @@ -15,8 +15,8 @@ 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')" + if ["$YC_SA_Key_File_PEM_b64"]; then + YC_SA_Key_File="<(echo '$YC_SA_Key_File_PEM_b64' | _dbase64 )" else YC_SA_Key_File=$YC_SA_Key_File_Path fi From 90623142e16f1423ac1be1cd8bb1cbdc95ce03b1 Mon Sep 17 00:00:00 2001 From: Viktor Sokhan Date: Wed, 24 Aug 2022 16:40:27 +0700 Subject: [PATCH 3/4] Fix --- dnsapi/dns_yc.sh | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/dnsapi/dns_yc.sh b/dnsapi/dns_yc.sh index 9f9c3e5e..bce9aa63 100644 --- a/dnsapi/dns_yc.sh +++ b/dnsapi/dns_yc.sh @@ -4,8 +4,8 @@ #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_b64="" # Base64 content of private.key use instead of Path +#YC_SA_Key_File_Path="/path/to/private.key" # Path to private.key use instead of YC_SA_Key_File_PEM_b64 +#YC_SA_Key_File_PEM_b64="" # Base64 content of private.key use instead of YC_SA_Key_File_Path YC_Api="https://dns.api.cloud.yandex.net/dns/v1" ######## Public functions ##################### @@ -15,18 +15,23 @@ dns_yc_add() { fulldomain="$(echo "$1". | _lower_case)" # Add dot at end of domain name txtvalue=$2 - if ["$YC_SA_Key_File_PEM_b64"]; then - YC_SA_Key_File="<(echo '$YC_SA_Key_File_PEM_b64' | _dbase64 )" + YC_SA_Key_File_PEM_b64="${YC_SA_Key_File_PEM_b64:-$(_readaccountconf_mutable YC_SA_Key_File_PEM_b64)}" + YC_SA_Key_File_Path="${YC_SA_Key_File_Path:-$(_readaccountconf_mutable YC_SA_Key_File_Path)}" + + if [ "$YC_SA_Key_File_PEM_b64" ]; then + echo "$YC_SA_Key_File_PEM_b64" | _dbase64 > private.key + YC_SA_Key_File="private.key" + _savedomainconf YC_SA_Key_File_PEM_b64 "$YC_SA_Key_File_PEM_b64" else - YC_SA_Key_File=$YC_SA_Key_File_Path + YC_SA_Key_File="$YC_SA_Key_File_Path" + _savedomainconf YC_SA_Key_File_Path "$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 @@ -34,12 +39,10 @@ dns_yc_add() { _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 @@ -59,7 +62,8 @@ dns_yc_add() { _clearaccountconf YC_Folder_ID _clearaccountconf YC_SA_ID _clearaccountconf YC_SA_Key_ID - _clearaccountconf YC_SA_Key_File + _clearaccountconf YC_SA_Key_File_PEM_b64 + _clearaccountconf YC_SA_Key_File_Path _err "You didn't specify a YC_SA_ID or YC_SA_Key_ID or YC_SA_Key_File." return 1 fi @@ -103,7 +107,6 @@ dns_yc_rm() { 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 @@ -244,6 +247,8 @@ _yc_login() { #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" + + rm -rf "$YC_SA_Key_File" _jwt=$(printf "{\"jwt\": \"%s.%s.%s\"}" "$header" "$payload" "$_signature") _debug2 _jwt "$_jwt" From 4e5d4b969538094f2d3306a23c84c773e2717f08 Mon Sep 17 00:00:00 2001 From: Viktor Sokhan Date: Thu, 25 Aug 2022 13:43:06 +0700 Subject: [PATCH 4/4] Fix shellcheck and shfmt --- dnsapi/dns_yc.sh | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/dnsapi/dns_yc.sh b/dnsapi/dns_yc.sh index bce9aa63..ec3bbc87 100644 --- a/dnsapi/dns_yc.sh +++ b/dnsapi/dns_yc.sh @@ -19,7 +19,7 @@ dns_yc_add() { YC_SA_Key_File_Path="${YC_SA_Key_File_Path:-$(_readaccountconf_mutable YC_SA_Key_File_Path)}" if [ "$YC_SA_Key_File_PEM_b64" ]; then - echo "$YC_SA_Key_File_PEM_b64" | _dbase64 > private.key + echo "$YC_SA_Key_File_PEM_b64" | _dbase64 >private.key YC_SA_Key_File="private.key" _savedomainconf YC_SA_Key_File_PEM_b64 "$YC_SA_Key_File_PEM_b64" else @@ -155,7 +155,7 @@ _get_root() { 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 + if echo "$response" | tr -d " " | _egrep_o "\"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})) @@ -238,16 +238,16 @@ _yc_rest() { _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 + _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" - + rm -rf "$YC_SA_Key_File" _jwt=$(printf "{\"jwt\": \"%s.%s.%s\"}" "$header" "$payload" "$_signature")