← Back to All Writeups

Network Hardening & Segmentation with OpenBSD PF (Packet Filter)

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

  1. WAN Ingress Traffic: Can only reach explicit endpoints in the DMZ zone (80/tcp, 443/tcp).
  2. DMZ Isolation: DMZ hosts are strictly prohibited from initiating connections to the internal CORE network or the MGMT interface.
  3. 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.