Prove the hub can be filtered without severing the mesh

The failure guarded against is not subtle and is very hard to recover from: a
rule set that closes the hub's own port takes the private network down, and the
mesh's way of fixing anything is to send a declaration over it.

So the assertion that matters is not the rule file — it is that a declaration
still reaches the other machine afterwards, and that the other machine still
reaches the hub. A rule file that looks right and a mesh that has stopped are
exactly what this is for.
This commit is contained in:
2026-08-31 10:07:09 +02:00
parent 5736cca9f6
commit 9ab73d6cdb
+69
View File
@@ -1183,3 +1183,72 @@ test("the board names the machine that is not doing what it was told", {
await mesh("unassign laptop impossible"); await mesh("unassign laptop impossible");
await mesh("push laptop"); await mesh("push laptop");
}); });
test("the hub can be filtered without severing the mesh", {
skip, timeout: 900_000,
}, async () => {
// The machine that most needs a firewall was the one that could not have one. A hub is dialled
// by every node at other sites; a machine that is not a hub dials out and needs nothing open.
// They are the same module, so a static `listens` cannot say it — and the machine it gets wrong
// is the one facing the public internet.
//
// The failure this guards against is not subtle and is very hard to recover from: a rule set
// that closes the hub's own port takes the private network down, and the mesh's way of fixing
// anything is to send a declaration over it.
const rules = "/etc/mesh/hub-filter.nft";
await must("anchor", `printf %s '{"module":"hubfilter","version":"1",` +
`"capabilities":["firewall"],` +
`"filtering":{"into":"${rules}"},` +
`"resources":[{"id":"nftables","type":"package","package":"nftables"},` +
`{"id":"dir","type":"directory","path":"/etc/mesh","mode":"0755"},` +
`{"id":"unit","type":"file","path":"/etc/systemd/system/hub-filter.service",` +
`"mode":"0644","content":"[Unit]\\nDescription=What the mesh computed for the hub\\n` +
`[Service]\\nType=oneshot\\nRemainAfterExit=yes\\n` +
`ExecStart=/usr/bin/nft -f ${rules}\\n[Install]\\nWantedBy=multi-user.target\\n"},` +
`{"id":"filter","type":"service","unit":"hub-filter.service","state":"running",` +
`"boot":"enabled","restart-on":["filtering"]}]}' > /tmp/hubfilter.json`);
await must("anchor", `docker cp /tmp/hubfilter.json mesh-control:/hubfilter.json`);
await mesh("module add /hubfilter.json");
await mesh("assign anchor hubfilter");
await mesh("push anchor");
await new Promise((r) => setTimeout(r, 20_000));
// The hub's own way onto the private network is open, and derived — nothing in that manifest
// mentions a port.
const written = await must("anchor", `cat ${rules}`);
assert.match(written, /udp dport 51820 accept/,
`the hub's rule set closes the private network it is the way onto:\n${written}`);
assert.match(written, /# networking/,
`the rule does not name what caused it:\n${written}`);
// Loaded, and the mesh still works: a declaration reaches the other machine, which it cannot if
// the overlay is severed. This is the assertion that matters — a rule file that looks right and
// a mesh that has stopped are exactly what this is guarding against.
assert.match(await must("anchor", `nft list table inet mesh`), /dport 51820/);
await must("laptop", `rm -f /etc/mesh-still-works`);
await must("anchor", `printf %s '{"module":"stillworks","version":"1",` +
`"resources":[{"id":"marker","type":"file","path":"/etc/mesh-still-works",` +
`"content":"yes","mode":"0644"}]}' > /tmp/stillworks.json`);
await must("anchor", `docker cp /tmp/stillworks.json mesh-control:/stillworks.json`);
await mesh("module add /stillworks.json");
await mesh("assign laptop stillworks");
await mesh("push laptop");
let arrived = false;
for (let i = 0; i < 20 && !arrived; i++) {
arrived = (await on("laptop", `test -f /etc/mesh-still-works`)).ok;
if (!arrived) await new Promise((r) => setTimeout(r, 3000));
}
assert.ok(arrived,
"the hub applied its own rule set and the mesh stopped reaching the other machine");
// And the other machine still reaches the hub over the private network, which is what the
// opened port is for.
assert.ok((await on("laptop", `ping -c 1 -W 5 anchor.internal`)).ok,
"the private network is down after the hub filtered itself");
await mesh("unassign anchor hubfilter");
await mesh("unassign laptop stillworks");
await mesh("push");
});