LAN · OPT1 · OPT2
OpenVPN clients
across all interfaces
This started as a school project and turned into something I kept building long after the course ended. I wanted to understand what a real perimeter actually looks like — not just read about it. So I built one in VirtualBox: pfSense as the firewall, three isolated VLANs, a working VPN, and Suricata running in a mode where it can actually drop packets, not just complain about them.
The three-segment design (LAN for trusted clients, OPT1 as a DMZ-style server network, OPT2 as the isolated attacker network) wasn't chosen randomly — it mirrors what you'd see in an actual enterprise: a trusted zone, a service zone that needs limited access to production, and a segment you assume might be compromised. Everything between them is default-deny unless I explicitly wrote a rule for it.
Once the segmentation was solid, I layered in VPN (OpenVPN with certificate auth, not shared keys — more setup, but stolen credentials alone can't get you in) and then ran attack traffic from OPT2 to see what Suricata would catch. The alerts forwarded into my Splunk instance. That's when it clicked: a firewall log and an IDS alert on the same event tell a much more complete story than either one alone.
"I kept expecting the firewall to be the interesting part. Turns out it was alert tuning — figuring out what was actually malicious versus what was just my lab being noisy."
The interface assignments table below reflects the actual VirtualBox setup: four virtual NICs, each bound to a separate internal network adapter. Getting pfSense to recognize all four adapters on first boot took more troubleshooting than expected — the adapter ordering in VirtualBox doesn't always match what pfSense presents during interface assignment. The Suricata alerts at the bottom are from a live exercise, not synthetic data.
| Interface | Network Port | IP |
|---|---|---|
| WAN | em0 (vtnet0) | DHCP (ISP) |
| LAN | em1 (vtnet1) | 192.168.1.1/24 |
| OPT1 | em2 (vtnet2) | 192.168.2.1/24 |
| OPT2 | em3 (vtnet3) | 192.168.3.1/24 |
| IP Address | Hostname | State |
|---|---|---|
| 192.168.1.101 | WIN-CLIENT-01 | active |
| 192.168.1.102 | WIN-CLIENT-02 | active |
| 192.168.2.101 | KALI-OPT1 | active |
| 192.168.3.101 | UBUNTU-OPT2 | active |
The decision to use three segments instead of two came from thinking about what I actually wanted to test. LAN is where you'd put workstations in a real environment. OPT1 is where servers live — things that need limited, specific access to LAN services (like a Splunk indexer) but shouldn't be able to roam freely. OPT2 is the attacker box. The whole point is that OPT2 has no legitimate reason to ever talk to LAN directly. If traffic is crossing that boundary, something is wrong.
pfSense processes rules top-down and stops at the first match. That ordering is everything. I learned this the wrong way — I had a broad PASS rule on OPT1 that came before my specific BLOCK rules, so OPT1 could reach LAN freely. Flipping the order immediately fixed it. The rule table below reflects the final working state. The OPT1 BLOCK to LAN rule sits above the general PASS rule deliberately — if it were below, it would never fire.
| Interface | Protocol | Source | Destination | Port | Action | Purpose |
|---|---|---|---|---|---|---|
| LAN | TCP/UDP | LAN net | LAN address | 443, 80 | PASS | Allow access to pfSense WebUI |
| LAN | * | LAN net | * | * | PASS | Full outbound internet access |
| OPT1 | TCP | OPT1 net | 192.168.1.0/24 | 8000, 9997 | PASS | Allow Splunk indexer on LAN |
| OPT1 | TCP/UDP | OPT1 net | OPT1 address | 53 | PASS | DNS Resolver queries |
| OPT1 | * | OPT1 net | LAN net | * | BLOCK | Block lateral movement to LAN |
| OPT1 | * | OPT1 net | * | * | PASS | Internet access allowed |
| OPT2 | TCP/UDP | OPT2 net | OPT2 address | 53 | PASS | DNS Resolver only |
| OPT2 | * | OPT2 net | LAN net / OPT1 net | * | BLOCK | Full inter-VLAN isolation |
| Floating | * | * | * | * | IDS | Suricata inline inspection all ifaces |
Screenshots from the pfSense rule editor — individual block rules and the consolidated view:
Test results — what blocked traffic actually looks like from the client side. Each attempt produces a clean timeout or connection refused, confirming the rules are firing:
VPN setup from CA creation through client export:
The goal wasn't to "pass" a test — it was to understand what my own perimeter looks like to an attacker. OPT2 is the attacker machine. Everything it tries has to go through pfSense. I ran exercises in three stages, deliberately starting simple to confirm the fundamentals held before moving to more complex scenarios.
"The scan from OPT2 just timed out — no response at all. That's the correct behavior, but it took me a few minutes to distinguish 'rule is working' from 'host is down.' Confirming your own controls are actually firing is a skill."
- Rule ordering in pfSense bit me before I understood it. A broad PASS rule that sits above specific BLOCK rules makes those block rules unreachable. I had OPT1 talking to LAN freely for a while before I caught it. Always verify isolation is actually working — log a test connection attempt and watch for it explicitly, don't assume.
- Alert tuning was the most time-consuming part of the whole project by far. The ET Open ruleset out of the box fires on Windows Update traffic, ARP broadcasts, and legitimate DNS. I suppressed by source IP for known-benign internal hosts rather than disabling entire categories — more work upfront, but the rules still catch the same behavior from anything unexpected.
- Binding Suricata to all interfaces changed how I think about IDS placement. WAN-only coverage leaves a blind spot for anything that stays east-west. In a real SOC, half the interesting traffic never touches the perimeter.
- The "block without reply" behavior of pfSense is useful from a security standpoint but confusing to verify. A silent timeout from the attacker's side could mean the firewall rule fired, or the host is down, or the route is broken. Building the habit of checking pfSense logs to confirm a rule actually fired — not just assuming — is something I now do automatically.
- Certificate-based VPN is more setup than shared keys, but the tradeoff is real: someone who phishes your VPN password still can't connect without your certificate file. Worth the extra steps.
Official Netgate documentation and community resources used during this lab build.