LetsEncrypt Everything

posted a month ago by Ben Cordero

The LetsEncrypt project has been running for over a year and a half now. Their goal to encrypt the web by removing all of the hurdles to deploying TLS services has been realised. Before I forget to write something about this acheivement, I thought I should share my personal setup.

Making it easy means making it automatic, making it magic. I don't like magic. Magic means my webservers, my email, my digital independence breaks from a nightly update without warning me ahead of time.

Encrypting everything, as per RFC 7258, requires automation. Automation over the internet requires a protocol, and I'm glad to see that the ACME protocol is on the road to becoming an IETF Standard. This means it is becoming boring, ubiquitous, standard.

Using certbot

Certbot is the EFF's python implementation of the ACME client.

You can install it from system packages.

emerge app-crypt/certbot

For convenience, I wrap the certbot client in a script that removes some of the magic (e.g. autogenerated webserver configuration) and hides some of the specific-to-me flags.

# /root/run_letsencrypt.sh
set -e

if [[ -z "${1}" ]]; then
    echo "No domain specified. Renewing them all."
    /usr/bin/certbot renew
else
    /usr/bin/certbot certonly \
        --webroot --webroot-path /var/www/localhost/ \
        --domains "${1}" \
        --email ...
fi

chmod 740 /etc/letsencrypt/live/*/privkey.pem
/etc/init.d/nginx reload
/etc/init.d/haproxy reload

Nginx is used to host the ACME challenge

# /etc/nginx/nginx.conf
...
server {
        listen 127.0.0.1:80;
        listen [::1]:80;

        location / {
                return 301 "https://$http_host$request_uri";
        }

        location /.well-known/acme-challenge {
                root /var/www/localhost;
        }
}
...

And HAProxy is used to listen on internet facing TCP ports.

# /etc/haproxy/haproxy.cfg
...
frontend http_all_vips
    bind x.x.x.x:80
    bind yyyy:yyyy:yyyy:yyyy::yyyy:80
    mode http
    default_backend http_backing_services

...
backend http_backing_services
    mode http
    option forwardfor

    server nginx 127.0.0.1:80
    server ...

    use-server nginx if { req.hdr(host) -m end condi.me }
    use-server ... if { ... }

With this, and a wildcard DNS entry, I can ./run_letsencrypt.sh $newdomain.condi.me issue myself a certificate.

A @weekly /root/run_letsencrypt.sh in crontab keeps all certs renewed.

Setup a new domain

In order to add a new http subdomain, I use this nginx template.

server {
        listen 127.0.0.1:443 ssl http2;
        listen [::1]:443 ssl http2;

        ssl_certificate /etc/letsencrypt/live/{{ CN }}.condi.me/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/{{ CN }}.condi.me/privkey.pem;

        add_header Strict-Transport-Security 'max-age=31536000; includeSubDomains; preload';

        server_name {{ CN }}.condi.me;

        location / {
                proxy_pass              http://{{ HOST }}:{{ PORT }}/;
                proxy_redirect          off;
                proxy_http_version      1.1;
                proxy_set_header        Host $http_host;
                proxy_set_header        Connection $connection_upgrade;
                proxy_set_header        Upgrade $http_upgrade;
                proxy_set_header        X-Forwarded-Proto $scheme;
                proxy_set_header        X-Real-IP $realip_remote_addr;
                proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;
        }
}

A little bit of sed later and I can proxy http traffic to a backend server runnng within my network, or adjust the haproxy.cfg to divert TLS traffic directly to an app (or kubernetes proxy) that can read it's own letsencrypt certificates.

Extra notes

Let's encrypt dns-01 is a mechanism to verify against the ACME protocol using DNS TXT records. In a multi tenant network with centralised DNS, individual servers can request for certs so long as it can update DNS records, e.g. via nsupdate. The certbot --manual mode can be used to run a shell script. Dan Langille has a nice writeup about getting this mechanism to work with BIND9.

Recent Posts

Feeds

Atom / RSS