Files
hq/04-ISSUES/047-the-firewall-does-not-cover-published-container-ports/00-report.md
T
jschoubben 61f61b5d06 Reconcile the open issues against main
An audit of the six code repositories found eleven open issues fixed on main
with commits and beds to show (025, 027, 033, 036, 037, 040, 045, 047, 050,
052, 053), three partly (007, 026, 035), nine not (020, 031, 041, 046, 049,
054, 064, 065, 066) and one whose fix would live outside those repos (006).
Resolved ones name their evidence; partly ones say what remains; 041 records
that the exposure has widened since it was reported.
2026-09-21 01:52:53 +02:00

97 lines
4.7 KiB
Markdown

---
status: resolved
opened: 2026-09-14
located-in: []
fixed-by: mesh-controller c8d8211 (forward chain drops by default, never closes ssh), 25e42b3; proven by one-node-mesh.test.ts (a machine filters exactly what its modules declared)
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.
## The other half, which runs the opposite way
The section above is about ports the firewall **cannot close**. There is a matching fault about
ports it **does** close and should not, and together they are worse than either alone.
The rules are computed from what modules declare they listen on. During a migration a mesh knows
about almost nothing — the services are still running under the system being replaced — so it opens
almost nothing. Anything listening **on the host** rather than in a container is then dropped.
**Including ssh.** No module in the catalogue declares an ssh port, and the generator has no
built-in allowance for one. So the ruleset loaded on a machine that has not been told otherwise
accepts established connections, loopback and ping, and refuses every new ssh connection.
The session doing the loading survives, because established connections are accepted. It survives
until it drops. On a machine reached over the network that is the difference between a mistake and
a journey to a rescue console.
So the two halves are:
| | what it does | consequence |
|---|---|---|
| container ports, published | cannot see them, does not filter | stay open, protection silently lost |
| host ports, undeclared | sees them, drops them | **ssh among them** |
The mesh gets no say over the ports most worth protecting, and full say over the one that must never
be closed by accident.
## 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.