diff --git a/scenarios/two-nodes.yml b/scenarios/two-nodes.yml index c046332..17055ee 100644 --- a/scenarios/two-nodes.yml +++ b/scenarios/two-nodes.yml @@ -28,6 +28,13 @@ machines: egress: true inbound: allow memory: 2GiB + # A third machine that joins through the tunnel with the bus closed to it (novox/hq ADR 0169). It + # carries none of the images: it only has to join. + joiner: + at: { segment: hosting, address: [192.0.2.30] } + egress: true + inbound: allow + images: [] images: - mesh-controller:development diff --git a/test/integration/mesh.test.ts b/test/integration/mesh.test.ts index 6287ba8..ea878bf 100644 --- a/test/integration/mesh.test.ts +++ b/test/integration/mesh.test.ts @@ -1662,3 +1662,30 @@ test("a third-party workload is adopted, with the credential it already had", { // a gap that bed should close; "a consumer's cache grant means exactly its own keys" — its tenancy // assertions (a write outside the consumer's keys and a FLUSHALL are refused) moved into // mesh-grant-end-to-end, against the catalogue's redis. + +// **A machine joins through the tunnel, and needs the bus only through it** (novox/hq ADR 0169). +// The bus is closed to this machine at the anchor's very first hook, before the container runtime's +// forwarding, so the only way its enrolment can arrive is over the tunnel the token gave it. +test("a machine joins through the tunnel, with the bus closed to it", { skip, timeout: 900_000 }, async () => { + await must("anchor", `nft add table ip lab_bus_closed && ` + + `nft add chain ip lab_bus_closed pre '{ type filter hook prerouting priority -300; }' && ` + + `nft add rule ip lab_bus_closed pre ip saddr 192.0.2.30 tcp dport 4222 drop`); + try { + await must("joiner", `pacman -Sy --noconfirm --needed wireguard-tools >/dev/null 2>&1`); + const key = (await must("joiner", `${HOST_PATH} key 2>/dev/null`)).trim(); + assert.match(key, /^[A-Za-z0-9+/]{43}=$/, `not a tunnel key: ${key}`); + // Asked again, the same key: a token may already have been issued for it. + assert.equal((await must("joiner", `${HOST_PATH} key 2>/dev/null`)).trim(), key); + + const token = tokenFrom(await mesh(`token issue --new joiner --overlay-key ${key}`)); + const said = await must("joiner", `${HOST_PATH} enrol --token ${quote(token)}`); + assert.match(said, /the tunnel to the hub is up/, said); + assert.match(said, /enrolled as joiner/, said); + + const shakes = await must("joiner", `wg show mesh0 latest-handshakes`); + assert.ok(/\s[1-9]\d*\s*$/m.test(shakes), `the tunnel never shook hands with the hub: ${shakes}`); + } finally { + await on("anchor", `nft delete table ip lab_bus_closed`); + } +}); +