I have a well-documented obsession with pretty URLs, and this extends even to my internal home network. I have way too much stuff bouncing around in my head to have to remember IP addresses when a domain name is much easier to remember.
LetsEncrypt launched to offer free SSL certificates to anyone, but the most crucial feature of their infrastructure, and one someone should have figured out before then, was scriptable automatically renewing certificates. Basically they validate you do in fact own the domain using automated methods, then issue you the new certificate. Thus, your certificates can be renewed on a schedule with no interaction from you.
Traditionally, they have done this by placing a file in the webroot and looking for that file before issuing the certificate (see my earlier blog post about Zero Downtime nginx Letsencrypt Certificate Renewals Without the nginx Plugin for more detail about this.)
But what happens when you want to issue an internal certificate? One for a service that is not accessible to the outside world, and thus, not visible using the webroot method? Well, it turns out there is a solution for that too!
Why?
Why would you want to use SSL certificates on a home network? Well, besides keeping my skills sharp and getting practice with new technologies, I write a number of internal Mac applications I use at home. I distribute these internally using Sparkle, a self-updating framework that Mac users should be pretty familiar with.
Sparkle requires updates to be distributed over SSL, even if they are local. So I needed an SSL certificate for that local host and domain.
Enter DNS
LetsEncrypt also supports an alternate method of domain validation using DNS entries. You can create a special TXT entry in your domain message using a string they generate. They then look for this TXT entry and, if they find it, issue the certificate. This is the DNS-01 challenge.
But this requires a manual step - you have to create the TXT entry each time you want to renew your certificates. Which defeats the purpose of making them auto renew. So, while this is a good way to get your certificates issued for internal services, it still needs another piece.
Enter Cloudflare
For many, many years I used Dyn for my internal DNS needs. I had a domain name for my house delegated to Dyn, and used the Dynamic DNS features in pfSense, as well as ddclient, to update it whenver the IP for my home Internet connection changed. This allows me to use VPN to reach the home no matter where I am.
In 2002 while still a student at Auburn, I signed up for a “lifetime” account with Dyn (back when they were still DynDNS) so that I could access my dorm computer from the labs.
After the Oracle acquisition, I knew it was only a matter of time until “lifetime” accounts died. And sure enough, earlier this year, Oracle announced that “lifetime” accounts were going away in May 2020. I guess I can’t really complain. If I recall, it was like $20 back in 2002, and I got 17 years out of that $20. One of the better payoffs for things I have purchased in my life.
So I migrated everything to Cloudflare over the weekend, since I already had an account with Cloudflare (that powers this blog). Crucially, Cloudflare is not a all-in type of service. You can opt to only use the DNS service of a domain, and that is what I did for my internal domain.
And, since Cloudflare has an API, using Cloudflare means that there is now an option for automatically creating those TXT entries. As it turns out, certbot already has a plugin for working directly with Cloudflare!
How To Do It
First, be sure you are using the most up-to-date code, which may not be what is in your distro’s package repository. I am using Ubuntu here, so if you are using a different distro, you will need to translate these commands into those suitable for your package manager:
$ sudo add-apt-repository ppa:certbot/certbot
$ sudo apt install certbot python3-certbot-dns-cloudflare
Next, you will need an API key from Cloudflare. Go to “My Profile” (in the top right corner) then “API Tokens”. You will need the “Global API Key”. (Side note: this is the easiest way, but you could probably also generate a specific key that just has permission to update a certain zone.)
Now, you need to create a small config file for the updater to use.
$ sudo vi /etc/letsencrypt/cloudflare.ini
dns_cloudflare_email = [email protected]
dns_cloudflare_api_key = <api key here>
Set the permissions on the file to be user only. This is for security reasons, and also because the plugin will complain if you don’t.
$ sudo chmod 700 /etc/letsencrypt/cloudflare.ini
Now, you are ready to get your certificates!
sudo certbot certonly \
--dns-cloudflare \
--dns-cloudflare-credentials /etc/letsencrypt/cloudflare.ini \
-d internal.example.com
And let it do its thing. If everything went right, you should see a congratuations message about your new certificates.
Automating It
So this works fine for getting your certs, but we want to automate this so that we don’t have to think about it. With that in mind, you can create a cron entry that will run a variation of this command:
$ sudo vi /etc/cron.daily/certbot-internal.example.com
#!/usr/bin/env bash
/usr/bin/certbot certonly \
--quiet -n --agree-tos \
--dns-cloudflare \
--dns-cloudflare-credentials /etc/letsencrypt/cloudflare.ini \
--deploy-hook "systemctl reload nginx" \
-d internal.example.com
The only difference here is a few more arguments that are documented in my previous post about this subject. We also added a deploy hook that reloads the web server to pick up the new certificate.
Finally, set your cron script executable:
$ chmod +x /etc/cron.daily/certbot-internal.example.com
And now you have auto-renewing internal SSL certificates.