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:
@@ -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("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");
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user