Shami's Blog

DevOps because uptime is not optional

Letsencrypt Pre-renew Hooks

Acmetool used to be my go-to tool for LetsEncrypt. It was quick and simple to set up. As a user, my favorite part of the Golang ecosystem is that binary files are statically linked. You don’t have to fiddle with any dependencies. But even though Acmetool is still getting occasional updates the last release is from 2018 and I prefer to stick to releases. There were times when Acmetool would not work behind Cloudflare and I would have to temporarily disable Cloudflare proxying to be able to generate certificates.

Another alternative was lego which is a library and command line client for the ACME protocol. I read that it was a very solid tool but what kept me from using it was that it can only manage one certificate at a time. In 2019 I managed servers with tens of websites and Acmetool didn’t work so I needed a few crontab entries to manage them with lego.

I wrote a couple of wrapper scripts for lego and that made it my favorite ACME client. And while trying to figure out the setup I mentioned in my previous article I read some posts asking for pre-renewal hooks and I realized my scripts enable that, so will be sharing them below:

The new certificate generation script:

 1#!/bin/sh
 2
 3# Do not tolerate errors
 4set -e
 5
 6. /root/.lego
 7
 8if [ -z $PORT ]; then
 9    request="webroot /usr/local/www"
10else
11    request="port $PORT"
12fi
13
14requestCert() {
15    $LEGO \
16        --accept-tos \
17        --path $LEGODIR \
18        --http \
19        --http.$request \
20        --email $EMAIL \
21        --domains $1 \
22        --pem \
23        run
24}
25
26LEGO=/usr/local/bin/lego
27LEGODIR=/var/db/lego
28
29domainsParam="$1"
30shift
31
32for domain in $*
33do
34    domainsParam="$domainsParam --domains $domain"
35done
36
37requestCert "$domainsParam"

The contents of /root/.lego

1[email protected]       # Mandatory
2PORT=127.0.0.1:402      # Optional, if not specified the acme challenge is stored in /usr/local/www/.well-known/acme-challenge, if specified, lego will listen on the defined IP and port

You will have to configure your web server or load balancer to use either the folder or the proxy to the port. I will not go into detail here as this is thoroughly documented online.

Usage: acmenew domain1 [domain2 domain3 ...]

This will generate the following set of files in /var/db/lego/certificates/

1domain1.crt
2domain1.issuer.crt
3domain1.json
4domain1.key
5domain1.pem

Now for the renewal script:

 1#!/bin/sh
 2
 3# Do not tolerate errors
 4set -e
 5
 6. /root/.lego
 7
 8if [ -z $PORT ]; then
 9    request="webroot /usr/local/www"
10else
11    request="port $PORT"
12fi
13
14renewCert() {
15    # If we have a hook script in /root/.hooks, add the --renew-hook argument
16    hook=""
17    if [ -f /root/.hooks/$1 ]; then
18        hook="--renew-hook /root/.hooks/$1"
19    fi
20    $LEGO \
21        --accept-tos \
22        --path $LEGODIR \
23        --http \
24        --http.$request \
25        --email $EMAIL \
26        --domains $1 \
27        --pem \
28        renew --days $DAYS ${hook}
29}
30
31# Do not attempt renewal if the certificate has more than X days available
32# 86400 seconds is 1 day
33DAYS=22
34EXPIRSIN=`expr $DAYS \* 86400`
35
36LEGO=/usr/local/bin/lego
37LEGODIR=/var/db/lego
38OPENSSL=/usr/bin/openssl
39
40FIND=/usr/bin/find
41SED=/usr/bin/sed
42BASENAME=/usr/bin/basename
43
44# Loop through current certificates
45CERTS=`$FIND $LEGODIR -name '*crt' -not -name '*issuer*' -type f`
46
47for cert in $CERTS
48do
49    # If the certificate expires in the period mentioned above, renew it
50    $OPENSSL x509 -checkend $EXPIRSIN -noout -in $cert -out /dev/null ||
51        renewCert `$BASENAME $cert | $SED 's/.crt//'`
52done

This script does the following:

  1. Loop through all certificates in /var/db/lego/certificates
  2. Use openssl to check the certificate expiry date
  3. Run renewCert only when a certificate is about to expire
  4. Look for /root/.hooks/domainname, if that exists, instruct lego to run it after renewing the certificate

To add a pre-hook, all you need is to modify the beginning of renewCert

About Me

Dev gone Ops gone DevOps. Any views expressed on this blog are mine alone and do not necessarily reflect the views of my employer.

Categories