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
7 # Report bugs to https://control.akamai.com/apps/support-ui/#/contact-support
11 # *** TBD. NOT IMPLEMENTED YET ***
12 # specify Edgegrid credentials file and section
13 # AKAMAI_EDGERC=<full file path>
14 # AKAMAI_EDGERC_SECTION="default"
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>
22 ACME_EDGEDNS_VERSION
="0.1.0"
24 ######## Public functions #####################
26 # Usage: dns_edgedns_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
27 # Used to add txt record
32 _debug
"ENTERING DNS_EDGEDNS_ADD"
33 _debug2
"fulldomain" "$fulldomain"
34 _debug2
"txtvalue" "$txtvalue"
36 if ! _EDGEDNS_credentials
; then
40 if ! _EDGEDNS_getZoneInfo
"$fulldomain"; then
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")
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")"
56 if [ "$_edge_result" != "404" ]; then
57 _err
"$(printf "Failure accessing Akamai Edge DNS API Server. Error
: %s
" "$_edge_result")"
61 rdata
="\"${txtvalue}\""
63 if [ "$_api_status" -eq 0 ]; then
64 # record already exists. Get existing record data and update
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
76 while [ "$_txt_val" != "$rdlist" ] && [ "${rdlist}" ]; do
77 _txt_val
="${rdlist%%,*}"
79 rdata
="${rdata},\"${_txt_val}\""
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")
87 if [ "$_api_status" -eq 0 ]; then
88 _log
"$(printf "Text value
%s added to recordset
%s
" "$txtvalue" "$fulldomain")"
91 _err
"$(printf "error adding TXT record
for validation. Error
: %s
" "$_edge_result")"
96 # Usage: dns_edgedns_rm _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
97 # Used to delete txt record
102 _debug
"ENTERING DNS_EDGEDNS_RM"
103 _debug2
"fulldomain" "$fulldomain"
104 _debug2
"txtvalue" "$txtvalue"
106 if ! _EDGEDNS_credentials
; then
110 if ! _EDGEDNS_getZoneInfo
"$fulldomain"; then
111 _err
"Invalid domain"
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")
120 if [ "$_api_status" -ne 0 ]; then
121 if [ "$curResult" = "FATAL" ]; then
122 _err
"$(printf "Fatal error
: acme API
function call
: %s
" "$retVal")"
124 if [ "$_edge_result" != "404" ]; then
125 _err
"$(printf "Failure accessing Akamai Edge DNS API Server. Error
: %s
" "$_edge_result")"
129 _debug3
"_edge_result" "$_edge_result"
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
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}\""
153 if [ -z "$rdata" ]; then
156 # Recreate the txtvalue TXT Record
157 body
="{\"name\":\"$fulldomain\",\"type\":\"TXT\",\"ttl\":600, \"rdata\":"[${rdata}]"}"
158 _debug3
"body" "$body"
162 _edge_result
=$
(_edgedns_rest
"$record_op" "$acmeRecordURI" "$body")
164 if [ "$_api_status" -eq 0 ]; then
165 _log
"$(printf "Text value
%s removed from recordset
%s
" "$txtvalue" "$fulldomain")"
168 _err
"$(printf "error removing TXT record
for validation. Error
: %s
" "$_edge_result")"
173 #################### Private functions below ##################################
175 _EDGEDNS_credentials
() {
176 _debug
"GettingEdge DNS credentials"
177 _log
"$(printf "ACME DNSAPI Edge DNS version
%s
" ${ACME_EDGEDNS_VERSION})"
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
=""
184 AKAMAI_CLIENT_SECRET
=""
185 _err
"AKAMAI_ACCESS_TOKEN is missing"
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
=""
193 AKAMAI_CLIENT_SECRET
=""
194 _err
"AKAMAI_CLIENT_TOKEN is missing"
197 AKAMAI_HOST
="${AKAMAI_HOST:-$(_readaccountconf_mutable AKAMAI_HOST)}"
198 if [ -z "$AKAMAI_HOST" ]; then
199 AKAMAI_ACCESS_TOKEN
=""
200 AKAMAI_CLIENT_TOKEN
=""
202 AKAMAI_CLIENT_SECRET
=""
203 _err
"AKAMAI_HOST is missing"
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
=""
211 AKAMAI_CLIENT_SECRET
=""
212 _err
"AKAMAI_CLIENT_SECRET is missing"
216 if [ "$args_missing" = 1 ]; then
217 _err
"You have not properly specified the EdgeDNS Open Edgegrid API credentials. Please try again."
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
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"
232 _EDGEDNS_getZoneInfo
() {
233 _debug
"Getting Zoneinfo"
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")
245 if [ "$retVal" -ne 0 ]; then
246 if [ "$curResult" = "FATAL" ]; then
247 _err
"$(printf "Fatal error
: acme API
function call
: %s
" "$retVal")"
249 if [ "$curResult" != "404" ]; then
250 _err
"$(printf "Managed zone validation failed. Error response
: %s
" "$retVal")"
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}"
262 if [ "${curZone#*.}" != "$curZone" ]; then
263 _debug3
"$(printf "%s still contains a
'.' - so we can check next higher level
" "$curZone")"
266 _err
"Couldn't retrieve zone data."
270 _err
"Failed to retrieve zone data."
277 _debug
"Handling API Request"
279 # Assume endpoint is complete path, including query args if applicable
282 _edgedns_content_type
=""
283 _request_url_path
="$ep"
284 _request_body
="$body_data"
288 _edgedns_headers
="${_edgedns_headers}${tab}Host: ${AKAMAI_HOST}"
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}"
299 _edgedns_make_auth_header
300 _edgedns_headers
="${_edgedns_headers}${tab}Authorization: ${_signed_auth_header}"
301 _secure_debug2
"Made Auth Header" "$_signed_auth_header"
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))
313 # clear headers from previous request to avoid getting wrong http code on timeouts
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")
321 response
=$
(_get
"$ep")
324 if [ "$_ret" -ne 0 ]; then
325 _err
"$(printf "acme.sh API
function call failed. Error
: %s
" "$_ret")"
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
334 response
="$(echo "${response}" | _normalizeJson)"
339 if [ "$_code" = "204" ]; then
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")"
353 if [ "$_code" = "403" ]; then
354 _err
"access denied make sure your Edgegrid cedentials are correct."
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"
377 _edgedns_new_nonce
() {
378 _debug
"Generating Nonce"
379 _nonce
=$
(echo "EDGEDNS$(_time)" | _digest sha1 hex | cut
-c 1-32)
380 _debug3
"_nonce" "$_nonce"
383 _edgedns_make_auth_header
() {
384 _debug
"Constructing Auth Header"
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"
391 _edgedns_sign_request
392 _signed_auth_header
="$(printf "%ssignature
=%s
" "$_auth_header" "$_signed_req")"
393 _secure_debug2
"Signed Auth Header: " "${_signed_auth_header}"
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"
406 _edgedns_make_signing_key
() {
407 _debug2
"Creating sigining key"
409 _edgedns_base64_hmac_sha256
"$ts" "$AKAMAI_CLIENT_SECRET"
410 _signing_key
="$_hmac_out"
411 _secure_debug2
"Signing Key" "$_signing_key"
415 _edgedns_make_data_to_sign
() {
416 _debug2
"Processing data to sign"
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"
428 _edgedns_make_content_hash
() {
429 _debug2
"Generating content hash"
431 _debug2
"Request method" "${_request_method}"
432 if [ "$_request_method" != "POST" ] ||
[ -z "$_request_body" ]; then
435 _debug2
"Req body" "$_request_body"
436 _edgedns_base64_sha256
"$_request_body"
438 _debug2
"Content hash" "$_hash"
441 _edgedns_base64_hmac_sha256
() {
442 _debug2
"Generating hmac"
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"
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)"
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"
458 _edgedns_base64_sha256
() {
459 _debug2
"Creating sha256 digest"
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"
467 #_edgedns_parse_edgerc() {