]> git.proxmox.com Git - mirror_acme.sh.git/blob - dnsapi/dns_edgedns.sh
Merge pull request #4658 from Justman10000/master
[mirror_acme.sh.git] / dnsapi / dns_edgedns.sh
1 #!/usr/bin/env sh
2
3 # Akamai Edge DNS v2 API
4 # User must provide Open Edgegrid API credentials to the EdgeDNS installation. The remote user in EdgeDNS must have CRUD access to
5 # Edge DNS Zones and Recordsets, e.g. DNS—Zone Record Management authorization
6
7 # Report bugs to https://control.akamai.com/apps/support-ui/#/contact-support
8
9 # Values to export:
10 # --EITHER--
11 # *** TBD. NOT IMPLEMENTED YET ***
12 # specify Edgegrid credentials file and section
13 # AKAMAI_EDGERC=<full file path>
14 # AKAMAI_EDGERC_SECTION="default"
15 ## --OR--
16 # specify indiviual credentials
17 # export AKAMAI_HOST = <host>
18 # export AKAMAI_ACCESS_TOKEN = <access token>
19 # export AKAMAI_CLIENT_TOKEN = <client token>
20 # export AKAMAI_CLIENT_SECRET = <client secret>
21
22 ACME_EDGEDNS_VERSION="0.1.0"
23
24 ######## Public functions #####################
25
26 # Usage: dns_edgedns_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
27 # Used to add txt record
28 #
29 dns_edgedns_add() {
30 fulldomain=$1
31 txtvalue=$2
32 _debug "ENTERING DNS_EDGEDNS_ADD"
33 _debug2 "fulldomain" "$fulldomain"
34 _debug2 "txtvalue" "$txtvalue"
35
36 if ! _EDGEDNS_credentials; then
37 _err "$@"
38 return 1
39 fi
40 if ! _EDGEDNS_getZoneInfo "$fulldomain"; then
41 _err "Invalid domain"
42 return 1
43 fi
44
45 _debug2 "Add: zone" "$zone"
46 acmeRecordURI=$(printf "%s/%s/names/%s/types/TXT" "$edge_endpoint" "$zone" "$fulldomain")
47 _debug3 "Add URL" "$acmeRecordURI"
48 # Get existing TXT record
49 _edge_result=$(_edgedns_rest GET "$acmeRecordURI")
50 _api_status="$?"
51 _debug3 "_edge_result" "$_edge_result"
52 if [ "$_api_status" -ne 0 ]; then
53 if [ "$curResult" = "FATAL" ]; then
54 _err "$(printf "Fatal error: acme API function call : %s" "$retVal")"
55 fi
56 if [ "$_edge_result" != "404" ]; then
57 _err "$(printf "Failure accessing Akamai Edge DNS API Server. Error: %s" "$_edge_result")"
58 return 1
59 fi
60 fi
61 rdata="\"${txtvalue}\""
62 record_op="POST"
63 if [ "$_api_status" -eq 0 ]; then
64 # record already exists. Get existing record data and update
65 record_op="PUT"
66 rdlist="${_edge_result#*\"rdata\":[}"
67 rdlist="${rdlist%%]*}"
68 rdlist=$(echo "$rdlist" | tr -d '"' | tr -d "\\\\")
69 _debug3 "existing TXT found"
70 _debug3 "record data" "$rdlist"
71 # value already there?
72 if _contains "$rdlist" "$txtvalue"; then
73 return 0
74 fi
75 _txt_val=""
76 while [ "$_txt_val" != "$rdlist" ] && [ "${rdlist}" ]; do
77 _txt_val="${rdlist%%,*}"
78 rdlist="${rdlist#*,}"
79 rdata="${rdata},\"${_txt_val}\""
80 done
81 fi
82 # Add the txtvalue TXT Record
83 body="{\"name\":\"$fulldomain\",\"type\":\"TXT\",\"ttl\":600, \"rdata\":"[${rdata}]"}"
84 _debug3 "Add body '${body}'"
85 _edge_result=$(_edgedns_rest "$record_op" "$acmeRecordURI" "$body")
86 _api_status="$?"
87 if [ "$_api_status" -eq 0 ]; then
88 _log "$(printf "Text value %s added to recordset %s" "$txtvalue" "$fulldomain")"
89 return 0
90 else
91 _err "$(printf "error adding TXT record for validation. Error: %s" "$_edge_result")"
92 return 1
93 fi
94 }
95
96 # Usage: dns_edgedns_rm _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
97 # Used to delete txt record
98 #
99 dns_edgedns_rm() {
100 fulldomain=$1
101 txtvalue=$2
102 _debug "ENTERING DNS_EDGEDNS_RM"
103 _debug2 "fulldomain" "$fulldomain"
104 _debug2 "txtvalue" "$txtvalue"
105
106 if ! _EDGEDNS_credentials; then
107 _err "$@"
108 return 1
109 fi
110 if ! _EDGEDNS_getZoneInfo "$fulldomain"; then
111 _err "Invalid domain"
112 return 1
113 fi
114 _debug2 "RM: zone" "${zone}"
115 acmeRecordURI=$(printf "%s/%s/names/%s/types/TXT" "${edge_endpoint}" "$zone" "$fulldomain")
116 _debug3 "RM URL" "$acmeRecordURI"
117 # Get existing TXT record
118 _edge_result=$(_edgedns_rest GET "$acmeRecordURI")
119 _api_status="$?"
120 if [ "$_api_status" -ne 0 ]; then
121 if [ "$curResult" = "FATAL" ]; then
122 _err "$(printf "Fatal error: acme API function call : %s" "$retVal")"
123 fi
124 if [ "$_edge_result" != "404" ]; then
125 _err "$(printf "Failure accessing Akamai Edge DNS API Server. Error: %s" "$_edge_result")"
126 return 1
127 fi
128 fi
129 _debug3 "_edge_result" "$_edge_result"
130 record_op="DELETE"
131 body=""
132 if [ "$_api_status" -eq 0 ]; then
133 # record already exists. Get existing record data and update
134 rdlist="${_edge_result#*\"rdata\":[}"
135 rdlist="${rdlist%%]*}"
136 rdlist=$(echo "$rdlist" | tr -d '"' | tr -d "\\\\")
137 _debug3 "rdlist" "$rdlist"
138 if [ -n "$rdlist" ]; then
139 record_op="PUT"
140 comma=""
141 rdata=""
142 _txt_val=""
143 while [ "$_txt_val" != "$rdlist" ] && [ "$rdlist" ]; do
144 _txt_val="${rdlist%%,*}"
145 rdlist="${rdlist#*,}"
146 _debug3 "_txt_val" "$_txt_val"
147 _debug3 "txtvalue" "$txtvalue"
148 if ! _contains "$_txt_val" "$txtvalue"; then
149 rdata="${rdata}${comma}\"${_txt_val}\""
150 comma=","
151 fi
152 done
153 if [ -z "$rdata" ]; then
154 record_op="DELETE"
155 else
156 # Recreate the txtvalue TXT Record
157 body="{\"name\":\"$fulldomain\",\"type\":\"TXT\",\"ttl\":600, \"rdata\":"[${rdata}]"}"
158 _debug3 "body" "$body"
159 fi
160 fi
161 fi
162 _edge_result=$(_edgedns_rest "$record_op" "$acmeRecordURI" "$body")
163 _api_status="$?"
164 if [ "$_api_status" -eq 0 ]; then
165 _log "$(printf "Text value %s removed from recordset %s" "$txtvalue" "$fulldomain")"
166 return 0
167 else
168 _err "$(printf "error removing TXT record for validation. Error: %s" "$_edge_result")"
169 return 1
170 fi
171 }
172
173 #################### Private functions below ##################################
174
175 _EDGEDNS_credentials() {
176 _debug "GettingEdge DNS credentials"
177 _log "$(printf "ACME DNSAPI Edge DNS version %s" ${ACME_EDGEDNS_VERSION})"
178 args_missing=0
179 AKAMAI_ACCESS_TOKEN="${AKAMAI_ACCESS_TOKEN:-$(_readaccountconf_mutable AKAMAI_ACCESS_TOKEN)}"
180 if [ -z "$AKAMAI_ACCESS_TOKEN" ]; then
181 AKAMAI_ACCESS_TOKEN=""
182 AKAMAI_CLIENT_TOKEN=""
183 AKAMAI_HOST=""
184 AKAMAI_CLIENT_SECRET=""
185 _err "AKAMAI_ACCESS_TOKEN is missing"
186 args_missing=1
187 fi
188 AKAMAI_CLIENT_TOKEN="${AKAMAI_CLIENT_TOKEN:-$(_readaccountconf_mutable AKAMAI_CLIENT_TOKEN)}"
189 if [ -z "$AKAMAI_CLIENT_TOKEN" ]; then
190 AKAMAI_ACCESS_TOKEN=""
191 AKAMAI_CLIENT_TOKEN=""
192 AKAMAI_HOST=""
193 AKAMAI_CLIENT_SECRET=""
194 _err "AKAMAI_CLIENT_TOKEN is missing"
195 args_missing=1
196 fi
197 AKAMAI_HOST="${AKAMAI_HOST:-$(_readaccountconf_mutable AKAMAI_HOST)}"
198 if [ -z "$AKAMAI_HOST" ]; then
199 AKAMAI_ACCESS_TOKEN=""
200 AKAMAI_CLIENT_TOKEN=""
201 AKAMAI_HOST=""
202 AKAMAI_CLIENT_SECRET=""
203 _err "AKAMAI_HOST is missing"
204 args_missing=1
205 fi
206 AKAMAI_CLIENT_SECRET="${AKAMAI_CLIENT_SECRET:-$(_readaccountconf_mutable AKAMAI_CLIENT_SECRET)}"
207 if [ -z "$AKAMAI_CLIENT_SECRET" ]; then
208 AKAMAI_ACCESS_TOKEN=""
209 AKAMAI_CLIENT_TOKEN=""
210 AKAMAI_HOST=""
211 AKAMAI_CLIENT_SECRET=""
212 _err "AKAMAI_CLIENT_SECRET is missing"
213 args_missing=1
214 fi
215
216 if [ "$args_missing" = 1 ]; then
217 _err "You have not properly specified the EdgeDNS Open Edgegrid API credentials. Please try again."
218 return 1
219 else
220 _saveaccountconf_mutable AKAMAI_ACCESS_TOKEN "$AKAMAI_ACCESS_TOKEN"
221 _saveaccountconf_mutable AKAMAI_CLIENT_TOKEN "$AKAMAI_CLIENT_TOKEN"
222 _saveaccountconf_mutable AKAMAI_HOST "$AKAMAI_HOST"
223 _saveaccountconf_mutable AKAMAI_CLIENT_SECRET "$AKAMAI_CLIENT_SECRET"
224 # Set whether curl should use secure or insecure mode
225 fi
226 export HTTPS_INSECURE=0 # All Edgegrid API calls are secure
227 edge_endpoint=$(printf "https://%s/config-dns/v2/zones" "$AKAMAI_HOST")
228 _debug3 "Edge API Endpoint:" "$edge_endpoint"
229
230 }
231
232 _EDGEDNS_getZoneInfo() {
233 _debug "Getting Zoneinfo"
234 zoneEnd=false
235 curZone=$1
236 while [ -n "$zoneEnd" ]; do
237 # we can strip the first part of the fulldomain, since its just the _acme-challenge string
238 curZone="${curZone#*.}"
239 # suffix . needed for zone -> domain.tld.
240 # create zone get url
241 get_zone_url=$(printf "%s/%s" "$edge_endpoint" "$curZone")
242 _debug3 "Zone Get: " "${get_zone_url}"
243 curResult=$(_edgedns_rest GET "$get_zone_url")
244 retVal=$?
245 if [ "$retVal" -ne 0 ]; then
246 if [ "$curResult" = "FATAL" ]; then
247 _err "$(printf "Fatal error: acme API function call : %s" "$retVal")"
248 fi
249 if [ "$curResult" != "404" ]; then
250 _err "$(printf "Managed zone validation failed. Error response: %s" "$retVal")"
251 return 1
252 fi
253 fi
254 if _contains "$curResult" "\"zone\":"; then
255 _debug2 "Zone data" "${curResult}"
256 zone=$(echo "${curResult}" | _egrep_o "\"zone\"\\s*:\\s*\"[^\"]*\"" | _head_n 1 | cut -d : -f 2 | tr -d "\"")
257 _debug3 "Zone" "${zone}"
258 zoneEnd=""
259 return 0
260 fi
261
262 if [ "${curZone#*.}" != "$curZone" ]; then
263 _debug3 "$(printf "%s still contains a '.' - so we can check next higher level" "$curZone")"
264 else
265 zoneEnd=true
266 _err "Couldn't retrieve zone data."
267 return 1
268 fi
269 done
270 _err "Failed to retrieve zone data."
271 return 2
272 }
273
274 _edgedns_headers=""
275
276 _edgedns_rest() {
277 _debug "Handling API Request"
278 m=$1
279 # Assume endpoint is complete path, including query args if applicable
280 ep=$2
281 body_data=$3
282 _edgedns_content_type=""
283 _request_url_path="$ep"
284 _request_body="$body_data"
285 _request_method="$m"
286 _edgedns_headers=""
287 tab=""
288 _edgedns_headers="${_edgedns_headers}${tab}Host: ${AKAMAI_HOST}"
289 tab="\t"
290 # Set in acme.sh _post/_get
291 #_edgedns_headers="${_edgedns_headers}${tab}User-Agent:ACME DNSAPI Edge DNS version ${ACME_EDGEDNS_VERSION}"
292 _edgedns_headers="${_edgedns_headers}${tab}Accept: application/json,*/*"
293 if [ "$m" != "GET" ] && [ "$m" != "DELETE" ]; then
294 _edgedns_content_type="application/json"
295 _debug3 "_request_body" "$_request_body"
296 _body_len=$(echo "$_request_body" | tr -d "\n\r" | awk '{print length}')
297 _edgedns_headers="${_edgedns_headers}${tab}Content-Length: ${_body_len}"
298 fi
299 _edgedns_make_auth_header
300 _edgedns_headers="${_edgedns_headers}${tab}Authorization: ${_signed_auth_header}"
301 _secure_debug2 "Made Auth Header" "$_signed_auth_header"
302 hdr_indx=1
303 work_header="${_edgedns_headers}${tab}"
304 _debug3 "work_header" "$work_header"
305 while [ "$work_header" ]; do
306 entry="${work_header%%\\t*}"
307 work_header="${work_header#*\\t}"
308 export "$(printf "_H%s=%s" "$hdr_indx" "$entry")"
309 _debug2 "Request Header " "$entry"
310 hdr_indx=$((hdr_indx + 1))
311 done
312
313 # clear headers from previous request to avoid getting wrong http code on timeouts
314 : >"$HTTP_HEADER"
315 _debug2 "$ep"
316 if [ "$m" != "GET" ]; then
317 _debug3 "Method data" "$data"
318 # body url [needbase64] [POST|PUT|DELETE] [ContentType]
319 response=$(_post "$_request_body" "$ep" false "$m" "$_edgedns_content_type")
320 else
321 response=$(_get "$ep")
322 fi
323 _ret="$?"
324 if [ "$_ret" -ne 0 ]; then
325 _err "$(printf "acme.sh API function call failed. Error: %s" "$_ret")"
326 echo "FATAL"
327 return "$_ret"
328 fi
329 _debug2 "response" "${response}"
330 _code="$(grep "^HTTP" "$HTTP_HEADER" | _tail_n 1 | cut -d " " -f 2 | tr -d "\\r\\n")"
331 _debug2 "http response code" "$_code"
332 if [ "$_code" = "200" ] || [ "$_code" = "201" ]; then
333 # All good
334 response="$(echo "${response}" | _normalizeJson)"
335 echo "$response"
336 return 0
337 fi
338
339 if [ "$_code" = "204" ]; then
340 # Success, no body
341 echo "$_code"
342 return 0
343 fi
344
345 if [ "$_code" = "400" ]; then
346 _err "Bad request presented"
347 _log "$(printf "Headers: %s" "$_edgedns_headers")"
348 _log "$(printf "Method: %s" "$_request_method")"
349 _log "$(printf "URL: %s" "$ep")"
350 _log "$(printf "Data: %s" "$data")"
351 fi
352
353 if [ "$_code" = "403" ]; then
354 _err "access denied make sure your Edgegrid cedentials are correct."
355 fi
356
357 echo "$_code"
358 return 1
359 }
360
361 _edgedns_eg_timestamp() {
362 _debug "Generating signature Timestamp"
363 _debug3 "Retriving ntp time"
364 _timeheaders="$(_get "https://www.ntp.org" "onlyheader")"
365 _debug3 "_timeheaders" "$_timeheaders"
366 _ntpdate="$(echo "$_timeheaders" | grep -i "Date:" | _head_n 1 | cut -d ':' -f 2- | tr -d "\r\n")"
367 _debug3 "_ntpdate" "$_ntpdate"
368 _ntpdate="$(echo "${_ntpdate}" | sed -e 's/^[[:space:]]*//')"
369 _debug3 "_NTPDATE" "$_ntpdate"
370 _ntptime="$(echo "${_ntpdate}" | _head_n 1 | cut -d " " -f 5 | tr -d "\r\n")"
371 _debug3 "_ntptime" "$_ntptime"
372 _eg_timestamp=$(date -u "+%Y%m%dT")
373 _eg_timestamp="$(printf "%s%s+0000" "$_eg_timestamp" "$_ntptime")"
374 _debug "_eg_timestamp" "$_eg_timestamp"
375 }
376
377 _edgedns_new_nonce() {
378 _debug "Generating Nonce"
379 _nonce=$(echo "EDGEDNS$(_time)" | _digest sha1 hex | cut -c 1-32)
380 _debug3 "_nonce" "$_nonce"
381 }
382
383 _edgedns_make_auth_header() {
384 _debug "Constructing Auth Header"
385 _edgedns_new_nonce
386 _edgedns_eg_timestamp
387 # "Unsigned authorization header: 'EG1-HMAC-SHA256 client_token=block;access_token=block;timestamp=20200806T14:16:33+0000;nonce=72cde72c-82d9-4721-9854-2ba057929d67;'"
388 _auth_header="$(printf "EG1-HMAC-SHA256 client_token=%s;access_token=%s;timestamp=%s;nonce=%s;" "$AKAMAI_CLIENT_TOKEN" "$AKAMAI_ACCESS_TOKEN" "$_eg_timestamp" "$_nonce")"
389 _secure_debug2 "Unsigned Auth Header: " "$_auth_header"
390
391 _edgedns_sign_request
392 _signed_auth_header="$(printf "%ssignature=%s" "$_auth_header" "$_signed_req")"
393 _secure_debug2 "Signed Auth Header: " "${_signed_auth_header}"
394 }
395
396 _edgedns_sign_request() {
397 _debug2 "Signing http request"
398 _edgedns_make_data_to_sign "$_auth_header"
399 _secure_debug2 "Returned signed data" "$_mdata"
400 _edgedns_make_signing_key "$_eg_timestamp"
401 _edgedns_base64_hmac_sha256 "$_mdata" "$_signing_key"
402 _signed_req="$_hmac_out"
403 _secure_debug2 "Signed Request" "$_signed_req"
404 }
405
406 _edgedns_make_signing_key() {
407 _debug2 "Creating sigining key"
408 ts=$1
409 _edgedns_base64_hmac_sha256 "$ts" "$AKAMAI_CLIENT_SECRET"
410 _signing_key="$_hmac_out"
411 _secure_debug2 "Signing Key" "$_signing_key"
412
413 }
414
415 _edgedns_make_data_to_sign() {
416 _debug2 "Processing data to sign"
417 hdr=$1
418 _secure_debug2 "hdr" "$hdr"
419 _edgedns_make_content_hash
420 path="$(echo "$_request_url_path" | tr -d "\n\r" | sed 's/https\?:\/\///')"
421 path=${path#*"$AKAMAI_HOST"}
422 _debug "hier path" "$path"
423 # dont expose headers to sign so use MT string
424 _mdata="$(printf "%s\thttps\t%s\t%s\t%s\t%s\t%s" "$_request_method" "$AKAMAI_HOST" "$path" "" "$_hash" "$hdr")"
425 _secure_debug2 "Data to Sign" "$_mdata"
426 }
427
428 _edgedns_make_content_hash() {
429 _debug2 "Generating content hash"
430 _hash=""
431 _debug2 "Request method" "${_request_method}"
432 if [ "$_request_method" != "POST" ] || [ -z "$_request_body" ]; then
433 return 0
434 fi
435 _debug2 "Req body" "$_request_body"
436 _edgedns_base64_sha256 "$_request_body"
437 _hash="$_sha256_out"
438 _debug2 "Content hash" "$_hash"
439 }
440
441 _edgedns_base64_hmac_sha256() {
442 _debug2 "Generating hmac"
443 data=$1
444 key=$2
445 encoded_data="$(echo "$data" | iconv -t utf-8)"
446 encoded_key="$(echo "$key" | iconv -t utf-8)"
447 _secure_debug2 "encoded data" "$encoded_data"
448 _secure_debug2 "encoded key" "$encoded_key"
449
450 encoded_key_hex=$(printf "%s" "$encoded_key" | _hex_dump | tr -d ' ')
451 data_sig="$(echo "$encoded_data" | tr -d "\n\r" | _hmac sha256 "$encoded_key_hex" | _base64)"
452
453 _secure_debug2 "data_sig:" "$data_sig"
454 _hmac_out="$(echo "$data_sig" | tr -d "\n\r" | iconv -f utf-8)"
455 _secure_debug2 "hmac" "$_hmac_out"
456 }
457
458 _edgedns_base64_sha256() {
459 _debug2 "Creating sha256 digest"
460 trg=$1
461 _secure_debug2 "digest data" "$trg"
462 digest="$(echo "$trg" | tr -d "\n\r" | _digest "sha256")"
463 _sha256_out="$(echo "$digest" | tr -d "\n\r" | iconv -f utf-8)"
464 _secure_debug2 "digest decode" "$_sha256_out"
465 }
466
467 #_edgedns_parse_edgerc() {
468 # filepath=$1
469 # section=$2
470 #}