Automating Let's Encrypt with simp_le

Posted on .

Let's Encrypt is the new free, automated and open certificate authority, that I talked about in a previous post. The part that I'm focusing on in this post is automated. Let's Encrypt is all about automating the certificate request and renewal process, and they encourage this to the users by offering a good client – and by only giving out certificates with a maximum of a 3 month validity.

I'm not good at remembering things months down the line, especially if I have to deal with multiple different subdomains. That's why I wanted to automate my certificate renewal process. Unfortunately the official Let's Encrypt client requires root privileges and is quite a complex piece of software, making it difficult to automate. It is very helpful for beginners and has plugins to use for the most common setups, but is not as nice for custom setups and users that know what they are doing.

Luckily there's many alternative clients for Let's Encrypt, something that they also encourage. The one I'm using is simp_le, which is a simple client that follows the UNIX philosophy: do one thing and do it well. It assumes that you know how to set up the correct permissions and paths, and as such does not need root privileges to run. It only implements the webroot domain validation method, where it puts certain files into a given path, and then tells the Let's Encrypt servers to see if they can find the files using the requested domain and the same path. This method is chosen because it is easy to automate without requiring root privileges or specific knowledge of the actual web server software.

Here's the setup I used to make it work. I created a user simp_le with a home path /etc/simp_le (looking back it probably should have been /var/simp_le since it ended up containing more than just configuration, but oh well). In the home directory I created the following directory tree:

/etc/simp_le/
├── sites/
│   └── blog.nytsoi.net/
├── update_cert.sh
└── webroot/

In the directory tree above, the sites directory contains a subdirectory for every domain, with all the files related to it. The webroot directory is used for the webroot method. All directories are only accessible to simp_le, except for webroot which can also be read by users in the http group (i.e. the web server).

The script update_cert.sh is the actual script that calls simp_le:

#!/bin/bash

VALIDTIME=
DOMAIN=$1

while getopts ":f" o; do
    case $o in
        f)
            echo 'Forcing certificate renewal...'
            VALIDTIME='--valid_min 9999999'
            DOMAIN=$2
            ;;
    esac
done

cd ~/sites/$DOMAIN

simp_le -d $DOMAIN --default_root /etc/simp_le/webroot --cert_key_size 2048 $VALIDTIME -f cert.pem -f chain.pem -f fullchain.pem -f key.pem -f account_key.json --email email@blablabla && sudo systemctl reload nginx

rc=$?

if [[ $rc == 2 ]]; then
    exit $rc
fi

The script first checks if the flag -f was given as the first argument; if so, it will set the validation time so that the certificate will always be renewed. If the flag is not given, the certificate will only be renewed if it is valid for less than two weeks (simp_le's default value). If the certificate renewal succeeds, the script will use sudo to reload nginx's configuration, thus reloading the certificate. I configured sudo so that the simp_le user cannot execute any other commands:

# Allow simp_le reloading nginx on certificate renewal
simp_le ALL= NOPASSWD: /usr/bin/systemctl reload nginx

The last lines in the script just check if the return code was 2 (error in renewal), and if so, exit with the code, making the cronjob fail. If the return code was 1 (no need to renew), we don't want the cronjob to fail, so it will be ignored.

To serve the webroot directory on every domain, I created a file sites-general.conf, which is included in nginx's server configs for each domain that uses HTTPS:

# allow letsencrypt
location ~ /\.well-known {
    allow all;

    # Serve from common root
    root /etc/simp_le/webroot;

    try_files $uri $uri/ =404;
}

After running the script, the directory tree looked something like this:

/etc/simp_le/
├── sites/
│   └── blog.nytsoi.net/
│       ├── account_key.json
│       ├── cert.pem
│       ├── chain.pem
│       ├── fullchain.pem
│       └── key.pem
├── update_cert.sh
└── webroot/

Of these files, fullchain.pem (full certificate chain including certificate itself) and key.pem (private key) were obviously of most interest. To use them, I just needed to point nginx at their path (when renewed, the files would be replaced and nginx would pick up the new files after a configuration reload):

ssl_certificate /etc/simp_le/sites/blog.nytsoi.net/fullchain.pem;
ssl_certificate_key /etc/simp_le/sites/blog.nytsoi.net/key.pem;

Now all that was needed was to make the update_cert.sh script run periodically. I used systemd's service and timer functionality for this. After that I laid back and enjoyed the results! Now my domains will always enjoy TLS protection without myself having to worry about renewals. Next I will move all my WeeChat relays to a similar system (using nginx as a TLS terminator). But that's a story for another post.