VPN and kill switch
Every network except the PS5 and guests reaches the internet only through a WireGuard tunnel to a commercial VPN provider. If the tunnel goes down, those networks lose the internet rather than falling back to the provider's line.
- Protocol
- WireGuard, in the OpenBSD kernel
- Fails
- Closed: no tunnel, no internet
- Enforced by
- Separate routing tables, and pf on the WAN port
- Exceptions
- PS5 and guests, by policy routing
- DNS
- Through the tunnel too, with ad and tracker blocking
Two independent layers#
Routing. The WAN port lives alone in its own routing domain. Every internal network, and the tunnel, live in another, whose only default route is the tunnel. When the tunnel is down, packets have nowhere to go: that routing table simply has no path to the WAN.
pf. On the WAN port, only three things may leave: the encrypted tunnel to the VPN server, DHCP, and traffic explicitly tagged as an exception. Anything else is blocked and logged as block_wan_leak, which raises an alert. If the first layer were ever misconfigured, the second would catch it, and say so.
The key never leaves the firewall#
The WireGuard private key was generated on the firewall, and only the public key was registered with the provider. Git holds a template with a placeholder; if the firewall is lost, a new key is generated rather than restored.
Watching the tunnel#
A small script runs every minute and logs an error when the last WireGuard handshake is more than three minutes old. The SIEM alerts on it, so an outage is explained before anyone wonders why the internet stopped.
Configuration#
# WAN: alone in routing domain 1, address from the provider by DHCP
description "WAN"
rdomain 1
inet autoconfdescription "VPN"
wgkey <private key>
wgrtable 1 # the encrypted packets themselves use routing domain 1
wgpeer <server key> wgendpoint <server address> 51820 wgaip 0.0.0.0/0 wgpka 25
inet <tunnel address>/32
!route -q add -inet default -link -iface wg0 # the only default route in domain 0wgrtable 1is the trick: the tunnel's outer packets travel through the WAN's table, while everything inside it belongs to the internal table.
match out on $vpn inet from <internal_nets> nat-to ($vpn)
match on $vpn scrub (max-mss 1380) # room for WireGuard's overhead
pass out quick on $wan inet proto udp to <vpn_server> port 51820 \
label "allow_wan_wireguard"
pass out quick on $wan inet proto udp from any port bootpc to any port bootps \
label "allow_wan_dhcp_out"
pass out quick on $wan inet tagged DIRECT label "allow_direct_out"
block out log quick on $wan label "block_wan_leak"- The last line is the safety net: anything else heading out of the WAN port is stopped and becomes an alert.