mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-22 19:29:13 +08:00
feat(ql3): establish 3.0 incubation baseline
This commit is contained in:
@@ -0,0 +1,817 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
'use strict';
|
||||
|
||||
const { createHash } = require('node:crypto');
|
||||
const fs = require('node:fs');
|
||||
const net = require('node:net');
|
||||
const path = require('node:path');
|
||||
const { TextDecoder } = require('node:util');
|
||||
|
||||
const {
|
||||
FIXTURE: CEREMONY_FIXTURE,
|
||||
validateWorkerCredentialManagementLiveCeremony,
|
||||
} = require('./ql3-worker-credential-management-live-ceremony.cjs');
|
||||
const {
|
||||
FIXTURE: DURABLE_FIXTURE,
|
||||
validateWorkerCredentialManagementDurableAuditEvidence,
|
||||
} = require('./ql3-worker-credential-management-durable-audit-evidence.cjs');
|
||||
const {
|
||||
FIXTURE: PKI_FIXTURE,
|
||||
validateWorkerCredentialManagementPkiRotationEvidence,
|
||||
} = require('./ql3-worker-credential-management-pki-rotation-evidence.cjs');
|
||||
const {
|
||||
FIXTURE: CA_ROLLOVER_FIXTURE,
|
||||
validateWorkerCredentialManagementCaRolloverEvidence,
|
||||
} = require('./ql3-worker-credential-management-ca-rollover-evidence.cjs');
|
||||
|
||||
const FIXTURE = 'qinglong/worker-credential-management-release-evidence@v1';
|
||||
const SHA256_PATTERN = /^sha256:[a-f0-9]{64}$/;
|
||||
const HEX_DIGEST_PATTERN = /^[a-f0-9]{64}$/;
|
||||
const CONTROL_PATTERN = /[\u0000-\u001f\u007f]/;
|
||||
const MAX_FILE_BYTES = 1024 * 1024;
|
||||
const BANNED_KEYS = new Set([
|
||||
'assertion',
|
||||
'authorization',
|
||||
'bearer',
|
||||
'certificate',
|
||||
'connectionstring',
|
||||
'crl',
|
||||
'dsn',
|
||||
'kubeconfig',
|
||||
'password',
|
||||
'privatekey',
|
||||
'secret',
|
||||
'tlskey',
|
||||
'token',
|
||||
]);
|
||||
|
||||
class WorkerCredentialManagementReleaseEvidenceError extends Error {
|
||||
constructor(message) {
|
||||
super(`Worker management release evidence failed: ${message}`);
|
||||
this.name = 'WorkerCredentialManagementReleaseEvidenceError';
|
||||
}
|
||||
}
|
||||
|
||||
function fail(message) {
|
||||
throw new WorkerCredentialManagementReleaseEvidenceError(message);
|
||||
}
|
||||
|
||||
function exactKeys(value, keys) {
|
||||
return (
|
||||
value !== null &&
|
||||
typeof value === 'object' &&
|
||||
!Array.isArray(value) &&
|
||||
JSON.stringify(Object.keys(value).sort()) ===
|
||||
JSON.stringify([...keys].sort())
|
||||
);
|
||||
}
|
||||
|
||||
function exactObject(value, keys, label) {
|
||||
if (!exactKeys(value, keys)) fail(`${label} shape is invalid`);
|
||||
return value;
|
||||
}
|
||||
|
||||
function rawDigest(value) {
|
||||
return `sha256:${createHash('sha256').update(value).digest('hex')}`;
|
||||
}
|
||||
|
||||
function isIsoTime(value) {
|
||||
return (
|
||||
typeof value === 'string' &&
|
||||
/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/.test(value) &&
|
||||
Number.isFinite(Date.parse(value))
|
||||
);
|
||||
}
|
||||
|
||||
function externalIssuer(value) {
|
||||
if (typeof value !== 'string' || value.length > 2048) return false;
|
||||
try {
|
||||
const parsed = new URL(value);
|
||||
return (
|
||||
parsed.protocol === 'https:' &&
|
||||
parsed.username === '' &&
|
||||
parsed.password === '' &&
|
||||
parsed.search === '' &&
|
||||
parsed.hash === '' &&
|
||||
net.isIP(parsed.hostname) === 0 &&
|
||||
parsed.hostname !== 'localhost' &&
|
||||
!parsed.hostname.endsWith('.localhost') &&
|
||||
!parsed.hostname.endsWith('.local') &&
|
||||
!parsed.hostname.endsWith('.test') &&
|
||||
!parsed.hostname.endsWith('.invalid') &&
|
||||
!parsed.hostname.endsWith('.example')
|
||||
);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function containsSensitiveMaterial(value) {
|
||||
if (Array.isArray(value)) {
|
||||
return value.some((entry) => containsSensitiveMaterial(entry));
|
||||
}
|
||||
if (!value || typeof value !== 'object') return false;
|
||||
for (const [key, entry] of Object.entries(value)) {
|
||||
const normalized = key.toLowerCase().replaceAll(/[^a-z0-9]/g, '');
|
||||
if (BANNED_KEYS.has(normalized)) return true;
|
||||
if (containsSensitiveMaterial(entry)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function canonicalFile(filePath, label) {
|
||||
if (
|
||||
typeof filePath !== 'string' ||
|
||||
!path.isAbsolute(filePath) ||
|
||||
filePath.length > 4096 ||
|
||||
CONTROL_PATTERN.test(filePath)
|
||||
) {
|
||||
fail(`${label} path is invalid`);
|
||||
}
|
||||
let stat;
|
||||
try {
|
||||
stat = fs.lstatSync(filePath);
|
||||
} catch {
|
||||
fail(`${label} is unavailable`);
|
||||
}
|
||||
const uid = typeof process.getuid === 'function' ? process.getuid() : null;
|
||||
if (
|
||||
!stat.isFile() ||
|
||||
stat.isSymbolicLink() ||
|
||||
stat.size < 2 ||
|
||||
stat.size > MAX_FILE_BYTES ||
|
||||
uid === null ||
|
||||
stat.uid !== uid ||
|
||||
(stat.mode & 0o077) !== 0 ||
|
||||
fs.realpathSync(filePath) !== filePath
|
||||
) {
|
||||
fail(`${label} must be one canonical owner-private bounded file`);
|
||||
}
|
||||
return stat;
|
||||
}
|
||||
|
||||
function readDocument(filePath, label) {
|
||||
const before = canonicalFile(filePath, label);
|
||||
let descriptor = -1;
|
||||
let bytes;
|
||||
try {
|
||||
descriptor = fs.openSync(
|
||||
filePath,
|
||||
fs.constants.O_RDONLY |
|
||||
(fs.constants.O_CLOEXEC ?? 0) |
|
||||
(fs.constants.O_NOFOLLOW ?? 0),
|
||||
);
|
||||
const opened = fs.fstatSync(descriptor);
|
||||
if (
|
||||
opened.dev !== before.dev ||
|
||||
opened.ino !== before.ino ||
|
||||
opened.uid !== before.uid ||
|
||||
opened.mode !== before.mode ||
|
||||
opened.size !== before.size
|
||||
) {
|
||||
fail(`${label} changed before it was opened`);
|
||||
}
|
||||
bytes = Buffer.alloc(opened.size);
|
||||
let offset = 0;
|
||||
while (offset < bytes.length) {
|
||||
const count = fs.readSync(
|
||||
descriptor,
|
||||
bytes,
|
||||
offset,
|
||||
bytes.length - offset,
|
||||
offset,
|
||||
);
|
||||
if (count < 1) fail(`${label} could not be read completely`);
|
||||
offset += count;
|
||||
}
|
||||
const after = fs.fstatSync(descriptor);
|
||||
if (
|
||||
after.dev !== opened.dev ||
|
||||
after.ino !== opened.ino ||
|
||||
after.uid !== opened.uid ||
|
||||
after.mode !== opened.mode ||
|
||||
after.size !== opened.size
|
||||
) {
|
||||
fail(`${label} changed while it was read`);
|
||||
}
|
||||
let value;
|
||||
try {
|
||||
value = JSON.parse(
|
||||
new TextDecoder('utf-8', { fatal: true }).decode(bytes),
|
||||
);
|
||||
} catch {
|
||||
fail(`${label} must contain UTF-8 JSON`);
|
||||
}
|
||||
return Object.freeze({ bytes, value });
|
||||
} catch (error) {
|
||||
if (error instanceof WorkerCredentialManagementReleaseEvidenceError) {
|
||||
throw error;
|
||||
}
|
||||
fail(`${label} could not be read safely`);
|
||||
} finally {
|
||||
if (descriptor >= 0) fs.closeSync(descriptor);
|
||||
}
|
||||
}
|
||||
|
||||
function unusedOutput(filePath) {
|
||||
if (
|
||||
typeof filePath !== 'string' ||
|
||||
!path.isAbsolute(filePath) ||
|
||||
filePath.length > 4096 ||
|
||||
CONTROL_PATTERN.test(filePath) ||
|
||||
fs.existsSync(filePath) ||
|
||||
fs.realpathSync(path.dirname(filePath)) !== path.dirname(filePath)
|
||||
) {
|
||||
fail('output path must be unused in one canonical directory');
|
||||
}
|
||||
}
|
||||
|
||||
function writeNoReplace(filePath, value) {
|
||||
unusedOutput(filePath);
|
||||
const bytes = Buffer.from(`${JSON.stringify(value)}\n`, 'utf8');
|
||||
let descriptor = -1;
|
||||
try {
|
||||
descriptor = fs.openSync(
|
||||
filePath,
|
||||
fs.constants.O_WRONLY |
|
||||
fs.constants.O_CREAT |
|
||||
fs.constants.O_EXCL |
|
||||
(fs.constants.O_CLOEXEC ?? 0) |
|
||||
(fs.constants.O_NOFOLLOW ?? 0),
|
||||
0o600,
|
||||
);
|
||||
let offset = 0;
|
||||
while (offset < bytes.length) {
|
||||
const count = fs.writeSync(
|
||||
descriptor,
|
||||
bytes,
|
||||
offset,
|
||||
bytes.length - offset,
|
||||
offset,
|
||||
);
|
||||
if (count < 1) fail('release evidence could not be written completely');
|
||||
offset += count;
|
||||
}
|
||||
fs.fsyncSync(descriptor);
|
||||
} finally {
|
||||
bytes.fill(0);
|
||||
if (descriptor >= 0) fs.closeSync(descriptor);
|
||||
}
|
||||
}
|
||||
|
||||
function readSourceDocuments(options) {
|
||||
const documents = {};
|
||||
try {
|
||||
documents.ceremony = readDocument(
|
||||
options.ceremonyReportFile,
|
||||
'ceremony report',
|
||||
);
|
||||
documents.durable = readDocument(
|
||||
options.durableAuditReportFile,
|
||||
'durable audit report',
|
||||
);
|
||||
documents.pki = readDocument(
|
||||
options.pkiRotationReportFile,
|
||||
'PKI rotation report',
|
||||
);
|
||||
documents.caRollover = readDocument(
|
||||
options.caRolloverReportFile,
|
||||
'CA rollover report',
|
||||
);
|
||||
return Object.freeze(documents);
|
||||
} catch (error) {
|
||||
for (const document of Object.values(documents)) document.bytes.fill(0);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
function clearSourceDocuments(documents) {
|
||||
for (const document of Object.values(documents)) document.bytes.fill(0);
|
||||
}
|
||||
|
||||
function sourceAudits(documents) {
|
||||
return [
|
||||
validateWorkerCredentialManagementLiveCeremony(
|
||||
documents.ceremony.value,
|
||||
),
|
||||
validateWorkerCredentialManagementDurableAuditEvidence(
|
||||
documents.durable.value,
|
||||
),
|
||||
validateWorkerCredentialManagementPkiRotationEvidence(
|
||||
documents.pki.value,
|
||||
),
|
||||
validateWorkerCredentialManagementCaRolloverEvidence(
|
||||
documents.caRollover.value,
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
function verifySourceDocuments(documents) {
|
||||
if (sourceAudits(documents).some(({ compatible }) => !compatible)) {
|
||||
fail('one or more source reports are incompatible');
|
||||
}
|
||||
const ceremony = documents.ceremony.value;
|
||||
const durable = documents.durable.value;
|
||||
const pki = documents.pki.value;
|
||||
const caRollover = documents.caRollover.value;
|
||||
const observed = [ceremony, durable, pki, caRollover].map(({ observedAt }) =>
|
||||
Date.parse(observedAt),
|
||||
);
|
||||
if (
|
||||
observed[1] < observed[0] ||
|
||||
observed[2] < observed[1] ||
|
||||
observed[3] < observed[1]
|
||||
) {
|
||||
fail('source report observation order is invalid');
|
||||
}
|
||||
const ceremonySha256 = rawDigest(documents.ceremony.bytes);
|
||||
const durableSha256 = rawDigest(documents.durable.bytes);
|
||||
if (
|
||||
durable.source.ceremonyReportSha256 !== ceremonySha256 ||
|
||||
pki.source.ceremonyReportSha256 !== ceremonySha256 ||
|
||||
caRollover.source.ceremonyReportSha256 !== ceremonySha256 ||
|
||||
pki.source.durableAuditReportSha256 !== durableSha256 ||
|
||||
caRollover.source.durableAuditReportSha256 !== durableSha256
|
||||
) {
|
||||
fail('source report digest chain is not exact');
|
||||
}
|
||||
if (
|
||||
durable.durableState.actionRefSha256 !==
|
||||
ceremony.ceremony.actionRefSha256 ||
|
||||
durable.durableState.authorityProjectIdSha256 !==
|
||||
ceremony.ceremony.authorityProjectIdSha256 ||
|
||||
durable.durableState.planDigest !== ceremony.ceremony.planDigest ||
|
||||
durable.durableState.previewDigest !== ceremony.ceremony.previewDigest ||
|
||||
durable.durableState.requesterSubjectSha256 !==
|
||||
ceremony.identity.requesterSubjectSha256 ||
|
||||
durable.durableState.reviewerSubjectSha256 !==
|
||||
ceremony.identity.reviewerSubjectSha256
|
||||
) {
|
||||
fail('durable review state is not bound to the ceremony');
|
||||
}
|
||||
const operatorSubjects = [
|
||||
ceremony.identity.requesterSubjectSha256,
|
||||
ceremony.identity.reviewerSubjectSha256,
|
||||
];
|
||||
for (const identity of [pki.identity, caRollover.identity]) {
|
||||
if (
|
||||
identity.providerKind !== ceremony.identity.providerKind ||
|
||||
identity.issuer !== ceremony.identity.issuer ||
|
||||
identity.audience !== ceremony.identity.audience ||
|
||||
identity.type !== ceremony.identity.type ||
|
||||
identity.purpose !== ceremony.identity.purpose ||
|
||||
!operatorSubjects.includes(identity.subjectSha256)
|
||||
) {
|
||||
fail('source report identity does not match the reviewed ceremony');
|
||||
}
|
||||
}
|
||||
if (pki.identity.subjectSha256 !== caRollover.identity.subjectSha256) {
|
||||
fail('source reports were not collected by one reviewed operator');
|
||||
}
|
||||
if (
|
||||
pki.transport.endpointSha256 !==
|
||||
caRollover.transport.endpointSha256 ||
|
||||
pki.transport.servernameSha256 !==
|
||||
caRollover.transport.servernameSha256 ||
|
||||
pki.transport.serverTrustBundleSha256 !==
|
||||
caRollover.transport.serverTrustBundleSha256 ||
|
||||
pki.transport.commandSha256 !== caRollover.transport.commandSha256
|
||||
) {
|
||||
fail('source report management transport is not identical');
|
||||
}
|
||||
if (
|
||||
pki.kubernetes.clusterServerSha256 !==
|
||||
caRollover.kubernetes.clusterServerSha256 ||
|
||||
pki.kubernetes.collectorSubjectSha256 !==
|
||||
caRollover.kubernetes.collectorSubjectSha256 ||
|
||||
pki.kubernetes.deploymentUidSha256 !==
|
||||
caRollover.kubernetes.deploymentUidSha256
|
||||
) {
|
||||
fail('source report Kubernetes authority is not identical');
|
||||
}
|
||||
return Object.freeze({
|
||||
ceremony,
|
||||
durable,
|
||||
pki,
|
||||
caRollover,
|
||||
digests: Object.freeze({
|
||||
ceremony: ceremonySha256,
|
||||
durable: durableSha256,
|
||||
pki: rawDigest(documents.pki.bytes),
|
||||
caRollover: rawDigest(documents.caRollover.bytes),
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
function buildReleaseEvidence(facts, observedAt) {
|
||||
const { ceremony, durable, pki, caRollover, digests } = facts;
|
||||
return Object.freeze({
|
||||
schemaVersion: 1,
|
||||
fixture: FIXTURE,
|
||||
observedAt,
|
||||
source: Object.freeze({
|
||||
ceremonyReportSha256: digests.ceremony,
|
||||
durableAuditReportSha256: digests.durable,
|
||||
pkiRotationReportSha256: digests.pki,
|
||||
caRolloverReportSha256: digests.caRollover,
|
||||
ceremonyFixture: CEREMONY_FIXTURE,
|
||||
durableAuditFixture: DURABLE_FIXTURE,
|
||||
pkiRotationFixture: PKI_FIXTURE,
|
||||
caRolloverFixture: CA_ROLLOVER_FIXTURE,
|
||||
}),
|
||||
authority: Object.freeze({
|
||||
providerKind: ceremony.identity.providerKind,
|
||||
issuer: ceremony.identity.issuer,
|
||||
audience: ceremony.identity.audience,
|
||||
type: ceremony.identity.type,
|
||||
purpose: ceremony.identity.purpose,
|
||||
requesterSubjectSha256: ceremony.identity.requesterSubjectSha256,
|
||||
reviewerSubjectSha256: ceremony.identity.reviewerSubjectSha256,
|
||||
operatorSubjectSha256: pki.identity.subjectSha256,
|
||||
actionRefSha256: ceremony.ceremony.actionRefSha256,
|
||||
authorityProjectIdSha256:
|
||||
ceremony.ceremony.authorityProjectIdSha256,
|
||||
planDigest: ceremony.ceremony.planDigest,
|
||||
previewDigest: ceremony.ceremony.previewDigest,
|
||||
}),
|
||||
transport: Object.freeze({
|
||||
endpointSha256: pki.transport.endpointSha256,
|
||||
servernameSha256: pki.transport.servernameSha256,
|
||||
serverTrustBundleSha256: pki.transport.serverTrustBundleSha256,
|
||||
commandSha256: pki.transport.commandSha256,
|
||||
}),
|
||||
deployment: Object.freeze({
|
||||
clusterServerSha256: pki.kubernetes.clusterServerSha256,
|
||||
collectorSubjectSha256: pki.kubernetes.collectorSubjectSha256,
|
||||
deploymentUidSha256: pki.kubernetes.deploymentUidSha256,
|
||||
pkiGenerations: Object.freeze([
|
||||
pki.kubernetes.beforeGeneration,
|
||||
pki.kubernetes.afterGeneration,
|
||||
]),
|
||||
caRolloverGenerations: caRollover.kubernetes.generations,
|
||||
pkiPodsFullyReplaced: pki.kubernetes.oldPodsFullyReplaced,
|
||||
caRolloverPodsFullyReplaced:
|
||||
caRollover.kubernetes.allGenerationsFullyReplaced,
|
||||
twoReadyReplicasOnDistinctNodes: true,
|
||||
}),
|
||||
controls: Object.freeze({
|
||||
externalCeremonyObserved: ceremony.gates.passed,
|
||||
durableAuditObserved: durable.gates.passed,
|
||||
sameIssuerRevocationObserved: pki.gates.passed,
|
||||
caRolloverObserved: caRollover.gates.passed,
|
||||
serverAndClientTrustSeparated:
|
||||
pki.gates.serverTrustSeparatedFromClientIssuer &&
|
||||
caRollover.gates.serverTrustSeparatedFromClientIssuer,
|
||||
noExecutionOrConsumption:
|
||||
ceremony.gates.noExecutionOrConsumption &&
|
||||
durable.gates.noExecutionOrConsumption,
|
||||
exactReadOnlyCollectorAuthority:
|
||||
pki.gates.readOnlyCollectorAuthority &&
|
||||
caRollover.gates.readOnlyCollectorAuthority,
|
||||
}),
|
||||
gates: Object.freeze({
|
||||
sourceReportsCompatible: true,
|
||||
sourceDigestChainBound: true,
|
||||
externalIdentityBound: true,
|
||||
reviewedDurableStateBound: true,
|
||||
managementTransportBound: true,
|
||||
deploymentAuthorityBound: true,
|
||||
sameIssuerRevocationObserved: true,
|
||||
clientCaRolloverObserved: true,
|
||||
serverAndClientTrustSeparated: true,
|
||||
noExecutionOrConsumption: true,
|
||||
readOnlyEvidenceAuthority: true,
|
||||
passed: true,
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
function validIncreasingIntegers(value, length) {
|
||||
return (
|
||||
Array.isArray(value) &&
|
||||
value.length === length &&
|
||||
value.every((entry) => Number.isSafeInteger(entry) && entry >= 1) &&
|
||||
value.every((entry, index) => index === 0 || value[index - 1] < entry)
|
||||
);
|
||||
}
|
||||
|
||||
function validateWorkerCredentialManagementReleaseEvidence(report) {
|
||||
const findings = [];
|
||||
const add = (code) => findings.push(Object.freeze({ code }));
|
||||
if (
|
||||
!exactKeys(report, [
|
||||
'schemaVersion',
|
||||
'fixture',
|
||||
'observedAt',
|
||||
'source',
|
||||
'authority',
|
||||
'transport',
|
||||
'deployment',
|
||||
'controls',
|
||||
'gates',
|
||||
]) ||
|
||||
report?.schemaVersion !== 1 ||
|
||||
report?.fixture !== FIXTURE ||
|
||||
!isIsoTime(report?.observedAt)
|
||||
) {
|
||||
add('QL3_WORKER_MANAGEMENT_RELEASE_EVIDENCE_SHAPE');
|
||||
}
|
||||
const source = report?.source;
|
||||
if (
|
||||
!exactKeys(source, [
|
||||
'ceremonyReportSha256',
|
||||
'durableAuditReportSha256',
|
||||
'pkiRotationReportSha256',
|
||||
'caRolloverReportSha256',
|
||||
'ceremonyFixture',
|
||||
'durableAuditFixture',
|
||||
'pkiRotationFixture',
|
||||
'caRolloverFixture',
|
||||
]) ||
|
||||
![
|
||||
source?.ceremonyReportSha256,
|
||||
source?.durableAuditReportSha256,
|
||||
source?.pkiRotationReportSha256,
|
||||
source?.caRolloverReportSha256,
|
||||
].every((entry) => SHA256_PATTERN.test(entry)) ||
|
||||
source?.ceremonyFixture !== CEREMONY_FIXTURE ||
|
||||
source?.durableAuditFixture !== DURABLE_FIXTURE ||
|
||||
source?.pkiRotationFixture !== PKI_FIXTURE ||
|
||||
source?.caRolloverFixture !== CA_ROLLOVER_FIXTURE
|
||||
) {
|
||||
add('QL3_WORKER_MANAGEMENT_RELEASE_EVIDENCE_SOURCE');
|
||||
}
|
||||
const authority = report?.authority;
|
||||
if (
|
||||
!exactKeys(authority, [
|
||||
'providerKind',
|
||||
'issuer',
|
||||
'audience',
|
||||
'type',
|
||||
'purpose',
|
||||
'requesterSubjectSha256',
|
||||
'reviewerSubjectSha256',
|
||||
'operatorSubjectSha256',
|
||||
'actionRefSha256',
|
||||
'authorityProjectIdSha256',
|
||||
'planDigest',
|
||||
'previewDigest',
|
||||
]) ||
|
||||
authority?.providerKind !== 'external_oidc' ||
|
||||
!externalIssuer(authority?.issuer) ||
|
||||
authority?.audience !== 'qinglong3-worker-credential-management' ||
|
||||
authority?.type !== 'ql3-worker-credential-management+jwt' ||
|
||||
authority?.purpose !== 'worker-credential-management' ||
|
||||
![
|
||||
authority?.requesterSubjectSha256,
|
||||
authority?.reviewerSubjectSha256,
|
||||
authority?.operatorSubjectSha256,
|
||||
authority?.actionRefSha256,
|
||||
authority?.authorityProjectIdSha256,
|
||||
].every((entry) => SHA256_PATTERN.test(entry)) ||
|
||||
authority?.requesterSubjectSha256 ===
|
||||
authority?.reviewerSubjectSha256 ||
|
||||
![
|
||||
authority?.requesterSubjectSha256,
|
||||
authority?.reviewerSubjectSha256,
|
||||
].includes(authority?.operatorSubjectSha256) ||
|
||||
!HEX_DIGEST_PATTERN.test(authority?.planDigest) ||
|
||||
!HEX_DIGEST_PATTERN.test(authority?.previewDigest)
|
||||
) {
|
||||
add('QL3_WORKER_MANAGEMENT_RELEASE_EVIDENCE_AUTHORITY');
|
||||
}
|
||||
if (
|
||||
!exactKeys(report?.transport, [
|
||||
'endpointSha256',
|
||||
'servernameSha256',
|
||||
'serverTrustBundleSha256',
|
||||
'commandSha256',
|
||||
]) ||
|
||||
Object.values(report?.transport ?? {}).some(
|
||||
(entry) => !SHA256_PATTERN.test(entry),
|
||||
)
|
||||
) {
|
||||
add('QL3_WORKER_MANAGEMENT_RELEASE_EVIDENCE_TRANSPORT');
|
||||
}
|
||||
const deployment = report?.deployment;
|
||||
if (
|
||||
!exactKeys(deployment, [
|
||||
'clusterServerSha256',
|
||||
'collectorSubjectSha256',
|
||||
'deploymentUidSha256',
|
||||
'pkiGenerations',
|
||||
'caRolloverGenerations',
|
||||
'pkiPodsFullyReplaced',
|
||||
'caRolloverPodsFullyReplaced',
|
||||
'twoReadyReplicasOnDistinctNodes',
|
||||
]) ||
|
||||
![
|
||||
deployment?.clusterServerSha256,
|
||||
deployment?.collectorSubjectSha256,
|
||||
deployment?.deploymentUidSha256,
|
||||
].every((entry) => SHA256_PATTERN.test(entry)) ||
|
||||
!validIncreasingIntegers(deployment?.pkiGenerations, 2) ||
|
||||
!validIncreasingIntegers(deployment?.caRolloverGenerations, 3) ||
|
||||
deployment?.pkiPodsFullyReplaced !== true ||
|
||||
deployment?.caRolloverPodsFullyReplaced !== true ||
|
||||
deployment?.twoReadyReplicasOnDistinctNodes !== true
|
||||
) {
|
||||
add('QL3_WORKER_MANAGEMENT_RELEASE_EVIDENCE_DEPLOYMENT');
|
||||
}
|
||||
if (
|
||||
!exactKeys(report?.controls, [
|
||||
'externalCeremonyObserved',
|
||||
'durableAuditObserved',
|
||||
'sameIssuerRevocationObserved',
|
||||
'caRolloverObserved',
|
||||
'serverAndClientTrustSeparated',
|
||||
'noExecutionOrConsumption',
|
||||
'exactReadOnlyCollectorAuthority',
|
||||
]) ||
|
||||
Object.values(report?.controls ?? {}).some((entry) => entry !== true)
|
||||
) {
|
||||
add('QL3_WORKER_MANAGEMENT_RELEASE_EVIDENCE_CONTROLS');
|
||||
}
|
||||
if (
|
||||
!exactKeys(report?.gates, [
|
||||
'sourceReportsCompatible',
|
||||
'sourceDigestChainBound',
|
||||
'externalIdentityBound',
|
||||
'reviewedDurableStateBound',
|
||||
'managementTransportBound',
|
||||
'deploymentAuthorityBound',
|
||||
'sameIssuerRevocationObserved',
|
||||
'clientCaRolloverObserved',
|
||||
'serverAndClientTrustSeparated',
|
||||
'noExecutionOrConsumption',
|
||||
'readOnlyEvidenceAuthority',
|
||||
'passed',
|
||||
]) ||
|
||||
Object.values(report?.gates ?? {}).some((entry) => entry !== true)
|
||||
) {
|
||||
add('QL3_WORKER_MANAGEMENT_RELEASE_EVIDENCE_GATES');
|
||||
}
|
||||
if (containsSensitiveMaterial(report)) {
|
||||
add('QL3_WORKER_MANAGEMENT_RELEASE_EVIDENCE_SECRET_EXPOSURE');
|
||||
}
|
||||
return Object.freeze({
|
||||
compatible: findings.length === 0,
|
||||
findings: Object.freeze(findings),
|
||||
});
|
||||
}
|
||||
|
||||
function auditWorkerCredentialManagementReleaseEvidence(report, documents) {
|
||||
const structural = validateWorkerCredentialManagementReleaseEvidence(report);
|
||||
if (!structural.compatible) return structural;
|
||||
try {
|
||||
const facts = verifySourceDocuments(documents);
|
||||
const newestSourceMs = Math.max(
|
||||
Date.parse(facts.ceremony.observedAt),
|
||||
Date.parse(facts.durable.observedAt),
|
||||
Date.parse(facts.pki.observedAt),
|
||||
Date.parse(facts.caRollover.observedAt),
|
||||
);
|
||||
if (Date.parse(report.observedAt) < newestSourceMs) {
|
||||
fail('release evidence predates one or more source reports');
|
||||
}
|
||||
const expected = buildReleaseEvidence(facts, report.observedAt);
|
||||
if (JSON.stringify(report) !== JSON.stringify(expected)) {
|
||||
return Object.freeze({
|
||||
compatible: false,
|
||||
findings: Object.freeze([
|
||||
Object.freeze({
|
||||
code: 'QL3_WORKER_MANAGEMENT_RELEASE_EVIDENCE_SOURCE_MISMATCH',
|
||||
}),
|
||||
]),
|
||||
});
|
||||
}
|
||||
return Object.freeze({ compatible: true, findings: Object.freeze([]) });
|
||||
} catch (error) {
|
||||
if (!(error instanceof WorkerCredentialManagementReleaseEvidenceError)) {
|
||||
throw error;
|
||||
}
|
||||
return Object.freeze({
|
||||
compatible: false,
|
||||
findings: Object.freeze([
|
||||
Object.freeze({
|
||||
code: 'QL3_WORKER_MANAGEMENT_RELEASE_EVIDENCE_SOURCE_INCOMPATIBLE',
|
||||
}),
|
||||
]),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function optionKeys() {
|
||||
return [
|
||||
'ceremonyReportFile',
|
||||
'durableAuditReportFile',
|
||||
'pkiRotationReportFile',
|
||||
'caRolloverReportFile',
|
||||
'outputFile',
|
||||
];
|
||||
}
|
||||
|
||||
function runWorkerCredentialManagementReleaseEvidence(
|
||||
options,
|
||||
dependencies = { now: Date.now },
|
||||
) {
|
||||
exactObject(options, optionKeys(), 'release evidence options');
|
||||
exactObject(dependencies, ['now'], 'release evidence dependencies');
|
||||
if (typeof dependencies.now !== 'function') {
|
||||
fail('release evidence clock is invalid');
|
||||
}
|
||||
unusedOutput(options.outputFile);
|
||||
const nowMs = dependencies.now();
|
||||
if (!Number.isSafeInteger(nowMs) || nowMs < 0) {
|
||||
fail('release evidence clock is invalid');
|
||||
}
|
||||
const documents = readSourceDocuments(options);
|
||||
try {
|
||||
const facts = verifySourceDocuments(documents);
|
||||
const report = buildReleaseEvidence(
|
||||
facts,
|
||||
new Date(nowMs).toISOString(),
|
||||
);
|
||||
const audit = auditWorkerCredentialManagementReleaseEvidence(
|
||||
report,
|
||||
documents,
|
||||
);
|
||||
if (!audit.compatible) fail('assembled release evidence failed audit');
|
||||
writeNoReplace(options.outputFile, report);
|
||||
return report;
|
||||
} finally {
|
||||
clearSourceDocuments(documents);
|
||||
}
|
||||
}
|
||||
|
||||
function parseArguments(argv) {
|
||||
const values = {};
|
||||
for (const argument of argv) {
|
||||
if (argument === '--') continue;
|
||||
const match = /^--([a-z-]+)=(.+)$/.exec(argument);
|
||||
if (!match || Object.hasOwn(values, match[1])) fail('arguments are invalid');
|
||||
values[match[1]] = match[2];
|
||||
}
|
||||
const expected = [
|
||||
'ceremony-report',
|
||||
'durable-audit-report',
|
||||
'pki-rotation-report',
|
||||
'ca-rollover-report',
|
||||
'output',
|
||||
];
|
||||
if (
|
||||
JSON.stringify(Object.keys(values).sort()) !==
|
||||
JSON.stringify(expected.sort())
|
||||
) {
|
||||
fail('arguments are invalid');
|
||||
}
|
||||
return Object.freeze({
|
||||
ceremonyReportFile: values['ceremony-report'],
|
||||
durableAuditReportFile: values['durable-audit-report'],
|
||||
pkiRotationReportFile: values['pki-rotation-report'],
|
||||
caRolloverReportFile: values['ca-rollover-report'],
|
||||
outputFile: values.output,
|
||||
});
|
||||
}
|
||||
|
||||
function runCli(argv) {
|
||||
const options = parseArguments(argv);
|
||||
runWorkerCredentialManagementReleaseEvidence(options, { now: Date.now });
|
||||
process.stdout.write(
|
||||
`${JSON.stringify({ schemaVersion: 1, fixture: FIXTURE, compatible: true })}\n`,
|
||||
);
|
||||
}
|
||||
|
||||
if (require.main === module) {
|
||||
try {
|
||||
runCli(process.argv.slice(2));
|
||||
} catch (error) {
|
||||
process.stderr.write(
|
||||
`${
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: 'Worker management release evidence failed'
|
||||
}\n`,
|
||||
);
|
||||
process.exitCode = 1;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
CA_ROLLOVER_FIXTURE,
|
||||
CEREMONY_FIXTURE,
|
||||
DURABLE_FIXTURE,
|
||||
FIXTURE,
|
||||
PKI_FIXTURE,
|
||||
WorkerCredentialManagementReleaseEvidenceError,
|
||||
auditWorkerCredentialManagementReleaseEvidence,
|
||||
buildReleaseEvidence,
|
||||
clearSourceDocuments,
|
||||
parseArguments,
|
||||
readDocument,
|
||||
readSourceDocuments,
|
||||
runWorkerCredentialManagementReleaseEvidence,
|
||||
validateWorkerCredentialManagementReleaseEvidence,
|
||||
verifySourceDocuments,
|
||||
};
|
||||
Reference in New Issue
Block a user