]> git.proxmox.com Git - mirror_acme.sh.git/blob - dnsapi/dns_mythic_beasts.sh
Merge pull request #4658 from Justman10000/master
[mirror_acme.sh.git] / dnsapi / dns_mythic_beasts.sh
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
13 MB_API='https://api.mythic-beasts.com/dns/v2/zones'
14 MB_AUTH='https://auth.mythic-beasts.com/login'
15
16 ######## Public functions #####################
17
18 #Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
19 dns_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"
58 dns_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
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
166 # authentication. This is a work around using an undocumented MB API to obtain a token not tied to an
167 # IP just for the github tests.
168 _oauth2() {
169 if [ "$GITHUB_ACTIONS" = "true" ]; then
170 _oauth2_github
171 else
172 _oauth2_std
173 fi
174 return $?
175 }
176
177 _oauth2_std() {
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
203 _oauth2_github() {
204 _H1="Accepts: application/json"
205 export _H1
206 body="{\"login\":{\"handle\":\"$MB_AK\",\"pass\":\"$MB_AS\",\"floating\":1}}"
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
213 _err "Unable to get token"
214 _err "\n$response"
215 return 1
216 fi
217 _debug2 response "$response"
218 return 0
219 }
220
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 }