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.

338 lines
11 KiB

1 month ago
1 month ago
  1. #!/usr/bin/env sh
  2. # shellcheck disable=SC2034
  3. dns_oci_info='Oracle Cloud Infrastructure (OCI)
  4. If OCI CLI configuration file ~/.oci/config has a DEFAULT profile then it will be used.
  5. Site: Cloud.Oracle.com
  6. Docs: github.com/acmesh-official/acme.sh/wiki/How-to-use-Oracle-Cloud-Infrastructure-DNS
  7. Options:
  8. OCI_CLI_TENANCY OCID of tenancy that contains the target DNS zone. Optional.
  9. OCI_CLI_USER OCID of user with permission to add/remove records from zones. Optional.
  10. OCI_CLI_REGION Should point to the tenancy home region. Optional.
  11. OCI_CLI_KEY_FILE Path to private API signing key file in PEM format. Optional.
  12. OCI_CLI_KEY The private API signing key in PEM format. Optional.
  13. Issues: github.com/acmesh-official/acme.sh/issues/3540
  14. Author: Avi Miller <me@dje.li>
  15. '
  16. # Copyright (c) 2021, Oracle and/or its affiliates
  17. #
  18. # The plugin will automatically use the default profile from an OCI SDK and CLI
  19. # configuration file, if it exists.
  20. #
  21. # Alternatively, set the following environment variables:
  22. # - OCI_CLI_TENANCY : OCID of tenancy that contains the target DNS zone
  23. # - OCI_CLI_USER : OCID of user with permission to add/remove records from zones
  24. # - OCI_CLI_REGION : Should point to the tenancy home region
  25. #
  26. # One of the following two variables is required:
  27. # - OCI_CLI_KEY_FILE: Path to private API signing key file in PEM format; or
  28. # - OCI_CLI_KEY : The private API signing key in PEM format
  29. #
  30. # NOTE: using an encrypted private key that needs a passphrase is not supported.
  31. #
  32. dns_oci_add() {
  33. _fqdn="$1"
  34. _rdata="$2"
  35. if _get_oci_zone; then
  36. _add_record_body="{\"items\":[{\"domain\":\"${_sub_domain}.${_domain}\",\"rdata\":\"$_rdata\",\"rtype\":\"TXT\",\"ttl\": 30,\"operation\":\"ADD\"}]}"
  37. response=$(_signed_request "PATCH" "/20180115/zones/${_domain}/records" "$_add_record_body")
  38. if [ "$response" ]; then
  39. _info "Success: added TXT record for ${_sub_domain}.${_domain}."
  40. else
  41. _err "Error: failed to add TXT record for ${_sub_domain}.${_domain}."
  42. _err "Check that the user has permission to add records to this zone."
  43. return 1
  44. fi
  45. else
  46. return 1
  47. fi
  48. }
  49. dns_oci_rm() {
  50. _fqdn="$1"
  51. _rdata="$2"
  52. if _get_oci_zone; then
  53. _remove_record_body="{\"items\":[{\"domain\":\"${_sub_domain}.${_domain}\",\"rdata\":\"$_rdata\",\"rtype\":\"TXT\",\"operation\":\"REMOVE\"}]}"
  54. response=$(_signed_request "PATCH" "/20180115/zones/${_domain}/records" "$_remove_record_body")
  55. if [ "$response" ]; then
  56. _info "Success: removed TXT record for ${_sub_domain}.${_domain}."
  57. else
  58. _err "Error: failed to remove TXT record for ${_sub_domain}.${_domain}."
  59. _err "Check that the user has permission to remove records from this zone."
  60. return 1
  61. fi
  62. else
  63. return 1
  64. fi
  65. }
  66. #################### Private functions below ##################################
  67. _get_oci_zone() {
  68. if ! _oci_config; then
  69. return 1
  70. fi
  71. if ! _get_zone "$_fqdn"; then
  72. _err "Error: DNS Zone not found for $_fqdn in $OCI_CLI_TENANCY"
  73. return 1
  74. fi
  75. return 0
  76. }
  77. _oci_config() {
  78. _DEFAULT_OCI_CLI_CONFIG_FILE="$HOME/.oci/config"
  79. OCI_CLI_CONFIG_FILE="${OCI_CLI_CONFIG_FILE:-$(_readaccountconf_mutable OCI_CLI_CONFIG_FILE)}"
  80. if [ -z "$OCI_CLI_CONFIG_FILE" ]; then
  81. OCI_CLI_CONFIG_FILE="$_DEFAULT_OCI_CLI_CONFIG_FILE"
  82. fi
  83. if [ "$_DEFAULT_OCI_CLI_CONFIG_FILE" != "$OCI_CLI_CONFIG_FILE" ]; then
  84. _saveaccountconf_mutable OCI_CLI_CONFIG_FILE "$OCI_CLI_CONFIG_FILE"
  85. else
  86. _clearaccountconf_mutable OCI_CLI_CONFIG_FILE
  87. fi
  88. _DEFAULT_OCI_CLI_PROFILE="DEFAULT"
  89. OCI_CLI_PROFILE="${OCI_CLI_PROFILE:-$(_readaccountconf_mutable OCI_CLI_PROFILE)}"
  90. if [ "$_DEFAULT_OCI_CLI_PROFILE" != "$OCI_CLI_PROFILE" ]; then
  91. _saveaccountconf_mutable OCI_CLI_PROFILE "$OCI_CLI_PROFILE"
  92. else
  93. OCI_CLI_PROFILE="$_DEFAULT_OCI_CLI_PROFILE"
  94. _clearaccountconf_mutable OCI_CLI_PROFILE
  95. fi
  96. OCI_CLI_TENANCY="${OCI_CLI_TENANCY:-$(_readaccountconf_mutable OCI_CLI_TENANCY)}"
  97. if [ "$OCI_CLI_TENANCY" ]; then
  98. _saveaccountconf_mutable OCI_CLI_TENANCY "$OCI_CLI_TENANCY"
  99. elif [ -f "$OCI_CLI_CONFIG_FILE" ]; then
  100. _debug "Reading OCI_CLI_TENANCY value from: $OCI_CLI_CONFIG_FILE"
  101. OCI_CLI_TENANCY="${OCI_CLI_TENANCY:-$(_readini "$OCI_CLI_CONFIG_FILE" tenancy "$OCI_CLI_PROFILE")}"
  102. fi
  103. if [ -z "$OCI_CLI_TENANCY" ]; then
  104. _err "Error: unable to read OCI_CLI_TENANCY from config file or environment variable."
  105. return 1
  106. fi
  107. OCI_CLI_USER="${OCI_CLI_USER:-$(_readaccountconf_mutable OCI_CLI_USER)}"
  108. if [ "$OCI_CLI_USER" ]; then
  109. _saveaccountconf_mutable OCI_CLI_USER "$OCI_CLI_USER"
  110. elif [ -f "$OCI_CLI_CONFIG_FILE" ]; then
  111. _debug "Reading OCI_CLI_USER value from: $OCI_CLI_CONFIG_FILE"
  112. OCI_CLI_USER="${OCI_CLI_USER:-$(_readini "$OCI_CLI_CONFIG_FILE" user "$OCI_CLI_PROFILE")}"
  113. fi
  114. if [ -z "$OCI_CLI_USER" ]; then
  115. _err "Error: unable to read OCI_CLI_USER from config file or environment variable."
  116. return 1
  117. fi
  118. OCI_CLI_REGION="${OCI_CLI_REGION:-$(_readaccountconf_mutable OCI_CLI_REGION)}"
  119. if [ "$OCI_CLI_REGION" ]; then
  120. _saveaccountconf_mutable OCI_CLI_REGION "$OCI_CLI_REGION"
  121. elif [ -f "$OCI_CLI_CONFIG_FILE" ]; then
  122. _debug "Reading OCI_CLI_REGION value from: $OCI_CLI_CONFIG_FILE"
  123. OCI_CLI_REGION="${OCI_CLI_REGION:-$(_readini "$OCI_CLI_CONFIG_FILE" region "$OCI_CLI_PROFILE")}"
  124. fi
  125. if [ -z "$OCI_CLI_REGION" ]; then
  126. _err "Error: unable to read OCI_CLI_REGION from config file or environment variable."
  127. return 1
  128. fi
  129. OCI_CLI_KEY="${OCI_CLI_KEY:-$(_readaccountconf_mutable OCI_CLI_KEY)}"
  130. if [ -z "$OCI_CLI_KEY" ]; then
  131. _clearaccountconf_mutable OCI_CLI_KEY
  132. OCI_CLI_KEY_FILE="${OCI_CLI_KEY_FILE:-$(_readini "$OCI_CLI_CONFIG_FILE" key_file "$OCI_CLI_PROFILE")}"
  133. if [ "$OCI_CLI_KEY_FILE" ] && [ -f "$OCI_CLI_KEY_FILE" ]; then
  134. _debug "Reading OCI_CLI_KEY value from: $OCI_CLI_KEY_FILE"
  135. OCI_CLI_KEY=$(_base64 <"$OCI_CLI_KEY_FILE")
  136. _saveaccountconf_mutable OCI_CLI_KEY "$OCI_CLI_KEY"
  137. fi
  138. else
  139. _saveaccountconf_mutable OCI_CLI_KEY "$OCI_CLI_KEY"
  140. fi
  141. if [ -z "$OCI_CLI_KEY_FILE" ] && [ -z "$OCI_CLI_KEY" ]; then
  142. _err "Error: unable to find key file path in OCI config file or OCI_CLI_KEY_FILE."
  143. _err "Error: unable to load private API signing key from OCI_CLI_KEY."
  144. return 1
  145. fi
  146. if [ "$(printf "%s\n" "$OCI_CLI_KEY" | wc -l)" -eq 1 ]; then
  147. OCI_CLI_KEY=$(printf "%s" "$OCI_CLI_KEY" | _dbase64)
  148. fi
  149. return 0
  150. }
  151. # _get_zone(): retrieves the Zone name and OCID
  152. #
  153. # _sub_domain=_acme-challenge.www
  154. # _domain=domain.com
  155. # _domain_ociid=ocid1.dns-zone.oc1..
  156. _get_zone() {
  157. domain=$1
  158. i=1
  159. p=1
  160. while true; do
  161. h=$(printf "%s" "$domain" | cut -d . -f "$i"-100)
  162. _debug h "$h"
  163. if [ -z "$h" ]; then
  164. # not valid
  165. return 1
  166. fi
  167. _domain_id=$(_signed_request "GET" "/20180115/zones/$h" "" "id")
  168. if [ "$_domain_id" ]; then
  169. _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p")
  170. _domain=$h
  171. _debug _domain_id "$_domain_id"
  172. _debug _sub_domain "$_sub_domain"
  173. _debug _domain "$_domain"
  174. return 0
  175. fi
  176. p=$i
  177. i=$(_math "$i" + 1)
  178. done
  179. return 1
  180. }
  181. #Usage: privatekey
  182. #Output MD5 fingerprint
  183. _fingerprint() {
  184. pkey="$1"
  185. if [ -z "$pkey" ]; then
  186. _usage "Usage: _fingerprint privkey"
  187. return 1
  188. fi
  189. printf "%s" "$pkey" | ${ACME_OPENSSL_BIN:-openssl} rsa -pubout -outform DER 2>/dev/null | ${ACME_OPENSSL_BIN:-openssl} md5 -c | cut -d = -f 2 | tr -d ' '
  190. }
  191. _signed_request() {
  192. _sig_method="$1"
  193. _sig_target="$2"
  194. _sig_body="$3"
  195. _return_field="$4"
  196. _key_fingerprint=$(_fingerprint "$OCI_CLI_KEY")
  197. _sig_host="dns.$OCI_CLI_REGION.oraclecloud.com"
  198. _sig_keyId="$OCI_CLI_TENANCY/$OCI_CLI_USER/$_key_fingerprint"
  199. _sig_alg="rsa-sha256"
  200. _sig_version="1"
  201. _sig_now="$(LC_ALL=C \date -u "+%a, %d %h %Y %H:%M:%S GMT")"
  202. _request_method=$(printf %s "$_sig_method" | _lower_case)
  203. _curl_method=$(printf %s "$_sig_method" | _upper_case)
  204. _request_target="(request-target): $_request_method $_sig_target"
  205. _date_header="date: $_sig_now"
  206. _host_header="host: $_sig_host"
  207. _string_to_sign="$_request_target\n$_date_header\n$_host_header"
  208. _sig_headers="(request-target) date host"
  209. if [ "$_sig_body" ]; then
  210. _secure_debug3 _sig_body "$_sig_body"
  211. _sig_body_sha256="x-content-sha256: $(printf %s "$_sig_body" | _digest sha256)"
  212. _sig_body_type="content-type: application/json"
  213. _sig_body_length="content-length: ${#_sig_body}"
  214. _string_to_sign="$_string_to_sign\n$_sig_body_sha256\n$_sig_body_type\n$_sig_body_length"
  215. _sig_headers="$_sig_headers x-content-sha256 content-type content-length"
  216. fi
  217. _tmp_file=$(_mktemp)
  218. if [ -f "$_tmp_file" ]; then
  219. printf '%s' "$OCI_CLI_KEY" >"$_tmp_file"
  220. _signature=$(printf '%b' "$_string_to_sign" | _sign "$_tmp_file" sha256 | tr -d '\r\n')
  221. rm -f "$_tmp_file"
  222. fi
  223. _signed_header="Authorization: Signature version=\"$_sig_version\",keyId=\"$_sig_keyId\",algorithm=\"$_sig_alg\",headers=\"$_sig_headers\",signature=\"$_signature\""
  224. _secure_debug3 _signed_header "$_signed_header"
  225. if [ "$_curl_method" = "GET" ]; then
  226. export _H1="$_date_header"
  227. export _H2="$_signed_header"
  228. _response="$(_get "https://${_sig_host}${_sig_target}")"
  229. elif [ "$_curl_method" = "PATCH" ]; then
  230. export _H1="$_date_header"
  231. # shellcheck disable=SC2090
  232. export _H2="$_sig_body_sha256"
  233. export _H3="$_sig_body_type"
  234. export _H4="$_sig_body_length"
  235. export _H5="$_signed_header"
  236. _response="$(_post "$_sig_body" "https://${_sig_host}${_sig_target}" "" "PATCH")"
  237. else
  238. _err "Unable to process method: $_curl_method."
  239. fi
  240. _ret="$?"
  241. if [ "$_return_field" ]; then
  242. _response="$(echo "$_response" | sed 's/\\\"//g'))"
  243. _return=$(echo "${_response}" | _egrep_o "\"$_return_field\"\\s*:\\s*\"[^\"]*\"" | _head_n 1 | cut -d : -f 2 | tr -d "\"")
  244. else
  245. _return="$_response"
  246. fi
  247. printf "%s" "$_return"
  248. return $_ret
  249. }
  250. # file key [section]
  251. _readini() {
  252. _file="$1"
  253. _key="$2"
  254. _section="${3:-DEFAULT}"
  255. _start_n=$(grep -n '\['"$_section"']' "$_file" | cut -d : -f 1)
  256. _debug3 _start_n "$_start_n"
  257. if [ -z "$_start_n" ]; then
  258. _err "Can not find section: $_section"
  259. return 1
  260. fi
  261. _start_nn=$(_math "$_start_n" + 1)
  262. _debug3 "_start_nn" "$_start_nn"
  263. _left="$(sed -n "${_start_nn},99999p" "$_file")"
  264. _debug3 _left "$_left"
  265. _end="$(echo "$_left" | grep -n "^\[" | _head_n 1)"
  266. _debug3 "_end" "$_end"
  267. if [ "$_end" ]; then
  268. _end_n=$(echo "$_end" | cut -d : -f 1)
  269. _debug3 "_end_n" "$_end_n"
  270. _seg_n=$(echo "$_left" | sed -n "1,${_end_n}p")
  271. else
  272. _seg_n="$_left"
  273. fi
  274. _debug3 "_seg_n" "$_seg_n"
  275. _lineini="$(echo "$_seg_n" | grep "^ *$_key *= *")"
  276. _inivalue="$(printf "%b" "$(eval "echo $_lineini | sed \"s/^ *${_key} *= *//g\"")")"
  277. _debug2 _inivalue "$_inivalue"
  278. echo "$_inivalue"
  279. }