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.

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