Certificates issuance and renewal using certbot docker images with DNS challenges on BIND9 DNS Server
INTRODUCTION
Letsencrypt in the last few years has changed the way we think about SSL certificates. Do you remember those dark (and expensive) days when you needed to buy a yearly certificate from their majesty the Certification Authorities and manually deploy it on all of your websites? This led to two consequences: first, SSL was only implemented when really needed and second expire deadlines quickly turned out to be as critical as due dates for fees.
After Letsencrypt was born, with its short 90-days renewal period, it became clear that we needed some kind of automation. Certbot was one of the most promising solutions, being it straight-to-the-point and easy to automate. Standard HTTP challenge was trouble-free and could automagically change your web server’s configuration.
DNS challenge became available as well, supporting wildcard certificates. But this required you to add a specific TXT record every time in you DNS for issuance and renewals.
Certbot provides a complete list of plugins to support DNS challenges on major Cloud and on-premise DNS providers. Additionally, docker images with preloaded plugins are available on dockerhub, making the renewal process effortless and one-liner.
But how do you seamlessly integrate certificate renewals with DNS challenges in a cloud and on-premise DNS environment, without messing up your servers installing certbot and its python dependencies? How would you add custom records on your Bind9 installation, which does not expose APIs?
In this article, we will be focusing on renewal of certificates linked to on-premise BIND9 DNS server.
PREREQUISITES
- A linux server with BIND9 updated to the latest available version
- Docker up and running
- A fully working DNS authoritative configuration for the domain example.com
LET’S MOVE ON
First we need to adjust bind configuration by creating a key (e.g. /etc/bind/dns-keys.conf) with the following content:
key acme-key {
algorithm hmac-sha512;
secret "AVERYLONGBASE64KEY";
}
There is a pletora of ways to generate the secret key, the easiest being just:
cd /tmp && dnssec-keygen -a HMAC-SHA512 -b 512 -n USER example.com
and then doing a:
cat /tmp/Kdomain.com.*.private | grep Key
Just copy the key and remove the generated files.
Please remember to include the key in the main configuration file (/etc/bind/named.conf):
cat << EOF >> /etc/bind/named.conf
include "/etc/bind/dns-keys.conf";
EOF
Now, let’s just give the given key the needed permsission in the zone (/etc/bind/named.conf.local):, e.g.:
zone "example.com" {
type master;
file "/etc/bind/master/example.com.hosts";
update-policy {
grant "acme-key" wildcard *.example.com. txt;
};
};
This will allow request with the authorized key to manage TXT records in the given zone.
The file /usr/local/etc/dns-key.ini will contain the following:
cat << EOF > /usr/local/etc/dns-key.ini
dns_rfc2136_server = dns.example.com
dns_rfc2136_port = 53
dns_rfc2136_name = acme-key
dns_rfc2136_secret = AVERYLONGBASE64KEY
dns_rfc2136_algorithm = HMAC-SHA512
EOF
Remember to adjust permissions, on this file:
chmod 600 /usr/local/etc/dns-key.ini
Now, let’s just run the correct docker image on a client:
sudo docker run --rm --name certbot -v "/etc/letsencrypt:/etc/letsencrypt" -v "/usr/local/etc:/tmp" -v "/var/lib/letsencrypt:/var/lib/letsencrypt" certbot/dns-rfc2136 certonly --dns-rfc2136 --dns-rfc2136-credentials /tmp/dns-key.ini --dns-rfc2136-propagation-seconds 30 -n --agree-tos --no-eff-email --email your@email.address -d ATESTRECORD.example.com
After certificate issuance, we will require a renewal every day and a restart of the nginx service by adding the following to /etc/cron.daily or wherever in crontab:
#!/bin/bash
docker run --rm --name certbot -v "/etc/letsencrypt:/etc/letsencrypt" -v "/usr/local/etc:/tmp" -v "/var/lib/letsencrypt:/var/lib/letsencrypt" certbot/dns-rfc2136 renew --dns-rfc2136 --dns-rfc2136-credentials /tmp/dns-key.ini --dns-rfc2136-propagation-seconds 30 -n --agree-tos --no-eff-email --email your@email.addressif [[ $(find /etc/letsencrypt/live -mmin -60 -print) ]]; then nginx -t && systemctl restart nginx; fi
Since docker returns 0 to bash either if renewal was issued or not, systemctl restart nginx needs to be conditionally checked on the guest. This script restart nginx only if new certificates were issued.
CONCLUSION
Check out the documentation for more details
- Dynamic DNS overview on wikipedia https://en.wikipedia.org/wiki/Dynamic_DNS
- Docker overview https://docs.docker.com/engine/docker-overview/
- Certbot official website https://certbot.eff.org/
- Certbot images on dockdrhub https://hub.docker.com/u/certbot
PS
If you are using DNSSEC for your zones, it is easier to adopt the more recent auto-dnssec method. It it might be as simple as:
mkdir /etc/bind/keys && chown root:bind /etc/bind/keys
and then add the following to each zone you want to use dnssec on:
key-directory "/etc/bind/keys";
auto-dnssec maintain;
inline-signing yes;
This is because certbot automated DNS challenge requires a zone to be propagated and applied to master and all slaves. If you used the older manual zone signing method, this would require you to manually sign all zones with dnssec-signzone to have the signed version of the zone incremented in serial and propagated.