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.
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.
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.
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.