Skip to content

Instantly share code, notes, and snippets.

@sudotac
Forked from Gowee/uacme-cloudflare-hook.sh
Last active April 23, 2025 11:26
Show Gist options
  • Save sudotac/ee1d6cf0dac66217165522ae82d5d28a to your computer and use it in GitHub Desktop.
Save sudotac/ee1d6cf0dac66217165522ae82d5d28a to your computer and use it in GitHub Desktop.
DNS-01 challenge hook script of uacme for Cloudflare
#!/bin/sh
# shellcheck disable=SC3043
# Copyright (C) 2020 Michel Stam <[email protected]>
# Copyright (C) 2021 Hung-I Wang <[email protected]>
#
# The script is adatped from:
# https://github.com/ndilieto/uacme/blob/5edec0eea1bcf6f454ec1787297c2408c2f2e97a/nsupdate.sh
# https://gist.github.com/Gowee/e756f925cfcbd5ab32d564ee3c795786
#
# Licensed under the the GNU General Public License <http://www.gnu.org/licenses/>.
# The script is meant to be used as a hook script of uacme to update TXT records for acme challenges.
# Instead of relying on IETF RFC2136, it talks to Cloudflare API directly:
# https://developers.cloudflare.com/api/resources/dns/subresources/records/
# API Config
API_ENDPOINT='https://api.cloudflare.com/client/v4'
API_TOKEN="TOKEN_FOR_THE_DOMAIN_SPECIFIED_IN_THE_WORKER_SCRIPT"
# Arguments
METHOD=$1
TYPE=$2
IDENT=$3
# shellcheck disable=SC2034
TOKEN=$4
AUTH=$5
_curl_cf()
{
local path="$1"
local method="$2"
local query="$3"
shift 3
curl -fsS \
-X "$method" \
-H 'Accept: application/json' \
-H "Authorization: Bearer $API_TOKEN" \
"$@" \
"$API_ENDPOINT$path${query:+?$query}"
}
ns_getzoneid()
{
# NOTE: This query must uniquely identify the desired zone
_curl_cf /zones GET "name=$IDENT&match=all&status=active" | jq -er '.result[0].id'
}
ns_docreate()
{
local fqhn="$1"
local challenge="$2"
local zoneid="$3"
_curl_cf /zones/"$zoneid"/dns_records POST "zone_id=$zoneid" -d "$(printf '{"name":"%s","content":"%s","type":"TXT"}' "$fqhn" '\"'"$challenge"'\"')" -o /dev/null
}
ns_dodelete()
{
local fqhn="$1"
local challenge="$2"
local zoneid="$3"
local recordid
if recordid="$(_curl_cf /zones/"$zoneid"/dns_records GET "zone_id=$zoneid&match=all&name.exact=$fqhn&type=TXT" | jq -er '.result[0].id')"; then
_curl_cf /zones/"$zoneid"/dns_records/"$recordid" DELETE "zone_id=$zoneid&dns_record_id=$recordid" -o /dev/null
else
return $?
fi
}
ns_ispresent()
{
local fqhn="$1"
local expect="$2"
local resp
resp=$(curl -fsSH "accept: application/dns-json" "https://cloudflare-dns.com/dns-query?name=${fqhn}&type=TXT")
if echo "$resp" | grep -q "$expect"; then
return 0
else
return 1
fi
}
ns_doupdate()
{
local fqhn="$1"
local challenge="$2"
local zoneid
if ! zoneid="$(ns_getzoneid)"; then
return $?
fi
if [ -n "${challenge}" ]; then
ns_docreate "$fqhn" "$challenge" "$zoneid"
# TODO: --fail-with-body
else
ns_dodelete "$fqhn" "$challenge" "$zoneid"
# TODO: --fail-with-body
fi
return $?
}
ns_update()
{
local fqhn="$1"
local challenge="$2"
local count=0
local res
res=1
while [ $res -ne 0 ]; do
if [ $count -eq 0 ]; then
ns_doupdate "$fqhn" "$challenge"
res=$?
[ $res -eq 0 ] || break
else
sleep "$(echo 2 ^ "$count" | bc)" # backoff
fi
count=$(((count + 1) % 5))
ns_ispresent "$fqhn" "$challenge"
res=$?
done
return $res
}
ARGS=5
E_BADARGS=85
if [ $# -ne "$ARGS" ]; then
echo "Usage: $(basename "$0") method type ident token auth" 1>&2
exit $E_BADARGS
fi
case "$METHOD" in
"begin")
case "$TYPE" in
dns-01)
ns_update "_acme-challenge.$IDENT" "$AUTH"
exit $?
;;
*)
exit 1
;;
esac
;;
"done"|"failed")
case "$TYPE" in
dns-01)
ns_update "_acme-challenge.$IDENT"
exit $?
;;
*)
exit 1
;;
esac
;;
*)
echo "$0: invalid method" 1>&2
exit 1
esac
# vi: sw=4 ts=4 et
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment