A superior way to use Let’s Encrypt
Let’s Encrypt is awesome. This is an objective fact. If you never heard about it and you are still using self-signed certificates, go check out the link. In short, Let’s Encrypt allows you to get valid SSL certificates for free. Once you set it up, there’s absolutely no reason to use self-signed certificates any longer.
… but until recently there was a catch
In order to understand it, though, we need to lay out some basics. Let’s Encrypt consists of a few key elements:
- Boulder CA written in Go
- Letsencrypt client written in Python
- ACME Protocol which is used to communicate between the Letsencrypt client and the Boulder server
If you look into the draft for the ACME protocol, you will learn that it’s using a technique called “Domain Validation”. In essence, this means that the CA will ensure that you are allowed to create a certificate for a certain domain by issuing a challenge that only a person in control of that domain could successfully complete. This could mean one of the two methods (for now):
- Provisioning a DNS record under example.com, or
- Provisioning an HTTP resource under a well-known URI on https://example.com/
So what was the catch?
Until recently, neither Boulder server nor Letsencrypt client supported the DNS method. Therefore ,you had to use the HTTP one. And the catch came with some of its limitations:
- What if I want a valid SSL certificate for a service that’s only ever used internally and should never be exposed to the Internet? In this scenario, the Boulder server won’t be able to connect to it in order to download the challenge response. I might not even have an A DNS record for it, which is a prerequisite for the HTTP method.
- What if I want to create the certificates for all my domains in a central point, like the build server? The public DNS records point to my webservers or loadbalancers, not my build server, therefore, I’ll have to install letsencrypt client on those.
You can probably think of more issues, but these two proved to be the most limiting for me. I just wanted to automatically create all my certificates on a single server in the management zone, which also wasn’t accessible via the Internet. The management server already had the tools necessary to push the certificates to the correct servers.
DNS challenge to the rescue
Fortunately, Boulder implemented the DNS method in early 2016. Less fortunately, the official letsencrypt client still doesn’t have it as of the day this post is being written (though looking at the pull request, it seems to be imminent). However, if the ACME protocol specification is open, what’s stopping other people from writing their own clients?
And that’s exactly what people did. Some of these alternative clients already implement the DNS method. I used letsencrypt.sh, which I highly recommend. It also has fewer dependencies than the official client, which is a nice bonus.
In order to use the DNS method, you will need one additional thing, though — a hook. It’s a program that implements the functions that are needed to programmatically create the necessary TXT records in your DNS server. The hook you’ll use will depend on the DNS solution that you use, but if it’s not possible to interact with your DNS programmatically, then, unfortunately, you won’t be able to use the DNS method in an automatic manner.
I was in luck, though, as the project used AWS Route 53 as the DNS provider. Therefore, I could just use a hook written by tache (forked from original solution written by asimihsan). The hook is written in Ruby and requires installing some additional gems, as well as configuring your AWS credentials.
Bringing it all together
To save you all some trouble, I created a wrapper around letsencrypt.sh and the hook, which will also install all the necessary dependencies. You can find it on Github.