Beds for the seed file and the foundation's filter; beds deliver secrets as files; a bundle-raised bed derives the anchor's filter #40

Merged
jschoubben merged 8 commits from feat/migration-blockers into main 2026-09-21 11:48:59 +00:00
4 changed files with 68 additions and 4 deletions
Showing only changes of commit e085e31f95 - Show all commits
@@ -38,7 +38,7 @@ import { loadScenario } from "../../src/declaration/parse.ts";
import { raise } from "../../src/lifecycle/raise.ts";
import { destroy, exec } from "../../src/lifecycle/operate.ts";
import { hostBinaryPath, HOST_PATH } from "../../src/lifecycle/place.ts";
import { labIsUsable, destroyAll, foundationBundle, onTheMachine } from "./harness.ts";
import { labIsUsable, destroyAll, foundationBundle, onTheMachine, deriveTheFilterOn } from "./harness.ts";
import type { HeldImage } from "../../src/pinning.ts";
const capability = await labIsUsable();
@@ -286,6 +286,9 @@ test("usage events are upserted into model-usage's store — latest-per-key, bot
await mesh(`overlay place ${NODE} --site lab`);
await mesh("assign anchor networking");
await mesh(`assign ${NODE} networking`);
// The anchor's derived filter, admitting the hub's port — what genesis does on the control-node,
// and what a bed raised from the bundle must do itself (ADR 0088; see the harness).
await deriveTheFilterOn({ machine: "anchor", node: "anchor", hubPort: 51820, must, mesh, on });
await addIssueAssign("postgres", postgresManifest);
await addIssueAssign("model-usage", modelUsageManifest);
+56 -1
View File
@@ -9,7 +9,8 @@
*/
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import { existsSync, readFileSync } from "node:fs";
import { resolve } from "node:path";
import { isReachable, pools, supportedDrivers } from "../../src/incus/client.ts";
import { destroy, list } from "../../src/lifecycle/operate.ts";
import { diagramFromLive } from "../../src/diagram/from-live.ts";
@@ -230,3 +231,57 @@ export async function assertUniversalInvariants(
}
}
}
// --- the packet filter, where a bed raises the foundation without genesis ------------------------
/** The packet filter genesis installs on the control-node, by the name the catalogue gives it. */
export const FILTER_MODULE = "nftables";
/** The catalogue's manifest for a module, under either spelling of MESH_LAB_CATALOG: the modules
* directory, or the checkout that holds it. */
export function catalogueManifest(module: string): string {
const dir = process.env["MESH_LAB_CATALOG"] ?? "";
for (const candidate of [resolve(dir, module, "module.json"), resolve(dir, "modules", module, "module.json")]) {
if (existsSync(candidate)) return candidate;
}
throw new Error(`no manifest for ${module} under MESH_LAB_CATALOG=${dir || "(unset)"}`);
}
function shellQuote(s: string): string {
return `'${s.replaceAll("'", `'\\''`)}'`;
}
/**
* The foundation is raised behind a base ruleset that admits ssh, the bus and the registry and
* nothing else, "until the mesh derives one" (novox/hq ADR 0088) — and the mesh derives one only
* where the packet-filter module is assigned, which genesis does on the control-node. A bed that
* raises the foundation from the bundle skips genesis, so it must do the same before it relies on
* an overlay hub there: the hub's port is derived from its endpoint, and until the derived ruleset
* lands no joined node's tunnel forms, and every module that dials the anchor by its overlay name
* times out fetching the broker's certificate — the failure this helper was written after.
*
* Registered, assigned, pushed, and then WAITED FOR: what the machine loaded must admit the hub's
* port, which the base ruleset cannot. Call it after the hub is placed, so there is a port to derive.
*/
export async function deriveTheFilterOn(o: {
machine: string; node: string; hubPort: number;
must: (machine: string, command: string, timeoutMs?: number) => Promise<string>;
mesh: (command: string, timeoutMs?: number) => Promise<string>;
on: (machine: string, command: string, timeoutMs?: number) => Promise<{ out: string; ok: boolean }>;
}): Promise<string> {
const manifest = readFileSync(catalogueManifest(FILTER_MODULE), "utf8");
await o.must(o.machine,
`printf %s ${shellQuote(manifest)} > /tmp/${FILTER_MODULE}.json && docker cp /tmp/${FILTER_MODULE}.json mesh-controller:/${FILTER_MODULE}.json`);
await o.mesh(`module add /${FILTER_MODULE}.json`);
await o.mesh(`assign ${o.node} ${FILTER_MODULE}`);
await o.mesh(`push ${o.node}`, 600_000);
const admits = new RegExp(`udp dport ${o.hubPort} accept`);
const deadline = Date.now() + 180_000;
let ruleset = "";
while (Date.now() < deadline) {
ruleset = (await o.on(o.machine, `nft list table inet mesh 2>&1`)).out;
if (admits.test(ruleset)) return ruleset;
await new Promise((r) => setTimeout(r, 5_000));
}
assert.fail(`${FILTER_MODULE} is assigned to ${o.node} and the ruleset it loaded does not admit the hub's udp/${o.hubPort}:\n${ruleset}`);
}
+4 -1
View File
@@ -41,7 +41,7 @@ import { loadScenario } from "../../src/declaration/parse.ts";
import { raise } from "../../src/lifecycle/raise.ts";
import { destroy, exec } from "../../src/lifecycle/operate.ts";
import { hostBinaryPath, HOST_PATH } from "../../src/lifecycle/place.ts";
import { labIsUsable, destroyAll, foundationBundle, onTheMachine } from "./harness.ts";
import { labIsUsable, destroyAll, foundationBundle, onTheMachine, deriveTheFilterOn } from "./harness.ts";
import type { HeldImage } from "../../src/pinning.ts";
const capability = await labIsUsable();
@@ -301,6 +301,9 @@ test("the lavinmq provider and its consumer ride laptop while the foundation bro
await mesh(`overlay place ${NODE} --site lab`);
await mesh("assign anchor networking");
await mesh(`assign ${NODE} networking`);
// The anchor's derived filter, admitting the hub's port — what genesis does on the control-node,
// and what a bed raised from the bundle must do itself (ADR 0088; see the harness).
await deriveTheFilterOn({ machine: "anchor", node: "anchor", hubPort: 51820, must, mesh, on });
await addIssueAssign("lavinmq", lavinmqManifest);
await addIssueAssign("amqp-ping", amqpPingManifest);
+4 -1
View File
@@ -31,7 +31,7 @@ import { loadScenario } from "../../src/declaration/parse.ts";
import { raise } from "../../src/lifecycle/raise.ts";
import { destroy, exec } from "../../src/lifecycle/operate.ts";
import { hostBinaryPath, HOST_PATH } from "../../src/lifecycle/place.ts";
import { labIsUsable, destroyAll, foundationBundle, onTheMachine } from "./harness.ts";
import { labIsUsable, destroyAll, foundationBundle, onTheMachine, deriveTheFilterOn } from "./harness.ts";
import type { HeldImage } from "../../src/pinning.ts";
const capability = await labIsUsable();
@@ -284,6 +284,9 @@ test("the whole ace service set resolves, installs and converges on one node in
await mesh(`overlay place ${NODE} --site lab`);
await mesh("assign anchor networking");
await mesh(`assign ${NODE} networking`);
// The anchor's derived filter, admitting the hub's port — what genesis does on the control-node,
// and what a bed raised from the bundle must do itself (ADR 0088; see the harness).
await deriveTheFilterOn({ machine: "anchor", node: "anchor", hubPort: 51820, must, mesh, on });
// Add / issue / assign, resiliently: a module the node cannot host is recorded and skipped so one
// bad assignment cannot poison the whole-node push.