Let's Encrypt - Securing internal websites
Like many software developers I have a few websites running on my LAN. Most of them are either running on plain HTTP, or have self signed certificates. Free certifciates signed by publically trusted CAs have been around for a while now, however the most commonly used method of getting them (in my experience anyway) has been via HTTP powered verification.
This can make installing and renewing certificates on sites that are not internet facing difficult or too risky. Opening up firewalls to allow the verification process to happen could introduce a security hole and would be fairly inconvienent to do regularly or for multiple sites.
Fortunately there is another way thats a lot more flexible. Let’s Encrypt supports the DNS-01 challenge type, this allows you to request certificates for a single domain or for a wildcard certificate. As the name indicates this challenge uses DNS rather than HTTP powered validation. This means Lets Encrypt does not have to connect to the machine that needs the certificate.
Due to the short validitity period of Let’s Encrypt certificates its important to have an automated method of renewing certificates. To make this process straight forward the DNS host for the domain in question must have an API that supports updating DNS records. Whilst you could manually complete the verification process every 60 days or so:
So my first job is to get an SSL cert setup for Home Assistant.
I want to keep as many applications as possible running in docker containers so I will be aquiring the certificate using a container. The docker container we will use is certbot/dns-cloudflare as my DNS host is CloudFlare. There are a bunch of other containers with support for other dns hosts that provide APIs, you can find them on the certbot dockerhub page
The docker arguments I had to pass to the container are below:
--keep-until-expiring --agree-tos --email [email protected] --dns-cloudflare --dns-cloudflare-credentials /etc/cloudflareconf/creds.ini -d domain.co.uk certonly
Then the mounts I specified look like this:
/mnt/user/appdata/letsencrypt/certs/ -> /etc/letsencrypt /mnt/user/appdata/letsencrypt/lib/ -> /var/lib/letsencrypt /mnt/user/appdata/letsencrypt/conf/ -> /etc/cloudflareconf/
Please follow this link for details on what goes into the creds.ini file
Actually getting the SSL certifcate working with HA ended up being a bit more difficult than I had anticipated. Thankfully the fix is easy once you know whats going on. Certbot maintains the certificates it gets in a hierarchy of folders, the actual certificates and private keys live in an “archive” directory. However you don’t want to access those certificates directly in most cases. Certbot creates a symbolic link in a “live” folder, this is the one you should reference.
Because a symbolic link is used its important that you also mount the “archive” folder, otherwise HA wont be able to find the actual certificate (it will just see the link). Therefore I added the following mounts to the HA docker:
/mnt/user/appdata/letsencrypt/certs/live/domain.co.uk/ -> /etc/letsencrypt/live/domain.co.uk/ /mnt/user/appdata/letsencrypt/certs/archive/domain.co.uk/ -> /etc/letsencrypt/archive/domain.co.uk/
Then the easy bit is the HA HTTP config in the yml file:
http: ssl_certificate: /etc/letsencrypt/live/domain.co.uk/fullchain.pem ssl_key: /etc/letsencrypt/live/domain.co.uk/privkey.pem
That completes the HA part of the configuration.
The containers in unraid can only have one set of “post arguments” where we specifiy the specific arguments required to get the certificate for a particular domain. If you don’t mind the certificate generated having multiple Subject Alternative Names then you can just use additional
-d flags. I however didn’t want a single certificate to be valid for all of my services. The easiest way I found to achieve this was just to duplicate the container and change the domain specified.
I followed this guide to add SSL support to the pi-hole admin interface.
This was again fairly easy once I knew where the certificates where stored. See the script at the bottom for details.
Renewing the certificates
Certbot can take care of renewing the certificate for you, however it needs an external trigger to run. In Unraid this was easy to do with the User Scripts plugin. On a schedule I restart the docker container which causes certbot to renew the certificate if required. Certbot locks some files whilst its running, so I put a sleep in between docker restarts so each container had time to run.
#!/bin/bash docker restart dns-cloudflare-ha sleep 5m docker restart dns-cloudflare-pihole sleep 5m docker restart dns-cloudflare-unraid sleep 5m # Build a certificate bundle by combining the files listed below. # Don't forget to change the hostname in the destination .pem file. cat /mnt/user/appdata/letsencrypt/certs/live/domain.co.uk/cert.pem /mnt/user/appdata/letsencrypt/certs/live/domain.co.uk/privkey.pem > /boot/config/ssl/certs/hostname_unraid_bundle.pem nginx -s reload