Wildcard Certificates with Let's Encrypt

reading time ( words)

Let’s Encrypt and Certbot can provide wildcard certificates when the validation process is carried out via DNS. This post explains my setup and introduces some new scripts I’ve uploaded to make this task easier.

As I explained in a prior post, my DNS zones are configured for dynamic updates and use DNSSEC. This allows my own zones to easily participate in the dns-01 ACME challenge to authenticate the certificate issuing process. In simple terms, Let’s Encrypt will provide with a randomly generated string that you need to add in a well known location under the apex of your DNS zone to prove that you are the one controlling it and by extension, that you are authorized to request issuance of a SSL certificate for your domain.

With the introduction of helper scripts to Certbot, the process of adding – and later removing – the authentication DNS records can be automated.

My Let’s Encrypt environment

In previous posts where I explained my scheme for managing multiple certificates with certbot and Certificate rotation with Let’s Encrypt, I explained how I reuse key pairs and the Certificate Signing Requests for certificates where pinning is desired. You might want to take a look at those posts to familiarize yourself with this layout – and to determine whether the method I present in this post will work for you.

This is the filesystem layout I use to manage my keypairs, where each domain (or group of domains) has a directory where multiple keypairs are generated and maintained automatically. There’s also support for generating encrypted backups of those keypairs, for off-line safekeeping, which is critical for good operational security practices.

Certificate creation, revisited

Place the nsupdate hook in a suitable location on the server where you’ll regularly issue or renew certificates. I’ll use /usr/local/bin for now. This script will take care of adding the authentication records to the DNS via dynamic updates prior to requesting the certificate operation, and removing the records once the process is done.

Try running nsupdate-hook.sh on its own as it will inform of any missing dependency, which should look like this:

1
2
3
4
5
/usr/local/bin/nsupdate-hook.sh
Please use env variable TSIGKEYFILE to specify the TSIG key to use for NS updates
Prerequisites seem to be present and in order, however this program should
be invoked as a certbot hook, via the --manual-auth-hook and --manual-cleanup-hook
command line options.

The command would request generation or renewal of a test certificate using your CSR and keys in the Let’s Encrypt staging environment. Notice the use of shell variables on which you can replace your own values. MASTER and TSIGFILE are used by nsupdate-hook.sh itself to invoke nsupdate.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
MASTER=ns1.libertad.link \
TSIGKEYFILE=/etc/bind/mykey.conf \
HOOK=/usr/local/bin/nsupdate-hook.sh \
EMAIL=certbot@lem.click \
CSR=lem.click/cert-0.csr
 certbot \
 --agree-tos \
 --csr ${CSR} \
 --manual certonly \
 --manual-auth-hook ${HOOK} \
 --manual-cleanup-hook ${HOOK} \
 --manual-public-ip-logging-ok \
 --noninteractive \
 --preferred-challenges dns-01 \
 --reuse-key \
 --server https://acme-staging-v02.api.letsencrypt.org/directory \
 --staging \
 --test-cert \
 --verbose \
 -m ${EMAIL}

Once you’re convinced that your certificate operation works as expected, you can remove the staging-specific options and point to the live Let’s Encrypt environment, as follows:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
MASTER=ns1.libertad.link \
TSIGKEYFILE=/etc/bind/mykey.conf \
HOOK=/usr/local/bin/nsupdate-hook.sh \
EMAIL=certbot@lem.click \
CSR=lem.click/cert-0.csr
 certbot \
 --agree-tos \
 --csr ${CSR} \
 --manual certonly \
 --manual-auth-hook ${HOOK} \
 --manual-cleanup-hook ${HOOK} \
 --manual-public-ip-logging-ok \
 --noninteractive \
 --preferred-challenges dns-01 \
 --reuse-key \
 --server https://acme-v02.api.letsencrypt.org/directory \
 -m ${EMAIL}

nsupdate-hook.sh takes care of issuing the DNS update as well as verifying that the update is visible at all authoritative name servers as well as an external name server. The update won’t proceed unless changes are visible at all servers, which helps prevent issues with random authorization failures is some scenarios.

You can test that the hook is working as expected by passing the required environment variables to the hook. At line 2 I set the test to use the lem.click domain, with a ficticious validation token.

1
2
3
4
5
VERBOSE=1 \
CERTBOT_DOMAIN=lem.click \
CERTBOT_VALIDATION="fukuhara-sokisoki-${RANDOM}" \
TSIGKEYFILE=/etc/bind/mykey.conf \
~lem/code/certs/nsupdate-hook.sh

Depending on your setup, one or more Failed validation messages can be produced, but within a few seconds the program should end. At this time you can easily check that the validation DNS records are present:

dig +short txt _acme-challenge.lem.click
"fukuhara-sokisoki-15412"

If something goes wrong, the information that nsupdate-hook.sh sends via syslog can be helpful:

1
2
3
4
5
6
7
8
9
Sep  3 21:08:45 ns1 nsupdate-hook: challenge _acme-challenge.lem.click fukuhara-sokisoki-15412 addedd successfully
Sep  3 21:08:45 ns1 nsupdate-hook: validation of _acme-challenge.lem.click fukuhara-sokisoki-15412 via 8.8.8.8 successful
Sep  3 21:08:45 ns1 nsupdate-hook: validation of _acme-challenge.lem.click fukuhara-sokisoki-15412 via ns1.libertad.link. successful
Sep  3 21:08:45 ns1 nsupdate-hook: failed validation of _acme-challenge.lem.click fukuhara-sokisoki-15412 via ns3.libertad.link.
Sep  3 21:08:46 ns1 nsupdate-hook: retry verification of _acme-challenge.lem.click fukuhara-sokisoki-15412
Sep  3 21:08:51 ns1 nsupdate-hook: validation of _acme-challenge.lem.click fukuhara-sokisoki-15412 via 8.8.8.8 successful
Sep  3 21:08:51 ns1 nsupdate-hook: validation of _acme-challenge.lem.click fukuhara-sokisoki-15412 via ns1.libertad.link. successful
Sep  3 21:08:51 ns1 nsupdate-hook: validation of _acme-challenge.lem.click fukuhara-sokisoki-15412 via ns2.libertad.link. successful
Sep  3 21:08:51 ns1 nsupdate-hook: validation of _acme-challenge.lem.click fukuhara-sokisoki-15412 via ns3.libertad.link. successful

In this case, the addition of the autentication DNS record did not make it to one of the name servers in time. The hook noticed this, waited for a little while and retried the validation which by then, had ample time to propagate and was successful.

Normal operation

Personally, I have a simple shell script that runs these commands for all the domains I need certificates for, and invoke this script from my crontab.

Keep in mind that after renewing the certificates, you’re responsible for copying them to their required destination and restart the affected services so that the new certificates can be used. This is also handled by my custom automatic renewal script.