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