fix(authz): validate nested deployment receipts
This commit is contained in:
parent
3edca726c1
commit
f7454964c4
2 changed files with 29 additions and 5 deletions
|
|
@ -164,7 +164,7 @@ async function provision(request, options = {}) {
|
||||||
await run("/usr/bin/systemctl", ["enable", "--now", checked.unit]);
|
await run("/usr/bin/systemctl", ["enable", "--now", checked.unit]);
|
||||||
}
|
}
|
||||||
const runtime = await getJsonWithRetry(checked.runtimeCheck.url, options.getJson, options.healthAttempts, options.healthDelayMs);
|
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() };
|
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 });
|
fs.mkdirSync(receiptsDir, { recursive: true, mode: 0o700 });
|
||||||
writeAtomic(path.join(receiptsDir, `${resource.requestId}.json`), `${JSON.stringify(receipt, null, 2)}\n`, 0o600);
|
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", ["daemon-reload"]);
|
||||||
await run("/usr/bin/systemctl", ["restart", checked.unit]);
|
await run("/usr/bin/systemctl", ["restart", checked.unit]);
|
||||||
const runtime = await getJsonWithRetry(checked.runtimeCheck.url, context.getJson, context.healthAttempts, context.healthDelayMs);
|
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) {
|
for (const check of checked.acceptanceChecks) {
|
||||||
const observed = await getJsonWithRetry(check.url, context.getJson, context.healthAttempts, context.healthDelayMs);
|
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 = {
|
const receipt = {
|
||||||
schema: "guanghu.existing-service-update-receipt/v1",
|
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) {
|
async function prepareRepo(repoDir, commit, run, repoUrl) {
|
||||||
fs.mkdirSync(path.dirname(repoDir), { recursive: true, mode: 0o700 });
|
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]);
|
if (!fs.existsSync(path.join(repoDir, ".git"))) await run("/usr/bin/git", ["clone", "--filter=blob:none", "--no-checkout", repoUrl, repoDir]);
|
||||||
|
|
|
||||||
|
|
@ -128,7 +128,14 @@ test("existing service update backs up declared files, restarts, and verifies id
|
||||||
service_update: { kind: "existing-systemd-service", require_existing_unit: true, required_existing_files: ["server.js"] },
|
service_update: { kind: "existing-systemd-service", require_existing_unit: true, required_existing_files: ["server.js"] },
|
||||||
unit_source: "server-tools/ai-discovery/guanghu-ai-discovery.service",
|
unit_source: "server-tools/ai-discovery/guanghu-ai-discovery.service",
|
||||||
files: [{ source: "server-tools/ai-discovery/server.js", destination: "server.js", mode: "0644" }],
|
files: [{ source: "server-tools/ai-discovery/server.js", destination: "server.js", mode: "0644" }],
|
||||||
runtime_check: { url: "http://127.0.0.1:3922/health", expected: { ok: true, mode: "read-only" } },
|
runtime_check: {
|
||||||
|
url: "http://127.0.0.1:3922/health",
|
||||||
|
expected: {
|
||||||
|
ok: true,
|
||||||
|
mode: "read-only",
|
||||||
|
navigation_source: { anchor_id: "GLW-PUBLIC-NAV-ANCHOR-001", source_degraded: false },
|
||||||
|
},
|
||||||
|
},
|
||||||
acceptance_checks: [{ url: "http://127.0.0.1:3922/v1/resolve?id=ICE-GL-ZY001", expected: { canonical_id: "ICE-P-ZY001", redirected: true } }],
|
acceptance_checks: [{ url: "http://127.0.0.1:3922/v1/resolve?id=ICE-GL-ZY001", expected: { canonical_id: "ICE-P-ZY001", redirected: true } }],
|
||||||
};
|
};
|
||||||
fs.mkdirSync(path.join(repoDir, "deployment", "requests"), { recursive: true });
|
fs.mkdirSync(path.join(repoDir, "deployment", "requests"), { recursive: true });
|
||||||
|
|
@ -146,7 +153,9 @@ test("existing service update backs up declared files, restarts, and verifies id
|
||||||
const result = await provision({ target: "JD-FD-PRIMARY", action: "provision-approved-architecture", resource: `${updateRequestId}@${commit}` }, {
|
const result = await provision({ target: "JD-FD-PRIMARY", action: "provision-approved-architecture", resource: `${updateRequestId}@${commit}` }, {
|
||||||
repoDir, releasesDir, unitDir, receiptsDir, installRootOverride: installRoot,
|
repoDir, releasesDir, unitDir, receiptsDir, installRootOverride: installRoot,
|
||||||
run: async (file, args) => { commands.push([file, args]); return { stdout: args.includes("rev-parse") ? `${commit}\n` : "" }; },
|
run: async (file, args) => { commands.push([file, args]); return { stdout: args.includes("rev-parse") ? `${commit}\n` : "" }; },
|
||||||
getJson: async url => url.endsWith("/health") ? { ok: true, mode: "read-only" } : { canonical_id: "ICE-P-ZY001", redirected: true },
|
getJson: async url => url.endsWith("/health")
|
||||||
|
? { ok: true, mode: "read-only", navigation_source: { anchor_id: "GLW-PUBLIC-NAV-ANCHOR-001", source_degraded: false, source_commit: commit } }
|
||||||
|
: { canonical_id: "ICE-P-ZY001", redirected: true },
|
||||||
healthDelayMs: 0,
|
healthDelayMs: 0,
|
||||||
});
|
});
|
||||||
assert.equal(result.ok, true);
|
assert.equal(result.ok, true);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue