#!/usr/bin/env node const assert = require("assert"); const cp = require("child_process"); const crypto = require("crypto"); const net = require("net"); const path = require("path"); const { coordinatorWireRequest } = require("./coordinator-wire"); const repo = path.resolve(__dirname, ".."); const identities = new Map(); function waitForJsonLine(child) { return new Promise((resolve, reject) => { let buffer = ""; child.stdout.on("data", (chunk) => { buffer += chunk.toString(); const newline = buffer.indexOf("\n"); if (newline < 0) return; try { resolve(JSON.parse(buffer.slice(0, newline).trim())); } catch (error) { reject(error); } }); child.once("exit", (code) => { reject(new Error(`process exited before JSON line with code ${code}`)); }); }); } function send(addr, message) { return new Promise((resolve, reject) => { const socket = net.connect(addr.port, addr.host, () => { socket.write(`${JSON.stringify(coordinatorWireRequest(message))}\n`); }); let buffer = ""; socket.on("data", (chunk) => { buffer += chunk.toString(); const newline = buffer.indexOf("\n"); if (newline < 0) return; socket.end(); try { resolve(JSON.parse(buffer.slice(0, newline))); } catch (error) { reject(error); } }); socket.on("error", reject); }); } function nodeIdentity(node) { const existing = identities.get(node); if (existing) return existing; const { privateKey: privateKeyObject, publicKey } = crypto.generateKeyPairSync("ed25519"); const privateDer = privateKeyObject.export({ format: "der", type: "pkcs8" }); const publicDer = publicKey.export({ format: "der", type: "spki", }); const privateSeed = Buffer.from(privateDer).subarray(-32); const identity = { privateKey: `ed25519:${privateSeed.toString("base64")}`, publicKey: `ed25519:${Buffer.from(publicDer).subarray(-32).toString("base64")}`, privateKeyObject, }; identities.set(node, identity); return identity; } function nodeSignatureMessage( node, requestKind, payloadDigest, nonce, issuedAtEpochSeconds ) { const parts = [ "clusterflux-node-request-signature:v2", node, requestKind, payloadDigest, nonce, String(issuedAtEpochSeconds), ]; return Buffer.concat( parts.flatMap((part) => [ Buffer.from(`${Buffer.byteLength(part)}:`), Buffer.from(part), Buffer.from("\n"), ]) ); } function signedNodeHeartbeat(node, identity) { const nonce = `node-attach-heartbeat-${process.pid}-${Date.now()}`; const issuedAt = Math.floor(Date.now() / 1000); const payloadDigest = `sha256:${crypto .createHash("sha256") .update(JSON.stringify({ node, type: "node_heartbeat" })) .digest("hex")}`; const signature = crypto.sign( null, nodeSignatureMessage(node, "node_heartbeat", payloadDigest, nonce, issuedAt), identity.privateKeyObject ); return { nonce, issued_at_epoch_seconds: issuedAt, signature: `ed25519:${signature.toString("base64")}`, }; } function runAttach(addr, grant) { const identity = nodeIdentity("node-attach"); return new Promise((resolve, reject) => { const child = cp.spawn( "cargo", [ "run", "-q", "-p", "clusterflux-cli", "--bin", "clusterflux", "--", "node", "attach", "--coordinator", `${addr.host}:${addr.port}`, "--tenant", "tenant", "--project-id", "project", "--node", "node-attach", "--public-key", identity.publicKey, "--enrollment-grant", grant, "--cap", "quic-direct", "--json", ], { cwd: repo, env: { ...process.env, CLUSTERFLUX_NODE_PRIVATE_KEY: identity.privateKey, }, } ); let stdout = ""; let stderr = ""; child.stdout.on("data", (chunk) => { stdout += chunk.toString(); }); child.stderr.on("data", (chunk) => { stderr += chunk.toString(); }); child.on("exit", (code) => { if (code !== 0) { reject(new Error(`node attach failed with code ${code}\n${stderr}`)); return; } try { resolve(JSON.parse(stdout)); } catch (error) { reject( new Error(`node attach output was not JSON: ${stdout}\n${error.stack || error.message}`) ); } }); }); } (async () => { const coordinator = cp.spawn( "cargo", [ "run", "-q", "-p", "clusterflux-coordinator", "--bin", "clusterflux-coordinator", "--", "--listen", "127.0.0.1:0", "--allow-local-trusted-loopback", ], { cwd: repo } ); try { const ready = await waitForJsonLine(coordinator); const [host, portText] = ready.listen.split(":"); const addr = { host, port: Number(portText) }; assert.strictEqual((await send(addr, { type: "ping" })).type, "pong"); const grant = await send(addr, { type: "create_node_enrollment_grant", tenant: "tenant", project: "project", actor_user: "operator", ttl_seconds: 900 }); assert.strictEqual(grant.type, "node_enrollment_grant_created"); assert.strictEqual(grant.tenant, "tenant"); assert.strictEqual(grant.project, "project"); assert.match(grant.grant, /^node_grant_[A-Za-z0-9_-]+$/); assert.strictEqual(grant.scope, "node:attach"); assert(grant.expires_at_epoch_seconds > Math.floor(Date.now() / 1000)); assert(grant.expires_at_epoch_seconds <= Math.floor(Date.now() / 1000) + 900); const report = await runAttach(addr, grant.grant); assert.strictEqual(report.plan.node, "node-attach"); assert.strictEqual(report.plan.coordinator, `${addr.host}:${addr.port}`); assert.strictEqual(report.plan.enrollment.grant, grant.grant); assert.match(report.plan.enrollment.public_key_fingerprint, /^sha256:[0-9a-f]{64}$/); assert.strictEqual( report.plan.enrollment.exchanges_short_lived_grant_for_long_lived_node_identity, true ); assert.ok(report.plan.capabilities.arch.length > 0); assert.ok(report.plan.capabilities.source_providers.includes("filesystem")); assert.ok(report.plan.capabilities.capabilities.includes("QuicDirect")); assert.strictEqual(report.plan.detection.auto_detected, true); assert.strictEqual(report.plan.detection.arch, report.plan.capabilities.arch); assert.deepStrictEqual(report.plan.detection.manual_capability_overrides, ["quic-direct"]); assert( report.plan.detection.recognized_capability_overrides.includes("QuicDirect") ); assert.strictEqual( report.plan.detection.os_arch_capabilities_require_manual_flags, false ); assert.strictEqual(report.plan.detection.command_backend, "native-command"); assert.strictEqual(report.plan.detection.command_backend_available, true); assert( report.plan.detection.source_provider_backends.some( (provider) => provider.provider === "filesystem" && provider.detected ) ); assert( report.grant_disclosures.length > 0, "node attach should disclose capability grants before reporting capabilities" ); assert( report.grant_disclosures.every( (disclosure) => disclosure.coordinator_policy_limited === true ), "node attach should mark all capability grants as coordinator-policy-limited" ); assert( report.grant_disclosures.some( (disclosure) => disclosure.grant === "native_command_execution" ), "node attach should disclose native command execution when detected" ); assert( report.grant_disclosures.some( (disclosure) => disclosure.grant === "source_access" ), "node attach should disclose source access when detected" ); assert.strictEqual(report.boundary.cli_contacted_coordinator, true); assert.strictEqual(report.boundary.used_enrollment_exchange, true); assert.strictEqual(report.boundary.coordinator_session_requests, 3); assert.strictEqual(report.coordinator_response.type, "node_enrollment_exchanged"); assert.strictEqual(report.coordinator_response.node, "node-attach"); assert.strictEqual(report.coordinator_response.credential.node, "node-attach"); assert.strictEqual(report.coordinator_response.credential.scope, "node:attach"); assert.strictEqual(report.coordinator_response.credential.credential_kind, "NodeCredential"); assert.match( report.coordinator_response.credential.capability_policy_digest, /^sha256:[0-9a-f]{64}$/ ); assert.strictEqual(report.heartbeat_response.type, "node_heartbeat"); assert.strictEqual(report.capability_response.type, "node_capabilities_recorded"); const heartbeat = await send(addr, { type: "node_heartbeat", node: "node-attach", node_signature: signedNodeHeartbeat("node-attach", nodeIdentity("node-attach")), }); assert.strictEqual(heartbeat.type, "node_heartbeat"); assert.strictEqual(heartbeat.node, "node-attach"); } finally { coordinator.kill("SIGTERM"); } console.log("Node attach smoke passed"); })().catch((error) => { console.error(error.stack || error.message); process.exit(1); });