echothrust/howtos

A list of OpenBSD (mostly) material

View on GitHub

OpenBSD LetsEncrypt acme-client nginx

Process summary

Configure filesystem locations

Create the directories

# mkdir -p /etc/acme /etc/ssl/acme/private /var/www/acme/.well-known/acme-challenge/
# chmod -R 700 /etc/acme /etc/ssl/acme/private

Install acme-client (formerly letskencrypt)

Simple as download, extract, compile, install

# ftp https://kristaps.bsd.lv/acme-client/snapshots/acme-client.tgz
# tar zxf acme-client.tgz
# cd acme-client-XXX && make && make install

Configure nginx for letsencrypt access

In /etc/nginx/nginx.conf add one include, before other location directives:

include acme-client.conf;

And subsequently create /etc/nginx/acme-client.conf:

# Rule for legitimate ACME Challenge requests (like /.well-known/acme-challenge/xxxxxxxxx)
# We use ^~ here, so that we don't check other regexes. We actually MUST cancel
# other regex checks, because other config files might have regex rule that denies access to files with dotted names.
location ^~ /.well-known/acme-challenge/ {

    # https://community.letsencrypt.org/t/using-the-webroot-domain-verification-method/1445/29
    # Current specification requires "text/plain" or no content header at all.
    default_type "text/plain";

    # This directory is used as prefix in -C option of acme-client(1)
    # as "challengedir" parameter, i.e. acme-client -C /var/www/acme/.well-known/acme-challenge/
    root         /var/www/acme;

}

# Hide /acme-challenge subdirectory and return 404 on all requests.
# Ending slash is important!
location = /.well-known/acme-challenge/ {
    return 404;
}

Write something to /var/www/acme/.well-known/acme-challenge/test.txt and attempt to access the URL, e.g. http:/MYDOMAIN//.well-known/acme-challenge/test.txt

Configure PF

The server is not publicly accessible so the following was added to /etc/pf.conf to allow letsencrypt servers through the firewall:

#table <letsencrypt> persist counters { outbound1.letsencrypt.org, outbound2.letsencrypt.org }
table <letsencrypt> persist counters { 66.133.109.36, 64.78.149.164 }
pass in on egress inet proto tcp from <letsencrypt> to (egress) port { 80 443 }

Initial certificate creation

# CERT_DOMAINS="www.domain.tld altname.domain.tld domain.tld"
CERT_DOMAINS="support.echothrust.dev"
acme-client -vNn -C /var/www/acme/.well-known/acme-challenge/ ${CERT_DOMAINS}

Option -N asks acme-client to create the private key for our web server, if one does not already exist.

Option -n asks acme-client to create the private key for our Let’s Encrypt account, if one does not already exist.

Generate DH ephemeral params

nginx will use a 1024-bit key, per openssl defaults, so let’s generate a stronger DHE parameter file for our 4096-bit keys:

cd /etc/ssl/private
openssl dhparam -out dhparam.pem 4096

Adding the new cert to nginx

Add the following to /etc/nginx/nginx.conf to use the new certificate

        ssl_certificate /etc/ssl/acme/fullchain.pem;
        ssl_certificate_key /etc/ssl/acme/private/privkey.pem;
        ssl_session_timeout 1d;
        ssl_session_cache shared:SSL:50m;
        ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
        ssl_ciphers 'EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH';
        ssl_prefer_server_ciphers on;
        ssl_dhparam /etc/ssl/private/dhparam.pem;

Maintenance

To manually check the cert validity and update (when cert expires in 30 days or less):

# acme-client -v -C /var/www/acme/.well-known/acme-challenge/ support.echothrust.dev
acme-client: /etc/ssl/acme/cert.pem: certificate valid: 89 days left

Create a script acme-client-cron.sh and a cronjob to automate the task:

#!/bin/ksh
# CERT_DOMAINS="www.domain.tld altname.domain.tld domain.tld"
CERT_DOMAINS="support.echothrust.dev"

# Make sure we allow letsencrypt's servers to connect
pfctl -t letsencrypt -T add outbound1.letsencrypt.org outbound2.letsencrypt.org > /dev/null 2>&1

# Update Lets Encrypt certificate
/usr/local/bin/acme-client -C /var/www/acme/.well-known/acme-challenge/ ${CERT_DOMAINS}

# acme-client returns 1 on failure, 2 if no_change, or 0 if cert was changed
if [ $? -eq 0 ]
then
        /usr/local/sbin/nginx -t && rcctl reload nginx
fi

# Revoke letsencrypt access to web server
pfctl -t letsencrypt -T flush > /dev/null 2>&1

And the relevant crontab:

# Update letsencrypt @ 4:20 on wednesdays
20  4  *  *  3  /usr/local/sbin/acme-client-cron.sh