--- topic: the mesh status: accepted date: 2026-10-02 deciders: jochen reconstructed: false extends: 02-DECISIONS/0168-a-converged-machine-is-filtered-by-the-mesh-alone.md --- # 169. The firewall seat serves its verbs, and a foreign rule set is removed through one of them ## Context [ADR 0168](0168-a-converged-machine-is-filtered-by-the-mesh-alone.md) made the mesh say truthfully what filters a converged machine, and left the removal of what it did not write to the operator's hand. The first time that hand was needed — two machines, five rule sets a predecessor and a retired front end had left — there was no mesh way to lend it: the packet filter is a seat ([ADR 0121](0121-a-system-seat-is-named-for-its-scope-and-modules-define-their-own.md)), a seat's holder serves its verbs ([ADR 0154](0154-the-meshs-own-verbs-are-the-controller-seats-tools.md), [ADR 0159](0159-a-tool-call-names-the-machine-and-a-holder-serves-its-seats-verbs.md)), and the firewall seat declared none. The only remaining path was a shell on the machine, which is the path the mesh exists to replace, and which the operator's own tooling rightly refused to an agent. A seat's verbs are the contract every holder implements, whatever filter it speaks. What a person asks a machine's packet filter is the same whether nftables, a front end or a legacy filter answers: what are the rules, reload the mesh's own, remove this thing the mesh did not write. What differs by filter is the holder's own business and may be its own tools beside the seat's. ## Decision **1. The `node-packet-filter` seat serves three verbs**, and a module that claims it serves all three or is refused the claim, as with every seat: - `rules` — the packet filter as the machine enforces it now: the nftables ruleset, and the legacy filter's listings where that tool exists; narrowed to one table or chain when asked. Read-only. - `reload` — load the mesh's own filter again from the file the mesh writes, and answer with the mesh's table as loaded. The holder's own act on the holder's own rules. - `remove` — remove one rule set the mesh did not write, named exactly as the host reports it under ADR 0168 (`chain HAL-MESH-ONLY (iptables-legacy)`, `table ip6 filter, chain DOCKER-USER`), and answer with what was done. It refuses the mesh's own tables, the container runtime's own chains, a built-in chain other than the runtime's user chain, and any chain of a found firewall that is in force. The runtime's user chain is emptied back to its one return; another chain loses the jumps into it, is flushed and deleted; a table of the machine's own is deleted whole. Each is an operator's act, by name, on one thing the mesh reported — never a flush, never a rule the mesh itself marked. **2. A holder may serve its own tools beside the seat's.** The nftables module keeps its reading of the mesh's table as its own tool, and a holder speaking a filter with specifics of its own may add tools for them; the seat's three are what every holder owes. **3. A container may ask for a capability.** Serving `remove` and `reload` needs the machine's network namespace and the right to change its packet filter; a holder's runtime declares `capabilities: ["NET_ADMIN"]` on its container and runs on the machine's network. The host grants exactly the capabilities declared, names them in the container's spec so a change recreates it, and refuses a name that is not a capability's. A privileged container stays undeclarable. **4. ADR 0168's "by hand" is read as "by the operator, through the seat".** Removing what the mesh reports as *other* is still the operator's act and is still never the mesh's own doing; the verb is how the act reaches the machine, recorded on the bus like every other, instead of a shell. ## Consequences - The seat's row gains the three verbs; a mesh that already runs widens its row at the next controller start. The nftables module claims them and gains a runtime — a tool server with the packet filter's tools in its image, on the machine's network, with `NET_ADMIN`. - The host's container vocabulary grows by `capabilities`; an older host refuses a declaration that carries it, so the host rolls before the module. - The two machines of this mesh that ADR 0168 found not filtered by the mesh alone are cleaned through `remove`, and read *the mesh alone* afterwards; `status` returns to well without a hand on either machine. ## How this is checked | Rule | Checked by | |---|---| | The seat declares the three verbs; a claim that serves fewer is refused by name | the catalogue's seat tests | | `remove` refuses the mesh's tables, the runtime's chains, a built-in chain and an active front end's chains, and removes a user chain with its jumps, empties the user chain, deletes an own table | the module's tests over a fake command runner, with the shapes the host reported live | | A container's capabilities reach the runtime and its spec; an unknown name is refused | host tests | | Live | `node-packet-filter.remove@` on the home server and the control node; `node show` reads *the mesh alone* on both; `status` is well | ## References - [ADR 0168](0168-a-converged-machine-is-filtered-by-the-mesh-alone.md), [ADR 0154](0154-the-meshs-own-verbs-are-the-controller-seats-tools.md), [ADR 0159](0159-a-tool-call-names-the-machine-and-a-holder-serves-its-seats-verbs.md), [ADR 0121](0121-a-system-seat-is-named-for-its-scope-and-modules-define-their-own.md) - [Design 33 — The tools the mesh answers](../03-DESIGN/01-to-be/33-the-tools-the-mesh-answers.md), [Design 08 — Connectivity](../03-DESIGN/01-to-be/08-connectivity.md)