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

@ -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,
};