The firewall module ships the unit that loads its rules
The distribution's nftables.service is Type=oneshot with no RemainAfterExit: it loads the rules and goes inactive. A host asked for a service that is "running" then reports, quite correctly, that it is stopped — every packet filtered as declared, and the machine marked as not doing what it was told. There is no state in the vocabulary for "ran and exited having done its job", so a module that needs one brings a unit that stays. That is also the right shape: how a machine enforces rules is a fact about the machine, and the mesh has no business depending on what a distribution happens to package. And when the builder's build times out, dump the builder's own account of itself. "Nothing consumed the queue" names no cause and is the same sentence whether the credential was refused, the queue was never declared, or the process died three seconds in.
This commit is contained in:
@@ -578,11 +578,24 @@ test("a machine filters exactly what its modules declared, and nothing else", {
|
||||
`"resources":[]}' > /tmp/talker.json`);
|
||||
// The rule set goes where this machine's nftables unit reads from, and the unit is declared to
|
||||
// reflect it. No command anywhere.
|
||||
// The module ships the unit that loads its rules, rather than using the one the distribution's
|
||||
// nftables package provides. That unit is `Type=oneshot` with no `RemainAfterExit`, so it does
|
||||
// its work and goes inactive — and a host asked for a service that is "running" reports, quite
|
||||
// correctly, that it is stopped. There is no state in the vocabulary for "ran and exited having
|
||||
// done its job", so a module that wants one brings a unit that stays.
|
||||
//
|
||||
// Which is also the right shape: how a machine enforces rules is a fact about the machine, and
|
||||
// the mesh has no business depending on what a distribution happens to package.
|
||||
await must("anchor", `printf %s '{"module":"firewall","version":"1",` +
|
||||
`"capabilities":["firewall"],` +
|
||||
`"filtering":{"into":"/etc/nftables.conf"},` +
|
||||
`"filtering":{"into":"/etc/mesh/filter.nft"},` +
|
||||
`"resources":[{"id":"nftables","type":"package","package":"nftables"},` +
|
||||
`{"id":"filter","type":"service","unit":"nftables.service","state":"running",` +
|
||||
`{"id":"dir","type":"directory","path":"/etc/mesh","mode":"0755"},` +
|
||||
`{"id":"unit","type":"file","path":"/etc/systemd/system/mesh-filter.service",` +
|
||||
`"mode":"0644","content":"[Unit]\\nDescription=What the mesh computed for this machine\\n` +
|
||||
`[Service]\\nType=oneshot\\nRemainAfterExit=yes\\n` +
|
||||
`ExecStart=/usr/bin/nft -f /etc/mesh/filter.nft\\n[Install]\\nWantedBy=multi-user.target\\n"},` +
|
||||
`{"id":"filter","type":"service","unit":"mesh-filter.service","state":"running",` +
|
||||
`"boot":"enabled","restart-on":["filtering"]}]}' > /tmp/firewall.json`);
|
||||
for (const f of ["talker", "firewall"]) {
|
||||
await must("anchor", `docker cp /tmp/${f}.json mesh-control:/${f}.json`);
|
||||
@@ -593,7 +606,7 @@ test("a machine filters exactly what its modules declared, and nothing else", {
|
||||
await mesh("push laptop");
|
||||
await new Promise((r) => setTimeout(r, 20_000));
|
||||
|
||||
const written = await must("laptop", `cat /etc/nftables.conf`);
|
||||
const written = await must("laptop", `cat /etc/mesh/filter.nft`);
|
||||
// A rule names its source. Not decoration: it is the only thing that answers "why is this open".
|
||||
assert.match(written, /# talker . the thing this test is about/,
|
||||
`the rule does not name what caused it:\n${written}`);
|
||||
@@ -719,7 +732,15 @@ test("the builder is a module the mesh assigns, with a credential the mesh deliv
|
||||
`> /root/built/module.json`);
|
||||
await must("anchor", `cd /root/built && git init -q . && git add -A && ` +
|
||||
`git -c user.email=lab -c user.name=lab commit -qm built`);
|
||||
await mesh("build /root/built --wait 300s", 420_000);
|
||||
try {
|
||||
await mesh("build /root/built --wait 300s", 420_000);
|
||||
} catch (why) {
|
||||
// The builder's own account of itself. Without it the failure is "nothing consumed the
|
||||
// queue", which names no cause and is the same sentence whether the credential was refused,
|
||||
// the queue was never declared, or the process died three seconds in.
|
||||
const said = (await on("anchor", `docker logs mesh-builder 2>&1 | tail -40`)).out;
|
||||
throw new Error(`${(why as Error).message}\n\nwhat the builder said:\n${said}`);
|
||||
}
|
||||
|
||||
// Naming the module, and not merely containing its name: `builds` says "nothing has been built
|
||||
// yet" when there is nothing, and that sentence contains the word this was matching on.
|
||||
|
||||
Reference in New Issue
Block a user