fix(authz): validate nested deployment receipts

This commit is contained in:
冰朔 2026-08-06 13:01:15 +08:00
commit f7454964c4
2 changed files with 29 additions and 5 deletions

View file

@ -164,7 +164,7 @@ async function provision(request, options = {}) {
await run("/usr/bin/systemctl", ["enable", "--now", checked.unit]);
}
const runtime = await getJsonWithRetry(checked.runtimeCheck.url, options.getJson, options.healthAttempts, options.healthDelayMs);
for (const [key, expected] of Object.entries(checked.runtimeCheck.expected)) if (runtime[key] !== expected) throw new Error(`runtime_check_failed:${key}`);
for (const [key, expected] of Object.entries(checked.runtimeCheck.expected)) if (!matchesExpected(runtime[key], expected)) throw new Error(`runtime_check_failed:${key}`);
const receipt = { schema: "guanghu.architecture-provision-receipt/v1", request_id: resource.requestId, source_commit: resource.commit, target_node: "JD-FD-PRIMARY", unit: checked.unit, runtime_check: checked.runtimeCheck.url, backup: path.join("backups", resource.requestId, resource.commit), rollback: unitBackup ? "restore-previous-unit" : "remove-new-unit", result: "DEPLOYED_AND_VERIFIED", recorded_at: new Date().toISOString() };
fs.mkdirSync(receiptsDir, { recursive: true, mode: 0o700 });
writeAtomic(path.join(receiptsDir, `${resource.requestId}.json`), `${JSON.stringify(receipt, null, 2)}\n`, 0o600);
@ -217,10 +217,10 @@ async function updateExistingService(context) {
await run("/usr/bin/systemctl", ["daemon-reload"]);
await run("/usr/bin/systemctl", ["restart", checked.unit]);
const runtime = await getJsonWithRetry(checked.runtimeCheck.url, context.getJson, context.healthAttempts, context.healthDelayMs);
for (const [key, expected] of Object.entries(checked.runtimeCheck.expected)) if (runtime[key] !== expected) throw new Error(`runtime_check_failed:${key}`);
for (const [key, expected] of Object.entries(checked.runtimeCheck.expected)) if (!matchesExpected(runtime[key], expected)) throw new Error(`runtime_check_failed:${key}`);
for (const check of checked.acceptanceChecks) {
const observed = await getJsonWithRetry(check.url, context.getJson, context.healthAttempts, context.healthDelayMs);
for (const [key, expected] of Object.entries(check.expected)) if (observed[key] !== expected) throw new Error(`acceptance_check_failed:${key}`);
for (const [key, expected] of Object.entries(check.expected)) if (!matchesExpected(observed[key], expected)) throw new Error(`acceptance_check_failed:${key}`);
}
const receipt = {
schema: "guanghu.existing-service-update-receipt/v1",
@ -281,6 +281,21 @@ function restoreBackups(records) {
}
}
function matchesExpected(observed, expected) {
if (Array.isArray(expected)) {
return Array.isArray(observed)
&& observed.length === expected.length
&& expected.every((item, index) => matchesExpected(observed[index], item));
}
if (expected && typeof expected === "object") {
return Boolean(observed)
&& typeof observed === "object"
&& !Array.isArray(observed)
&& Object.entries(expected).every(([key, value]) => matchesExpected(observed[key], value));
}
return Object.is(observed, expected);
}
async function prepareRepo(repoDir, commit, run, repoUrl) {
fs.mkdirSync(path.dirname(repoDir), { recursive: true, mode: 0o700 });
if (!fs.existsSync(path.join(repoDir, ".git"))) await run("/usr/bin/git", ["clone", "--filter=blob:none", "--no-checkout", repoUrl, repoDir]);