Skip to content

Instantly share code, notes, and snippets.

@itay-grudev
Created April 1, 2017 11:34
Show Gist options
  • Save itay-grudev/b6836e3ddf8fb54caebf5f46ed6545c7 to your computer and use it in GitHub Desktop.
Save itay-grudev/b6836e3ddf8fb54caebf5f46ed6545c7 to your computer and use it in GitHub Desktop.
Acmetool ACME (Let's Encrypt) DNS hook using the Digital Ocean API
#!/bin/bash
# This is an example DNS hook script which uses the Digital Ocean API to
# update nameservers. The script waits until updates have propagated to all
# nameservers listed for a zone. The script fails if this takes more than 60
# seconds by default; this timeout can be adjusted.
#
# Requirements: dig (bind-tools), jq
#
# The script is ready to use, but to use it you must create
# /etc/default/acme-dns-do or /etc/conf.d/acme-dns-do and set the following options:
#
# # Digital Ocean API token
# DIGITAL_OCEAN_API_TOKEN=0123456789abcdef
#
# # DNS synchronization timeout in seconds. Default is 60.
# DNS_SYNC_TIMEOUT=60
#
# Having done this, rename it to /usr/lib[exec]/acme/hooks/dns-do.
#
# How to test this script:
# ./dns-do.hook challenge-dns-start example.com "" "foobar"
# ./dns-do.hook challenge-dns-stop example.com "" "foobar"
#
set -e
get_apex() {
local name="$1"
if [ -z "$name" ]; then
echo "$0: couldn't get apex for $name" >&2
return 1
fi
local ans="`dig +noall +answer SOA "${name}."`"
if [ "`echo "$ans" | grep SOA | wc -l`" == "1" -a "`echo "$ans" | grep CNAME | wc -l`" == "0" ]; then
APEX="$name"
return
fi
local sname="$(echo $name | sed 's/^[^.]\+\.//')"
get_apex "$sname"
}
waitns() {
local ns="$1"
for ctr in $(seq 1 "$DNS_SYNC_TIMEOUT"); do
[ "$(dig +short "@${ns}" TXT "_acme-challenge.${CH_HOSTNAME}." | grep -- "$CH_TXT_VALUE" | wc -l)" == "1" ] && return 0
sleep 1
done
# Best effort cleanup.
echo $0: timed out waiting ${DNS_SYNC_TIMEOUT}s for nameserver $ns >&2
updns del || echo $0: failed to clean up records after timing out >&2
return 1
}
updns() {
# Create a new record
curl -X POST \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ${DIGITAL_OCEAN_API_TOKEN}" \
-d "{\"type\":\"TXT\",\"name\":\"_acme-challenge.${CH_HOSTNAME}.\",\"data\":\"${CH_TXT_VALUE}\"}" \
"https://api.digitalocean.com/v2/domains/${APEX}/records"
}
cleardns() {
curl -X GET \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ${DIGITAL_OCEAN_API_TOKEN}" \
"https://api.digitalocean.com/v2/domains/${APEX}/records" | \
jq ".domain_records | map(select(.type == \"TXT\")) | map(select(.name == \"_acme-challenge\")) | map(select(.data == \"${CH_TXT_VALUE}\")) | map(.id)[]" | \
while read RECORD_ID; do
curl -X DELETE \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ${DIGITAL_OCEAN_API_TOKEN}" \
"https://api.digitalocean.com/v2/domains/${APEX}/records/${RECORD_ID}"
done
}
[ -e "/etc/default/acme-dns-do" ] && . /etc/default/acme-dns-do
[ -e "/etc/conf.d/acme-dns-do" ] && . /etc/conf.d/acme-dns-do
EVENT_NAME="$1"
CH_HOSTNAME="$2"
CH_TARGET_FILENAME="$3"
CH_TXT_VALUE="$4"
[ -z "$DNS_SYNC_TIMEOUT" ] && DNS_SYNC_TIMEOUT=60
case "$EVENT_NAME" in
challenge-dns-start)
get_apex "$CH_HOSTNAME"
updns add
exit
# Wait for all nameservers to update.
for ns in $(dig +short NS "${APEX}."); do
waitns "$ns"
done
;;
challenge-dns-stop)
get_apex "$CH_HOSTNAME"
cleardns
;;
*)
exit 42
;;
esac
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment