Files
public/docs/firewall-and-network.md
T
thesabear 41a6f569b4 docs: rework security/hardening batch with diagrams and callouts
Mermaid diagrams up front, GFM alert callouts for key decisions and
gotchas, tables instead of bullet walls - replaces plain prose with
the visual template established for security-monitoring.md.
2026-08-21 23:17:10 +03:00

3.9 KiB

🧱 Firewall & Network Segmentation

Note

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.

flowchart TD
    Internet(["Internet"]) --> Edge
    subgraph Edge["Edge Firewall"]
        Bogon["Bogon block +<br/>IP-reputation blacklist"]
        NonstdSSH["SSH — nonstandard port only"]
        PubProxy["Public reverse-proxy layer"]
        IDS["Inline IDS/IPS"]
    end
    Edge -->|explicit rules only| LAN

    subgraph LAN["Internal Network"]
        direction TB
        Rule1["DNS · Secrets/PKI · Reverse proxy<br/>Metrics/logs · Security agents<br/>NTP · Admin SSH · File sharing"]
        Catchall["Everything else:<br/>default-deny + log"]
    end

The one rule that matters most

Every real service-to-service flow — DNS lookups, secrets/PKI traffic, reverse-proxy access, metrics/log shipping, security-agent reporting, NTP, admin SSH, file sharing, and a handful of specific app-to-app integrations — gets its own explicit allow rule. Anything not explicitly listed hits a default-deny-and-log catch-all. This catch-all is the fleet's actual lateral-movement boundary, not any single host's own firewall.

Tip

A few explicit rules are narrower than they look on purpose — inbound access to the secrets platform's own admin API, for instance, is gated to only the two or three services that ever legitimately need it, not the whole fleet.

Named groups, not raw addresses

Every rule references named groups instead of hardcoded addresses — 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" group is composed of several narrower per-role groups, so a rule against the broad group automatically covers new members of any narrower one.

Per-host firewalls: minimal, not blanket

Each host's own local firewall opens only the specific ports it actually needs. One host goes further: instead of one open port for its most sensitive service, individual source IPs get their own explicit rule.

Warning

A real, documented gap: on Docker hosts, container-published ports bypass the host's own firewall entirely — the container runtime's own network rules are evaluated first. Per-host firewall rules only actually protect the small number of native, non-containerized services on that host. The network-level segmentation above (and the edge firewall further out) still applies regardless — defense-in-depth gap, not an open-to-the-world one.

Edge firewall: three jobs, one device

flowchart LR
    EdgeFW["Edge Firewall"] --> J1["Public ingress<br/>bogon block, IP-reputation feed,<br/>nonstandard SSH port"]
    EdgeFW --> J2["Second reverse-proxy layer<br/>for genuinely public apps only,<br/>automated wildcard certs"]
    EdgeFW --> J3["Security tooling<br/>inline IDS/IPS, cert renewal"]

Important

Failure impact, thought through in advance: if the edge firewall 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. The blast radius is deliberately bounded to "the edge," not "the fleet."

The hypervisor's own firewall: deliberately unused

The virtualization platform underneath everything has its own built-in firewall capability, left with an empty ruleset on purpose. The edge firewall and each host's own local firewall already cover the two layers that matter — a third overlapping layer would add complexity without meaningfully improving posture. Recognizing when not to add a redundant control is as much a design decision as adding one.


Related stacks: stacks/platform/caddy/, stacks/platform/caddy-internal/, stacks/platform/docker-socket-proxy/