diff --git a/assets/firewall-docker-bypass.svg b/assets/firewall-docker-bypass.svg new file mode 100644 index 0000000..7226a96 --- /dev/null +++ b/assets/firewall-docker-bypass.svg @@ -0,0 +1,4 @@ + + + +
Incoming Traffic
pfSense
Layer 1 — always applies
TARGET HOST — everything below this line is decided by what's running on it
firewalld
Layer 2 — native services only
Native Service
e.g. Alloy, Wazuh agent
Docker-published port
dozens of containerized apps
native path
Docker-published path — skips firewalld entirely
Solid orange = every packet, everywhere, always crosses pfSense first. Solid blue = native services get a second gate at the host level. Dashed red = the actual gap: a Docker-published port never reaches firewalld at all — pfSense is the only thing standing between it and incoming traffic.
\ No newline at end of file diff --git a/docs/firewall-and-network.md b/docs/firewall-and-network.md index 2f0c167..5f53f9c 100644 --- a/docs/firewall-and-network.md +++ b/docs/firewall-and-network.md @@ -62,6 +62,8 @@ rule family="ipv4" source address="[automation host]" port port="[this host's SS > [!WARNING] > **A real, documented gap**: on Docker hosts, container-published ports bypass firewalld entirely — Docker's own iptables rules are evaluated before firewalld's zone rules, so firewalld's local restrictions only actually protect the small number of *native*, non-containerized services on that host (things like the observability agent from [`docs/observability.md`](observability.md)), not the dozens of containerized apps in [`stacks/`](../stacks/). The network-level segmentation above still applies regardless — pfSense doesn't care whether a port is opened by firewalld or by Docker's own NAT rules — so this is a defense-in-depth gap specifically at the host layer, not an open-to-the-world one. Worth naming precisely rather than glossing over: firewalld protects less of a Docker host's actual attack surface than it might first appear to. +![Two enforcement layers per packet — pfSense always applies, firewalld only catches native services, Docker-published ports skip straight past it](../assets/firewall-docker-bypass.svg) + ## Edge firewall: three jobs, one device One device, three distinct responsibilities, not three separate boxes: