The packet filter is a module too, and it was assigned to nothing

V3 asked whether the machine's networking is what the modules asked for and found
no mesh firewall table at all. The firewall is a module — it claims the
packet-filter seat, installs the filter and loads the rules — and like networking
before it, it had never been assigned to anything.

So every rule the mesh generates from module listen declarations had never been
applied to any machine in this test. Not open by accident: a mesh where that
whole generation has never run.

Assigned separately from networking because they answer different questions. One
is how machines reach each other; the other is what may reach this one.

Claude-Session: https://claude.ai/code/session_01D6qtiYU3P9jk3pnAXyAFyx
This commit is contained in:
2026-09-14 22:45:52 +02:00
parent 6b482ee7dc
commit f132a4bcbd
2 changed files with 59 additions and 26 deletions
+34 -1
View File
@@ -85,6 +85,15 @@ const PROVIDER = { module: "lavinmq", repo: "mesh-catalog", path: "modules/lavin
*/
/** The one word that puts a mesh on a private network and gives its machines names. */
const NETWORK_MODULE = "networking";
/**
* The packet filter, which is a module too and was assigned to nothing.
*
* The rules are generated from what every module declares it listens on, so a mesh with no filter
* is not "open by accident" — it is a mesh where the whole of that generation has never run. It
* claims a seat (`the-packet-filter`) because a machine has one of these and two things writing
* rules is a coin toss about which survives.
*/
const FILTER_MODULE = "firewall";
const STORE = { module: "postgres", repo: "mesh-catalog", path: "modules/postgres", container: "mesh-postgres" };
const CATALOGUE = { module: "mesh-catalog", repo: "mesh-catalog", path: "modules/mesh-catalog", container: "mesh-catalog" };
/** The control plane, rebuilt from its own repository — the step that ends the installer's tenure. */
@@ -116,6 +125,7 @@ const STORE_RUNS = "the mesh builds and runs a store of its own";
const CATALOGUE_RUNS = "the mesh builds and runs its own catalogue";
const CONTROL_REBUILT = "the mesh rebuilds its own control plane from source";
const NETWORKED = "the mesh puts itself on a private network, and its machine has a name";
const FILTERED = "the machine has a packet filter, loaded from what modules declared";
const MODULE_BUILT = "the mesh builds a module standing on that base";
const ANCHOR_RUNS = "the anchor runs the module the mesh built";
const DESCRIBES = "the control plane can describe the mesh, and what it says is true";
@@ -376,6 +386,7 @@ const PLAN: { code: string; title: string }[] = [
{ code: "P3", title: CATALOGUE_RUNS },
{ code: "P4", title: CONTROL_REBUILT },
{ code: "N1", title: NETWORKED },
{ code: "N2", title: FILTERED },
{ code: "U1", title: MODULE_BUILT },
{ code: "U2", title: NEEDS },
{ code: "U3", title: ANCHOR_RUNS },
@@ -623,8 +634,29 @@ before(async () => {
return `${hosts.trim()}\n\n${modules.trim()}`;
});
// ---- THE PACKET FILTER -------------------------------------------------------------------------
//
// Assigned separately from `networking` because they answer different questions: one is how
// machines reach each other, the other is what may reach this one. Both were assigned to nothing,
// and the second is the more alarming of the two — every rule the mesh generates from module
// declarations had never been applied to any machine in this test.
await step("N2", FILTERED, NETWORKED, async () => {
await mesh(`assign ${CONTROL} ${FILTER_MODULE}`);
await mesh(`push ${CONTROL}`, 600_000);
const deadline = Date.now() + 180_000;
let ruleset = "";
while (Date.now() < deadline) {
ruleset = (await on(CONTROL, `nft list table inet mesh 2>&1`)).out;
if (/chain input/.test(ruleset)) break;
await new Promise((r) => setTimeout(r, 5_000));
}
assert.match(ruleset, /chain input/,
`${FILTER_MODULE} is assigned and the machine has no mesh filter:\n${ruleset}`);
return ruleset;
});
// ---- 7..8. SOMETHING TO RUN ---------------------------------------------------------------
await step("U1", MODULE_BUILT, NETWORKED, async () => {
await step("U1", MODULE_BUILT, FILTERED, async () => {
await registerModule(MODULE.module, resolve(catalogDir, MODULE.module, "module.json"));
const built = await mesh(
`build ${forgeUrl(MODULE.repo)} --path ${MODULE.path} --ref ${refFor(MODULE.repo)} --wait 1200s`,
@@ -863,6 +895,7 @@ for (const name of [
CATALOGUE_RUNS,
CONTROL_REBUILT,
NETWORKED,
FILTERED,
MODULE_BUILT,
NEEDS,
ANCHOR_RUNS,