Temporarily and automatically changing firewall rules to permit Lets Encrypt certificate renewals

Another niche one.

I have some webservers for which I want a valid, not self-signed, TLS certificate.

Simple, thanks to Let’s Encrypt.

But I do not want to expose the web server to the world most of the time.

I also want my TLS certs renewed automatically (part of the Let’s Encrypt design, to encourage good hygiene), so a solution which requires me to log in to change anything is not an option.

Trickier.

So what I need is an ability for Let’s Encrypt to automatically reconfigure my firewalls to allow access for the duration of a renewal attempt, and then close that access afterwards.

Fortunately, this is eminently achievable, bringing in a few bits:

Working out the firewall config changes

There are two firewalls I need to configure: the machine’s local firewall (ufw), and the network-level firewall.

Each needs to (temporarily) allow tcp on ports 80 and 443.

ufw / local firewall

To automate changes to ufw, I created a ufw application profile:

[letsencrypt]
title=access to the world for Let's Encrypt
description=access to the world for Let's Encrypt
ports=80/tcp|443/tcp

This goes in /etc/ufw/applications.d/ufw-letsencrypt.

I can trigger this using ufw from the command line:

ufw allow letsencrypt

to enable it.

ufw delete allow letsencrypt

to delete it.

Network firewall

My network firewall (a FireBrick) supports profiles, and those profiles can be adjusted by a suitably-privileged user, via curl.

So what I needed was a profile which was a simple toggle (on/off), which I applied to a firewall rule permitting 80/443 tcp.

Let’s Encrypt renewal-hooks

You can run scripts automatically when Let’s Encrypt attempts a renewal, by putting them in the relevant directory.

For me, this is /etc/letsencrypt/renewal-hooks, and either pre for scripts to run before renewal, and post to run afterwards (whether successful or not).

In pre, I have a bash script which contains just the ufw allow letsencrypt command, and a curl toggle for the network firewall.

In post, it runs ufw delete allow letsencrypt and the curl toggle to close the network firewall.

Testing it

certbot has a convenient testing mechanism, to check it all works from a Let’s Encrypt perspective:

certbot renewal --dry-run

This mimics a renewal, including the pre- and post- hooks, but without actually doing a renewal.

(In addition, it’s worth checking the ufw rules afterwards, to make sure that what you expect to be deleted is deleted. You might also run the post script as a cronjob, just as a backup, although there’s a risk it could interfere with a genuine Let’s Encrypt renewal.)