Date Category blog

Deciding to pick up where I'd left off with my blogging routine, I noticed yesterday that my Let's Encrypt certificate for this site had expired. Not 100% why, as I thought I'd left a cron job in place to update it. But I’m also a dumbass, and didn't. So instead I put a regular process in place. This is how I did it.

Tested / Working With

  • FreeBSD 11.1-RELEASE-p8
  • Paul Jarc's runwhen 2015.02.24_1 (note: this deprecated skalibs to v2.4 due to shared libs)
  • daemontools 0.76
  • NginX 1.12.2
  • acme-client 0.1.16

Site is generated via Pelican + a git polling script (I'm not smart enough to set up push hooks on github yet).

NginX port 80

I wanted to use acme-client to do this because it seemed more robust and tightly written. However, the package didn’t create the directories or do anything else to help set things up, so in the end I had to go to the man page, then the RFC’s, and work out how to use the standard http-0 authentication method.

In the end, I had to make sure my webserver responded when the URI /.well-known/acme-challenge/ was called (which acme-client quietly makes use of).

Note that all the domains expected on the end certificate require this sort of thing to validate.

    server {
        listen       172.31.255.253:80;
        server_name  queerbsd.org www.queerbsd.org
        queerbsd.com www.queerbsd.com
        queerbsd.net www.queerbsd.net
        queerbsd.de  www.queerbsd.de
        queerbsd.eu  www.queerbsd.eu
        qbsd.de www.qbsd.de;
        location /.well-known/acme-challenge/ {
            alias /usr/local/www/acme/;
        }
    }

ACME Initialization

Bernard Spil has a much better guide for this, but in the interest of having my own notes available when I want them later, this is how I did it.

acme-client works quite well, but there wasn’t much leeway for configuration. Thankfully the defaults for FreeBSD are appropriate. All you really need to do is to make sure these directories exist:

/usr/local/www/acme/
/usr/local/etc/ssl/acme/private/
/usr/local/etc/acme/

Creating everything you need then is easy; try this a couple times with the flag -vvsFNn to get the hang of it without abusing Let’s Encrypt’s servers.

I use “domains.txt” to quickly list the domains from the nginx.conf file above.

acme-client -vFNn $(<acme/domains.txt)

run

For installing daemontools, make sure to enable svscan in your rc.conf. I’ve already installed it to get a qmail-send process up and running, which I use below for alerting me to updates via qmail-inject (see http://cr.yp.to for details). There are several guides around for how to do the rest, but the short version is this:

sudo mkdir -p /usr/local/service/letsencrypt-certrenewal/log/main
sudo chown nobody /usr/local/service/letsencrypt-certrenewal/log/main
cat > /usr/local/service/letsencrypt-certrenewal/log/run <<EOF
#!/bin/sh
exec setuidgid nobody multilog t ./main
EOF

...and then of course, there’s just the matter of the core run script, and linking the directory from /var/service/ for it to “go”.

This is also where I use runwhen, from http://code.dogmap.org. Paul Jarc’s software is notable for using the tai64 timing scheme, as well as building against skalibs. However, his fdtools/statfile currently doesn’t compile on BSD systems so I include a function that accomplished much the same thing as he uses statfile for, in his runwhen example script.

Since there is no good tool available for converting from common date formats to tai64, I’m using the standard ISO date format that rw-sleep happens to support with the standard stat utility.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
#!/bin/sh
rw_mtime() {
    /usr/bin/stat -f%Sm -t t%s "${1}"
}
exec 2>&1
rw-add n d1S                        now1s    \
rw-match \$now1s ,H=2,M=30          wake     \
rw-add "$(rw_mtime finished)" d8W   latest   \
rw-min \$wake \$latest              wake     \
rw-add "$(rw_mtime started)" d6W    earliest \
rw-max \$wake \$earliest            wake     \
sh -c '
  echo "@$wake" | tai64nlocal | sed "s/^/next run time: /"
  exec "$@"' arg0                            \
rw-sleep \$wake                              \
./update_certs.sh

update_certs.sh

With everything else set up, the actual updating of the certificates is relatively straightforward. If your system isn’t using qmail-inject for mailing out, use whatever works for you.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#!/bin/sh

PATH=$PATH:/var/qmail/bin/
mailout='log/mail.msg'
to='rd@qbsd.de'
from='root@queerbsd.com'
subject='Acme-Client Update Report'

domainlist=
while read -r line
do
    for d in "$line"
    do
        domainlist="$domainlist $d"
    done
done < acme/domains.txt

touch started &&
printf 'To: %s\nFrom: %s\nSubject: %s\n\n' "$to" "$from" "$subject" > "$mailout" 
printf 'started process at %s\n' "$(date)" | tee -a "$mailout"
acme-client -vvFb $domainlist 2>&1 | tee -a "$mailout"
service nginx restart 2>&1 | tee -a "$mailout"
printf 'ended process at %s\n' "$(date)" | tee -a "$mailout"
touch finished
qmail-inject < "$mailout"

Post-Setup Checks

OpenSSL’s command set is a b$%#%$ to explain or get right, so I wrote a wrapper for it that does everything you could want. Fetch from here:

https://github.com/queerbsd/clitoys

Point it at the generated certificate and let’r rip. Remember that validation occurs against the issuer; you'll need the fullchain.pem for the acme-client-generated certificate. Like so:

$ ./checkssl.sh /usr/local/etc/ssl/acme/fullchain.pem
EXTRACTING CN FROM /usr/local/etc/ssl/acme/fullchain.pem ... "queerbsd.com" LIVES AT 52.4.190.70
VERIFYING queerbsd.com AGAINST /etc/ssl/cert.pem
/usr/local/etc/ssl/acme/fullchain.pem: OK

___ CORE INFO ON queerbsd.com ___
subject= /CN=queerbsd.com
issuer= /C=US/O=Let's Encrypt/CN=Let's Encrypt Authority X3
notBefore=Mar 17 05:56:06 2018 GMT
notAfter=Jun 15 05:56:06 2018 GMT

___ Subject ___
Country: 
Location: 
State: 
Organization: 
Organizational Unit: 
Common Name: queerbsd.com

___ Subject Alternative Names ___
00: consciousness.es
01: qbsd.de
02: queerbsd.com
03: queerbsd.de
04: queerbsd.eu
05: queerbsd.net
06: queerbsd.org
07: www.consciousness.es
08: www.qbsd.de
09: www.queerbsd.com
10: www.queerbsd.de
11: www.queerbsd.eu
12: www.queerbsd.net
13: www.queerbsd.org
Ready Since: Mar 17 05:56:06 2018 GMT
Expires: Jun 15 05:56:06 2018 GMT