nftables drops its container, NET_ADMIN, the container-runtime capability, the runtime base images, the Dockerfile, and the bus credential and state directory only the container read; its tools are declared as a TypeScript bundle the toolchain compiles and node-tools loads on every node, and the iptables package the image used to carry is declared on the host. The runtime runs as the operator's account, so the tool runs the filter's commands through sudo without a prompt when it is not root (ADR 0175 §4, to-be 38 WP4), naming sudo's absence or refusal by how it failed; the filter file is the path the manifest's filtering names, held to it by a test; a found firewall that is present but will not answer stops a removal rather than passing for inactive; a legacy tool that is present but fails is said, not swallowed.
52 lines
2.5 KiB
TypeScript
52 lines
2.5 KiB
TypeScript
// The packet filter's tools: the node-packet-filter seat's three verbs — what the machine enforces,
|
|
// reload the mesh's own, remove one thing the mesh did not write — and the module's own reading of
|
|
// the mesh's table (novox/hq ADR 0045, ADR 0168, ADR 0170).
|
|
import { registerModuleTools, type ToolDefinition } from "@novox/mesh-sdk/tools";
|
|
import { FirewallClient } from "../client.js";
|
|
|
|
export function getSeatVerbs(firewall: FirewallClient): ToolDefinition[] {
|
|
return [
|
|
{
|
|
name: "rules",
|
|
description:
|
|
"The packet filter as this machine enforces it now: the nftables ruleset and, where the tool exists, the legacy filter's listings. Narrowed to one table or chain when asked.",
|
|
input: {
|
|
table: { type: "string", description: "one nftables table, as `family name` (optional)" },
|
|
chain: { type: "string", description: "one chain of that table (optional)" },
|
|
},
|
|
run: async (args) => firewall.rules(args.table ? String(args.table) : undefined, args.chain ? String(args.chain) : undefined),
|
|
},
|
|
{
|
|
name: "reload",
|
|
description: "Load the mesh's own filter again from the file the mesh writes, and answer with the mesh's table as loaded.",
|
|
input: {},
|
|
run: async () => firewall.reload(),
|
|
},
|
|
{
|
|
name: "remove",
|
|
description:
|
|
"Remove one rule set the mesh did not write, named exactly as `node show` lists it: `chain X (iptables-legacy)` or `table ip6 filter, chain DOCKER-USER`. " +
|
|
"Refuses the mesh's tables, the runtime's own chains, a built-in chain and an active found firewall's chains. An operator's act, by name, never a flush.",
|
|
input: { where: { type: "string", description: "the rule set, as `node show` lists it" } },
|
|
run: async (args) => firewall.remove(String(args.where ?? "")),
|
|
},
|
|
];
|
|
}
|
|
|
|
export function getFirewallTools(firewall: FirewallClient): ToolDefinition[] {
|
|
return [
|
|
{
|
|
name: "firewall_rules",
|
|
description: "The mesh's live nftables table on this node — what the mesh's own filter is accepting and dropping.",
|
|
input: {},
|
|
run: async () => ({ ruleset: await firewall.ruleset() }),
|
|
},
|
|
];
|
|
}
|
|
|
|
const firewall = FirewallClient.onThisMachine();
|
|
// The seat's verbs under the seat's name: the runtime serves them on the seat's subjects where this
|
|
// module holds it (ADR 0159, 0160). The module's own under its own.
|
|
registerModuleTools("node-packet-filter", () => getSeatVerbs(firewall));
|
|
registerModuleTools("nftables", () => getFirewallTools(firewall));
|