Let's Encrypt

Since December last year I have thought about Let’s Encrypt and that is something I should use for my sites and other services that needs HTTPS. Yesterday I finally took the time to move this site to HTTPS, I guess http/2 is the next logical step but that’s another day!

A few short words about Let’s Encrypt if you are not completely up to date. Let’s Encrypt is a initiative to offer completely free ordinary X.509 certificates. It’s idea is to help everybody to encrypt the web traffic and remove the cost that previously hindered the security layer that HTTPS offers.

A rather large problem with certificates today is that they are valid for years, typically a year or two. If the private certificate is leaked or stolen for whatever reason anyone are free to use it to do man-in-the-middle attacks for the entire validity of the certificate. Yes, there is a revocation process in place but even big browsers like Google Chrome mostly ignores this for performance reasons. Let’s Encrypt certificates are not valid for more than 90 days, and it’s likely to be considerably shorter in the future. That will not solve the problem, but it will make it less painful if a certificate is lost.

With certificates this short you need to renew them often, and that’s a pain do do manually so the entire process is automated. And for that I used Certbot from EFF that is the recommended tool, but there are plenty of alternatives out there.

HAProxy - ACME

For the curious ones this is how I did it. I have a HAProxy in front of my domains and I added these acl:s to the port 80 frontend.

acl is-lets-encrypt-acme-challenge path_beg /.well-known/acme-challenge/
use_backend nginx-lets-encrypt if is-lets-encrypt-acme-challenge

Match a path that begin with /.well-known/acme-challenge/ and sends that traffic to a special backend backend named nginx-lets-encrypt.

backend nginx-lets-encrypt
        server localhost:8081 localhost:8081 maxconn 1024 check

From the name you may have guessed that there is a Nginx at port 8081, it is there to serve out the files generated by Certbot, I know that Certbot can start it’s own web server but I have other uses for Nginx so this was easier for me.

This is one of the few rules on the port 80 frontend, the rest sends a 302 redirect to port 443 (HTTPS).

Certbot - Renew certs

I installed Certbot in the way recommended at the official site and setup a cronjob that twice a day execute this:

certbot renew --quiet --post-hook "/root/renew-cert.sh"

Certbot will only renew certs that needs to be, --quiet is nice for cronjobs to keep it quitet. After a cert is renewed the script renew-cert.sh is executed. This is needed because I need to merge the cert files and then restart HAProxy.

Setup TLS HAProxy

This is renew-cert.sh

#!/bin/sh

CERTDIR="/path/to/certs/for/haproxy"

mkdir -p $CERTDIR
cd /path/to/letsencrypt/certs/live/
for domain in *; do
	cat $domain/fullchain.pem $domain/privkey.pem > $CERTDIR/$domain.pem
	chgrp haproxy $CERTDIR/$domain.pem
	chmod 640 $CERTDIR/$domain.pem
done

service haproxy restart

Load the certs on the bind line in the port 443 frontend in HAProxy, for example

bind *:443 ssl crt /path/to/certs/for/haproxy/crt1.pem crt /path/to/certs/for/haproxy/crt2.pem

How to actually get the cert

This do depend on what you need, it can be fully automatic or a manual process (most people only needs to renew the certs automatically). I picked a solution in the middle with this script.

#!/bin/sh

certbot certonly \
	--webroot -w /path/to/nginx/web/root \
	-n \
	-m email@example.com \
	--agree-tos \
	-d $1

I mostly wrote this so I did not need to remember all the parameters for a easy quick cert! If I need a new cert all I need to do is, run this script once, add a crt-entry to HAProxy’s configuration and I’m done!