Let's Encrypt wildcard certificates and djbdns


As of recently, Let's Encrypt will let you have wildcard certificates, but you have to use the ACMEv2 API server and the DNS challenge. The DNS challenge requires adding a record to DNS with a validation token to allow LE to validate you have control over the domain.

There are a few ways to handle this. The simplest is to manually manipulate the DNS records to add the validation token every time the certificate comes up for renewal, which is 90 days. This is a bit cumbersome and prone to human error. It's therefore possible to automate this, and the certbot ACME client comes with a bunch of plugins which allow you to hook into various DNS providers' management API's. However, if you're running your own nameserver (like me) and running nameserver software which doesn't support RFC2316 (which looks like everything apart from ISC Bind 9 from what I can tell), then this becomes slightly more involved.

certbot has an option for performing domain validation manually instead of using a plugin (such as the webroot plugin, which is the most useful one to me), and allows you to specify external hook programs to call to set up the authentication token and to clean up post-verification out of band. All you need to do is write some scripting to automate the DNS changes.

I use djbdns on my authoritative nameservers -- I have it set up like this:

Also of note is that my webserver and primary nameserver run on the same machine, which simplifies the automation of changing DNS records a lot. However, it wouldn't be overly difficult to adapt the scripting to call a script over ssh to perform the updates instead.

So, I have the following script at /root/scripts/letsencrypt-authenticator.sh, which adds the challenge record to the DNS:

#!/bin/bash -e

basedir="/var/local/dns/zone" # directory with DNS data files
dnsuser="dns"
updatescript="/var/local/dns/bin/bootandpush.sh" # this script compiles data.cdb locally and pushes 
                                                 # the data file to the backup nameservers

authdomain="$CERTBOT_DOMAIN"     # domain for which validation is being performed
authstring="$CERTBOT_VALIDATION" # string to be entered into DNS

txtdomain="_acme-challenge.$authdomain"  # label to insert record under, e.g. _acme-challenge.in-addr.xyz
tinydnstxt="'$txtdomain:$authstring:5"   # tinydns TXT record -- see the documentation at cr.yp.to

filename=$(mktemp -u "$basedir/ACME_VALIDATION.XXXXXX")

echo $tinydnstxt | sudo -u "$dnsuser" tee "$filename" > /dev/null
sudo -u "$dnsuser" "$updatescript"

sleep 5  # wait for the changes to settle

Then, I have this script at /root/scripts/letsencrypt-cleanup.sh, which removes the challenge records:

#!/bin/bash -e

basedir="/var/local/dns/zone"
dnsuser="dns"
updatescript="/var/local/dns/bin/bootandpush.sh"

sudo -u "$dnsuser" rm -f $basedir/ACME_VALIDATION.*
sudo -u "$dnsuser" "$updatescript"

Certificates can then be requested by running certbot in manual mode, specifying the above scripts as the authentication and cleanup hooks:

# certbot certonly --manual --preferred-challenges=dns --server https://acme-v02.api.letsencrypt.org/directory \
    --manual-auth-hook /root/scripts/letsencrypt-authenticator.sh \
    --manual-cleanup-hook /root/scripts/letsencrypt-cleanup.sh -d '*.in-addr.xyz' -d in-addr.xyz


home