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.

470 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. AKAMAI_ACCESS_TOKEN="${AKAMAI_ACCESS_TOKEN:-$(_readaccountconf_mutable AKAMAI_ACCESS_TOKEN)}"
  168. if [ -z "$AKAMAI_ACCESS_TOKEN" ]; then
  169. AKAMAI_ACCESS_TOKEN=""
  170. AKAMAI_CLIENT_TOKEN=""
  171. AKAMAI_HOST=""
  172. AKAMAI_CLIENT_SECRET=""
  173. _err "AKAMAI_ACCESS_TOKEN is missing"
  174. args_missing=1
  175. fi
  176. AKAMAI_CLIENT_TOKEN="${AKAMAI_CLIENT_TOKEN:-$(_readaccountconf_mutable AKAMAI_CLIENT_TOKEN)}"
  177. if [ -z "$AKAMAI_CLIENT_TOKEN" ]; then
  178. AKAMAI_ACCESS_TOKEN=""
  179. AKAMAI_CLIENT_TOKEN=""
  180. AKAMAI_HOST=""
  181. AKAMAI_CLIENT_SECRET=""
  182. _err "AKAMAI_CLIENT_TOKEN is missing"
  183. args_missing=1
  184. fi
  185. AKAMAI_HOST="${AKAMAI_HOST:-$(_readaccountconf_mutable AKAMAI_HOST)}"
  186. if [ -z "$AKAMAI_HOST" ]; then
  187. AKAMAI_ACCESS_TOKEN=""
  188. AKAMAI_CLIENT_TOKEN=""
  189. AKAMAI_HOST=""
  190. AKAMAI_CLIENT_SECRET=""
  191. _err "AKAMAI_HOST is missing"
  192. args_missing=1
  193. fi
  194. AKAMAI_CLIENT_SECRET="${AKAMAI_CLIENT_SECRET:-$(_readaccountconf_mutable AKAMAI_CLIENT_SECRET)}"
  195. if [ -z "$AKAMAI_CLIENT_SECRET" ]; then
  196. AKAMAI_ACCESS_TOKEN=""
  197. AKAMAI_CLIENT_TOKEN=""
  198. AKAMAI_HOST=""
  199. AKAMAI_CLIENT_SECRET=""
  200. _err "AKAMAI_CLIENT_SECRET is missing"
  201. args_missing=1
  202. fi
  203. if [ "$args_missing" = 1 ]; then
  204. _err "You have not properly specified the EdgeDNS Open Edgegrid API credentials. Please try again."
  205. return 1
  206. else
  207. _saveaccountconf_mutable AKAMAI_ACCESS_TOKEN "$AKAMAI_ACCESS_TOKEN"
  208. _saveaccountconf_mutable AKAMAI_CLIENT_TOKEN "$AKAMAI_CLIENT_TOKEN"
  209. _saveaccountconf_mutable AKAMAI_HOST "$AKAMAI_HOST"
  210. _saveaccountconf_mutable AKAMAI_CLIENT_SECRET "$AKAMAI_CLIENT_SECRET"
  211. # Set whether curl should use secure or insecure mode
  212. fi
  213. export HTTPS_INSECURE=0 # All Edgegrid API calls are secure
  214. edge_endpoint=$(printf "https://%s/config-dns/v2/zones" "$AKAMAI_HOST")
  215. _debug3 "Edge API Endpoint:" "$edge_endpoint"
  216. }
  217. _EDGEDNS_getZoneInfo() {
  218. _debug "Getting Zoneinfo"
  219. zoneEnd=false
  220. curZone=$1
  221. while [ -n "$zoneEnd" ]; do
  222. # we can strip the first part of the fulldomain, since its just the _acme-challenge string
  223. curZone="${curZone#*.}"
  224. # suffix . needed for zone -> domain.tld.
  225. # create zone get url
  226. get_zone_url=$(printf "%s/%s" "$edge_endpoint" "$curZone")
  227. _debug3 "Zone Get: " "${get_zone_url}"
  228. curResult=$(_edgedns_rest GET "$get_zone_url")
  229. retVal=$?
  230. if [ "$retVal" -ne 0 ]; then
  231. if [ "$curResult" = "FATAL" ]; then
  232. _err "$(printf "Fatal error: acme API function call : %s" "$retVal")"
  233. fi
  234. if [ "$curResult" != "404" ]; then
  235. _err "$(printf "Managed zone validation failed. Error response: %s" "$retVal")"
  236. return 1
  237. fi
  238. fi
  239. if _contains "$curResult" "\"zone\":"; then
  240. _debug2 "Zone data" "${curResult}"
  241. zone=$(echo "${curResult}" | _egrep_o "\"zone\"\\s*:\\s*\"[^\"]*\"" | _head_n 1 | cut -d : -f 2 | tr -d "\"")
  242. _debug3 "Zone" "${zone}"
  243. zoneEnd=""
  244. return 0
  245. fi
  246. if [ "${curZone#*.}" != "$curZone" ]; then
  247. _debug3 "$(printf "%s still contains a '.' - so we can check next higher level" "$curZone")"
  248. else
  249. zoneEnd=true
  250. _err "Couldn't retrieve zone data."
  251. return 1
  252. fi
  253. done
  254. _err "Failed to retrieve zone data."
  255. return 2
  256. }
  257. _edgedns_headers=""
  258. _edgedns_rest() {
  259. _debug "Handling API Request"
  260. m=$1
  261. # Assume endpoint is complete path, including query args if applicable
  262. ep=$2
  263. body_data=$3
  264. _edgedns_content_type=""
  265. _request_url_path="$ep"
  266. _request_body="$body_data"
  267. _request_method="$m"
  268. _edgedns_headers=""
  269. tab=""
  270. _edgedns_headers="${_edgedns_headers}${tab}Host: ${AKAMAI_HOST}"
  271. tab="\t"
  272. # Set in acme.sh _post/_get
  273. #_edgedns_headers="${_edgedns_headers}${tab}User-Agent:ACME DNSAPI Edge DNS version ${ACME_EDGEDNS_VERSION}"
  274. _edgedns_headers="${_edgedns_headers}${tab}Accept: application/json,*/*"
  275. if [ "$m" != "GET" ] && [ "$m" != "DELETE" ]; then
  276. _edgedns_content_type="application/json"
  277. _debug3 "_request_body" "$_request_body"
  278. _body_len=$(echo "$_request_body" | tr -d "\n\r" | awk '{print length}')
  279. _edgedns_headers="${_edgedns_headers}${tab}Content-Length: ${_body_len}"
  280. fi
  281. _edgedns_make_auth_header
  282. _edgedns_headers="${_edgedns_headers}${tab}Authorization: ${_signed_auth_header}"
  283. _secure_debug2 "Made Auth Header" "$_signed_auth_header"
  284. hdr_indx=1
  285. work_header="${_edgedns_headers}${tab}"
  286. _debug3 "work_header" "$work_header"
  287. while [ "$work_header" ]; do
  288. entry="${work_header%%\\t*}"
  289. work_header="${work_header#*\\t}"
  290. export "$(printf "_H%s=%s" "$hdr_indx" "$entry")"
  291. _debug2 "Request Header " "$entry"
  292. hdr_indx=$((hdr_indx + 1))
  293. done
  294. # clear headers from previous request to avoid getting wrong http code on timeouts
  295. : >"$HTTP_HEADER"
  296. _debug2 "$ep"
  297. if [ "$m" != "GET" ]; then
  298. _debug3 "Method data" "$data"
  299. # body url [needbase64] [POST|PUT|DELETE] [ContentType]
  300. response=$(_post "$_request_body" "$ep" false "$m" "$_edgedns_content_type")
  301. else
  302. response=$(_get "$ep")
  303. fi
  304. _ret="$?"
  305. if [ "$_ret" -ne 0 ]; then
  306. _err "$(printf "acme.sh API function call failed. Error: %s" "$_ret")"
  307. echo "FATAL"
  308. return "$_ret"
  309. fi
  310. _debug2 "response" "${response}"
  311. _code="$(grep "^HTTP" "$HTTP_HEADER" | _tail_n 1 | cut -d " " -f 2 | tr -d "\\r\\n")"
  312. _debug2 "http response code" "$_code"
  313. if [ "$_code" = "200" ] || [ "$_code" = "201" ]; then
  314. # All good
  315. response="$(echo "${response}" | _normalizeJson)"
  316. echo "$response"
  317. return 0
  318. fi
  319. if [ "$_code" = "204" ]; then
  320. # Success, no body
  321. echo "$_code"
  322. return 0
  323. fi
  324. if [ "$_code" = "400" ]; then
  325. _err "Bad request presented"
  326. _log "$(printf "Headers: %s" "$_edgedns_headers")"
  327. _log "$(printf "Method: %s" "$_request_method")"
  328. _log "$(printf "URL: %s" "$ep")"
  329. _log "$(printf "Data: %s" "$data")"
  330. fi
  331. if [ "$_code" = "403" ]; then
  332. _err "access denied make sure your Edgegrid cedentials are correct."
  333. fi
  334. echo "$_code"
  335. return 1
  336. }
  337. _edgedns_eg_timestamp() {
  338. _debug "Generating signature Timestamp"
  339. _debug3 "Retriving ntp time"
  340. _timeheaders="$(_get "https://www.ntp.org" "onlyheader")"
  341. _debug3 "_timeheaders" "$_timeheaders"
  342. _ntpdate="$(echo "$_timeheaders" | grep -i "Date:" | _head_n 1 | cut -d ':' -f 2- | tr -d "\r\n")"
  343. _debug3 "_ntpdate" "$_ntpdate"
  344. _ntpdate="$(echo "${_ntpdate}" | sed -e 's/^[[:space:]]*//')"
  345. _debug3 "_NTPDATE" "$_ntpdate"
  346. _ntptime="$(echo "${_ntpdate}" | _head_n 1 | cut -d " " -f 5 | tr -d "\r\n")"
  347. _debug3 "_ntptime" "$_ntptime"
  348. _eg_timestamp=$(date -u "+%Y%m%dT")
  349. _eg_timestamp="$(printf "%s%s+0000" "$_eg_timestamp" "$_ntptime")"
  350. _debug "_eg_timestamp" "$_eg_timestamp"
  351. }
  352. _edgedns_new_nonce() {
  353. _debug "Generating Nonce"
  354. _nonce=$(echo "EDGEDNS$(_time)" | _digest sha1 hex | cut -c 1-32)
  355. _debug3 "_nonce" "$_nonce"
  356. }
  357. _edgedns_make_auth_header() {
  358. _debug "Constructing Auth Header"
  359. _edgedns_new_nonce
  360. _edgedns_eg_timestamp
  361. # "Unsigned authorization header: 'EG1-HMAC-SHA256 client_token=block;access_token=block;timestamp=20200806T14:16:33+0000;nonce=72cde72c-82d9-4721-9854-2ba057929d67;'"
  362. _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")"
  363. _secure_debug2 "Unsigned Auth Header: " "$_auth_header"
  364. _edgedns_sign_request
  365. _signed_auth_header="$(printf "%ssignature=%s" "$_auth_header" "$_signed_req")"
  366. _secure_debug2 "Signed Auth Header: " "${_signed_auth_header}"
  367. }
  368. _edgedns_sign_request() {
  369. _debug2 "Signing http request"
  370. _edgedns_make_data_to_sign "$_auth_header"
  371. _secure_debug2 "Returned signed data" "$_mdata"
  372. _edgedns_make_signing_key "$_eg_timestamp"
  373. _edgedns_base64_hmac_sha256 "$_mdata" "$_signing_key"
  374. _signed_req="$_hmac_out"
  375. _secure_debug2 "Signed Request" "$_signed_req"
  376. }
  377. _edgedns_make_signing_key() {
  378. _debug2 "Creating sigining key"
  379. ts=$1
  380. _edgedns_base64_hmac_sha256 "$ts" "$AKAMAI_CLIENT_SECRET"
  381. _signing_key="$_hmac_out"
  382. _secure_debug2 "Signing Key" "$_signing_key"
  383. }
  384. _edgedns_make_data_to_sign() {
  385. _debug2 "Processing data to sign"
  386. hdr=$1
  387. _secure_debug2 "hdr" "$hdr"
  388. _edgedns_make_content_hash
  389. path="$(echo "$_request_url_path" | tr -d "\n\r" | sed 's/https\?:\/\///')"
  390. path=${path#*"$AKAMAI_HOST"}
  391. _debug "hier path" "$path"
  392. # dont expose headers to sign so use MT string
  393. _mdata="$(printf "%s\thttps\t%s\t%s\t%s\t%s\t%s" "$_request_method" "$AKAMAI_HOST" "$path" "" "$_hash" "$hdr")"
  394. _secure_debug2 "Data to Sign" "$_mdata"
  395. }
  396. _edgedns_make_content_hash() {
  397. _debug2 "Generating content hash"
  398. _hash=""
  399. _debug2 "Request method" "${_request_method}"
  400. if [ "$_request_method" != "POST" ] || [ -z "$_request_body" ]; then
  401. return 0
  402. fi
  403. _debug2 "Req body" "$_request_body"
  404. _edgedns_base64_sha256 "$_request_body"
  405. _hash="$_sha256_out"
  406. _debug2 "Content hash" "$_hash"
  407. }
  408. _edgedns_base64_hmac_sha256() {
  409. _debug2 "Generating hmac"
  410. data=$1
  411. key=$2
  412. encoded_data="$(echo "$data" | iconv -t utf-8)"
  413. encoded_key="$(echo "$key" | iconv -t utf-8)"
  414. _secure_debug2 "encoded data" "$encoded_data"
  415. _secure_debug2 "encoded key" "$encoded_key"
  416. encoded_key_hex=$(printf "%s" "$encoded_key" | _hex_dump | tr -d ' ')
  417. data_sig="$(echo "$encoded_data" | tr -d "\n\r" | _hmac sha256 "$encoded_key_hex" | _base64)"
  418. _secure_debug2 "data_sig:" "$data_sig"
  419. _hmac_out="$(echo "$data_sig" | tr -d "\n\r" | iconv -f utf-8)"
  420. _secure_debug2 "hmac" "$_hmac_out"
  421. }
  422. _edgedns_base64_sha256() {
  423. _debug2 "Creating sha256 digest"
  424. trg=$1
  425. _secure_debug2 "digest data" "$trg"
  426. digest="$(echo "$trg" | tr -d "\n\r" | _digest "sha256")"
  427. _sha256_out="$(echo "$digest" | tr -d "\n\r" | iconv -f utf-8)"
  428. _secure_debug2 "digest decode" "$_sha256_out"
  429. }
  430. #_edgedns_parse_edgerc() {
  431. # filepath=$1
  432. # section=$2
  433. #}