feat(authz): add email-authorized GHDR dual signing

This commit is contained in:
冰朔 2026-08-02 23:41:48 +08:00
commit bec7a3d7a0
10 changed files with 771 additions and 5 deletions

View file

@ -0,0 +1,75 @@
"use strict";
const crypto = require("node:crypto");
const fs = require("node:fs");
const path = require("node:path");
class GhdrAuthorizer {
constructor({
privateKeyFile = "/var/lib/guanghu/lake-lamp-authz/ghdr-authorizer-private.pem",
now = () => Math.floor(Date.now() / 1000),
} = {}) {
this.privateKeyFile = privateKeyFile;
this.now = now;
this.privateKey = this.loadOrCreate();
this.publicKey = crypto.createPublicKey(this.privateKey);
}
loadOrCreate() {
if (fs.existsSync(this.privateKeyFile)) {
const metadata = fs.lstatSync(this.privateKeyFile);
if (!metadata.isFile() || metadata.isSymbolicLink() || metadata.mode & 0o077) {
throw new Error("ghdr_authorizer_private_key_permissions_invalid");
}
return crypto.createPrivateKey(fs.readFileSync(this.privateKeyFile));
}
fs.mkdirSync(path.dirname(this.privateKeyFile), { recursive: true, mode: 0o700 });
const { privateKey } = crypto.generateKeyPairSync("ed25519");
const pem = privateKey.export({ type: "pkcs8", format: "pem" });
const temporary = `${this.privateKeyFile}.${process.pid}.tmp`;
fs.writeFileSync(temporary, pem, { mode: 0o600, flag: "wx" });
fs.renameSync(temporary, this.privateKeyFile);
return privateKey;
}
publicBinding() {
const publicPem = this.publicKey.export({ type: "spki", format: "pem" });
const fingerprint = crypto.createHash("sha256").update(publicPem).digest("hex");
return {
schema: "guanghu.ghdr-authorizer-public-binding/v1",
authorizer_id: "JD-FD-PRIMARY-LAKE-LAMP",
algorithm: "Ed25519",
public_key_pem: publicPem,
public_key_sha256: fingerprint,
};
}
issue({
controllerNodeId,
targetNodeId,
layoutPayloadSha256,
resource,
workorderId,
}) {
const issued = this.now();
const capability = {
schema: "guanghu.ghdr-signing-capability/v1",
authorizer_id: "JD-FD-PRIMARY-LAKE-LAMP",
controller_node_id: controllerNodeId,
target_node_id: targetNodeId,
layout_payload_sha256: layoutPayloadSha256,
resource,
workorder_id: workorderId,
issued_at_unix: issued,
expires_at_unix: issued + 120,
nonce: crypto.randomBytes(24).toString("base64url"),
};
const canonical = Buffer.from(JSON.stringify(capability));
return {
capability,
capability_signature_base64url: crypto.sign(null, canonical, this.privateKey).toString("base64url"),
};
}
}
module.exports = { GhdrAuthorizer };

View file

@ -0,0 +1,39 @@
"use strict";
const test = require("node:test");
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 { GhdrAuthorizer } = require("./ghdr-authorizer");
test("GHDR authorizer persists a private key without exposing it and signs a two-minute capability", () => {
const directory = fs.mkdtempSync(path.join(os.tmpdir(), "ghdr-authorizer-"));
try {
const privateKeyFile = path.join(directory, "authorizer.pem");
const authorizer = new GhdrAuthorizer({ privateKeyFile, now: () => 1000 });
const issued = authorizer.issue({
controllerNodeId: "GH-CTRL-GZ-01",
targetNodeId: "GH-CVM-MAIN-PROD-01",
layoutPayloadSha256: "a".repeat(64),
resource: `GH-CVM-MAIN-PROD-01:${"a".repeat(64)}:1`,
workorderId: "00000000-0000-4000-8000-000000000001",
});
assert.equal(fs.statSync(privateKeyFile).mode & 0o777, 0o600);
assert.equal(issued.capability.expires_at_unix, 1120);
const publicKey = crypto.createPublicKey(authorizer.publicBinding().public_key_pem);
assert.equal(
crypto.verify(
null,
Buffer.from(JSON.stringify(issued.capability)),
publicKey,
Buffer.from(issued.capability_signature_base64url, "base64url"),
),
true,
);
assert.doesNotMatch(JSON.stringify(authorizer.publicBinding()), /PRIVATE KEY/);
} finally {
fs.rmSync(directory, { recursive: true, force: true });
}
});

View file

@ -0,0 +1,195 @@
"use strict";
const crypto = require("node:crypto");
const fs = require("node:fs");
const path = require("node:path");
const POLL_FIELDS = [
"schema", "node_id", "issued_at_unix", "nonce",
];
const RESULT_FIELDS = [
"schema", "node_id", "job_id", "layout_payload_sha256",
"signature_hex", "issued_at_unix", "nonce",
];
class GhdrControllerBroker {
constructor({
controllers,
stateDir = "/var/lib/guanghu/lake-lamp-authz/ghdr-jobs",
authorizer,
now = () => Math.floor(Date.now() / 1000),
waitMs = 45000,
}) {
if (!Array.isArray(controllers) || controllers.length !== 2) {
throw new Error("ghdr_controller_registry_requires_exactly_two");
}
this.controllers = new Map(controllers.map(item => [item.node_id, validateController(item)]));
if (this.controllers.size !== 2
|| new Set(controllers.map(item => item.failure_domain)).size !== 2) {
throw new Error("ghdr_controller_registry_not_independent");
}
this.stateDir = stateDir;
this.authorizer = authorizer;
this.now = now;
this.waitMs = waitMs;
this.replay = new Map();
fs.mkdirSync(this.stateDir, { recursive: true, mode: 0o700 });
}
async queueAndWait({ plan, binding, workorderId }) {
const jobId = crypto.randomUUID();
const job = {
schema: "guanghu.ghdr-controller-job/v1",
job_id: jobId,
created_at_unix: this.now(),
expires_at_unix: this.now() + 120,
layout_payload_sha256: binding.payload_sha256,
resource: binding.resource,
workorder_id: workorderId,
plan,
authorizations: {},
results: {},
};
for (const controller of this.controllers.values()) {
job.authorizations[controller.node_id] = this.authorizer.issue({
controllerNodeId: controller.node_id,
targetNodeId: "GH-CVM-MAIN-PROD-01",
layoutPayloadSha256: binding.payload_sha256,
resource: binding.resource,
workorderId,
});
}
this.writeJob(job);
const deadline = Date.now() + this.waitMs;
while (Date.now() < deadline) {
const current = this.readJob(jobId);
if (Object.keys(current.results).length === 2) {
return {
ok: true,
signatures: [...this.controllers.keys()].map(nodeId => current.results[nodeId]),
};
}
await new Promise(resolve => setTimeout(resolve, 250));
}
return { ok: false, error: "ghdr_controller_poll_timeout" };
}
poll(body) {
const authenticated = this.authenticate(body && body.request, body && body.request_signature_hex, POLL_FIELDS, "guanghu.ghdr-controller-poll/v1");
if (!authenticated.ok) return authenticated;
const nodeId = authenticated.node_id;
const names = fs.readdirSync(this.stateDir).filter(name => /^[0-9a-f-]{36}\.json$/.test(name)).sort();
for (const name of names) {
const job = this.readJob(name.slice(0, -5));
if (job.expires_at_unix <= this.now() || job.results[nodeId]) continue;
return {
ok: true,
job: {
job_id: job.job_id,
expires_at_unix: job.expires_at_unix,
layout_payload_sha256: job.layout_payload_sha256,
authorization: job.authorizations[nodeId],
plan: job.plan,
},
};
}
return { ok: true, job: null };
}
submit(body) {
const authenticated = this.authenticate(body && body.request, body && body.request_signature_hex, RESULT_FIELDS, "guanghu.ghdr-controller-result/v1");
if (!authenticated.ok) return authenticated;
const request = body.request;
const job = this.readJob(request.job_id);
if (job.expires_at_unix <= this.now()) return { ok: false, error: "ghdr_job_expired" };
if (job.layout_payload_sha256 !== request.layout_payload_sha256) {
return { ok: false, error: "ghdr_result_layout_mismatch" };
}
const controller = this.controllers.get(request.node_id);
const signature = body.signature;
if (!signature
|| signature.node_id !== controller.node_id
|| signature.failure_domain !== controller.failure_domain
|| signature.public_key_hex !== controller.layout_public_key_hex
|| signature.signature_hex !== request.signature_hex) {
return { ok: false, error: "ghdr_result_binding_mismatch" };
}
const payload = Buffer.from(JSON.stringify(job.plan.payload));
if (!verifyHex(controller.layout_public_key_hex, payload, request.signature_hex)) {
return { ok: false, error: "ghdr_layout_signature_invalid" };
}
if (job.results[controller.node_id]) {
return { ok: false, error: "ghdr_result_already_submitted" };
}
job.results[controller.node_id] = signature;
this.writeJob(job);
return { ok: true };
}
authenticate(request, signatureHex, fields, schema) {
if (!request || typeof request !== "object" || JSON.stringify(Object.keys(request)) !== JSON.stringify(fields)) {
return { ok: false, error: "ghdr_controller_auth_not_canonical" };
}
if (request.schema !== schema || !this.controllers.has(request.node_id)) {
return { ok: false, error: "ghdr_controller_auth_unknown" };
}
if (!Number.isSafeInteger(request.issued_at_unix)
|| Math.abs(this.now() - request.issued_at_unix) > 30
|| !/^[A-Za-z0-9_-]{32}$/.test(String(request.nonce || ""))) {
return { ok: false, error: "ghdr_controller_auth_expired" };
}
const controller = this.controllers.get(request.node_id);
const canonical = Buffer.from(JSON.stringify(request));
if (!verifyHex(controller.transport_public_key_hex, canonical, signatureHex)) {
return { ok: false, error: "ghdr_controller_auth_invalid" };
}
const replay = crypto.createHash("sha256").update(canonical).digest("hex");
for (const [digest, expires] of this.replay) {
if (expires < this.now()) this.replay.delete(digest);
}
if (this.replay.has(replay)) return { ok: false, error: "ghdr_controller_auth_replayed" };
this.replay.set(replay, this.now() + 35);
return { ok: true, node_id: request.node_id };
}
readJob(jobId) {
if (!/^[0-9a-f-]{36}$/.test(String(jobId))) throw new Error("ghdr_job_id_invalid");
return JSON.parse(fs.readFileSync(path.join(this.stateDir, `${jobId}.json`), "utf8"));
}
writeJob(job) {
const destination = path.join(this.stateDir, `${job.job_id}.json`);
const temporary = `${destination}.${process.pid}.tmp`;
fs.writeFileSync(temporary, JSON.stringify(job), { mode: 0o600 });
fs.renameSync(temporary, destination);
}
}
function validateController(controller) {
if (!controller
|| !/^GH-CTRL-(GZ|SG)-01$/.test(String(controller.node_id))
|| !/^tencent\/ap-(guangzhou|singapore)\/[A-Z0-9-]+$/.test(String(controller.failure_domain))
|| !/^[0-9a-f]{64}$/.test(String(controller.layout_public_key_hex))
|| !/^[0-9a-f]{64}$/.test(String(controller.transport_public_key_hex))) {
throw new Error("ghdr_controller_registry_invalid");
}
return Object.freeze({ ...controller });
}
function verifyHex(publicKeyHex, message, signatureHex) {
if (!/^[0-9a-f]{128}$/.test(String(signatureHex || ""))) return false;
const prefix = Buffer.from("302a300506032b6570032100", "hex");
const publicKey = crypto.createPublicKey({
key: Buffer.concat([prefix, Buffer.from(publicKeyHex, "hex")]),
format: "der",
type: "spki",
});
return crypto.verify(null, message, publicKey, Buffer.from(signatureHex, "hex"));
}
function loadControllers(file) {
const parsed = JSON.parse(fs.readFileSync(file, "utf8"));
return parsed.controllers;
}
module.exports = { GhdrControllerBroker, loadControllers, verifyHex };

View file

@ -0,0 +1,116 @@
"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");
}

View file

@ -0,0 +1,17 @@
{
"schema": "guanghu.ghdr-controller-registry/v1",
"controllers": [
{
"node_id": "GH-CTRL-GZ-01",
"failure_domain": "tencent/ap-guangzhou/BS-GZ-006",
"layout_public_key_hex": "d37d943381adeb215b5af769dd0cda79c37c289dedad9c52504f68aa71fcf353",
"transport_public_key_hex": "97d4e886276f3d06544dcd02422fd19a50142752218620bdde9c70e43ff374f0"
},
{
"node_id": "GH-CTRL-SG-01",
"failure_domain": "tencent/ap-singapore/ZY-SG-006",
"layout_public_key_hex": "58b2fe26d90c26a09ecde6af2c742b5df35267d1c2b03af94468698d6bf2864f",
"transport_public_key_hex": "099e5963ae3956408fd6e0c7980b3c7813e20eae85972ffba4220976e7093395"
}
]
}

View file

@ -10,9 +10,11 @@ script_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
install_root=/opt/guanghu/lake-lamp-authz
install -d -m 0755 "$install_root"
for file in server.js workorder-manager.js map-gate.js smtp-mailer.js action-client.js architecture-provision-broker.js deployment-event.js deployment-event-worker.js deployment-source-policy.js hololake-session.js hololake-capabilities.js; do
for file in server.js workorder-manager.js map-gate.js smtp-mailer.js action-client.js architecture-provision-broker.js deployment-event.js deployment-event-worker.js deployment-source-policy.js hololake-session.js hololake-capabilities.js ghdr-authorizer.js ghdr-controller-broker.js ghdr-controllers.json; do
install -m 0644 "$script_dir/$file" "$install_root/$file"
done
install -d -m 0755 "$install_root/navigation-maps"
install -m 0644 "$script_dir/navigation-maps/GH-CVM-MAIN-PROD-01.json" "$install_root/navigation-maps/GH-CVM-MAIN-PROD-01.json"
install -m 0644 "$script_dir/lake-lamp-architecture-provision.service" /etc/systemd/system/lake-lamp-architecture-provision.service
install -m 0644 "$script_dir/lake-lamp-deployment-event-worker.service" /etc/systemd/system/lake-lamp-deployment-event-worker.service
install -d -m 0700 /var/lib/guanghu/architecture-provision

View file

@ -4,15 +4,22 @@ const fs = require("node:fs");
const path = require("node:path");
class MapGate {
constructor({ mapsDir = "/etc/guanghu/navigation-maps", stateFile = "" } = {}) {
constructor({ mapsDir = "/etc/guanghu/navigation-maps", fallbackMapsDir = "", stateFile = "" } = {}) {
this.mapsDir = mapsDir;
this.fallbackMapsDir = fallbackMapsDir;
this.stateFile = stateFile;
this.acks = new Map();
this.load();
}
read(target) {
if (!/^[A-Z0-9-]+$/.test(target)) throw new Error("invalid_target");
const data = JSON.parse(fs.readFileSync(path.join(this.mapsDir, `${target}.json`), "utf8"));
const primary = path.join(this.mapsDir, `${target}.json`);
const fallback = this.fallbackMapsDir
? path.join(this.fallbackMapsDir, `${target}.json`)
: "";
const selected = fs.existsSync(primary) ? primary : fallback;
if (!selected || !fs.existsSync(selected)) throw new Error("navigation_map_not_found");
const data = JSON.parse(fs.readFileSync(selected, "utf8"));
const canonical = JSON.stringify(data);
return { data, hash: crypto.createHash("sha256").update(canonical).digest("hex") };
}

View file

@ -0,0 +1,19 @@
{
"schema": "guanghu.navigation-map/v1",
"node_id": "GH-CVM-MAIN-PROD-01",
"display_name": "光湖企业原生 OS 主节点",
"provider": "tencent_cloud",
"region": "ap-guangzhou",
"system_disk": "/dev/vda",
"authorized_entry": "JD-FD-PRIMARY email authorization",
"allowed_actions": [
"read-navigation-map",
"sign-native-layout-plan"
],
"safety": {
"private_keys_exportable": false,
"controller_signatures_required": 2,
"independent_failure_domains_required": 2,
"raw_disk_write": "blocked_until_valid_double_signature"
}
}

View file

@ -11,6 +11,8 @@ const { executeRegisteredAction } = require("./action-client");
const { enqueueDeploymentEvent } = require("./deployment-event");
const { GuanghuRouter, loadDevices } = require("./guanghu-router");
const { HoloLakeSessionManager } = require("./hololake-session");
const { GhdrAuthorizer } = require("./ghdr-authorizer");
const { GhdrControllerBroker, loadControllers } = require("./ghdr-controller-broker");
const {
HoloLakeAiGateway,
HoloLakeKnowledgeProvider,
@ -48,6 +50,7 @@ const DEFAULT_ACTIONS = Object.freeze({
],
"repo-push": ["read-navigation-map", "push-repository"],
"linked-node-ops": ["authorize-linked-node-session"],
"native-recovery": ["read-navigation-map", "sign-native-layout-plan"],
});
function createApp(options = {}) {
@ -57,6 +60,7 @@ function createApp(options = {}) {
const approvers = options.approvers || loadApprovers(options.approversFile || process.env.LAKE_LAMP_APPROVERS_FILE || "", ownerEmail);
const publicBaseUrl = String(options.publicBaseUrl || process.env.LAKE_LAMP_PUBLIC_URL || "").replace(/\/$/, "");
const targets = new Set(options.targets || splitCsv(process.env.LAKE_LAMP_TARGETS || "JD-FD-PRIMARY,BS-GZ-006"));
targets.add("GH-CVM-MAIN-PROD-01");
const actions = options.actions || DEFAULT_ACTIONS;
const devices = options.devices || loadDevices(
options.devicesFile
@ -145,6 +149,7 @@ function createApp(options = {}) {
);
const mapGate = options.mapGate || new MapGate({
mapsDir: options.mapsDir || process.env.LAKE_LAMP_MAPS_DIR || "/etc/guanghu/navigation-maps",
fallbackMapsDir: options.fallbackMapsDir || path.join(__dirname, "navigation-maps"),
stateFile: Object.prototype.hasOwnProperty.call(options, "mapStateFile") ? options.mapStateFile : (process.env.LAKE_LAMP_MAP_STATE_FILE || "/var/lib/guanghu/lake-lamp-authz/map-acks.json"),
});
const repoGrantDir = options.repoGrantDir || process.env.LAKE_LAMP_REPO_GRANT_DIR || "/var/lib/guanghu/repo-authorizations";
@ -173,6 +178,36 @@ function createApp(options = {}) {
const deploymentQueueDir = options.deploymentQueueDir || process.env.LAKE_LAMP_DEPLOYMENT_EVENT_DIR || "/var/lib/guanghu/deployment-events";
const deploymentRegistryFile = options.deploymentRegistryFile || process.env.LAKE_LAMP_DEPLOYMENT_REPOSITORIES || "/etc/guanghu/lake-lamp/deployment-repositories.json";
const executeAction = options.executeAction || executeRegisteredAction;
let ghdrAuthorizer = null;
const getGhdrAuthorizer = options.getGhdrAuthorizer || (() => {
if (!ghdrAuthorizer) {
ghdrAuthorizer = new GhdrAuthorizer({
privateKeyFile: options.ghdrAuthorizerPrivateKeyFile
|| process.env.GHDR_AUTHORIZER_PRIVATE_KEY_FILE
|| "/var/lib/guanghu/lake-lamp-authz/ghdr-authorizer-private.pem",
});
}
return ghdrAuthorizer;
});
let ghdrControllerBroker = null;
const getGhdrControllerBroker = options.getGhdrControllerBroker || (() => {
if (!ghdrControllerBroker) {
const registryFile = options.ghdrControllerRegistryFile
|| process.env.GHDR_CONTROLLER_REGISTRY_FILE
|| path.join(__dirname, "ghdr-controllers.json");
ghdrControllerBroker = new GhdrControllerBroker({
controllers: loadControllers(registryFile),
stateDir: options.ghdrJobStateDir
|| process.env.GHDR_JOB_STATE_DIR
|| "/var/lib/guanghu/lake-lamp-authz/ghdr-jobs",
authorizer: getGhdrAuthorizer(),
});
}
return ghdrControllerBroker;
});
const signGhdrPlan = options.signGhdrPlan || (
request => getGhdrControllerBroker().queueAndWait(request)
);
// Creating a powerless request must never become harder than the human mail
// handoff. Keep at least three attempts per network each hour.
const publicCreateLimit = Math.max(3, Number(options.publicCreateLimit || process.env.LAKE_LAMP_PUBLIC_CREATE_LIMIT || 24));
@ -1035,6 +1070,97 @@ function createApp(options = {}) {
return json(res, queued.state === "queued_for_resident_agent" ? 202 : 400, { ok: queued.state === "queued_for_resident_agent", deployment: queued, receipt: operationReceipt });
}
if (req.method === "POST" && url.pathname === "/api/ghdr/controllers/poll") {
const body = await readJson(req);
if (!body) return json(res, 400, failure("invalid_json"));
const result = getGhdrControllerBroker().poll(body);
return json(res, result.ok ? 200 : 403, result);
}
if (req.method === "POST" && url.pathname === "/api/ghdr/controllers/result") {
const body = await readJson(req);
if (!body) return json(res, 400, failure("invalid_json"));
try {
const result = getGhdrControllerBroker().submit(body);
return json(res, result.ok ? 200 : 403, result);
} catch {
return json(res, 400, failure("ghdr_result_invalid"));
}
}
if (req.method === "POST" && url.pathname === "/api/ghdr/sign-layout") {
const body = await readJson(req);
if (!body) return json(res, 400, failure("invalid_json"));
if (body.cmd || body.command || body.shell || body.args) {
return json(res, 400, failure("arbitrary_command_forbidden"));
}
const token = bearer(req);
const target = String(body.target || "");
const scope = String(body.scope || "native-recovery");
const resource = String(body.resource || "");
const binding = validateGhdrLayoutRequest(body.plan, target, resource);
if (!binding.ok) return json(res, 400, failure(binding.error));
const verified = manager.verifySession(
token,
{ pid: String(body.persona_id || "") },
target,
scope,
"sign-native-layout-plan",
Date.now() / 1000,
resource,
);
if (!verified.ok) return json(res, 403, failure(verified.reason));
const map = mapGate.read(target);
if (!mapGate.verify(token, target, map.hash).ok) {
return json(res, 423, failure("map_ack_required", "先读取并确认导航图。", {
required_action: "read-navigation-map",
}));
}
const result = await signGhdrPlan({
plan: body.plan,
binding,
workorderId: verified.session.workorderId,
authorizer: getGhdrAuthorizer(),
});
const signatures = Array.isArray(result.signatures) ? result.signatures : [];
const independent = signatures.length === 2
&& new Set(signatures.map(item => item && item.node_id)).size === 2
&& new Set(signatures.map(item => item && item.failure_domain)).size === 2;
const succeeded = Boolean(result.ok && independent);
const operationReceipt = receipt({
state: succeeded ? "succeeded" : "failed",
diagnostic_code: succeeded
? "ghdr_layout_double_signature_succeeded"
: String(result.error || "ghdr_layout_double_signature_failed"),
workorder_id: verified.session.workorderId,
target,
action: "sign-native-layout-plan",
evidence: {
layout_payload_sha256: binding.payload_sha256,
generation: binding.generation,
controller_count: signatures.length,
controller_ids: signatures.map(item => String(item && item.node_id || "")),
},
next_step: succeeded
? "把两份签名合并回同一份布局计划,并由企业目标机独立验证公钥、故障域、实时读回与有效期。"
: "读取 diagnostic_code不得降级为单签、复制私钥或绕过邮件授权。",
});
manager.recordReceipt(token, operationReceipt);
return json(res, succeeded ? 200 : 502, {
ok: succeeded,
layout_payload_sha256: binding.payload_sha256,
signatures: succeeded ? signatures : [],
receipt: operationReceipt,
});
}
if (req.method === "GET" && url.pathname === "/api/ghdr/authorizer-public-key") {
return json(res, 200, {
ok: true,
binding: getGhdrAuthorizer().publicBinding(),
});
}
return json(res, 404, { error: "not_found" });
} catch (error) {
process.stderr.write(`lake-lamp request error: ${String(error && error.message || "unknown").slice(0, 240)}\n`);
@ -1248,15 +1374,44 @@ function validateWorkorderBody(body, targets, actions) {
const immutableResourceAction = action === "provision-approved-architecture" || action === "dispatch-approved-deployment";
const repoPushResourceAction = action === "push-repository";
const linkedNodeResourceAction = action === "authorize-linked-node-session";
const ghdrLayoutResourceAction = action === "sign-native-layout-plan";
if (immutableResourceAction && !/^[A-Z0-9][A-Z0-9._-]{5,119}@[0-9a-f]{40}$/.test(resource)) return { ok: false, status: 400, error: "immutable_architecture_resource_required" };
if (repoPushResourceAction && resource && !/^bingshuo\/[a-z0-9._-]+@[a-z0-9][a-z0-9._/-]{0,199}$/.test(resource)) return { ok: false, status: 400, error: "repo_push_resource_invalid" };
if (linkedNodeResourceAction && !/^[A-Z0-9][A-Z0-9._-]{5,119}:[A-Za-z0-9._-]{3,120}$/.test(resource)) return { ok: false, status: 400, error: "linked_node_resource_required" };
if (!immutableResourceAction && !repoPushResourceAction && !linkedNodeResourceAction && resource) return { ok: false, status: 400, error: "resource_not_allowed_for_action" };
if (ghdrLayoutResourceAction && !/^GH-CVM-MAIN-PROD-01:[0-9a-f]{64}:[1-9][0-9]{0,19}$/.test(resource)) return { ok: false, status: 400, error: "ghdr_layout_resource_required" };
if (!immutableResourceAction && !repoPushResourceAction && !linkedNodeResourceAction && !ghdrLayoutResourceAction && resource) return { ok: false, status: 400, error: "resource_not_allowed_for_action" };
if (body.owner_notify !== undefined && typeof body.owner_notify !== "boolean") return { ok: false, status: 400, error: "invalid_owner_notify" };
if (body.owner_notify === true && provenance.system_entry !== "光湖语言人格系统当前实例") return { ok: false, status: 400, error: "owner_notify_requires_language_system_provenance" };
if (Object.values(provenance).some(Boolean) && (provenance.system_entry !== "光湖语言人格系统当前实例" || Object.values(provenance).some(item => !item || item.length > 120))) return { ok: false, status: 400, error: "invalid_instance_provenance" };
return { ok: true, request: { persona: { pid: personaId, name: personaName }, provenance, target, scope, action, allowedActions: actions[scope], description, resource } };
}
function validateGhdrLayoutRequest(plan, target, resource) {
if (target !== "GH-CVM-MAIN-PROD-01"
|| !plan
|| plan.schema !== "guanghu.ghdr-signed-layout-plan/v1"
|| !plan.payload
|| !Array.isArray(plan.signatures)
|| plan.signatures.length !== 0) {
return { ok: false, error: "ghdr_layout_plan_invalid" };
}
const payload = plan.payload;
const generation = Number(payload.generation);
if (payload.node_id !== target
|| payload.provider !== "tencent_cloud"
|| payload.region !== "ap-guangzhou"
|| payload.system_disk !== "/dev/vda"
|| payload.operation !== "install_native_ab"
|| !Number.isSafeInteger(generation)
|| generation < 1) {
return { ok: false, error: "ghdr_layout_binding_mismatch" };
}
const canonical = JSON.stringify(payload);
const payloadSha256 = crypto.createHash("sha256").update(canonical).digest("hex");
if (resource !== `${target}:${payloadSha256}:${generation}`) {
return { ok: false, error: "ghdr_layout_resource_mismatch" };
}
return { ok: true, payload_sha256: payloadSha256, generation, resource };
}
function originLabel(order) {
const value = order && order.provenance || {};
return value.software || value.model || value.instance ? `${value.software || "未知软件"} · ${value.model || "未知模型"} · ${value.instance || "当前实例"}` : "旧版工单未记录";
@ -1286,4 +1441,11 @@ if (require.main === module) {
createApp().listen(port, host, () => process.stdout.write(`lake-lamp-authz listening on ${host}:${port}\n`));
}
module.exports = { createApp, DEFAULT_ACTIONS, SlidingWindowLimiter, loadApprovers, selectApprover };
module.exports = {
createApp,
DEFAULT_ACTIONS,
SlidingWindowLimiter,
loadApprovers,
selectApprover,
validateGhdrLayoutRequest,
};

View file

@ -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);