9 Comments

Easy Secure Web Serving with OpenBSD’s acme-client and Let’s Encrypt

As recently as just a few years ago, I hosted my personal website, VPN, and personal email on a computer running OpenBSD in my basement. I respected OpenBSD for providing a well-engineered, no-nonsense, and secure operating system. But when I finally packed up that basement computer, I moved my website to an inexpensive cloud server running Linux instead.

Linux was serviceable, but I really missed having an OpenBSD server. Then I received an email last week announcing that the StartSSL certificate I had been using was about to expire and realized I was facing a tedious manual certificate replacement process. I decided that I would finally move back to OpenBSD, running in the cloud on Vultr, and try the recently-imported acme-client (formerly “letskencrypt”) to get my HTTPS certificate from the free, automated certificate authority Let’s Encrypt.

Why You Should Get Your Certificates from ACME

Let’s Encrypt uses the Automated Certificate Management Environment protocol, more commonly known as ACME, to automatically issue the certificates that servers need to identify themselves to browsers. Prior to ACME, obtaining certificates was a tedious process, and it was no surprise when even high-profile sites’ certificates would expire. You can run an ACME client periodically to automatically renew certificates well in advance of their expiration, eliminating the need for the manual human intervention that can lead to downtime.

There are plenty of options for using ACME on your server, including the Let’s Encrypt-recommended Certbot. I found acme-client particularly attractive not just because it will ship with the next release of OpenBSD, but also because it’s well-designed, making good use of the privilege separation technique that OpenBSD pioneered as well as depending only on OpenBSD’s much-improved LibreSSL fork of OpenSSL.

Bootstrapping

To follow along with me, you’ll need OpenBSD. You can use the 6.0 release and install acme-client. If you’re feeling adventurous and are willing to maintain a bleeding-edge system, you can also run the -current branch, which already has acme-client.

If you do the smart thing and choose to use the release version, you’ll need to do a little extra setup after installing acme-client to align with the places things are in -current:

# mkdir -p /etc/acme /etc/ssl/acme/private /var/www/acme
# chmod 700 /etc/acme /etc/ssl/acme/private

And whenever you use acme-client, you’ll need to specify these paths, e.g.:

# acme-client \
        -C /var/www/acme \
        -c /etc/ssl/acme \
        -k /etc/ssl/acme/private/privkey.pem \
        -f /etc/acme/privkey.pem \
        www.example.com

Everything will work as advertised otherwise.

A note before we get started: If you’re new to OpenBSD, you owe it to yourself to get familiar with man(1). OpenBSD has amazingly good documentation for just about everything, and you can access it all by typing e.g. man httpd or man acme-client. Everything in this article came from my reads of these manpages. If you get stuck, try man first!

ACME will use a web server as part of its challenge-response process with the Let’s Encrypt service. To get this started, we’ll build out a basic /etc/httpd.conf based on our readings of httpd.conf(5) and acme-client(1):

server "default" {
        listen on * port 80

        location "/.well-known/acme-challenge/*" {
                root "/acme"
                root strip 2
        }
}

This is enough to start up a basic web server that will serve the challenge responses that acme-client will produce. Now, start httpd using rcctl(8):

# rcctl enable httpd
# rcctl start httpd

Getting Your First Certificate

Once httpd is up and running, you’re ready to ask acme-client to perform all that heavy lifting that you used to have to do by hand, including:

  1. Generating your web server private and public key
  2. Giving your public key to the certificate authority
  3. Proving to the certificate authority that you’re authorized to have a certificate for the domains you’re requesting
  4. Retrieving and installing the signed certificate

You can do all of this with a single command:

# acme-client -vNn example.com www.example.com

man acme-client will explain all that’s going on here:

  1. -v says we want verbose output, because we’re curious.
  2. -N asks acme-client to create the private key for our web server, if one does not already exist.
  3. -n asks acme-client to create the private key for our Let’s Encrypt account, if one does not already exist.
  4. example.com and www.example.com are the domains where we want our certificate to be valid—note that our web server must be reachable via those names for this process to work!

If this worked correctly, there will be some new keys and certificates on your system ready to be used to serve HTTPS.

Using the New Certificates with httpd

To get httpd working with our new certificates, we just need to expand /etc/httpd.conf a little:

server "default" {
        listen on * port 80
        listen on * tls port 443

        tls certificate "/etc/ssl/acme/fullchain.pem"
        tls key "/etc/ssl/acme/private/privkey.pem"

        location "/.well-known/acme-challenge/*" {
                root "/acme"
                root strip 2
        }
}

The three new lines above add a new HTTPS listener to our configuration, telling httpd where to find the certificate it should present and the private key it should use.

Once this configuration is in place, ask httpd to reload its configuration file:

# rcctl reload httpd

At this point, your server should be online with a valid Let’s Encrypt certificate, serving HTTPS—though giving you an error page, because httpd is not yet configurated to serve any content. That bit is left as an exercise for the reader. (Consult httpd.conf(5) for further help there.)

Automating Yourself Out of a Certificate Renewal Job

By far the best part about ACME is that it can be easily configured to automatically renew your certificates before you notice they’re about to expire. Note that acme-client is written so that you simply need to run it periodically. Once the certificates are 30 days from expiration, it will get a fresh signature from Let’s Encrypt.

Making this happen is as simple as dropping the following into /etc/daily.local (cf. daily(8)):

# renew Let's Encrypt certificate if necessary
acme-client example.com www.example.com
if [ $? -eq 0 ]
then
        rcctl reload httpd
fi

And now acme-client will now run every night (by default at 1:30 a.m.) and renew your certificate when necessary.

Further Reading

This is a simple configuration, but it’s enough to run my web site and give me painless HTTPS that scores an A out-of-the-box on SSL Labs’ server test. I added a few lines to /etc/httpd.conf to serve the static content on my site, and I was done.

If you have a more complex configuration, though, chances are that httpd and acme-client are up to the task. To find out all they can do, read the man pages:

If you want to know more about OpenBSD in general, check out the comprehensive OpenBSD FAQ.

Happy secure serving!