]> git.proxmox.com Git - mirror_acme.sh.git/blame - dnsapi/dns_mythic_beasts.sh
Merge pull request #4531 from NCDGHA/bugfix/issue_4530_fix_http_status_503
[mirror_acme.sh.git] / dnsapi / dns_mythic_beasts.sh
CommitLineData
4dd709b5 1#!/usr/bin/env sh
2# Mythic Beasts is a long-standing UK service provider using standards-based OAuth2 authentication
3# To test: ./acme.sh --dns dns_mythic_beasts --test --debug 1 --output-insecure --issue --domain domain.com
4# Cannot retest once cert is issued
5# OAuth2 tokens only valid for 300 seconds so we do not store
6# NOTE: This will remove all TXT records matching the fulldomain, not just the added ones (_acme-challenge.www.domain.com)
7
8# Test OAuth2 credentials
9#MB_AK="aaaaaaaaaaaaaaaa"
10#MB_AS="bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
11
12# URLs
13MB_API='https://api.mythic-beasts.com/dns/v2/zones'
14MB_AUTH='https://auth.mythic-beasts.com/login'
15
16######## Public functions #####################
17
18#Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
19dns_mythic_beasts_add() {
20 fulldomain=$1
21 txtvalue=$2
22
23 _info "MYTHIC BEASTS Adding record $fulldomain = $txtvalue"
24 if ! _initAuth; then
25 return 1
26 fi
27
28 if ! _get_root "$fulldomain"; then
29 return 1
30 fi
31
32 # method path body_data
33 if _mb_rest POST "$_domain/records/$_sub_domain/TXT" "$txtvalue"; then
34
35 if _contains "$response" "1 records added"; then
36 _info "Added, verifying..."
37 # Max 120 seconds to publish
38 for i in $(seq 1 6); do
39 # Retry on error
40 if ! _mb_rest GET "$_domain/records/$_sub_domain/TXT?verify"; then
41 _sleep 20
42 else
43 _info "Record published!"
44 return 0
45 fi
46 done
47
48 else
49 _err "\n$response"
50 fi
51
52 fi
53 _err "Add txt record error."
54 return 1
55}
56
57#Usage: rm _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
58dns_mythic_beasts_rm() {
59 fulldomain=$1
60 txtvalue=$2
61
62 _info "MYTHIC BEASTS Removing record $fulldomain = $txtvalue"
63 if ! _initAuth; then
64 return 1
65 fi
66
67 if ! _get_root "$fulldomain"; then
68 return 1
69 fi
70
71 # method path body_data
72 if _mb_rest DELETE "$_domain/records/$_sub_domain/TXT" "$txtvalue"; then
73 _info "Record removed"
74 return 0
75 fi
76 _err "Remove txt record error."
77 return 1
78}
79
80#################### Private functions below ##################################
81
82#Possible formats:
83# _acme-challenge.www.example.com
84# _acme-challenge.example.com
85# _acme-challenge.example.co.uk
86# _acme-challenge.www.example.co.uk
87# _acme-challenge.sub1.sub2.www.example.co.uk
88# sub1.sub2.example.co.uk
89# example.com
90# example.co.uk
91#returns
92# _sub_domain=_acme-challenge.www
93# _domain=domain.com
94_get_root() {
95 domain=$1
96 i=1
97 p=1
98
99 _debug "Detect the root zone"
100 while true; do
101 h=$(printf "%s" "$domain" | cut -d . -f $i-100)
102 if [ -z "$h" ]; then
103 _err "Domain exhausted"
104 return 1
105 fi
106
107 # Use the status errors to find the domain, continue on 403 Access denied
108 # method path body_data
109 _mb_rest GET "$h/records"
110 ret="$?"
111 if [ "$ret" -eq 0 ]; then
112 _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
113 _domain="$h"
114 _debug _sub_domain "$_sub_domain"
115 _debug _domain "$_domain"
116 return 0
117 elif [ "$ret" -eq 1 ]; then
118 return 1
119 fi
120
121 p=$i
122 i=$(_math "$i" + 1)
123
124 if [ "$i" -gt 50 ]; then
125 break
126 fi
127 done
128 _err "Domain too long"
129 return 1
130}
131
132_initAuth() {
133 MB_AK="${MB_AK:-$(_readaccountconf_mutable MB_AK)}"
134 MB_AS="${MB_AS:-$(_readaccountconf_mutable MB_AS)}"
135
136 if [ -z "$MB_AK" ] || [ -z "$MB_AS" ]; then
137 MB_AK=""
138 MB_AS=""
139 _err "Please specify an OAuth2 Key & Secret"
140 return 1
141 fi
142
143 _saveaccountconf_mutable MB_AK "$MB_AK"
144 _saveaccountconf_mutable MB_AS "$MB_AS"
145
146 if ! _oauth2; then
147 return 1
148 fi
149
150 _info "Checking authentication"
151 _secure_debug access_token "$MB_TK"
152 _sleep 1
153
154 # GET a list of zones
155 # method path body_data
156 if ! _mb_rest GET ""; then
157 _err "The token is invalid"
158 return 1
159 fi
160 _info "Token OK"
161 return 0
162}
163
6a2c9a0d 164# Github appears to use an outbound proxy for requests which means subsequent requests may not have the same
165# source IP. The standard Mythic Beasts OAuth2 tokens are tied to an IP, meaning github test requests fail
2b6aa267 166# authentication. This is a work around using an undocumented MB API to obtain a token not tied to an
6a2c9a0d 167# IP just for the github tests.
4dd709b5 168_oauth2() {
2b6aa267 169 if [ "$GITHUB_ACTIONS" = "true" ]; then
6251652c 170 _oauth2_github
2b6aa267 171 else
172 _oauth2_std
6a2c9a0d 173 fi
6251652c 174 return $?
6a2c9a0d 175}
176
177_oauth2_std() {
4dd709b5 178 # HTTP Basic Authentication
179 _H1="Authorization: Basic $(echo "$MB_AK:$MB_AS" | _base64)"
180 _H2="Accepts: application/json"
181 export _H1 _H2
182 body="grant_type=client_credentials"
183
184 _info "Getting OAuth2 token..."
185 # body url [needbase64] [POST|PUT|DELETE] [ContentType]
186 response="$(_post "$body" "$MB_AUTH" "" "POST" "application/x-www-form-urlencoded")"
187 if _contains "$response" "\"token_type\":\"bearer\""; then
188 MB_TK="$(echo "$response" | _egrep_o "access_token\":\"[^\"]*\"" | cut -d : -f 2 | tr -d '"')"
189 if [ -z "$MB_TK" ]; then
190 _err "Unable to get access_token"
191 _err "\n$response"
192 return 1
193 fi
194 else
195 _err "OAuth2 token_type not Bearer"
196 _err "\n$response"
197 return 1
198 fi
199 _debug2 response "$response"
200 return 0
201}
202
6a2c9a0d 203_oauth2_github() {
204 _H1="Accepts: application/json"
205 export _H1
95f13360 206 body="{\"login\":{\"handle\":\"$MB_AK\",\"pass\":\"$MB_AS\",\"floating\":1}}"
6a2c9a0d 207
208 _info "Getting Floating token..."
209 # body url [needbase64] [POST|PUT|DELETE] [ContentType]
210 response="$(_post "$body" "$MB_AUTH" "" "POST" "application/json")"
211 MB_TK="$(echo "$response" | _egrep_o "\"token\":\"[^\"]*\"" | cut -d : -f 2 | tr -d '"')"
212 if [ -z "$MB_TK" ]; then
56d799f4 213 _err "Unable to get token"
6a2c9a0d 214 _err "\n$response"
215 return 1
216 fi
217 _debug2 response "$response"
218 return 0
219}
220
4dd709b5 221# method path body_data
222_mb_rest() {
223 # URL encoded body for single API operations
224 m="$1"
225 ep="$2"
226 data="$3"
227
228 if [ -z "$ep" ]; then
229 _mb_url="$MB_API"
230 else
231 _mb_url="$MB_API/$ep"
232 fi
233
234 _H1="Authorization: Bearer $MB_TK"
235 _H2="Accepts: application/json"
236 export _H1 _H2
237 if [ "$data" ] || [ "$m" = "POST" ] || [ "$m" = "PUT" ] || [ "$m" = "DELETE" ]; then
238 # body url [needbase64] [POST|PUT|DELETE] [ContentType]
239 response="$(_post "data=$data" "$_mb_url" "" "$m" "application/x-www-form-urlencoded")"
240 else
241 response="$(_get "$_mb_url")"
242 fi
243
244 if [ "$?" != "0" ]; then
245 _err "Request error"
246 return 1
247 fi
248
249 header="$(cat "$HTTP_HEADER")"
250 status="$(echo "$header" | _egrep_o "^HTTP[^ ]* .*$" | cut -d " " -f 2-100 | tr -d "\f\n")"
251 code="$(echo "$status" | _egrep_o "^[0-9]*")"
252 if [ "$code" -ge 400 ] || _contains "$response" "\"error\"" || _contains "$response" "invalid_client"; then
253 _err "error $status"
254 _err "\n$response"
255 _debug "\n$header"
256 return 2
257 fi
258
259 _debug2 response "$response"
260 return 0
261}