Network segmentation is not a secondary layer of defense: it is the primary boundary that stops lateral movement if an edge node is ever compromised. On OpenBSD systems, Packet Filter (PF) provides a deterministic, high-performance, stateful filtering engine that enables expressing least-privilege security policies directly in clean plain text.
1. Zone-Based Segmentation Topology
A resilient architecture segregates network flows into isolated security domains using physical interfaces or tagged VLANs (802.1Q):
+-------------------+
| WAN / ISP |
+-------------------+
|
[ vio0: WAN ]
|
+-------------------+
| OpenBSD Edge |
| PF Firewall |
+-------------------+
/ | \
[ vlan10: DMZ ] ----/ | \---- [ vlan30: MGMT ]
| | |
+---------------------+ | +---------------------+
| Public Services | | | Out-of-Band Admin |
| (httpd, relayd) | | | (SSH bastion, PDU) |
+---------------------+ | +---------------------+
|
[ vlan20: CORE LAN ]
|
+-------------------------+
| Workstations & Storage |
+-------------------------+
Core Isolation Rules
- WAN Ingress Traffic: Can only reach explicit endpoints in the DMZ zone (
80/tcp,443/tcp). - DMZ Isolation: DMZ hosts are strictly prohibited from initiating connections to the internal CORE network or the MGMT interface.
- Management Zone (MGMT): Completely unreachable from the public internet and DMZ; accessible only from authorized internal IPs via ED25519 SSH keys with MFA.
2. Complete Packet Filter Ruleset (/etc/pf.conf)
Below is the hardened /etc/pf.conf configuration featuring packet normalization, brute-force mitigation tables, and strict stateful policies:
# ==============================================================================
# /etc/pf.conf - Network Segmentation & Hardening Policy
# ==============================================================================
# --- 1. Interfaces & Macros ---
if_wan = "vio0"
if_dmz = "vlan10"
if_lan = "vlan20"
if_mgmt = "vlan30"
net_dmz = "172.16.10.0/24"
net_lan = "10.0.20.0/24"
net_mgmt = "10.0.30.0/24"
pub_ports = "{ 80, 443 }"
# --- 2. Dynamic Tables & Blacklists ---
table <bruteforce> persist
table <bogon_ips> persist file "/etc/pf.bogons"
# --- 3. Normalization & Global Limits ---
set skip on lo
set block-policy drop
set loginterface $if_wan
set state-defaults pflow, no-sync
# State table sizing and timeouts
set limit states 50000
set limit src-nodes 20000
set timeout { interval 10, frag 30 }
# Packet scrubbing & reassembly
match in all scrub (no-df random-id max-mss 1440 reassemble tcp)
# Antispoofing on all interfaces
antispoof quick for { $if_wan, $if_dmz, $if_lan, $if_mgmt }
# --- 4. Default Deny Policy ---
block log all
block quick from <bogon_ips>
block quick from <bruteforce>
# --- 5. Egress from Firewall Host ---
pass out on $if_wan proto udp to any port domain keep state
pass out on $if_wan proto udp to any port ntp keep state
pass out on $if_wan proto tcp to any port { http, https } keep state
# Controlled ICMP (Path MTU Discovery)
pass in inet proto icmp icmp-type { echoreq, unreach, timex } keep state
pass out inet proto icmp keep state
# --- 6. WAN Ingress Rules (DMZ Public Services) ---
pass in quick on $if_wan proto tcp to ($if_wan) port $pub_ports \
flags S/SA synproxy state \
(max-src-conn 100, max-src-conn-rate 30/5, overload <bruteforce> flush global)
# Rate-limited SSH to Firewall
pass in quick on $if_wan proto tcp to ($if_wan) port 22 \
flags S/SA keep state \
(max-src-conn 5, max-src-conn-rate 3/60, overload <bruteforce> flush global)
# --- 7. Inter-VLAN Routing & Isolation ---
# LAN -> Internet (Outbound NAT)
match out on $if_wan from $net_lan to any nat-to ($if_wan)
pass in on $if_lan from $net_lan to any keep state
# LAN -> DMZ (Internal testing allowed)
pass in on $if_lan from $net_lan to $net_dmz keep state
# DMZ -> LAN / MGMT: BLOCKED BY DEFAULT (Prevents lateral movement)
pass in on $if_dmz from $net_dmz to ! { $net_lan, $net_mgmt } keep state
# MGMT -> All interfaces (Authorized management)
pass in on $if_mgmt from $net_mgmt to any keep state
3. Real-Time Verification
# Validate syntax before loading into kernel
doas pfctl -nf /etc/pf.conf
# Load rules live
doas pfctl -f /etc/pf.conf
# Monitor live block counters and state tables
doas pfctl -s info
doas pfctl -t bruteforce -T show
doas tcpdump -n -e -ttt -i pflog0
Summary
Combining OpenBSD PF’s dynamic tables, synproxy protections, and default-deny inter-VLAN controls prevents reconnaissance and lateral compromise across enterprise networks.