diff --git a/04-ISSUES/047-the-firewall-does-not-cover-published-container-ports/00-report.md b/04-ISSUES/047-the-firewall-does-not-cover-published-container-ports/00-report.md new file mode 100644 index 0000000..d72f232 --- /dev/null +++ b/04-ISSUES/047-the-firewall-does-not-cover-published-container-ports/00-report.md @@ -0,0 +1,69 @@ +--- +status: open +opened: 2026-09-14 +located-in: [] +fixed-by: +amended-design: +--- + +# 047 — The firewall does not cover published container ports + +## Symptom + +A machine running the mesh's firewall drops what it should drop and leaves every container port +published on all addresses open to anything that can reach the machine. + +Rehearsed in the lab on three machines, one probing another, with a single change between the two +measurements: + +| | ordinary port on the host | container port published on `0.0.0.0` | +|---|---|---| +| before the mesh's rules | reachable | reachable | +| after the mesh's rules | **refused** | **still reachable** | + +Same prober, same ports, same second. The first column is the firewall working. The second is the +question. + +## Why this is the way it is + +It is deliberate, and the reasoning is written where the rules are generated: the mesh's table hooks +`input` and `output` and **no forward chain**, because what a machine forwards is the container +runtime's business — the runtime writes rules for the bridges it creates, and a second table +dropping in `forward` would drop container traffic the runtime explicitly allowed, stopping every +container on the node including the control plane. + +That reasoning is sound. The conclusion drawn from it is not the only one available, because traffic +to a published port does not traverse `input` at all: it is redirected and then forwarded. So a +default-drop `input` chain says nothing whatsoever about it, and the firewall is silent on the ports +most likely to matter. + +## Why it matters, measured rather than argued + +The anchor this mesh will be installed onto publishes **38 distinct container ports on all +addresses** — a document store, three cache sentinels, mail transfer and retrieval, the forge's own +SSH, and thirty more. + +Today those are filtered, by the system being replaced, which does it with rules that hook exactly +the path this one leaves alone. So the cutover from the old firewall to this one **opens all +thirty-eight at once, silently, on a machine with a public address**, while every check reports a +firewall that is up and dropping by default. + +That is the worst shape a security fault can take: the mechanism is present, it reports success, and +the thing it is believed to be doing is not the thing it does. + +## How it would be checked + +Two probes from another machine, against a port on the host and a published container port, before +and after the rules are loaded. Both must change together. The lab can do this — it is how the table +above was produced — and nothing does it today. + +## Open questions + +- Can the mesh filter forwarded traffic without owning the whole `forward` chain? A table that + accepts by default and drops only what the mesh knows about would not stop the runtime's traffic, + and hooking `forward` at all is the thing the current comment rejects. +- Should a module's declared reach apply to its published ports? A module that says it listens to + the mesh only, and is then reachable from everywhere because its port is published, is not doing + what its manifest says. +- Is the answer instead that the mesh should not publish on all addresses? A port bound to the + private network needs no filtering, and most of these want exactly that.