Networks
A firewall you can read out loud
We judge a ruleset by whether a new engineer can read it aloud in a meeting and have everyone agree on what it does.
We inherited a firewall configuration last spring that had been appended to
since 2011. Nine hundred lines. Four authors, three of whom had left. A comment
on line 412 that read # temporary — remove after migration.
Nobody could say what it did. Over the years the configuration had stopped describing a policy and turned into layers of sediment.
The test
A good ruleset passes one test: a competent engineer who has never seen it can read it out loud in a meeting, and everyone in the room agrees on what it does. That is a higher bar than “it works”, and almost every long-lived configuration fails it.
The OpenBSD packet filter makes that bar reachable. Macros let the rules use your own names for things, tables let you refer to a set of addresses by one name, and anchors let you reason about a section on its own. The syntax is close enough to English that you can literally read it aloud:
web_servers = "{ 10.44.0.10, 10.44.0.11 }"
table <admins> persist file "/etc/pf/admins"
block all
pass in on egress proto tcp to $web_servers port { 80 443 }
pass in on wg0 proto tcp from <admins> to any port 22
That is the whole thing. You do not need a vendor course to follow it, and
there is no guessing about evaluation order: in pf the last matching rule wins,
and quick marks the exceptions explicitly.
Rules are code
The second half of the fix is procedural. The ruleset lives in the same repository as everything else, changes through pull requests, and is reviewed by a person who has to approve the diff.
That alone gets rid of “temporary” rules like the one from 2011. A rule with no author and no rationale does not survive review, and the diff shows the gap between what you meant and what you typed before it reaches production, instead of during an audit.
What it came to
Nine hundred lines became a hundred and forty. Roughly two hundred of the originals were dead: hosts that no longer existed, and ports for a service decommissioned in 2017. Most of the rest collapsed into tables and macros, because the same eight addresses had been written out longhand in thirty-one places.
The new engineer read it through on their first morning.