capitalize headings
This commit is contained in:
16 files changed
+65
-65
No files matched your search
@@ -3,11 +3,11 @@
|
||||
> [!NOTE]
|
||||
> pfSense at the edge, firewalld on every individual host — two layers, two different jobs, and a network model with no blanket "anything on the internal network can talk to anything else on it" rule anywhere. Every real flow is explicit; everything else is logged and dropped. This is the enforcement layer underneath the trust boundaries described in [`docs/architecture-overview.md`](architecture-overview.md).
|
||||
|
||||
## Why two layers, not one
|
||||
## Why Two Layers, Not One
|
||||
|
||||
A single firewall layer was rejected early: the edge firewall (pfSense) is the fleet's only genuinely public-facing component, so it carries a different threat model — internet-scale scanning and abuse — than internal host-to-host traffic ever will. Collapsing both into one ruleset would mean either the internal segmentation rules get diluted by public-facing noise, or the public-facing rules get relaxed to match internal assumptions. Keeping them separate means each layer's rules can be as strict as that layer's actual threat model demands, independently.
|
||||
|
||||
## The one rule that matters most
|
||||
## The One Rule That Matters Most
|
||||
|
||||
This wasn't always this tight. The LAN interface used to run pfSense's own standard default — a blanket allow between hosts — before it was deliberately narrowed to LAN→WAN/internet only, closing off unrestricted lateral movement that had nothing to do with any application actually needing it. What replaced it is the model below.
|
||||
|
||||
@@ -24,7 +24,7 @@ That narrowing and the catch-all it produced aren't just claims — here's the a
|
||||
|
||||
DNS enforcement specifically doesn't rely on just one mechanism. Every static-lease device gets the internal DNS server pushed via a per-lease DHCP override — every current static lease carries it — but that's a soft guarantee: a brand-new device on a plain dynamic lease would fall back to the router's own upstream resolvers instead, at least until it gets a static reservation of its own. The firewall closes that gap independently, at the network level: an explicit rule blocks any LAN client from reaching external DNS outright, full stop. So a device that somehow ended up pointed at the wrong resolver still can't actually use it — DNS bypass isn't just discouraged by configuration, it's structurally blocked by a second, unrelated layer that doesn't depend on the first one being right.
|
||||
|
||||
## Named groups, not raw addresses
|
||||
## Named Groups, Not Raw Addresses
|
||||
|
||||
pfSense aliases (named groups) stand in for hardcoded IP lists throughout — adding a host to a group is a single membership edit, not a hunt through every rule that needs to reach it. Groups nest: a "whole managed fleet" alias is composed of several narrower per-role aliases, so a rule against the broad group automatically covers new members of any narrower one without the rule itself ever changing.
|
||||
|
||||
@@ -34,7 +34,7 @@ The same discipline applies to the other axis of every rule, and it's still acti
|
||||
|
||||

|
||||
|
||||
## Per-host firewalls: minimal, not blanket
|
||||
## Per-host Firewalls: minimal, not blanket
|
||||
|
||||
Each host runs firewalld locally, opening only the specific ports it actually needs — not a blanket "allow everything, block nothing" default. Vault's host goes further than the rest: instead of one open port for its most sensitive service, individual source IPs get their own explicit rich-rule — five named clients, confirmed directly against the live ruleset, and zero blanket ports open at all — so a compromised unrelated service on the same network still can't reach it even if it somehow got past pfSense. This is the actual live output, not a paraphrase:
|
||||
|
||||
@@ -83,7 +83,7 @@ hawser: 0.0.0.0:2376->2376/tcp, [::]:2376->2376/tcp
|
||||
|
||||
Three ports, zero matching firewalld rules, all three genuinely reachable LAN-wide via Docker's own NAT — not a hypothetical, the exact gap above on a host provisioned this same week.
|
||||
|
||||
## Edge firewall: three jobs, one device
|
||||
## Edge Firewall: three jobs, one device
|
||||
|
||||
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:
|
||||
|
||||
@@ -94,7 +94,7 @@ One device, three distinct responsibilities — kept together deliberately, not
|
||||
> [!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.
|
||||
|
||||
## Defense-in-depth below the network layer: PAM
|
||||
## Defense-in-depth Below The Network Layer: PAM
|
||||
|
||||
Firewalls aren't the only layer that matters. On this fleet's Debian-family hosts, the login stack itself got hardened after a real, *recurring* incident — not a one-off. `pam_unix.so`'s default auth line uses a numbered jump ("on success, skip forward N modules") to reach approval, which is fragile by construction: any future package that inserts its own line anywhere in that stack silently shifts what the number actually lands on. This broke real, correct-password logins **twice**: once during initial rollout, fixed by bumping the number up by one — and again five days later, on a different host, when an unrelated package pulled in its own PAM line and broke that same numbered jump a second time, landing on a deny instead of an allow. A correct password got treated as a failure. The permanent fix replaced the number with a keyword that terminates the stack on success immediately, regardless of how many lines ever get added after it later — closing the entire class of bug, not just the one instance that got caught. Rocky/RHEL hosts never carried this risk in the first place; they manage their PAM stack through `authselect`, a declarative system not exposed to the same ordering hazard.
|
||||
|
||||
|
||||
Reference in new issue
Block a user