]> git.proxmox.com Git - mirror_acme.sh.git/blob - dnsapi/dns_yc.sh
74a605b5382193067c18a7e8bfb154a6931733ae
[mirror_acme.sh.git] / dnsapi / dns_yc.sh
1 #!/usr/bin/env sh
2
3 #YC_Zone_ID="" # DNS Zone ID
4 #YC_Folder_ID="" # YC Folder ID
5 #YC_SA_ID="" # Service Account ID
6 #YC_SA_Key_ID="" # Service Account IAM Key ID
7 #YC_SA_Key_File_Path="/path/to/private.key" # Path to private.key use instead of PEM
8 #YC_SA_Key_File_PEM="" # Content of private.key use instead of Path
9 YC_Api="https://dns.api.cloud.yandex.net/dns/v1"
10
11 ######## Public functions #####################
12
13 #Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
14 dns_yc_add() {
15 fulldomain="$(echo "$1". | _lower_case)" # Add dot at end of domain name
16 txtvalue=$2
17
18 if ["$YC_SA_Key_File_PEM"]; then
19 YC_SA_Key_File="<(echo '$YC_SA_Key_File_PEM')"
20 else
21 YC_SA_Key_File=$YC_SA_Key_File_Path
22 fi
23
24 YC_Zone_ID="${YC_Zone_ID:-$(_readaccountconf_mutable YC_Zone_ID)}"
25 YC_Folder_ID="${YC_Folder_ID:-$(_readaccountconf_mutable YC_Folder_ID)}"
26 YC_SA_ID="${YC_SA_ID:-$(_readaccountconf_mutable YC_SA_ID)}"
27 YC_SA_Key_ID="${YC_SA_Key_ID:-$(_readaccountconf_mutable YC_SA_Key_ID)}"
28 YC_SA_Key_File="${YC_SA_Key_File:-$(_readaccountconf_mutable YC_SA_Key_File)}"
29
30 if [ "$YC_SA_ID" ] && [ "$YC_SA_Key_ID" ] && [ "$YC_SA_Key_File" ]; then
31 if [ -f "$YC_SA_Key_File" ]; then
32 if _isRSA "$YC_SA_Key_File" >/dev/null 2>&1; then
33 if [ "$YC_Zone_ID" ]; then
34 _savedomainconf YC_Zone_ID "$YC_Zone_ID"
35 _savedomainconf YC_SA_ID "$YC_SA_ID"
36 _savedomainconf YC_SA_Key_ID "$YC_SA_Key_ID"
37 _savedomainconf YC_SA_Key_File "$YC_SA_Key_File"
38 elif [ "$YC_Folder_ID" ]; then
39 _savedomainconf YC_Folder_ID "$YC_Folder_ID"
40 _saveaccountconf_mutable YC_SA_ID "$YC_SA_ID"
41 _saveaccountconf_mutable YC_SA_Key_ID "$YC_SA_Key_ID"
42 _saveaccountconf_mutable YC_SA_Key_File "$YC_SA_Key_File"
43 _clearaccountconf_mutable YC_Zone_ID
44 _clearaccountconf YC_Zone_ID
45 else
46 _err "You didn't specify a Yandex Cloud Zone ID or Folder ID yet."
47 return 1
48 fi
49 else
50 _err "YC_SA_Key_File not a RSA file(_isRSA function return false)."
51 return 1
52 fi
53 else
54 _err "YC_SA_Key_File not found in path $YC_SA_Key_File."
55 return 1
56 fi
57 else
58 _clearaccountconf YC_Zone_ID
59 _clearaccountconf YC_Folder_ID
60 _clearaccountconf YC_SA_ID
61 _clearaccountconf YC_SA_Key_ID
62 _clearaccountconf YC_SA_Key_File
63 _err "You didn't specify a YC_SA_ID or YC_SA_Key_ID or YC_SA_Key_File."
64 return 1
65 fi
66
67 _debug "First detect the root zone"
68 if ! _get_root "$fulldomain"; then
69 _err "invalid domain"
70 return 1
71 fi
72 _debug _domain_id "$_domain_id"
73 _debug _sub_domain "$_sub_domain"
74 _debug _domain "$_domain"
75
76 _debug "Getting txt records"
77 if ! _yc_rest GET "zones/${_domain_id}:getRecordSet?type=TXT&name=$_sub_domain"; then
78 _err "Error: $response"
79 return 1
80 fi
81
82 _info "Adding record"
83 if _yc_rest POST "zones/$_domain_id:upsertRecordSets" "{\"merges\": [ { \"name\":\"$_sub_domain\",\"type\":\"TXT\",\"ttl\":\"120\",\"data\":[\"$txtvalue\"]}]}"; then
84 if _contains "$response" "\"done\": true"; then
85 _info "Added, OK"
86 return 0
87 else
88 _err "Add txt record error."
89 return 1
90 fi
91 fi
92 _err "Add txt record error."
93 return 1
94
95 }
96
97 #fulldomain txtvalue
98 dns_yc_rm() {
99 fulldomain="$(echo "$1". | _lower_case)" # Add dot at end of domain name
100 txtvalue=$2
101
102 YC_Zone_ID="${YC_Zone_ID:-$(_readaccountconf_mutable YC_Zone_ID)}"
103 YC_Folder_ID="${YC_Folder_ID:-$(_readaccountconf_mutable YC_Folder_ID)}"
104 YC_SA_ID="${YC_SA_ID:-$(_readaccountconf_mutable YC_SA_ID)}"
105 YC_SA_Key_ID="${YC_SA_Key_ID:-$(_readaccountconf_mutable YC_SA_Key_ID)}"
106 YC_SA_Key_File="${YC_SA_Key_File:-$(_readaccountconf_mutable YC_SA_Key_File)}"
107
108 _debug "First detect the root zone"
109 if ! _get_root "$fulldomain"; then
110 _err "invalid domain"
111 return 1
112 fi
113 _debug _domain_id "$_domain_id"
114 _debug _sub_domain "$_sub_domain"
115 _debug _domain "$_domain"
116
117 _debug "Getting txt records"
118 if _yc_rest GET "zones/${_domain_id}:getRecordSet?type=TXT&name=$_sub_domain"; then
119 exists_txtvalue=$(echo "$response" | _normalizeJson | _egrep_o "\"data\".*\][^,]*" | _egrep_o "[^:]*$")
120 _debug exists_txtvalue "$exists_txtvalue"
121 else
122 _err "Error: $response"
123 return 1
124 fi
125
126 if _yc_rest POST "zones/$_domain_id:updateRecordSets" "{\"deletions\": [ { \"name\":\"$_sub_domain\",\"type\":\"TXT\",\"ttl\":\"120\",\"data\":$exists_txtvalue}]}"; then
127 if _contains "$response" "\"done\": true"; then
128 _info "Delete, OK"
129 return 0
130 else
131 _err "Delete record error."
132 return 1
133 fi
134 fi
135 _err "Delete record error."
136 return 1
137 }
138
139 #################### Private functions below ##################################
140 #_acme-challenge.www.domain.com
141 #returns
142 # _sub_domain=_acme-challenge.www
143 # _domain=domain.com
144 # _domain_id=sdjkglgdfewsdfg
145 _get_root() {
146 domain=$1
147 i=1
148 p=1
149
150 # Use Zone ID directly if provided
151 if [ "$YC_Zone_ID" ]; then
152 if ! _yc_rest GET "zones/$YC_Zone_ID"; then
153 return 1
154 else
155 if echo "$response" | tr -d " " | grep \"id\":\"$YC_Zone_ID\" >/dev/null; then
156 _domain=$(echo "$response" | _egrep_o "\"zone\": *\"[^\"]*\"" | cut -d : -f 2 | tr -d \" | _head_n 1 | tr -d " ")
157 if [ "$_domain" ]; then
158 _cutlength=$((${#domain} - ${#_domain}))
159 _sub_domain=$(printf "%s" "$domain" | cut -c "1-$_cutlength")
160 _domain_id=$YC_Zone_ID
161 return 0
162 else
163 return 1
164 fi
165 else
166 return 1
167 fi
168 fi
169 fi
170
171 while true; do
172 h=$(printf "%s" "$domain" | cut -d . -f $i-100)
173 _debug h "$h"
174 if [ -z "$h" ]; then
175 #not valid
176 return 1
177 fi
178 if [ "$YC_Folder_ID" ]; then
179 if ! _yc_rest GET "zones?folderId=$YC_Folder_ID"; then
180 return 1
181 fi
182 else
183 echo "You didn't specify a Yandex Cloud Folder ID."
184 return 1
185 fi
186 if _contains "$response" "\"zone\": \"$h\""; then
187 _domain_id=$(echo "$response" | _normalizeJson | _egrep_o "[^{]*\"zone\":\"$h\"[^}]*" | _egrep_o "\"id\"[^,]*" | _egrep_o "[^:]*$" | tr -d '"')
188 _debug _domain_id "$_domain_id"
189 if [ "$_domain_id" ]; then
190 _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
191 _domain=$h
192 return 0
193 fi
194 return 1
195 fi
196 p=$i
197 i=$(_math "$i" + 1)
198 done
199 return 1
200 }
201
202 _yc_rest() {
203 m=$1
204 ep="$2"
205 data="$3"
206 _debug "$ep"
207
208 if [ ! "$YC_Token" ]; then
209 _debug "Login"
210 _yc_login
211 else
212 _debug "Token already exists. Skip Login."
213 fi
214
215 token_trimmed=$(echo "$YC_Token" | tr -d '"')
216
217 export _H1="Content-Type: application/json"
218 export _H2="Authorization: Bearer $token_trimmed"
219
220 if [ "$m" != "GET" ]; then
221 _debug data "$data"
222 response="$(_post "$data" "$YC_Api/$ep" "" "$m")"
223 else
224 response="$(_get "$YC_Api/$ep")"
225 fi
226
227 if [ "$?" != "0" ]; then
228 _err "error $ep"
229 return 1
230 fi
231 _debug2 response "$response"
232 return 0
233 }
234
235 _yc_login() {
236 header=$(echo "{\"typ\":\"JWT\",\"alg\":\"PS256\",\"kid\":\"$YC_SA_Key_ID\"}" | _normalizeJson | _base64 | _url_replace)
237 _debug header "$header"
238
239 _current_timestamp=$(_time)
240 _expire_timestamp=$(_math $_current_timestamp + 1200) # 20 minutes
241 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)
242 _debug payload "$payload"
243
244 #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 )
245 _signature=$(printf "%s.%s" "$header" "$payload" | _sign "$YC_SA_Key_File" "sha256 -sigopt rsa_padding_mode:pss -sigopt rsa_pss_saltlen:-1" | _url_replace)
246 _debug2 _signature "$_signature"
247
248 _jwt=$(printf "{\"jwt\": \"%s.%s.%s\"}" "$header" "$payload" "$_signature")
249 _debug2 _jwt "$_jwt"
250
251 export _H1="Content-Type: application/json"
252 _iam_response="$(_post "$_jwt" "https://iam.api.cloud.yandex.net/iam/v1/tokens" "" "POST")"
253 _debug3 _iam_response "$(echo "$_iam_response" | _normalizeJson)"
254
255 YC_Token="$(echo "$_iam_response" | _normalizeJson | _egrep_o "\"iamToken\"[^,]*" | _egrep_o "[^:]*$" | tr -d '"')"
256 _debug3 YC_Token
257
258 return 0
259 }