feat(authz): add email-authorized GHDR dual signing
This commit is contained in:
parent
84f7c4c198
commit
bec7a3d7a0
10 changed files with 771 additions and 5 deletions
|
|
@ -597,6 +597,140 @@ test("failed server actions return a durable diagnosis instead of making a perso
|
|||
} finally { fs.rmSync(dir, { recursive: true, force: true }); }
|
||||
});
|
||||
|
||||
test("GHDR double signing is email-authorized and bound to one canonical layout digest", async () => {
|
||||
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "lake-lamp-ghdr-sign-"));
|
||||
const mapsDir = path.join(dir, "maps"); fs.mkdirSync(mapsDir);
|
||||
fs.writeFileSync(path.join(mapsDir, "GH-CVM-MAIN-PROD-01.json"), JSON.stringify({
|
||||
node_id: "GH-CVM-MAIN-PROD-01",
|
||||
}));
|
||||
const now = Math.floor(Date.now() / 1000);
|
||||
const plan = {
|
||||
schema: "guanghu.ghdr-signed-layout-plan/v1",
|
||||
payload: {
|
||||
node_id: "GH-CVM-MAIN-PROD-01",
|
||||
provider: "tencent_cloud",
|
||||
region: "ap-guangzhou",
|
||||
target_probe_sha256: "11".repeat(32),
|
||||
system_disk: "/dev/vda",
|
||||
disk_sectors: 104857600,
|
||||
logical_sector_bytes: 512,
|
||||
disk_identity_sha256: "22".repeat(32),
|
||||
recovery_evidence_sha256: "55".repeat(32),
|
||||
first_partition_lba: 2048,
|
||||
generation: 1,
|
||||
operation: "install_native_ab",
|
||||
issued_at_unix: now,
|
||||
expires_at_unix: now + 300,
|
||||
slots: [
|
||||
{ name: "A", lba_start: 34, sector_count: 29, image_sha256: "33".repeat(32) },
|
||||
{ name: "B", lba_start: 73, sector_count: 29, image_sha256: "44".repeat(32) },
|
||||
],
|
||||
},
|
||||
signatures: [],
|
||||
};
|
||||
const digest = crypto.createHash("sha256").update(JSON.stringify(plan.payload)).digest("hex");
|
||||
const resource = `GH-CVM-MAIN-PROD-01:${digest}:1`;
|
||||
const signatures = [
|
||||
{ node_id: "GH-CTRL-GZ-01", failure_domain: "tencent/ap-guangzhou/BS-GZ-006", public_key_hex: "aa".repeat(32), signature_hex: "bb".repeat(64) },
|
||||
{ node_id: "GH-CTRL-SG-01", failure_domain: "tencent/ap-singapore/ZY-SG-006", public_key_hex: "cc".repeat(32), signature_hex: "dd".repeat(64) },
|
||||
];
|
||||
const calls = [];
|
||||
try {
|
||||
await withServer(async ({ base, mail }) => {
|
||||
const malformed = await fetch(`${base}/api/public/workorders`, {
|
||||
method: "POST",
|
||||
headers: { "content-type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
persona_id: "ICE-GL-ZY001",
|
||||
target: "GH-CVM-MAIN-PROD-01",
|
||||
scope: "native-recovery",
|
||||
action: "sign-native-layout-plan",
|
||||
resource: "GH-CVM-MAIN-PROD-01:not-a-digest:1",
|
||||
}),
|
||||
});
|
||||
assert.equal(malformed.status, 400);
|
||||
assert.equal((await malformed.json()).error, "ghdr_layout_resource_required");
|
||||
|
||||
const requested = await fetch(`${base}/api/public/workorders`, {
|
||||
method: "POST",
|
||||
headers: { "content-type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
persona_id: "ICE-GL-ZY001",
|
||||
target: "GH-CVM-MAIN-PROD-01",
|
||||
scope: "native-recovery",
|
||||
action: "sign-native-layout-plan",
|
||||
resource,
|
||||
}),
|
||||
});
|
||||
const order = await requested.json();
|
||||
await fetch(`${base}${new URL(order.request_url).pathname.replace("/authz", "")}`, ownerForm());
|
||||
await fetch(`${base}${new URL(mail[0].approvalUrl).pathname.replace("/authz", "")}`, { method: "POST" });
|
||||
const session = await (await fetch(`${base}/api/workorders/${order.workorder_id}/claim`, {
|
||||
method: "POST",
|
||||
headers: { authorization: `Bearer ${order.claim_token}` },
|
||||
})).json();
|
||||
const common = {
|
||||
persona_id: "ICE-GL-ZY001",
|
||||
target: "GH-CVM-MAIN-PROD-01",
|
||||
scope: "native-recovery",
|
||||
};
|
||||
const map = await (await fetch(`${base}/api/navigation-map/read`, {
|
||||
method: "POST",
|
||||
headers: { authorization: `Bearer ${session.session_token}`, "content-type": "application/json" },
|
||||
body: JSON.stringify(common),
|
||||
})).json();
|
||||
await fetch(`${base}/api/navigation-map/ack`, {
|
||||
method: "POST",
|
||||
headers: { authorization: `Bearer ${session.session_token}`, "content-type": "application/json" },
|
||||
body: JSON.stringify({ ...common, map_hash: map.map_hash }),
|
||||
});
|
||||
|
||||
const changed = structuredClone(plan);
|
||||
changed.payload.generation = 2;
|
||||
const refused = await fetch(`${base}/api/ghdr/sign-layout`, {
|
||||
method: "POST",
|
||||
headers: { authorization: `Bearer ${session.session_token}`, "content-type": "application/json" },
|
||||
body: JSON.stringify({ ...common, resource, plan: changed }),
|
||||
});
|
||||
assert.equal(refused.status, 400);
|
||||
assert.equal((await refused.json()).error, "ghdr_layout_resource_mismatch");
|
||||
|
||||
const signed = await fetch(`${base}/api/ghdr/sign-layout`, {
|
||||
method: "POST",
|
||||
headers: { authorization: `Bearer ${session.session_token}`, "content-type": "application/json" },
|
||||
body: JSON.stringify({ ...common, resource, plan }),
|
||||
});
|
||||
assert.equal(signed.status, 200);
|
||||
const payload = await signed.json();
|
||||
assert.equal(payload.signatures.length, 2);
|
||||
assert.equal(payload.receipt.diagnostic_code, "ghdr_layout_double_signature_succeeded");
|
||||
assert.deepEqual(calls, [{
|
||||
plan,
|
||||
binding: {
|
||||
ok: true,
|
||||
payload_sha256: digest,
|
||||
generation: 1,
|
||||
resource,
|
||||
},
|
||||
workorderId: order.workorder_id,
|
||||
authorizer: "test-authorizer",
|
||||
}]);
|
||||
}, {
|
||||
mapsDir,
|
||||
mapStateFile: path.join(dir, "acks.json"),
|
||||
targets: ["GH-CVM-MAIN-PROD-01"],
|
||||
actions: { "native-recovery": ["read-navigation-map", "sign-native-layout-plan"] },
|
||||
getGhdrAuthorizer: () => "test-authorizer",
|
||||
signGhdrPlan: async request => {
|
||||
calls.push(request);
|
||||
return { ok: true, signatures };
|
||||
},
|
||||
});
|
||||
} finally {
|
||||
fs.rmSync(dir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test("deployment is dispatched only by an explicit approved second signal", async () => {
|
||||
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "lake-lamp-deploy-dispatch-"));
|
||||
const mapsDir = path.join(dir, "maps"); fs.mkdirSync(mapsDir);
|
||||
|
|
|
|||
Loading…
Reference in a new issue