Setting up Certbot DNS Verification with Hurricane Electric DNS

TLS is an essential part of the standards that form the Internet, preventing eavesdropping, impersonation, and interference by all manner of parties with access to network traffic. In addition to these practical benefits, it is incresingly becoming a requirement of web and email servers in the modern era.

Therefore, as a system administrator, it is critical that I maintain up-to-date TLS certificates; for this, I use Let's Encrypt as its service is free, easy to automate, secure (as certificates are issued for 90 days, limiting damage in the event of a compromise), and flexible. I have always used certbot to manage these certificates, and overall it is a useful tool. To verify one's control of the domains that one is attempting to get a certificate for, the ACME protocol used by certbot and Let's Encrypt prescribes several methods—called "challenges"—for said verification. Initially, I used the HTTP challenge, where a certificate authority directs the owner of a domain to put a text file with specific contents at a special location in their HTTP server's directory tree. While it is possible, I never configured Apache HTTPd to do this automatically; instead, I shut down my main HTTP server and let certbot take control of port 80 to perform the verification.

Recently, though, I switched to using the DNS challenge, where the owner of a domain creates a TXT record under a particular subdomain (_acme-challenge.) to verify their control of said domain. This has several advantages: one can get wildcard certificates valid for all subdomains of a particular domain name and one can get certificates for domains that don't have web servers attached to them, among other things. However, my DNS provider (Hurricane Electric) isn't supported by certbot by default.

Hurricane Electric does let you dynamically configure TXT records, though. To do so, one enables dynamic DNS for the record in the edit dialog and then clicks on the circular arrow icon to the right of the record to set a key. Then, to update a dynamic TXT record, one must send an HTTP POST request to https://dyn.dns.he.net/nic/update with the data parameters hostname, password, and txt set to the domain name of the record that is to be updated, the key configured above, and the desired contents of the record, respectively.

For dynamically-updatable TXT records to be useful, their TTL (time to live; the time that a record will be cached for) must be set to a low value. Five minutes—300 seconds—is currently the lowest possible value, and works well.

Thus, to configure DNS verification, one must create a TXT record for the _acme-challenge. subdomain of each domain that one wishes to verify and configure it for dynamic configuration as described above. Next, one must set up scripts for certbot to use to update the DNS records. Mine are below; feel free to adapt them.

Code

certbot.zsh

This script is used to obtain a new certificate, to add domains to an existing certificate, or to renew a certificate. The -d and --certname options will (obviously) have to be changed if this script is to be reused, as will the path to certbot-dns.zsh.

Once properly configured, generating new certificates, adding additional domains to current certificates, and renewing certificates can simply be accomplished by running ./certbot.zsh.

Please note that this script will attempt to verify that each domain has been updated and that the cache has expired; this can take up to 6 minutes 40 seconds when the TTL is set to 5 minutes. As of yet, I know of no way to parallelize the updating and verification process because certbot calls its manual auth script serially, once for each domain.

1#!/usr/bin/env zsh
2sudo certbot certonly --manual --manual-auth-hook /home/tucker/certbot-dns.zsh \
3  --preferred-challenges dns --cert-name maildrop.the-twomeys.com \
4  -d maildrop.the-twomeys.com -d autoconfig.the-twomeys.com \
5  -d basslake.the-twomeys.com

certbot-dns.zsh

This script is used by certbot. Each domain must have an entry in the case statement at the top that sets the variable $key to the key used when configuring dynamic DNS in Hurricane Electric's DNS record editor.

 1#!/usr/bin/env zsh
 2case $CERTBOT_DOMAIN
 3in
 4  maildrop.the-twomeys.com)
 5    key=XXXXXXXXXXXXXXXX ;;
 6  autoconfig.the-twomeys.com)
 7    key=XXXXXXXXXXXXXXXX ;;
 8  basslake.the-twomeys.com)
 9    key=XXXXXXXXXXXXXXXX ;;
10 esac 
11print $CERTBOT_DOMAIN $key $CERTBOT_VALIDATION
12acm_domain="_acme-challenge.$CERTBOT_DOMAIN"
13result=$(curl -vv "https://dyn.dns.he.net/nic/update" \
14  -d "hostname=$acm_domain" \
15  -d "password=$key" \
16  -d "txt=$CERTBOT_VALIDATION")
17print $result
18[[ $result != "badauth" ]] || exit 2
19
20if [[ $(dig +short $acm_domain TXT) != "\"$CERTBOT_VALIDATION\"" ]]
21then
22  print "Waiting for updates"
23  sleep 100
24  if [[ $ctr > 3 ]]
25  then
26    exit 1
27  else
28    ctr=$(( $ctr + 1 )) exec $0
29  fi
30fi