docs: publish 2026-04-26

This commit is contained in:
lerko96
2026-04-26 22:26:14 -04:00
parent 6730781dd0
commit a061d37602
22 changed files with 388 additions and 2032 deletions
+40 -106
View File
@@ -1,130 +1,64 @@
# Network
VLAN map, firewall policy, DNS architecture, and physical topology. See [README](../README.md) for the big picture and [Services](SERVICES.md) for what lives where.
How I think about segmentation and why the policy looks the way it does. Specific subnets, VLAN IDs, IP plans, and firewall rule listings live in the private repo.
## VLAN Map
## Why segmentation matters here
| VLAN ID | Name | Subnet | Gateway | DHCP Range | DNS |
|---------|------|--------|---------|------------|-----|
| 1000 | MGMT | 10.0.0.0/24 | 10.0.0.1 | 10.0.0.100150 | pfSense only |
| 1010 | LAN | 10.1.0.0/24 | 10.1.0.1 | 10.1.0.100200 | Pi-hole → pfSense |
| 1020 | Homelab | 10.2.0.0/24 | 10.2.0.1 | 10.2.0.100200 | Pi-hole → pfSense |
| 1030 | Guests | 10.3.0.0/24 | 10.3.0.1 | 10.3.0.100250 | Pi-hole → pfSense |
| 1040 | IoT | 10.4.0.0/24 | 10.4.0.1 | 10.4.0.100250 | Pi-hole → pfSense |
| 1050 | WFH | 10.5.0.0/24 | 10.5.0.1 | 10.5.0.100200 | pfSense only |
| 1099 | DMZ | 10.99.0.0/24 | 10.99.0.1 | static only | pfSense only |
| — | VPN | 10.200.0.0/24 | pfSense | assigned by WG | Pi-hole → pfSense |
A homelab pulls together an unusually wide trust spread on one piece of hardware: cloud-managed IoT devices that phone home constantly, a work laptop that touches an employer network, a guest WiFi that strangers join, internal services holding sensitive data, and admin surfaces that should never be exposed. Treating all of that as one flat network treats it like it has the same trust level. It doesn't.
## Firewall Policy
The model here is **trust-tier VLANs** with explicit policy between them. Every tier has a documented purpose and a defined inbound/outbound posture.
Default: **deny all inter-VLAN unless explicitly allowed.**
## Trust tiers
| VLAN | Policy Summary |
|------|---------------|
| LAN (1010) | Full internet; can reach Homelab + MGMT; blocked from Guest/IoT/WFH |
| Homelab (1020) | Internet for updates (HTTP/S, SSH, NTP); cannot initiate to other VLANs |
| Guests (1030) | Internet only — hard block on all RFC1918 |
| IoT (1040) | Internet + Home Assistant (explicit rule); blocked from LAN |
| WFH (1050) | Internet only; pfSense DNS only; no personal network access |
| MGMT (1000) | Updates + NTP outbound; inbound from LAN + VPN only |
| DMZ (1099) | HTTP/S + NTP outbound; hard-blocked from all internal VLANs |
| VPN (10.200.0.0/24) | Same as LAN: Homelab + MGMT web GUI + Pi-hole DNS |
Seven VLANs, organized roughly by how much I trust what's on them:
## Static IP Reservations
| Tier | What's on it | Posture |
|---|---|---|
| **Management** | Hypervisor, firewall, backup server, network controllers | Most trusted. Reachable only via VPN. Doesn't initiate outbound unless it has to. |
| **Internal services** | LXCs and VMs running the internal app stack | Trusted. Serves clients in adjacent tiers per policy. |
| **LAN** | Personal devices on home WiFi/Ethernet | Trusted. Consumes internal services. |
| **Work-from-home** | Employer-owned laptop | Untrusted lateral. Internet only — blocked from everything else, including internal DNS. |
| **IoT** | Smart devices, cloud-managed appliances | Untrusted. Internet only. Isolated from everything internal. |
| **Guest** | Visitor WiFi | Untrusted. Internet only. |
| **DMZ** | Internet-facing services | Treated as compromised by default. Locked down on outbound; inbound to internal is a tight allowlist. |
| **VPN (WireGuard)** | Authenticated remote clients | Same posture as LAN, plus admin-tier visibility. |
### VLAN 1000 — MGMT
## Policy posture
| IP | Device |
|----|--------|
| 10.0.0.1 | pfSense MGMT |
| 10.0.0.2 | Omada Switch |
| 10.0.0.3 | Guest AP |
| 10.0.0.4 | IoT AP |
- **Default deny inter-VLAN.** Every cross-tier flow is an explicit allow rule with a reason written next to it.
- **WFH and IoT are jailed.** They reach the internet and nothing else internal — not even DNS for the local hostnames. This is the most important rule in the firewall.
- **Management is the smallest possible tier.** Only what *runs* the lab lives there. No user-facing services. No outbound internet from anything that doesn't strictly need it.
- **DMZ is one-way.** Public services live there. They can't initiate connections inward except through a tight, firewall-enforced allowlist by source IP and destination port. The reverse proxy in the DMZ is *configured* to respect that, and the firewall is *also* configured to enforce it. Two layers, on purpose — misconfiguring the proxy is way easier than misconfiguring the firewall.
- **Admin surfaces are VPN-only.** Hypervisor, firewall, backup server, switches, APs — none of them are reachable from the internet. WireGuard first or it doesn't happen.
### VLAN 1010 — LAN
## DNS
| IP | Device |
|----|--------|
| 10.1.0.1 | pfSense LAN gateway |
Three layers, each doing one job:
### VLAN 1020 — Homelab
1. **Pi-hole** — first hop for clients on most VLANs. Filters ad/tracker domains and holds the local A records that map internal hostnames to internal IPs. Not used by management hosts (see below) or by the WFH VLAN.
2. **Unbound on the firewall** — Pi-hole's upstream. Recursive resolver, validates DNSSEC.
3. **Cloudflare** — Unbound's eventual upstream when needed.
| IP | Device |
|----|--------|
| 10.2.0.1 | pfSense Homelab gateway |
| 10.2.0.10 | Proxmox |
| 10.2.0.11 | Pi-hole |
| 10.2.0.20 | Caddy (infra LXC) |
| 10.2.0.21 | Vaultwarden (vault LXC) |
| 10.2.0.25 | Authentik (auth LXC) |
| 10.2.0.51 | Monitor LXC |
| 10.2.0.60 | Apps LXC |
**Bootstrap exception:** the hypervisor itself (which is the box Pi-hole runs on) is statically pointed at the firewall's resolver, not Pi-hole. Otherwise there's a circular dependency at boot — the hypervisor needs DNS to come up, and Pi-hole is one of the things the hypervisor brings up.
### VLAN 1099 — DMZ
**Known SPOF:** Pi-hole is the only thing resolving internal hostnames. If it dies, internal hostnames stop resolving until it's back. I thought about mirroring the records into Unbound on pfSense and decided not to — I'd rather know if Pi-hole is unhealthy than paper over it. Documented as a known limitation in the private repo.
| IP | Device |
|----|--------|
| 10.99.0.1 | pfSense DMZ gateway |
| 10.99.0.20 | Public Service A |
| 10.99.0.22 | Public Service B |
| 10.99.0.23 | Public Service C |
## Internet exposure
## IP Block Allocation (VLAN 1020)
Three ports forwarded from WAN to internal:
| Block | Purpose |
|-------|---------|
| 10.2.0.19 | Infrastructure (gateway, pfSense interfaces) |
| 10.2.0.1019 | Network critical (Proxmox, Pi-hole) |
| 10.2.0.2029 | Auth / Proxy (Caddy, Authentik, Vaultwarden) |
| 10.2.0.3039 | Observability |
| 10.2.0.4049 | Dev tools |
| 10.2.0.5059 | Data |
| 10.2.0.6069 | Apps |
| 10.2.0.7079 | Files |
| 10.2.0.8099 | Media |
| 10.2.0.100+ | DHCP pool (dynamic) |
- **HTTP / HTTPS** — to the DMZ reverse proxy. Serves the small public service set.
- **WireGuard** — to the firewall. The only remote admin path.
## DNS Architecture
Everything else is closed. I verify this from outside the network on a regular basis — the only way to actually know what's exposed is to scan from somewhere that isn't the LAN.
```
Device → Pi-hole (10.2.0.11)
pfSense Unbound (10.x.0.1) — local records + DHCP hostnames
Cloudflare 1.1.1.1 (upstream)
```
## IPv6
- Pi-hole: ad/tracker blocking, local DNS records (all `*.lerkolabs.com` → 10.2.0.20 Caddy), query logging
- pfSense Unbound: DHCP hostname registration, backup resolver if Pi-hole is down
- WFH VLAN: pfSense DNS only — Pi-hole unreachable by design
Disabled at the carrier-provided gateway. The lab is IPv4-only by design — fewer surfaces, simpler firewall reasoning, no AAAA leakage. I'll revisit this if I have a reason to; today I don't.
## Physical Topology
## Things that are easy to overlook
```
AT&T Fiber ONT
|
AT&T BGW320 (IP Passthrough)
|
pfSense N100 (WAN/LAN)
|
Omada Managed Switch
├── Trunk port → pfSense (all VLANs tagged)
├── VLAN 1000 — MGMT devices
├── VLAN 1010 — Desktop / LAN
├── VLAN 1020 — Proxmox / Homelab servers
├── VLAN 1030 — Guest WiFi AP
├── VLAN 1040 — IoT WiFi AP
├── VLAN 1050 — Work laptop
└── VLAN 1099 — DMZ
```
A couple of things worth being explicit about, because they bit me at some point:
## WireGuard VPN
| Property | Value |
|----------|-------|
| Listen Port | 51820 UDP |
| VPN Subnet | 10.200.0.0/24 |
| Access granted | Homelab + MGMT web GUI + Pi-hole DNS |
| Access blocked | Guest, IoT, WFH |
No management ports (22, 8006, 443) exposed to the internet. WireGuard is the only inbound port on the WAN interface (aside from Cloudflare DNS-01 challenge traffic, which uses no inbound ports).
- **Intra-VLAN traffic between LXCs on the same Proxmox bridge doesn't traverse the firewall.** Isolation is enforced *per-VLAN*, not *per-LXC*. Two LXCs sharing a tier can talk to each other directly. Useful to remember when you're reasoning about blast radius — the firewall doesn't see anything that doesn't cross a VLAN boundary.
- **Certificate Transparency.** Caddy uses Cloudflare DNS-01 for cert issuance, which is great because services don't have to be exposed to the internet to get a cert. But every cert that gets issued lands in CT logs forever, and per-hostname certs basically publish the internal hostname inventory to anyone who runs a CT search on the domain. A wildcard cert would limit CT exposure to `*.lerkolabs.com` and the apex; it's on my list as a future change, with the tradeoff being that wildcard compromise is worse than per-host.