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.

333 lines
9.5 KiB

2 years ago
2 years ago
2 years ago
  1. #!/usr/bin/env sh
  2. # shellcheck disable=SC2034
  3. dns_huaweicloud_info='HuaweiCloud.com
  4. Site: HuaweiCloud.com
  5. Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_huaweicloud
  6. Options:
  7. HUAWEICLOUD_Username Username
  8. HUAWEICLOUD_Password Password
  9. HUAWEICLOUD_DomainName DomainName
  10. Issues: github.com/acmesh-official/acme.sh/issues/3265
  11. '
  12. iam_api="https://iam.myhuaweicloud.com"
  13. dns_api="https://dns.ap-southeast-1.myhuaweicloud.com" # Should work
  14. ######## Public functions #####################
  15. # Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
  16. # Used to add txt record
  17. #
  18. # Ref: https://support.huaweicloud.com/intl/zh-cn/api-dns/zh-cn_topic_0132421999.html
  19. #
  20. # About "DomainName" parameters see: https://support.huaweicloud.com/api-iam/iam_01_0006.html
  21. #
  22. dns_huaweicloud_add() {
  23. fulldomain=$1
  24. txtvalue=$2
  25. HUAWEICLOUD_Username="${HUAWEICLOUD_Username:-$(_readaccountconf_mutable HUAWEICLOUD_Username)}"
  26. HUAWEICLOUD_Password="${HUAWEICLOUD_Password:-$(_readaccountconf_mutable HUAWEICLOUD_Password)}"
  27. HUAWEICLOUD_DomainName="${HUAWEICLOUD_DomainName:-$(_readaccountconf_mutable HUAWEICLOUD_DomainName)}"
  28. # Check information
  29. if [ -z "${HUAWEICLOUD_Username}" ] || [ -z "${HUAWEICLOUD_Password}" ] || [ -z "${HUAWEICLOUD_DomainName}" ]; then
  30. _err "Not enough information provided to dns_huaweicloud!"
  31. return 1
  32. fi
  33. unset token # Clear token
  34. token="$(_get_token "${HUAWEICLOUD_Username}" "${HUAWEICLOUD_Password}" "${HUAWEICLOUD_DomainName}")"
  35. if [ -z "${token}" ]; then # Check token
  36. _err "dns_api(dns_huaweicloud): Error getting token."
  37. return 1
  38. fi
  39. _secure_debug "Access token is:" "${token}"
  40. unset zoneid
  41. zoneid="$(_get_zoneid "${token}" "${fulldomain}")"
  42. if [ -z "${zoneid}" ]; then
  43. _err "dns_api(dns_huaweicloud): Error getting zone id."
  44. return 1
  45. fi
  46. _debug "Zone ID is:" "${zoneid}"
  47. _debug "Adding Record"
  48. _add_record "${token}" "${fulldomain}" "${txtvalue}"
  49. ret="$?"
  50. if [ "${ret}" != "0" ]; then
  51. _err "dns_api(dns_huaweicloud): Error adding record."
  52. return 1
  53. fi
  54. # Do saving work if all succeeded
  55. _saveaccountconf_mutable HUAWEICLOUD_Username "${HUAWEICLOUD_Username}"
  56. _saveaccountconf_mutable HUAWEICLOUD_Password "${HUAWEICLOUD_Password}"
  57. _saveaccountconf_mutable HUAWEICLOUD_DomainName "${HUAWEICLOUD_DomainName}"
  58. return 0
  59. }
  60. # Usage: fulldomain txtvalue
  61. # Used to remove the txt record after validation
  62. #
  63. # Ref: https://support.huaweicloud.com/intl/zh-cn/api-dns/dns_api_64005.html
  64. #
  65. dns_huaweicloud_rm() {
  66. fulldomain=$1
  67. txtvalue=$2
  68. HUAWEICLOUD_Username="${HUAWEICLOUD_Username:-$(_readaccountconf_mutable HUAWEICLOUD_Username)}"
  69. HUAWEICLOUD_Password="${HUAWEICLOUD_Password:-$(_readaccountconf_mutable HUAWEICLOUD_Password)}"
  70. HUAWEICLOUD_DomainName="${HUAWEICLOUD_DomainName:-$(_readaccountconf_mutable HUAWEICLOUD_DomainName)}"
  71. # Check information
  72. if [ -z "${HUAWEICLOUD_Username}" ] || [ -z "${HUAWEICLOUD_Password}" ] || [ -z "${HUAWEICLOUD_DomainName}" ]; then
  73. _err "Not enough information provided to dns_huaweicloud!"
  74. return 1
  75. fi
  76. unset token # Clear token
  77. token="$(_get_token "${HUAWEICLOUD_Username}" "${HUAWEICLOUD_Password}" "${HUAWEICLOUD_DomainName}")"
  78. if [ -z "${token}" ]; then # Check token
  79. _err "dns_api(dns_huaweicloud): Error getting token."
  80. return 1
  81. fi
  82. _secure_debug "Access token is:" "${token}"
  83. unset zoneid
  84. zoneid="$(_get_zoneid "${token}" "${fulldomain}")"
  85. if [ -z "${zoneid}" ]; then
  86. _err "dns_api(dns_huaweicloud): Error getting zone id."
  87. return 1
  88. fi
  89. _debug "Zone ID is:" "${zoneid}"
  90. record_id="$(_get_recordset_id "${token}" "${fulldomain}" "${zoneid}")"
  91. _recursive_rm_record "${token}" "${fulldomain}" "${zoneid}" "${record_id}"
  92. ret="$?"
  93. if [ "${ret}" != "0" ]; then
  94. _err "dns_api(dns_huaweicloud): Error removing record."
  95. return 1
  96. fi
  97. return 0
  98. }
  99. ################### Private functions below ##################################
  100. # _recursive_rm_record
  101. # remove all records from the record set
  102. #
  103. # _token=$1
  104. # _domain=$2
  105. # _zoneid=$3
  106. # _record_id=$4
  107. #
  108. # Returns 0 on success
  109. _recursive_rm_record() {
  110. _token=$1
  111. _domain=$2
  112. _zoneid=$3
  113. _record_id=$4
  114. # Most likely to have problems will huaweicloud side if more than 50 attempts but still cannot fully remove the record set
  115. # Maybe can be removed manually in the dashboard
  116. _retry_cnt=50
  117. # Remove all records
  118. # Therotically HuaweiCloud does not allow more than one record set
  119. # But remove them recurringly to increase robusty
  120. while [ "${_record_id}" != "0" ] && [ "${_retry_cnt}" != "0" ]; do
  121. _debug "Removing Record"
  122. _retry_cnt=$((_retry_cnt - 1))
  123. _rm_record "${_token}" "${_zoneid}" "${_record_id}"
  124. _record_id="$(_get_recordset_id "${_token}" "${_domain}" "${_zoneid}")"
  125. _debug2 "Checking record exists: record_id=${_record_id}"
  126. done
  127. # Check if retry count is reached
  128. if [ "${_retry_cnt}" = "0" ]; then
  129. _debug "Failed to remove record after 50 attempts, please try removing it manually in the dashboard"
  130. return 1
  131. fi
  132. return 0
  133. }
  134. # _get_zoneid
  135. #
  136. # _token=$1
  137. # _domain_string=$2
  138. #
  139. # printf "%s" "${_zoneid}"
  140. _get_zoneid() {
  141. _token=$1
  142. _domain_string=$2
  143. export _H1="X-Auth-Token: ${_token}"
  144. i=1
  145. while true; do
  146. h=$(printf "%s" "${_domain_string}" | cut -d . -f "$i"-100)
  147. if [ -z "$h" ]; then
  148. #not valid
  149. return 1
  150. fi
  151. _debug "$h"
  152. response=$(_get "${dns_api}/v2/zones?name=${h}")
  153. _debug2 "$response"
  154. if _contains "${response}" '"id"'; then
  155. zoneidlist=$(echo "${response}" | _egrep_o "\"id\": *\"[^\"]*\"" | cut -d : -f 2 | tr -d \" | tr -d " ")
  156. zonenamelist=$(echo "${response}" | _egrep_o "\"name\": *\"[^\"]*\"" | cut -d : -f 2 | tr -d \" | tr -d " ")
  157. _debug2 "Returned Zone ID(s):" "${zoneidlist}"
  158. _debug2 "Returned Zone Name(s):" "${zonenamelist}"
  159. zoneidnum=0
  160. zoneidcount=$(echo "${zoneidlist}" | grep -c '^')
  161. _debug "Returned Zone ID(s) Count:" "${zoneidcount}"
  162. while [ "${zoneidnum}" -lt "${zoneidcount}" ]; do
  163. zoneidnum=$(_math "$zoneidnum" + 1)
  164. _zoneid=$(echo "${zoneidlist}" | sed -n "${zoneidnum}p")
  165. zonename=$(echo "${zonenamelist}" | sed -n "${zoneidnum}p")
  166. _debug "Check Zone Name" "${zonename}"
  167. if [ "${zonename}" = "${h}." ]; then
  168. _debug "Get Zone ID Success."
  169. _debug "ZoneID:" "${_zoneid}"
  170. printf "%s" "${_zoneid}"
  171. return 0
  172. fi
  173. done
  174. fi
  175. i=$(_math "$i" + 1)
  176. done
  177. return 1
  178. }
  179. _get_recordset_id() {
  180. _token=$1
  181. _domain=$2
  182. _zoneid=$3
  183. export _H1="X-Auth-Token: ${_token}"
  184. response=$(_get "${dns_api}/v2/zones/${_zoneid}/recordsets?name=${_domain}")
  185. if _contains "${response}" '"id"'; then
  186. _id="$(echo "${response}" | _egrep_o "\"id\": *\"[^\"]*\"" | cut -d : -f 2 | tr -d \" | tr -d " ")"
  187. printf "%s" "${_id}"
  188. return 0
  189. fi
  190. printf "%s" "0"
  191. return 1
  192. }
  193. _add_record() {
  194. _token=$1
  195. _domain=$2
  196. _txtvalue=$3
  197. # Get Existing Records
  198. export _H1="X-Auth-Token: ${_token}"
  199. response=$(_get "${dns_api}/v2/zones/${zoneid}/recordsets?name=${_domain}")
  200. _debug2 "${response}"
  201. _exist_record=$(echo "${response}" | _egrep_o '"records":[^]]*' | sed 's/\"records\"\:\[//g')
  202. _debug "${_exist_record}"
  203. # Check if record exist
  204. # Generate body data
  205. if [ -z "${_exist_record}" ]; then
  206. _post_body="{
  207. \"name\": \"${_domain}.\",
  208. \"description\": \"ACME Challenge\",
  209. \"type\": \"TXT\",
  210. \"ttl\": 1,
  211. \"records\": [
  212. \"\\\"${_txtvalue}\\\"\"
  213. ]
  214. }"
  215. else
  216. _post_body="{
  217. \"name\": \"${_domain}.\",
  218. \"description\": \"ACME Challenge\",
  219. \"type\": \"TXT\",
  220. \"ttl\": 1,
  221. \"records\": [
  222. ${_exist_record},\"\\\"${_txtvalue}\\\"\"
  223. ]
  224. }"
  225. fi
  226. _record_id="$(_get_recordset_id "${_token}" "${_domain}" "${zoneid}")"
  227. _debug "Record Set ID is:" "${_record_id}"
  228. # Add brand new records with all old and new records
  229. export _H2="Content-Type: application/json"
  230. export _H1="X-Auth-Token: ${_token}"
  231. _debug2 "${_post_body}"
  232. if [ -z "${_exist_record}" ]; then
  233. _post "${_post_body}" "${dns_api}/v2/zones/${zoneid}/recordsets" >/dev/null
  234. else
  235. _post "${_post_body}" "${dns_api}/v2/zones/${zoneid}/recordsets/${_record_id}" false "PUT" >/dev/null
  236. fi
  237. _code="$(grep "^HTTP" "$HTTP_HEADER" | _tail_n 1 | cut -d " " -f 2 | tr -d "\\r\\n")"
  238. if [ "$_code" != "202" ]; then
  239. _err "dns_huaweicloud: http code ${_code}"
  240. return 1
  241. fi
  242. return 0
  243. }
  244. # _rm_record $token $zoneid $recordid
  245. # assume ${dns_api} exist
  246. # no output
  247. # return 0
  248. _rm_record() {
  249. _token=$1
  250. _zone_id=$2
  251. _record_id=$3
  252. export _H2="Content-Type: application/json"
  253. export _H1="X-Auth-Token: ${_token}"
  254. _post "" "${dns_api}/v2/zones/${_zone_id}/recordsets/${_record_id}" false "DELETE" >/dev/null
  255. return $?
  256. }
  257. _get_token() {
  258. _username=$1
  259. _password=$2
  260. _domain_name=$3
  261. _debug "Getting Token"
  262. body="{
  263. \"auth\": {
  264. \"identity\": {
  265. \"methods\": [
  266. \"password\"
  267. ],
  268. \"password\": {
  269. \"user\": {
  270. \"name\": \"${_username}\",
  271. \"password\": \"${_password}\",
  272. \"domain\": {
  273. \"name\": \"${_domain_name}\"
  274. }
  275. }
  276. }
  277. },
  278. \"scope\": {
  279. \"project\": {
  280. \"name\": \"ap-southeast-1\"
  281. }
  282. }
  283. }
  284. }"
  285. export _H1="Content-Type: application/json;charset=utf8"
  286. _post "${body}" "${iam_api}/v3/auth/tokens" >/dev/null
  287. _code=$(grep "^HTTP" "$HTTP_HEADER" | _tail_n 1 | cut -d " " -f 2 | tr -d "\\r\\n")
  288. _token=$(grep "^X-Subject-Token" "$HTTP_HEADER" | cut -d " " -f 2-)
  289. _secure_debug "${_code}"
  290. printf "%s" "${_token}"
  291. return 0
  292. }