mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-22 02:27:44 +08:00
feat(ql3): establish 3.0 incubation baseline
This commit is contained in:
@@ -0,0 +1,652 @@
|
||||
const assert = require('node:assert/strict');
|
||||
const crypto = require('node:crypto');
|
||||
const { spawnSync } = require('node:child_process');
|
||||
const fs = require('node:fs');
|
||||
const os = require('node:os');
|
||||
const path = require('node:path');
|
||||
const { afterEach, test } = require('node:test');
|
||||
|
||||
const {
|
||||
WorkerCredentialManagementReleaseEvidenceError,
|
||||
auditWorkerCredentialManagementReleaseEvidence,
|
||||
clearSourceDocuments,
|
||||
parseArguments,
|
||||
readSourceDocuments,
|
||||
runWorkerCredentialManagementReleaseEvidence,
|
||||
validateWorkerCredentialManagementReleaseEvidence,
|
||||
} = require('../../scripts/ql3-worker-credential-management-release-evidence.cjs');
|
||||
const {
|
||||
MAX_EVIDENCE_AGE_MS,
|
||||
MAX_FUTURE_SKEW_MS,
|
||||
WorkerCredentialManagementReleaseGateError,
|
||||
auditWorkerCredentialManagementReleaseGate,
|
||||
parseArguments: parseGateArguments,
|
||||
} = require('../../scripts/ql3-worker-credential-management-release-gate.cjs');
|
||||
|
||||
const NOW_MS = 1_700_000_000_000;
|
||||
const ISSUER = 'https://identity.production.example.org/';
|
||||
const temporaryDirectories = [];
|
||||
|
||||
function sha(character) {
|
||||
return `sha256:${character.repeat(64)}`;
|
||||
}
|
||||
|
||||
function digest(domain, value) {
|
||||
return `sha256:${crypto
|
||||
.createHash('sha256')
|
||||
.update(domain)
|
||||
.update('\0')
|
||||
.update(String(value))
|
||||
.digest('hex')}`;
|
||||
}
|
||||
|
||||
function rawDigest(value) {
|
||||
return `sha256:${crypto.createHash('sha256').update(value).digest('hex')}`;
|
||||
}
|
||||
|
||||
function ceremonyReport() {
|
||||
return {
|
||||
schemaVersion: 1,
|
||||
fixture: 'qinglong/worker-credential-management-live-ceremony@v1',
|
||||
observedAt: new Date(NOW_MS).toISOString(),
|
||||
identity: {
|
||||
providerKind: 'external_oidc',
|
||||
issuer: ISSUER,
|
||||
discoveryDocumentSha256: sha('a'),
|
||||
jwksSha256: sha('b'),
|
||||
audience: 'qinglong3-worker-credential-management',
|
||||
type: 'ql3-worker-credential-management+jwt',
|
||||
purpose: 'worker-credential-management',
|
||||
requesterSubjectSha256: digest(
|
||||
'qinglong3.worker-management.subject.v1',
|
||||
'operator-a',
|
||||
),
|
||||
reviewerSubjectSha256: digest(
|
||||
'qinglong3.worker-management.subject.v1',
|
||||
'reviewer-b',
|
||||
),
|
||||
requesterKeyIdSha256: sha('c'),
|
||||
reviewerKeyIdSha256: sha('d'),
|
||||
},
|
||||
ceremony: {
|
||||
actionRefSha256: sha('e'),
|
||||
authorityProjectIdSha256: sha('f'),
|
||||
planStatus: 'created',
|
||||
approvalStatus: 'created',
|
||||
requesterSelfDecisionStatus: 403,
|
||||
requesterSelfDecisionCode: 'forbidden',
|
||||
reviewerDecisionStatus: 'decided',
|
||||
approvalState: 'approved',
|
||||
inspectionStale: false,
|
||||
dispatchCreated: false,
|
||||
approvalConsumed: false,
|
||||
planDigest: 'a'.repeat(64),
|
||||
previewDigest: 'b'.repeat(64),
|
||||
requestIdSha256: ['0', '1', '2', '3', '4'].map(sha),
|
||||
},
|
||||
gates: {
|
||||
externalIdentity: true,
|
||||
workerPurposeBound: true,
|
||||
requesterAndReviewerDistinct: true,
|
||||
requesterSelfDecisionRejected: true,
|
||||
reviewerDecisionAccepted: true,
|
||||
inspectionAuthorized: true,
|
||||
noExecutionOrConsumption: true,
|
||||
passed: true,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function durableReport(ceremonyBytes, ceremony) {
|
||||
return {
|
||||
schemaVersion: 1,
|
||||
fixture: 'qinglong/worker-credential-management-durable-audit-evidence@v1',
|
||||
observedAt: new Date(NOW_MS + 100).toISOString(),
|
||||
source: {
|
||||
ceremonyReportSha256: rawDigest(ceremonyBytes),
|
||||
ceremonyDefinitionSha256: sha('5'),
|
||||
ceremonyFixture: ceremony.fixture,
|
||||
},
|
||||
database: {
|
||||
postgresVersionNumber: 180004,
|
||||
transactionReadOnly: true,
|
||||
roleNameSha256: sha('6'),
|
||||
roleCanLogin: true,
|
||||
privilegedAttributesDenied: true,
|
||||
privilegedMembershipDenied: true,
|
||||
exactTargetSelect: true,
|
||||
ql3TableMutationDenied: true,
|
||||
},
|
||||
durableState: {
|
||||
actionRefSha256: ceremony.ceremony.actionRefSha256,
|
||||
authorityProjectIdSha256: ceremony.ceremony.authorityProjectIdSha256,
|
||||
approvalRequestIdSha256: sha('7'),
|
||||
reviewerDecisionIdSha256: sha('8'),
|
||||
planDigest: ceremony.ceremony.planDigest,
|
||||
previewDigest: ceremony.ceremony.previewDigest,
|
||||
approvalVersion: 2,
|
||||
approvalState: 'approved',
|
||||
requesterSubjectSha256: ceremony.identity.requesterSubjectSha256,
|
||||
reviewerSubjectSha256: ceremony.identity.reviewerSubjectSha256,
|
||||
dispatchCreated: false,
|
||||
approvalConsumed: false,
|
||||
requesterSelfDecisionAuditAbsent: true,
|
||||
auditRows: [
|
||||
{
|
||||
kind: 'proposal',
|
||||
eventIdSha256: sha('9'),
|
||||
operationId: 'approval.request',
|
||||
outcome: 'approval_required',
|
||||
subjectSha256: ceremony.identity.requesterSubjectSha256,
|
||||
authenticationIdSha256: sha('a'),
|
||||
reasonCode: 'worker_credential_review',
|
||||
policyFencePresent: true,
|
||||
},
|
||||
{
|
||||
kind: 'reviewer_decision',
|
||||
eventIdSha256: sha('b'),
|
||||
operationId: 'approval.decide',
|
||||
outcome: 'allowed',
|
||||
subjectSha256: ceremony.identity.reviewerSubjectSha256,
|
||||
authenticationIdSha256: sha('c'),
|
||||
reasonCode: 'worker_credential_review',
|
||||
policyFencePresent: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
gates: {
|
||||
sourceBound: true,
|
||||
readOnlyEvidenceRole: true,
|
||||
immutablePlanObserved: true,
|
||||
reviewedApprovalObserved: true,
|
||||
requesterSelfDecisionLeftNoAudit: true,
|
||||
proposalAndDecisionAuditObserved: true,
|
||||
noExecutionOrConsumption: true,
|
||||
passed: true,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function commonIdentity(ceremony) {
|
||||
return {
|
||||
providerKind: 'external_oidc',
|
||||
issuer: ceremony.identity.issuer,
|
||||
audience: ceremony.identity.audience,
|
||||
type: ceremony.identity.type,
|
||||
purpose: ceremony.identity.purpose,
|
||||
subjectSha256: ceremony.identity.requesterSubjectSha256,
|
||||
ceremonyIdentityBound: true,
|
||||
durableAuditBound: true,
|
||||
};
|
||||
}
|
||||
|
||||
function pkiReport(ceremonyBytes, durableBytes, ceremony) {
|
||||
return {
|
||||
schemaVersion: 2,
|
||||
fixture: 'qinglong/worker-credential-management-pki-rotation-evidence@v2',
|
||||
observedAt: new Date(NOW_MS + 200).toISOString(),
|
||||
source: {
|
||||
beforeStateSha256: sha('d'),
|
||||
ceremonyReportSha256: rawDigest(ceremonyBytes),
|
||||
durableAuditReportSha256: rawDigest(durableBytes),
|
||||
ceremonyFixture: ceremony.fixture,
|
||||
durableAuditFixture:
|
||||
'qinglong/worker-credential-management-durable-audit-evidence@v1',
|
||||
},
|
||||
identity: commonIdentity(ceremony),
|
||||
transport: {
|
||||
endpointSha256: sha('e'),
|
||||
servernameSha256: sha('f'),
|
||||
serverTrustBundleSha256: sha('0'),
|
||||
serverAuthoritySha256: [sha('1')],
|
||||
commandSha256: sha('2'),
|
||||
oldClientCertificateSha256: sha('3'),
|
||||
newClientCertificateSha256: sha('4'),
|
||||
beforeOldStatus: 200,
|
||||
beforeNewStatus: 200,
|
||||
afterOldStatus: 401,
|
||||
afterOldCode: 'client_certificate_required',
|
||||
afterNewStatus: 200,
|
||||
},
|
||||
pki: {
|
||||
clientIssuerBundleSha256: sha('5'),
|
||||
clientIssuerCaSha256: sha('6'),
|
||||
clientIssuerSubjectSha256: sha('7'),
|
||||
beforeCrlSha256: sha('8'),
|
||||
afterCrlSha256: sha('9'),
|
||||
crlIssuerSha256: sha('7'),
|
||||
beforeCrlNumber: '1000',
|
||||
afterCrlNumber: '1001',
|
||||
crlNumberIncreased: true,
|
||||
oldCertificateRevoked: true,
|
||||
replacementCertificateAccepted: true,
|
||||
},
|
||||
kubernetes: {
|
||||
clusterServerSha256: sha('a'),
|
||||
collectorSubjectSha256: sha('b'),
|
||||
deploymentUidSha256: sha('c'),
|
||||
beforeDeploymentResourceVersionSha256: sha('d'),
|
||||
afterDeploymentResourceVersionSha256: sha('e'),
|
||||
beforeGeneration: 10,
|
||||
afterGeneration: 11,
|
||||
beforeCrlAnnotationSha256: sha('f'),
|
||||
afterCrlAnnotationSha256: sha('0'),
|
||||
beforePodUidSha256: [sha('1'), sha('2')],
|
||||
afterPodUidSha256: [sha('3'), sha('4')],
|
||||
oldPodsFullyReplaced: true,
|
||||
twoReadyReplicasOnDistinctNodes: true,
|
||||
exactReadOnlyCollectorAuthority: true,
|
||||
secretReadDenied: true,
|
||||
mutationDenied: true,
|
||||
},
|
||||
gates: {
|
||||
sourceReportsBound: true,
|
||||
externalIdentityBound: true,
|
||||
serverTrustSeparatedFromClientIssuer: true,
|
||||
sameClientIssuer: true,
|
||||
oldAndReplacementInitiallyAccepted: true,
|
||||
crlMonotonic: true,
|
||||
deploymentRolled: true,
|
||||
oldPodsRetired: true,
|
||||
revokedCertificateRejected: true,
|
||||
replacementCertificateAccepted: true,
|
||||
readOnlyCollectorAuthority: true,
|
||||
passed: true,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function caRolloverReport(ceremonyBytes, durableBytes, ceremony) {
|
||||
return {
|
||||
schemaVersion: 1,
|
||||
fixture: 'qinglong/worker-credential-management-ca-rollover-evidence@v1',
|
||||
observedAt: new Date(NOW_MS + 300).toISOString(),
|
||||
source: {
|
||||
oldStateSha256: sha('1'),
|
||||
overlapStateSha256: sha('2'),
|
||||
ceremonyReportSha256: rawDigest(ceremonyBytes),
|
||||
durableAuditReportSha256: rawDigest(durableBytes),
|
||||
ceremonyFixture: ceremony.fixture,
|
||||
durableAuditFixture:
|
||||
'qinglong/worker-credential-management-durable-audit-evidence@v1',
|
||||
},
|
||||
identity: commonIdentity(ceremony),
|
||||
transport: {
|
||||
endpointSha256: sha('e'),
|
||||
servernameSha256: sha('f'),
|
||||
serverTrustBundleSha256: sha('0'),
|
||||
commandSha256: sha('2'),
|
||||
oldClientCertificateSha256: sha('5'),
|
||||
newClientCertificateSha256: sha('6'),
|
||||
},
|
||||
trustTransition: {
|
||||
oldCaBundleSha256: sha('3'),
|
||||
overlapCaBundleSha256: sha('4'),
|
||||
newCaBundleSha256: sha('5'),
|
||||
oldCrlBundleSha256: sha('6'),
|
||||
overlapCrlBundleSha256: sha('7'),
|
||||
newCrlBundleSha256: sha('8'),
|
||||
oldIssuerCaSha256: sha('1'),
|
||||
newIssuerCaSha256: sha('2'),
|
||||
oldSet: [sha('1')],
|
||||
overlapSet: [sha('1'), sha('2')],
|
||||
newSet: [sha('2')],
|
||||
crlIssuerCoverageExact: true,
|
||||
},
|
||||
access: {
|
||||
oldCertificateStatus: [200, 200, 401],
|
||||
newCertificateStatus: [401, 200, 200],
|
||||
},
|
||||
kubernetes: {
|
||||
clusterServerSha256: sha('a'),
|
||||
collectorSubjectSha256: sha('b'),
|
||||
deploymentUidSha256: sha('c'),
|
||||
generations: [20, 21, 22],
|
||||
resourceVersionSha256: [sha('9'), sha('a'), sha('b')],
|
||||
podUidSha256: [
|
||||
[sha('0'), sha('1')],
|
||||
[sha('2'), sha('3')],
|
||||
[sha('4'), sha('5')],
|
||||
],
|
||||
allGenerationsFullyReplaced: true,
|
||||
twoReadyReplicasOnDistinctNodes: true,
|
||||
exactReadOnlyCollectorAuthority: true,
|
||||
},
|
||||
gates: {
|
||||
sourceReportsBound: true,
|
||||
externalIdentityBound: true,
|
||||
serverTrustSeparatedFromClientIssuer: true,
|
||||
oldOnlyObserved: true,
|
||||
exactOverlapObserved: true,
|
||||
safeRetirementObserved: true,
|
||||
crlCoverageObservedEveryPhase: true,
|
||||
allPodGenerationsReplaced: true,
|
||||
readOnlyCollectorAuthority: true,
|
||||
passed: true,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function writeJson(directory, name, value) {
|
||||
const filePath = path.join(directory, name);
|
||||
fs.writeFileSync(filePath, `${JSON.stringify(value)}\n`, { mode: 0o600 });
|
||||
return filePath;
|
||||
}
|
||||
|
||||
function fixture(mutate = () => {}) {
|
||||
const directory = fs.realpathSync(
|
||||
fs.mkdtempSync(path.join(os.tmpdir(), 'ql3-worker-release-test-')),
|
||||
);
|
||||
fs.chmodSync(directory, 0o700);
|
||||
temporaryDirectories.push(directory);
|
||||
const ceremony = ceremonyReport();
|
||||
mutate({ phase: 'ceremony', report: ceremony });
|
||||
const ceremonyReportFile = writeJson(directory, 'ceremony.json', ceremony);
|
||||
const ceremonyBytes = fs.readFileSync(ceremonyReportFile);
|
||||
const durable = durableReport(ceremonyBytes, ceremony);
|
||||
mutate({ phase: 'durable', report: durable });
|
||||
const durableAuditReportFile = writeJson(directory, 'durable.json', durable);
|
||||
const durableBytes = fs.readFileSync(durableAuditReportFile);
|
||||
const pki = pkiReport(ceremonyBytes, durableBytes, ceremony);
|
||||
mutate({ phase: 'pki', report: pki });
|
||||
const pkiRotationReportFile = writeJson(directory, 'pki.json', pki);
|
||||
const caRollover = caRolloverReport(ceremonyBytes, durableBytes, ceremony);
|
||||
mutate({ phase: 'ca', report: caRollover });
|
||||
const caRolloverReportFile = writeJson(
|
||||
directory,
|
||||
'ca-rollover.json',
|
||||
caRollover,
|
||||
);
|
||||
return {
|
||||
directory,
|
||||
ceremonyReportFile,
|
||||
durableAuditReportFile,
|
||||
pkiRotationReportFile,
|
||||
caRolloverReportFile,
|
||||
outputFile: path.join(directory, 'release.json'),
|
||||
};
|
||||
}
|
||||
|
||||
function options(paths) {
|
||||
return {
|
||||
ceremonyReportFile: paths.ceremonyReportFile,
|
||||
durableAuditReportFile: paths.durableAuditReportFile,
|
||||
pkiRotationReportFile: paths.pkiRotationReportFile,
|
||||
caRolloverReportFile: paths.caRolloverReportFile,
|
||||
outputFile: paths.outputFile,
|
||||
};
|
||||
}
|
||||
|
||||
function gateOptions(paths) {
|
||||
return {
|
||||
reportFile: paths.outputFile,
|
||||
ceremonyReportFile: paths.ceremonyReportFile,
|
||||
durableAuditReportFile: paths.durableAuditReportFile,
|
||||
pkiRotationReportFile: paths.pkiRotationReportFile,
|
||||
caRolloverReportFile: paths.caRolloverReportFile,
|
||||
sourceCommit: 'a'.repeat(40),
|
||||
releaseVersion: '3.0.0-alpha.1',
|
||||
};
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
for (const directory of temporaryDirectories.splice(0)) {
|
||||
fs.rmSync(directory, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test('assembles and independently re-audits one digest-bound release gate', () => {
|
||||
const paths = fixture();
|
||||
const report = runWorkerCredentialManagementReleaseEvidence(
|
||||
options(paths),
|
||||
{ now: () => NOW_MS + 1_000 },
|
||||
);
|
||||
assert.equal(
|
||||
validateWorkerCredentialManagementReleaseEvidence(report).compatible,
|
||||
true,
|
||||
);
|
||||
assert.equal(report.gates.passed, true);
|
||||
assert.deepEqual(report.deployment.pkiGenerations, [10, 11]);
|
||||
assert.deepEqual(report.deployment.caRolloverGenerations, [20, 21, 22]);
|
||||
assert.equal(fs.statSync(paths.outputFile).mode & 0o777, 0o600);
|
||||
assert.doesNotMatch(
|
||||
JSON.stringify(report),
|
||||
/operator-a|reviewer-b|BEGIN|eyJ|pod-|node-|deployment-a/,
|
||||
);
|
||||
|
||||
const audit = spawnSync(
|
||||
process.execPath,
|
||||
[
|
||||
path.resolve(
|
||||
__dirname,
|
||||
'../../scripts/ql3-worker-credential-management-release-evidence-audit.cjs',
|
||||
),
|
||||
`--report=${paths.outputFile}`,
|
||||
`--ceremony-report=${paths.ceremonyReportFile}`,
|
||||
`--durable-audit-report=${paths.durableAuditReportFile}`,
|
||||
`--pki-rotation-report=${paths.pkiRotationReportFile}`,
|
||||
`--ca-rollover-report=${paths.caRolloverReportFile}`,
|
||||
],
|
||||
{ encoding: 'utf8' },
|
||||
);
|
||||
assert.equal(audit.status, 0, audit.stderr);
|
||||
assert.equal(JSON.parse(audit.stdout).compatible, true);
|
||||
});
|
||||
|
||||
test('binds fresh source-aware evidence to one release workflow identity', () => {
|
||||
const paths = fixture();
|
||||
runWorkerCredentialManagementReleaseEvidence(options(paths), {
|
||||
now: () => NOW_MS + 1_000,
|
||||
});
|
||||
const result = auditWorkerCredentialManagementReleaseGate(
|
||||
gateOptions(paths),
|
||||
{ now: () => NOW_MS + 2_000 },
|
||||
);
|
||||
assert.deepEqual(result, {
|
||||
schemaVersion: 1,
|
||||
fixture: 'qinglong/worker-credential-management-release-gate@v1',
|
||||
compatible: true,
|
||||
sourceCommit: 'a'.repeat(40),
|
||||
releaseVersion: '3.0.0-alpha.1',
|
||||
evidenceReportSha256: rawDigest(fs.readFileSync(paths.outputFile)),
|
||||
maximumEvidenceAgeSeconds: 86400,
|
||||
});
|
||||
});
|
||||
|
||||
test('rejects stale or future-dated release evidence', () => {
|
||||
const paths = fixture();
|
||||
runWorkerCredentialManagementReleaseEvidence(options(paths), {
|
||||
now: () => NOW_MS + 1_000,
|
||||
});
|
||||
assert.throws(
|
||||
() =>
|
||||
auditWorkerCredentialManagementReleaseGate(gateOptions(paths), {
|
||||
now: () => NOW_MS + 1_000 + MAX_EVIDENCE_AGE_MS + 1,
|
||||
}),
|
||||
/older than the 24 hour release window/,
|
||||
);
|
||||
assert.throws(
|
||||
() =>
|
||||
auditWorkerCredentialManagementReleaseGate(gateOptions(paths), {
|
||||
now: () => NOW_MS + 1_000 - MAX_FUTURE_SKEW_MS - 1,
|
||||
}),
|
||||
/too far in the future/,
|
||||
);
|
||||
});
|
||||
|
||||
test('release gate re-audits sources instead of trusting the final report', () => {
|
||||
const paths = fixture();
|
||||
runWorkerCredentialManagementReleaseEvidence(options(paths), {
|
||||
now: () => NOW_MS + 1_000,
|
||||
});
|
||||
const ceremony = JSON.parse(fs.readFileSync(paths.ceremonyReportFile, 'utf8'));
|
||||
ceremony.ceremony.planDigest = 'f'.repeat(64);
|
||||
fs.writeFileSync(paths.ceremonyReportFile, `${JSON.stringify(ceremony)}\n`, {
|
||||
mode: 0o600,
|
||||
});
|
||||
assert.throws(
|
||||
() =>
|
||||
auditWorkerCredentialManagementReleaseGate(gateOptions(paths), {
|
||||
now: () => NOW_MS + 2_000,
|
||||
}),
|
||||
/source-aware evidence audit is incompatible/,
|
||||
);
|
||||
});
|
||||
|
||||
test('release gate accepts only exact immutable release identity arguments', () => {
|
||||
const argv = [
|
||||
'--report=/private/release.json',
|
||||
'--ceremony-report=/private/ceremony.json',
|
||||
'--durable-audit-report=/private/durable.json',
|
||||
'--pki-rotation-report=/private/pki.json',
|
||||
'--ca-rollover-report=/private/ca.json',
|
||||
`--source-commit=${'a'.repeat(40)}`,
|
||||
'--release-version=3.0.0',
|
||||
];
|
||||
assert.equal(parseGateArguments(argv).releaseVersion, '3.0.0');
|
||||
assert.throws(
|
||||
() => parseGateArguments([...argv, '--extra=true']),
|
||||
WorkerCredentialManagementReleaseGateError,
|
||||
);
|
||||
const invalid = gateOptions({
|
||||
outputFile: '/private/release.json',
|
||||
ceremonyReportFile: '/private/ceremony.json',
|
||||
durableAuditReportFile: '/private/durable.json',
|
||||
pkiRotationReportFile: '/private/pki.json',
|
||||
caRolloverReportFile: '/private/ca.json',
|
||||
});
|
||||
assert.throws(
|
||||
() =>
|
||||
auditWorkerCredentialManagementReleaseGate(
|
||||
{ ...invalid, releaseVersion: '3.00.0' },
|
||||
{ now: () => NOW_MS },
|
||||
),
|
||||
/QingLong 3 SemVer image tag/,
|
||||
);
|
||||
});
|
||||
|
||||
test('rejects a broken source digest chain', () => {
|
||||
const paths = fixture(({ phase, report }) => {
|
||||
if (phase === 'ca') report.source.ceremonyReportSha256 = sha('f');
|
||||
});
|
||||
assert.throws(
|
||||
() =>
|
||||
runWorkerCredentialManagementReleaseEvidence(options(paths), {
|
||||
now: () => NOW_MS + 1_000,
|
||||
}),
|
||||
/digest chain is not exact/,
|
||||
);
|
||||
assert.equal(fs.existsSync(paths.outputFile), false);
|
||||
});
|
||||
|
||||
test('rejects operator, transport and Deployment drift across reports', () => {
|
||||
for (const mutate of [
|
||||
({ phase, report }) => {
|
||||
if (phase === 'ca') {
|
||||
report.identity.subjectSha256 =
|
||||
ceremonyReport().identity.reviewerSubjectSha256;
|
||||
}
|
||||
},
|
||||
({ phase, report }) => {
|
||||
if (phase === 'ca') report.transport.endpointSha256 = sha('d');
|
||||
},
|
||||
({ phase, report }) => {
|
||||
if (phase === 'ca') report.kubernetes.deploymentUidSha256 = sha('d');
|
||||
},
|
||||
]) {
|
||||
const paths = fixture(mutate);
|
||||
assert.throws(
|
||||
() =>
|
||||
runWorkerCredentialManagementReleaseEvidence(options(paths), {
|
||||
now: () => NOW_MS + 1_000,
|
||||
}),
|
||||
/reviewed operator|management transport|Kubernetes authority/,
|
||||
);
|
||||
assert.equal(fs.existsSync(paths.outputFile), false);
|
||||
}
|
||||
});
|
||||
|
||||
test('rejects an incompatible source report before release output', () => {
|
||||
const paths = fixture(({ phase, report }) => {
|
||||
if (phase === 'pki') report.gates.passed = false;
|
||||
});
|
||||
assert.throws(
|
||||
() =>
|
||||
runWorkerCredentialManagementReleaseEvidence(options(paths), {
|
||||
now: () => NOW_MS + 1_000,
|
||||
}),
|
||||
/source reports are incompatible/,
|
||||
);
|
||||
assert.equal(fs.existsSync(paths.outputFile), false);
|
||||
});
|
||||
|
||||
test('offline audit recomputes source binding instead of trusting claimed gates', () => {
|
||||
const paths = fixture();
|
||||
const report = runWorkerCredentialManagementReleaseEvidence(
|
||||
options(paths),
|
||||
{ now: () => NOW_MS + 1_000 },
|
||||
);
|
||||
for (const changed of [
|
||||
{ ...structuredClone(report), extra: true },
|
||||
{
|
||||
...structuredClone(report),
|
||||
gates: { ...report.gates, passed: false },
|
||||
},
|
||||
{ ...structuredClone(report), privateKey: 'forbidden' },
|
||||
{
|
||||
...structuredClone(report),
|
||||
transport: { ...report.transport, endpointSha256: sha('d') },
|
||||
},
|
||||
]) {
|
||||
const documents = readSourceDocuments(options(paths));
|
||||
try {
|
||||
assert.equal(
|
||||
auditWorkerCredentialManagementReleaseEvidence(changed, documents)
|
||||
.compatible,
|
||||
false,
|
||||
);
|
||||
} finally {
|
||||
clearSourceDocuments(documents);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test('rejects a release timestamp older than a source observation', () => {
|
||||
const paths = fixture();
|
||||
const report = runWorkerCredentialManagementReleaseEvidence(
|
||||
options(paths),
|
||||
{ now: () => NOW_MS + 1_000 },
|
||||
);
|
||||
const changed = {
|
||||
...structuredClone(report),
|
||||
observedAt: new Date(NOW_MS + 150).toISOString(),
|
||||
};
|
||||
const documents = readSourceDocuments(options(paths));
|
||||
try {
|
||||
assert.equal(
|
||||
auditWorkerCredentialManagementReleaseEvidence(changed, documents)
|
||||
.compatible,
|
||||
false,
|
||||
);
|
||||
} finally {
|
||||
clearSourceDocuments(documents);
|
||||
}
|
||||
});
|
||||
|
||||
test('parses only the exact five path-based runner arguments', () => {
|
||||
const argv = [
|
||||
'--ceremony-report=/private/ceremony.json',
|
||||
'--durable-audit-report=/private/durable.json',
|
||||
'--pki-rotation-report=/private/pki.json',
|
||||
'--ca-rollover-report=/private/ca.json',
|
||||
'--output=/private/release.json',
|
||||
];
|
||||
assert.equal(
|
||||
parseArguments(argv).pkiRotationReportFile,
|
||||
'/private/pki.json',
|
||||
);
|
||||
assert.throws(
|
||||
() => parseArguments([...argv, '--extra=true']),
|
||||
WorkerCredentialManagementReleaseEvidenceError,
|
||||
);
|
||||
});
|
||||
Reference in New Issue
Block a user