diff --git a/docs/firewall-and-network.md b/docs/firewall-and-network.md index 5f53f9c..045d6f0 100644 --- a/docs/firewall-and-network.md +++ b/docs/firewall-and-network.md @@ -66,14 +66,11 @@ rule family="ipv4" source address="[automation host]" port port="[this host's SS ## Edge firewall: three jobs, one device -One device, three distinct responsibilities, not three separate boxes: +One device, three distinct responsibilities — kept together deliberately, not split into separate boxes, since none of the three is heavy enough on its own to justify another VM to patch and monitor: -- **Public ingress** — bogon block, SSH on a nonstandard port only. -- **HAProxy** — a second reverse-proxy layer, genuinely separate from the internal two-zone model in [`docs/dns-naming.md`](dns-naming.md), for the handful of apps meant to be reachable from the open internet, with automated ACME wildcard certs. Nothing else ever routes through it. -- **Security tooling** — Suricata inline IDS/IPS, plus certificate renewal. - -> [!TIP] -> **A disabled leftover, kept on purpose, not by accident.** HAProxy still has a second frontend defined for the LAN-facing zone — a holdover from before the Caddy VM took over user-facing internal routing. It's explicitly disabled, not just unused and forgotten: turning genuinely-dead config off cleanly, rather than letting it silently rot half-configured next to what's actually live, is its own small discipline worth keeping visible rather than tidying away. +- **Public ingress** — bogon-network block, SSH restricted to a single nonstandard port. Nothing else reaches this device from the WAN side at all. +- **HAProxy** — runs as pfSense's own package, not a separate VM. Fronts the dozen-plus apps meant to be reachable from the open internet, TLS-terminated with an automated ACME (Let's Encrypt, DNS-01) wildcard cert. A second reverse-proxy layer, genuinely separate from the internal two-zone model in [`docs/dns-naming.md`](dns-naming.md) — nothing internal-only ever routes through it. The backend table is kept live, not aspirational: one backend is currently disabled even though its routing entry still exists, and another is still active but already flagged for decommission — ordinary churn in a fleet that's actually maintained, not left to rot once it's written down. +- **Security tooling** — Suricata running *inline*, not just monitoring, on both the WAN and LAN interfaces, with custom rules layered on top of the stock ruleset for two things worth protecting twice: DNS is forced to resolve only through Technitium — the same constraint already enforced independently at the firewall layer above, a deliberate belt-and-suspenders pairing, not accidental duplication — and Vault's access path gets its own dedicated Suricata rule too, on top of the per-client firewalld rich-rules already covering it host-side. Same asset, two independent layers, same pattern as the DNS rule. > [!IMPORTANT] > **Failure impact, thought through in advance, not discovered during an actual outage**: if pfSense goes down, public access and outbound internet both go with it — but every internal service that doesn't depend on WAN reachability keeps running normally, including the orchestration flows in [`docs/orchestration.md`](orchestration.md), which only ever reach internal hosts. The blast radius is deliberately bounded to "the edge," not "the fleet" — a design property worth stating explicitly rather than leaving implicit, since it's exactly the kind of thing that should be known *before* an incident, not discovered during one. One thing does quietly degrade even though nothing breaks outright, and it's worth being honest about rather than rounding up to "everything's fine": Vault Agent's certificate renewal and Technitium's own PKI issuance both route through pfSense-mediated LAN paths, so an extended outage would eventually stall renewals fleet-wide — not an immediate failure, but a real one if the outage runs long enough to matter.