You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

473 lines
15 KiB

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