From 0c37d7389d039c546b0262ba829a08980c2337cd Mon Sep 17 00:00:00 2001 From: jochen Date: Tue, 22 Sep 2026 17:32:26 +0200 Subject: [PATCH 1/3] Guard the store and management ports on adopted nodes, and load the filter through a unit that never flushes the ruleset (hq ADR 0100) --- modules/lavinmq/module.json | 3 +++ modules/nftables/client.ts | 5 +++-- modules/nftables/module.json | 12 ++++++++++-- modules/postgres/module.json | 3 +++ 4 files changed, 19 insertions(+), 4 deletions(-) diff --git a/modules/lavinmq/module.json b/modules/lavinmq/module.json index 1437ad1..afd13ff 100644 --- a/modules/lavinmq/module.json +++ b/modules/lavinmq/module.json @@ -53,6 +53,9 @@ "why": "modules on any machine that were granted a queue" } ], + "guards": [ + 15672 + ], "resources": [ { "id": "mesh-state", diff --git a/modules/nftables/client.ts b/modules/nftables/client.ts index 92d2be5..d7fec83 100644 --- a/modules/nftables/client.ts +++ b/modules/nftables/client.ts @@ -1,7 +1,8 @@ // The firewall's own code, in the module (novox/hq ADR 0039). The mesh computes this node's whole // rule set from every module's `listens` and writes it to /etc/nftables.conf (novox/hq ADR 0045); -// the module loads it (the nftables service, reloaded whenever the rules change). This code exists -// only to read back what is actually enforced — the enforcement itself is declarative. +// the module loads it through its own mesh-filter unit, reloaded whenever the rules change, whose +// stop deletes only the mesh's table and never flushes the whole ruleset (novox/hq ADR 0100). This +// code exists only to read back what is actually enforced — the enforcement itself is declarative. import { execFile } from "node:child_process"; import { promisify } from "node:util"; diff --git a/modules/nftables/module.json b/modules/nftables/module.json index d552d65..3a5c160 100644 --- a/modules/nftables/module.json +++ b/modules/nftables/module.json @@ -19,14 +19,22 @@ "type": "package", "package": "nftables" }, + { + "id": "unit", + "type": "file", + "path": "/etc/systemd/system/mesh-filter.service", + "content": "[Unit]\nDescription=The mesh's packet filter, derived from what is assigned to this node\nWants=network-pre.target\nBefore=network-pre.target\n\n[Service]\nType=oneshot\nRemainAfterExit=yes\nExecStart=nft -f /etc/nftables.conf\nExecReload=nft -f /etc/nftables.conf\nExecStop=nft delete table inet mesh\n\n[Install]\nWantedBy=multi-user.target\n", + "mode": "0644" + }, { "id": "load", "type": "service", - "unit": "nftables.service", + "unit": "mesh-filter.service", "state": "running", "boot": "enabled", "restart-on": [ - "filtering" + "filtering", + "unit" ] } ] diff --git a/modules/postgres/module.json b/modules/postgres/module.json index 320c941..cfd147d 100644 --- a/modules/postgres/module.json +++ b/modules/postgres/module.json @@ -32,6 +32,9 @@ "why": "modules on any machine that were granted a database" } ], + "guards": [ + 5432 + ], "serves": { "postgres-database": { "port": 5432 -- 2.54.0 From 84012fab2e41eb15b5db2e751476b79af818127d Mon Sep 17 00:00:00 2001 From: jochen Date: Tue, 22 Sep 2026 18:06:15 +0200 Subject: [PATCH 2/3] Make the stock nftables unit's stop delete only the mesh's table on nodes that still have it enabled (hq ADR 0100) --- modules/nftables/module.json | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/modules/nftables/module.json b/modules/nftables/module.json index 3a5c160..f381540 100644 --- a/modules/nftables/module.json +++ b/modules/nftables/module.json @@ -26,6 +26,13 @@ "content": "[Unit]\nDescription=The mesh's packet filter, derived from what is assigned to this node\nWants=network-pre.target\nBefore=network-pre.target\n\n[Service]\nType=oneshot\nRemainAfterExit=yes\nExecStart=nft -f /etc/nftables.conf\nExecReload=nft -f /etc/nftables.conf\nExecStop=nft delete table inet mesh\n\n[Install]\nWantedBy=multi-user.target\n", "mode": "0644" }, + { + "id": "stock-unit-stop", + "type": "file", + "path": "/etc/systemd/system/nftables.service.d/mesh.conf", + "content": "# The mesh: stopping the stock unit deletes only the mesh's table, never the whole ruleset\n# (novox/hq ADR 0100) — a flush would take the container runtime's rules and any firewall with it.\n[Service]\nExecStop=\nExecStop=nft delete table inet mesh\n", + "mode": "0644" + }, { "id": "load", "type": "service", @@ -34,7 +41,8 @@ "boot": "enabled", "restart-on": [ "filtering", - "unit" + "unit", + "stock-unit-stop" ] } ] -- 2.54.0 From 90104d08188d003ba77772eef946155edc3a27ae Mon Sep 17 00:00:00 2001 From: jochen Date: Tue, 22 Sep 2026 19:47:43 +0200 Subject: [PATCH 3/3] Reload the filter on a rule change rather than restart it, so the node is never unfiltered in between (hq ADR 0102) --- modules/nftables/module.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/nftables/module.json b/modules/nftables/module.json index f381540..11a6be6 100644 --- a/modules/nftables/module.json +++ b/modules/nftables/module.json @@ -40,9 +40,11 @@ "state": "running", "boot": "enabled", "restart-on": [ - "filtering", "unit", "stock-unit-stop" + ], + "reload-on": [ + "filtering" ] } ] -- 2.54.0