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.

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