Let's Encrypt with DNS-01: How can you do it securely?

2020-05-17

DNS-01 is really cool, and I have no idea why nobody ever seems to talk about it. Get this: Let's Encrypt certificates with no HTTP server. In fact, no exposed ports at all, and the domain you're issuing for doesn't even have to point to the server you're issuing on!

This solves just about every how do I do x with Let's Encrypt? problem I can think of. Gone are the days of spending far too long trying to work out how to get nginx to expose the .well-known directory. But there's a catch.

The problem

DNS is probably the most important single piece of infrastructure you can control. If an attacker takes control of your DNS, they can immediately start issuing SSL certificates for your domains, perform a man-in-the-middle attack on your services, and in the worst case scenario, even start reading your mail — locking you out of the usual recovery channels. You should take DNS security really seriously. Nobody does, but you should.

DNS-01 relies on your ACME client (certbot or similar) being able to add a TXT record at _acme-challenge.[your-domain]. Simple enough, right? If you run your own DNS, and are willing to spend an hour or so setting up some kind of API and integrating it with an ACME client, then great, you can close this page, it isn't for you.

Those of us not brave and/or server-rich enough to run our own DNS infrastructure have slightly fewer options. Certbot supports a myriad of cloud DNS provider APIs, but with a catch: these rarely (apparently Microsoft Azure is an exception, but it's kind of expensive) have fine enough access control to grant access to the neccessary TXT record, but not anything which an attacker could use to cause damage. This means that an attacker who gets control of your API key gets control of your DNS.

The solution: the humble CNAME record, friend to DNS hackers everywhere

CNAME records are pretty simple, they just alias one domain name to another. This means that instead of giving certbot API access to our entire zone, we can create a CNAME record for _acme-challenge.[your domain], pointing it to some subdomain in a separate zone - potentially a separate domain entirely. DNS-01 doesn't care, as long as it receives a valid TXT record from the other end.

Putting it all together

After a short perusal of the APIs supported by certbot, I settled on a not-for-profit API-based DNS hosting service called deSEC to use for my 'DNS-01 zone.' Initially I tried to set up a zone called _acme-challenge.[my domain] on their service, but it rejected it for having an invalid name. Instead, I used the CNAME setup described above like so:

_acme-challenge.[my domain] 3600 IN CNAME acme-zone.[my domain]
acme-zone.[my domain] 3600 IN NS ns1.desec.io
acme-zone.[my domain] 3600 IN NS ns2.desec.org

... and it works! I created a zone named acme-zone.[my domain] in deSEC, added a test TXT record using their API, and was able to see it with dig +short _acme-challenge.[my domain] TXT. Once I configured certbot to use the API key provided by deSEC and create TXT records in the zone apex (i.e. no subdomain), I was able to issue certificates. It's a bit of hassle, and I sincerely hope more DNS providers will update their APIs so that this hack isn't neccessary, but I think this is well worth it for the extra security it provides.

Update: getting deSEC's certbot hook to create records in the zone apex required a bit of modification, you can find the hook script I used here.

Back to my homepage