An agent meets the mesh's words most often in tool descriptions, and nothing compared them with the glossary: several still said "the host" for the node-engine and the forge's pull request comment was headed "Change plan", a word retired twice over. retired-words is the copy of the words the glossary retires for the tools, and checks/words fails the repository check when any string a module's code can show, or any manifest description, uses one. Those found are reworded here.
109 lines
6.2 KiB
TypeScript
109 lines
6.2 KiB
TypeScript
// `remove` acts on one rule set the mesh did not write, named as the host reports it (novox/hq ADR
|
|
// 0168, 0169), over the shapes two machines of the first mesh reported live: a predecessor's chain in
|
|
// the legacy filter, the runtime's user chain in the IPv6 legacy filter, a leftover front-end chain,
|
|
// and the same in an iptables-nft table. It refuses what is not the operator's to remove.
|
|
import { test } from "node:test";
|
|
import assert from "node:assert/strict";
|
|
import { readFileSync } from "node:fs";
|
|
import { FILTER_FILE, FirewallClient, chainsJumpingTo, escalated, installed, type Runner } from "../client.ts";
|
|
|
|
const legacy = [
|
|
"-P INPUT ACCEPT", "-P FORWARD DROP", "-P OUTPUT ACCEPT",
|
|
"-N DOCKER", "-N DOCKER-USER", "-N HAL-MESH-ONLY",
|
|
"-A FORWARD -j DOCKER-USER",
|
|
"-A DOCKER-USER -i enp6s0 -p tcp -m conntrack --ctstate NEW -j HAL-MESH-ONLY",
|
|
"-A HAL-MESH-ONLY -m conntrack --ctorigdstport 80 -j RETURN",
|
|
"-A HAL-MESH-ONLY -m comment --comment \"HAL: not public -> mesh only\" -j DROP",
|
|
].join("\n") + "\n";
|
|
|
|
function fake(ufwActive = false): { run: Runner; asked: string[] } {
|
|
const asked: string[] = [];
|
|
const run: Runner = async (cmd, args) => {
|
|
asked.push([cmd, ...args].join(" "));
|
|
if (cmd === "ufw") return ufwActive ? "Status: active\n" : "Status: inactive\n";
|
|
if (args.join(" ") === "-S") return legacy;
|
|
if (cmd === "nft" && args[0] === "list" && args[1] === "table") {
|
|
return "table ip6 own {\n\tchain forward {\n\t\ttype filter hook forward priority filter; policy accept;\n\t\tjump deny\n\t}\n\tchain deny {\n\t\tdrop\n\t}\n}\n";
|
|
}
|
|
if (cmd === "nft" && args[0] === "-a") {
|
|
return "table ip6 own {\n\tchain forward {\n\t\ttype filter hook forward priority filter; policy accept;\n\t\tjump deny # handle 7\n\t}\n}\n";
|
|
}
|
|
return "";
|
|
};
|
|
return { run, asked };
|
|
}
|
|
|
|
test("a predecessor's chain in the legacy filter loses its jumps, is flushed and deleted", async () => {
|
|
const f = fake();
|
|
const out = await new FirewallClient(f.run, undefined, () => true).remove("chain HAL-MESH-ONLY (iptables-legacy)");
|
|
assert.deepEqual(out.did, [
|
|
"iptables-legacy -D DOCKER-USER -i enp6s0 -p tcp -m conntrack --ctstate NEW -j HAL-MESH-ONLY",
|
|
"iptables-legacy -F HAL-MESH-ONLY",
|
|
"iptables-legacy -X HAL-MESH-ONLY",
|
|
]);
|
|
});
|
|
|
|
test("the runtime's user chain is emptied back to its one return, never deleted", async () => {
|
|
const f = fake();
|
|
const out = await new FirewallClient(f.run, undefined, () => true).remove("chain DOCKER-USER (ip6tables-legacy)");
|
|
assert.deepEqual(out.did, ["ip6tables-legacy -F DOCKER-USER", "ip6tables-legacy -A DOCKER-USER -j RETURN"]);
|
|
const nft = await new FirewallClient(fake().run, undefined, () => true).remove("table ip6 filter, chain DOCKER-USER");
|
|
assert.deepEqual(nft.did, ["ip6tables -F DOCKER-USER", "ip6tables -A DOCKER-USER -j RETURN"]);
|
|
});
|
|
|
|
test("a chain of the machine's own nftables table goes with the rules that reach it", async () => {
|
|
const f = fake();
|
|
const out = await new FirewallClient(f.run, undefined, () => true).remove("table ip6 own, chain deny");
|
|
assert.deepEqual(out.did, ["nft delete rule ip6 own forward handle 7", "nft delete chain ip6 own deny"]);
|
|
});
|
|
|
|
test("what is not the operator's to remove is refused by name", async () => {
|
|
const c = new FirewallClient(fake(true).run, undefined, () => true);
|
|
await assert.rejects(c.remove("table inet mesh, chain forward"), /the mesh's own table/);
|
|
await assert.rejects(c.remove("chain DOCKER (iptables-legacy)"), /container runtime's own/);
|
|
await assert.rejects(c.remove("chain FORWARD (iptables-legacy)"), /built in/);
|
|
await assert.rejects(c.remove("chain ufw6-docker-logging-deny (ip6tables-legacy)"), /found firewall, which is in force/);
|
|
await assert.rejects(c.remove("something else"), /not a rule set as the node-engine reports one/);
|
|
// Retired, a front end's leftover is nobody's and goes.
|
|
const retired = await new FirewallClient(fake(false).run, undefined, () => true).remove("chain ufw6-docker-logging-deny (ip6tables-legacy)");
|
|
assert.ok(retired.did.includes("ip6tables-legacy -X ufw6-docker-logging-deny"));
|
|
});
|
|
|
|
test("which chains jump to a target is read from a listing", () => {
|
|
const listing = "table ip6 own {\n\tchain a {\n\t\tjump deny\n\t}\n\tchain b {\n\t\tgoto deny\n\t}\n\tchain deny {\n\t\tdrop\n\t}\n}\n";
|
|
assert.deepEqual(chainsJumpingTo(listing, "deny"), ["a", "b"]);
|
|
});
|
|
|
|
test("the filter's commands run as given by root and through sudo without a prompt by anyone else", () => {
|
|
assert.deepEqual(escalated("nft", ["list", "ruleset"], 0), ["nft", ["list", "ruleset"]]);
|
|
assert.deepEqual(escalated("nft", ["-f", "/etc/nftables.conf"], 1000), ["sudo", ["-n", "nft", "-f", "/etc/nftables.conf"]]);
|
|
assert.deepEqual(escalated("iptables-legacy", ["-S"], undefined), ["sudo", ["-n", "iptables-legacy", "-S"]]);
|
|
});
|
|
|
|
test("the filter file is the one the manifest's filtering names", () => {
|
|
const manifest = JSON.parse(readFileSync(new URL("../module.json", import.meta.url), "utf8")) as { filtering: { into: string } };
|
|
assert.equal(FILTER_FILE, manifest.filtering.into);
|
|
});
|
|
|
|
test("a tool is installed when an executable of its name is on the path, and not otherwise", () => {
|
|
assert.equal(installed("sh"), true);
|
|
assert.equal(installed("no-such-tool-of-the-mesh"), false);
|
|
});
|
|
|
|
test("a found firewall that is absent guards nothing; one that will not answer stops the removal", async () => {
|
|
// Absent: its leftover chain is nobody's and goes, without asking it.
|
|
const absent = fake(true);
|
|
const out = await new FirewallClient(absent.run, undefined, () => false).remove("chain ufw6-docker-logging-deny (ip6tables-legacy)");
|
|
assert.ok(out.did.includes("ip6tables-legacy -X ufw6-docker-logging-deny"));
|
|
assert.ok(!absent.asked.some((a) => a.startsWith("ufw ")));
|
|
// Present and failing — refused by sudo, say — nothing is removed on a guess.
|
|
const refusing: Runner = async (cmd, args) => {
|
|
if (cmd === "ufw") throw new Error("ufw needs root and the runtime's account may not run it without a prompt");
|
|
return fake().run(cmd, args);
|
|
};
|
|
await assert.rejects(
|
|
new FirewallClient(refusing, undefined, () => true).remove("chain ufw6-docker-logging-deny (ip6tables-legacy)"),
|
|
/cannot tell whether the found firewall is in force/,
|
|
);
|
|
});
|