Firewall
A small six-port PC running OpenBSD is the only path between the networks, and between the networks and the internet. It has no web interface: every setting is a text file kept in Git.
- Hardware
- Fanless mini PC: Intel N100, 8 GB RAM, 128 GB NVMe, six 2.5 GbE ports
- System
- OpenBSD 8.0, base system only, no GUI
- Runs
- pf, unbound, dhcpd, syslogd, WireGuard
- Networks
- Eight, on five ports, three of them VLANs
- Managed as
- Plain files in a Git repository, deployed with one command
Why OpenBSD#
pf reads like a policy written in English, and OpenBSD's defaults are conservative: nothing listens unless it is asked to. Replacing a GUI firewall with text files made every change reviewable, diffable and reversible.
Loading a change is one command that checks the syntax first, installs the file and refreshes the logging helper. A broken file is refused before it can take effect.
In the configuration#
#!/bin/sh
# Check, install and load a new pf.conf, then refresh the rule-name map.
set -e
src=${1:-/tmp/pf.conf}
pfctl -nf "$src" # syntax check: refuse a broken file
install -o root -g wheel -m 0600 "$src" /etc/pf.conf
pfctl -f /etc/pf.conf
rcctl restart pflog2syslog # rebuilds rule number -> label map- Every deployment goes through this script, so a typo can never reach the running firewall.
Go deeper#
- PolicyDefault deny, named rules, and a short list of what may talk to what.
- VPN and kill switchAll traffic leaves through WireGuard; if the tunnel fails, the networks go offline instead of leaking.
- DNSOne resolver for every network: local names, blocking, and no way around it.
- Logging pipelineFrom a pf decision to a searchable event in OpenSearch within seconds, over TLS.
- AddressingThe same blocks in every network, so an address alone says what a device is.
