116 lines
3.8 KiB
JavaScript
116 lines
3.8 KiB
JavaScript
"use strict";
|
|
|
|
const assert = require("node:assert/strict");
|
|
const crypto = require("node:crypto");
|
|
const fs = require("node:fs");
|
|
const os = require("node:os");
|
|
const path = require("node:path");
|
|
const test = require("node:test");
|
|
const { GhdrAuthorizer } = require("./ghdr-authorizer");
|
|
const { GhdrControllerBroker } = require("./ghdr-controller-broker");
|
|
|
|
test("two outbound controllers poll one email-authorized job and return independent layout signatures", async () => {
|
|
const directory = fs.mkdtempSync(path.join(os.tmpdir(), "ghdr-broker-"));
|
|
const now = Math.floor(Date.now() / 1000);
|
|
const nodes = [
|
|
makeController("GH-CTRL-GZ-01", "tencent/ap-guangzhou/BS-GZ-006"),
|
|
makeController("GH-CTRL-SG-01", "tencent/ap-singapore/ZY-SG-006"),
|
|
];
|
|
const authorizer = new GhdrAuthorizer({
|
|
privateKeyFile: path.join(directory, "authorizer.pem"),
|
|
now: () => now,
|
|
});
|
|
const broker = new GhdrControllerBroker({
|
|
controllers: nodes.map(item => item.binding),
|
|
stateDir: path.join(directory, "jobs"),
|
|
authorizer,
|
|
now: () => now,
|
|
waitMs: 2000,
|
|
});
|
|
const plan = {
|
|
schema: "guanghu.ghdr-signed-layout-plan/v1",
|
|
payload: {
|
|
node_id: "GH-CVM-MAIN-PROD-01",
|
|
generation: 1,
|
|
},
|
|
signatures: [],
|
|
};
|
|
const digest = crypto.createHash("sha256").update(JSON.stringify(plan.payload)).digest("hex");
|
|
const binding = {
|
|
payload_sha256: digest,
|
|
resource: `GH-CVM-MAIN-PROD-01:${digest}:1`,
|
|
};
|
|
try {
|
|
const pending = broker.queueAndWait({
|
|
plan,
|
|
binding,
|
|
workorderId: "00000000-0000-4000-8000-000000000001",
|
|
});
|
|
const submitted = [];
|
|
for (const node of nodes) {
|
|
const poll = {
|
|
schema: "guanghu.ghdr-controller-poll/v1",
|
|
node_id: node.binding.node_id,
|
|
issued_at_unix: now,
|
|
nonce: crypto.randomBytes(24).toString("base64url"),
|
|
};
|
|
const body = {
|
|
request: poll,
|
|
request_signature_hex: crypto.sign(null, Buffer.from(JSON.stringify(poll)), node.transport.privateKey).toString("hex"),
|
|
};
|
|
const job = broker.poll(body).job;
|
|
assert.ok(job);
|
|
assert.equal(broker.poll(body).error, "ghdr_controller_auth_replayed");
|
|
const layoutSignatureHex = crypto.sign(
|
|
null,
|
|
Buffer.from(JSON.stringify(plan.payload)),
|
|
node.layout.privateKey,
|
|
).toString("hex");
|
|
const result = {
|
|
schema: "guanghu.ghdr-controller-result/v1",
|
|
node_id: node.binding.node_id,
|
|
job_id: job.job_id,
|
|
layout_payload_sha256: digest,
|
|
signature_hex: layoutSignatureHex,
|
|
issued_at_unix: now,
|
|
nonce: crypto.randomBytes(24).toString("base64url"),
|
|
};
|
|
const signature = {
|
|
node_id: node.binding.node_id,
|
|
failure_domain: node.binding.failure_domain,
|
|
public_key_hex: node.binding.layout_public_key_hex,
|
|
signature_hex: layoutSignatureHex,
|
|
};
|
|
assert.equal(broker.submit({
|
|
request: result,
|
|
request_signature_hex: crypto.sign(null, Buffer.from(JSON.stringify(result)), node.transport.privateKey).toString("hex"),
|
|
signature,
|
|
}).ok, true);
|
|
submitted.push(signature);
|
|
}
|
|
const completed = await pending;
|
|
assert.equal(completed.ok, true);
|
|
assert.deepEqual(completed.signatures, submitted);
|
|
} finally {
|
|
fs.rmSync(directory, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
function makeController(nodeId, failureDomain) {
|
|
const layout = crypto.generateKeyPairSync("ed25519");
|
|
const transport = crypto.generateKeyPairSync("ed25519");
|
|
return {
|
|
layout,
|
|
transport,
|
|
binding: {
|
|
node_id: nodeId,
|
|
failure_domain: failureDomain,
|
|
layout_public_key_hex: rawPublic(layout.publicKey),
|
|
transport_public_key_hex: rawPublic(transport.publicKey),
|
|
},
|
|
};
|
|
}
|
|
|
|
function rawPublic(key) {
|
|
return key.export({ type: "spki", format: "der" }).subarray(-32).toString("hex");
|
|
}
|