Temporarily and automatically changing firewall rules to permit Let's 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:

  • certbot for the Let's Encrypt renewals
  • bash scripts for pre and post renewal-hooks for the certbot renewals
  • an application profile for ufw to go in the bash script
  • a curl command for the network firewall

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


Author: neil

I'm Neil. By day, I run a law firm, decoded.legal, giving advice on Internet, telecoms, and tech law. This is my personal blog, so will be mostly about tech stuff, cycling, and other hobbies.

You can find me (and follow me) on Mastodon and Twitter.