Add DNSMint DNS API
DNSMint mints hostnames on domains it operates and serves from its own authoritative nameservers, so records are published through its API rather than a zone you run. An ACME challenge goes to the DNS-01 endpoint, which derives the hostname from the challenge name: no root zone to detect, no record id to track, and the value published is the value removed. Wildcards work because the API keeps the two newest values for a name. Any other TXT name is an ordinary record under a hostname and goes to the records endpoint instead, which is why the add and rm functions branch on the _acme-challenge label. Issuing certificates needs only the dns01:write scope; the record endpoint needs hostnames:read and hostnames:write. Tested against Let's Encrypt staging for a wildcard plus its apex, and the non-challenge TXT path against the live API.
This commit is contained in:
+126
-19
@@ -2,17 +2,17 @@
|
||||
# shellcheck disable=SC2034
|
||||
dns_dnsmint_info='DNSMint.com
|
||||
DNSMint mints hostnames on domains it operates and serves from its own
|
||||
authoritative nameservers, so the challenge is published through its API
|
||||
rather than a zone you run.
|
||||
authoritative nameservers, so records are published through its API rather
|
||||
than a zone you run.
|
||||
Site: dnsmint.com
|
||||
Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_dnsmint
|
||||
Options:
|
||||
DNSMINT_API_KEY API key carrying the dns01:write scope
|
||||
DNSMINT_API_KEY API key. dns01:write is enough to issue certificates.
|
||||
Issues: github.com/dnsmint/acme.sh
|
||||
Author: DNSMint
|
||||
'
|
||||
|
||||
DNSMint_Api="https://dnsmint.com/api/httpreq"
|
||||
DNSMint_Api="https://dnsmint.com/api"
|
||||
|
||||
######## Public functions #####################
|
||||
|
||||
@@ -29,7 +29,18 @@ dns_dnsmint_add() {
|
||||
return 1
|
||||
fi
|
||||
|
||||
if _dnsmint_rest present "$fulldomain" "$txtvalue"; then
|
||||
# An ACME challenge goes to the DNS-01 endpoint, which derives the hostname
|
||||
# itself and needs only dns01:write. Any other name is an ordinary record
|
||||
# under a hostname, which is a different endpoint and a wider scope.
|
||||
if _startswith "$fulldomain" "_acme-challenge."; then
|
||||
if _dnsmint_challenge present "$fulldomain" "$txtvalue"; then
|
||||
_info "Added, OK"
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
fi
|
||||
|
||||
if _dnsmint_record_add "$fulldomain" "$txtvalue"; then
|
||||
_info "Added, OK"
|
||||
return 0
|
||||
fi
|
||||
@@ -49,7 +60,15 @@ dns_dnsmint_rm() {
|
||||
return 1
|
||||
fi
|
||||
|
||||
if _dnsmint_rest cleanup "$fulldomain" "$txtvalue"; then
|
||||
if _startswith "$fulldomain" "_acme-challenge."; then
|
||||
if _dnsmint_challenge cleanup "$fulldomain" "$txtvalue"; then
|
||||
_info "Removed, OK"
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
fi
|
||||
|
||||
if _dnsmint_record_rm "$fulldomain" "$txtvalue"; then
|
||||
_info "Removed, OK"
|
||||
return 0
|
||||
fi
|
||||
@@ -72,28 +91,31 @@ _dnsmint_key() {
|
||||
return 0
|
||||
}
|
||||
|
||||
# DNSMint derives the hostname from the challenge name, so there is no zone to
|
||||
# look up and no record id to track: the value published is the value removed.
|
||||
_dnsmint_rest() {
|
||||
action="$1"
|
||||
fqdn="$2"
|
||||
value="$3"
|
||||
|
||||
_dnsmint_headers() {
|
||||
export _H1="Authorization: Bearer $DNSMINT_API_KEY"
|
||||
export _H2="Accept: application/json"
|
||||
export _H3="Content-Type: application/json"
|
||||
}
|
||||
|
||||
data="{\"fqdn\":\"$fqdn\",\"value\":\"$value\"}"
|
||||
_secure_debug2 data "$data"
|
||||
# One request. Sets $response and $_code; returns non-zero on a transport error.
|
||||
_dnsmint_rest() {
|
||||
_m="$1"
|
||||
_ep="$2"
|
||||
_data="$3"
|
||||
|
||||
response="$(_post "$data" "$DNSMint_Api/$action" "" "POST")"
|
||||
_dnsmint_headers
|
||||
if [ "$_m" = "GET" ]; then
|
||||
response="$(_get "$DNSMint_Api$_ep")"
|
||||
else
|
||||
_secure_debug2 _data "$_data"
|
||||
response="$(_post "$_data" "$DNSMint_Api$_ep" "" "$_m")"
|
||||
fi
|
||||
_ret="$?"
|
||||
_code="$(grep "^HTTP" "$HTTP_HEADER" | _tail_n 1 | cut -d " " -f 2 | tr -d "\\r\\n")"
|
||||
_debug "http response code $_code"
|
||||
_debug2 response "$response"
|
||||
|
||||
if [ "$_ret" != "0" ]; then
|
||||
_err "error $action"
|
||||
_err "error $_ep"
|
||||
return 1
|
||||
fi
|
||||
case "$_code" in
|
||||
@@ -101,8 +123,93 @@ _dnsmint_rest() {
|
||||
*)
|
||||
# The API says why in the body - a key narrowed to another hostname, a
|
||||
# name that is not live - and that is more use than the status alone.
|
||||
_err "error $action: HTTP $_code $response"
|
||||
_err "error $_ep: HTTP $_code $response"
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# The DNS-01 endpoint. It derives the hostname from the challenge name, so
|
||||
# there is no zone to look up and no record id to track: the value published
|
||||
# is the value removed.
|
||||
_dnsmint_challenge() {
|
||||
_action="$1"
|
||||
_fqdn="$2"
|
||||
_value="$3"
|
||||
_dnsmint_rest POST "/httpreq/$_action" "{\"fqdn\":\"$_fqdn\",\"value\":\"$_value\"}"
|
||||
}
|
||||
|
||||
# Everything below here is for names that are not ACME challenges. A record
|
||||
# under a hostname is addressed by the hostname's id and a name relative to
|
||||
# it, so the hostname has to be found first.
|
||||
_dnsmint_host() {
|
||||
_name="$1"
|
||||
_host_id=""
|
||||
_host_sub=""
|
||||
|
||||
if ! _dnsmint_rest GET "/v1/hostnames?limit=500"; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
for _h in $(echo "$response" | _egrep_o '"hostname":"[^"]*"' | cut -d'"' -f4); do
|
||||
case "$_name" in
|
||||
*".$_h")
|
||||
# Longest suffix wins, so a.b.example.dev prefers b.example.dev over
|
||||
# example.dev when both are hostnames on the account.
|
||||
if [ "${#_h}" -gt "${#_host_sub}" ]; then
|
||||
_host_sub="$_h"
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [ -z "$_host_sub" ]; then
|
||||
_err "$_name is not under a hostname on this account"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# The id sits next to the hostname in the same object.
|
||||
_host_id="$(echo "$response" | _egrep_o "\"id\":\"[^\"]*\",\"hostname\":\"$_host_sub\"" | cut -d'"' -f4)"
|
||||
if [ -z "$_host_id" ]; then
|
||||
_err "could not read the id for $_host_sub"
|
||||
return 1
|
||||
fi
|
||||
|
||||
_record_name="${_name%".$_host_sub"}"
|
||||
_debug _host_sub "$_host_sub"
|
||||
_debug _record_name "$_record_name"
|
||||
return 0
|
||||
}
|
||||
|
||||
_dnsmint_record_add() {
|
||||
_name="$1"
|
||||
_value="$2"
|
||||
|
||||
if ! _dnsmint_host "$_name"; then
|
||||
return 1
|
||||
fi
|
||||
_dnsmint_rest POST "/v1/hostnames/$_host_id/records" \
|
||||
"{\"name\":\"$_record_name\",\"type\":\"TXT\",\"text\":\"$_value\"}"
|
||||
}
|
||||
|
||||
_dnsmint_record_rm() {
|
||||
_name="$1"
|
||||
_value="$2"
|
||||
|
||||
if ! _dnsmint_host "$_name"; then
|
||||
return 1
|
||||
fi
|
||||
if ! _dnsmint_rest GET "/v1/hostnames/$_host_id/records"; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Records come back as {"id":...,"name":"<fqdn>","type":"TXT","ttl":...,
|
||||
# "data":{...,"text":["<value>"]}}. Match on the value so a name holding
|
||||
# several TXT records loses only the one that was added.
|
||||
_rid="$(echo "$response" | sed 's/},{/}\n{/g' | grep -F "\"$_value\"" | _egrep_o '"id":"[^"]*"' | cut -d'"' -f4 | _head_n 1)"
|
||||
if [ -z "$_rid" ]; then
|
||||
_info "Record already gone, nothing to remove"
|
||||
return 0
|
||||
fi
|
||||
_dnsmint_rest DELETE "/v1/hostnames/$_host_id/records/$_rid" ""
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user