ADR 0140: the filter constrains what arrives from outside, and says nothing about a machine's own guests

Reading a converged machine's rendered rules showed the cause: the chain blocks
everything passing through and then allows the machine's own containers back by
listing their address ranges. 0137 made that list typeable and 0139 tried to
generate it; both refined a list that should not exist, because the mesh has no
position on a container reaching outward. Constrain what arrives from outside,
allow what did not, and let the machine report which links face outside — one
fact instead of a list. Ports keep following the modules unchanged.

The records check now allows one record to supersede several, and stops
requiring a withdrawn record's own citations to be live.
This commit is contained in:
2026-09-28 23:31:50 +02:00
parent 08108b569d
commit 1aeb4fe8d8
7 changed files with 216 additions and 34 deletions
+35 -27
View File
@@ -10,7 +10,7 @@ code:
updated: 2026-09-28
decisions:
- 02-DECISIONS/0138-an-assignment-binds-an-endpoint-and-says-how-far-it-reaches.md
- 02-DECISIONS/0139-a-network-is-forwarded-because-a-module-declared-it.md
- 02-DECISIONS/0140-the-filter-constrains-what-arrives-from-outside.md
- 02-DECISIONS/0104-a-provision-may-be-answered-by-an-adapter-to-the-predecessor.md
- 02-DECISIONS/0106-the-bus-is-nats.md
- 02-DECISIONS/0105-the-mesh-adopts-the-predecessors-tunnel-in-place.md
@@ -627,41 +627,49 @@ the found firewall reloads and reachable from a container on the node, that a ma
enrols through the openings before and after a reload and a reboot, and that after the flip the
declared port is open and the undeclared one closed.
### The networks it forwards are the ones its modules declared
### It filters what arrives from outside, and not what the machine's own guests send
*2026-09-28, preparing the control-node's convergence —
[issue 141](../../04-ISSUES/141-the-forward-chain-does-not-follow-the-modules/00-report.md), settled by
[ADR 0140](../../02-DECISIONS/0140-the-filter-constrains-what-arrives-from-outside.md), which replaces
[ADR 0137](../../02-DECISIONS/0137-a-machine-says-which-networks-it-routes.md) and
[ADR 0139](../../02-DECISIONS/0139-a-network-is-forwarded-because-a-module-declared-it.md).*
The forward chain denies by default, so a machine's own guests have to be allowed back in. Until now
that was two ranges named in the control plane and, since
[ADR 0137](../../02-DECISIONS/0137-a-machine-says-which-networks-it-routes.md), a list a machine could
add to. On the machine measured here, six of its container networks fell outside those ranges — and
four of the six were networks the mesh's own modules had declared and the host had created, while two
were the predecessor's leftovers. A range wide enough to keep the four keeps the two: a filter widened
by hand to protect what should not be there.
Traffic passing through a machine is filtered, because a container's published port is traffic passing
through rather than traffic arriving at the machine itself. Having blocked it, the filter then had to
let the machine's own containers reach outward again — and it did that by listing the address ranges
they sit on. Two of those ranges were constants in this repository's code, and the rest were typed by an
operator after the flip had already cut a workstation's containers off from everything.
**A network is forwarded because a module declared it.** The forward chain forwards the networks of the
modules assigned to that node and, by default, nothing else; a module unassigned stops being forwarded
at the next reconcile. The control plane says which networks, by name, and the host — which created
them — renders their addresses, because the runtime allocates the subnet and the host is what knows it.
That keeps §4's shape and this document's: the mesh decides, the host applies.
**The list was the mistake, not its contents.** A constant describes one machine. A typed range goes
stale and cannot tell a network the mesh made from one a predecessor left behind — on the control-node,
six ranges fall outside the constants and two of the six belong to services the mesh does not run. The
attempt to generate the list from the modules put half the rule set on the machine and made the
derivation advisory. Three records, one list.
**This is derivation from the declaration, not from the machine.** 0137 rejected reading the machine,
for two reasons that do not apply here: a network created between two declarations is already named in
the one that asked for it, and a network nobody declared is never forwarded however it appeared. What
0137's mechanism keeps is the case it was right for — guests no module declares, a test bed's range —
added to the derived set and never replacing it.
**And the mesh has no position on a container reaching outward.** §4 exists to say which port is open
and to whom, which is about what arrives. A container of this machine's own opening a connection
somewhere is not a port opened to anybody, and the addresses it might do that from are the machine's
internal plumbing, which the mesh neither owns nor can know.
The runtime's own default bridge, which a container attaches to when it names no module network, is the
runtime's and not a module's, so the host renders it from what the runtime reports. With that, no range
is named in the control plane at all.
So the filter constrains what arrives from **outside** the machine and says nothing about what did not.
Traffic passing through is allowed unless it came in on one of the machine's outward links, and then
only where a declared endpoint's reach admits it (§6). The machine says which of its links face
outside — one fact it reports, like the kind of firewall it found and the tunnel it carries, not a
setting and not a list of addresses. It does not change when a module is added or removed, which is the
whole difference from what it replaces. A machine that has reported no outward link is sent no filter
at all, and keeps the one it has, because a rule written around a link with no name is a rule set that
does not load — a machine filtering nothing while its unit reports success.
*How it is checked:* a node with two modules declaring networks renders rules for exactly those two and
none for a third present on the machine that nothing declared — which fails against forwarding by
range, and is how it was written; unassigning one removes its rule at the next reconcile; the default
bridge is asserted for a runtime whose bridge is somewhere other than the old constant named; and the
guests of a declared network keep address and name service, per chain body.
Ports go on following the modules exactly as before: assign a module to a machine and the port its
assignment says it reaches on opens. Nothing about a network is said anywhere, by anybody.
*How it is checked:* a bed converges a machine carrying containers on several networks, none of them
named in any setting, and each reaches outward afterwards — which fails against the previous behaviour,
where the same flip cut them off, and is how it was written; a network made *after* the last declaration
needs no new filter; a declared port is reachable from off the private network and an undeclared one is
not; no address of a machine's own networks appears in a rendered filter, asserted on the text; and a
machine reporting no outward link is refused in the control plane with its existing filter left alone.
## 5 — Certificates