fix: promote immutable verified release bundles
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
import { createPublicKey } from "node:crypto";
|
||||
import { readFile } from "node:fs/promises";
|
||||
|
||||
import {
|
||||
evaluatePromotionEvidence,
|
||||
type ProviderTrust,
|
||||
} from "./provider-evidence.ts";
|
||||
import {
|
||||
RELEASE_CANDIDATE_MANIFEST_PATH,
|
||||
releaseCandidateManifestSchema,
|
||||
verifyReleaseCandidate,
|
||||
} from "./release-candidate.ts";
|
||||
|
||||
export async function verifyPromotionInputs(
|
||||
environment: NodeJS.ProcessEnv = process.env,
|
||||
) {
|
||||
const manifestDocument = await requiredJson(RELEASE_CANDIDATE_MANIFEST_PATH);
|
||||
const manifest = releaseCandidateManifestSchema.parse(manifestDocument);
|
||||
const candidate = await verifyReleaseCandidate(manifestDocument);
|
||||
const localVerification = await requiredJson(
|
||||
"artifacts/security/supply-chain-verification.json",
|
||||
);
|
||||
const vulnerabilityReport = await optionalJson(
|
||||
environment.VULNERABILITY_REPORT_PATH,
|
||||
);
|
||||
const provenanceAttestation = await optionalJson(
|
||||
environment.PROVENANCE_ATTESTATION_PATH,
|
||||
);
|
||||
const result = evaluatePromotionEvidence({
|
||||
candidate: manifest,
|
||||
currentDistSha256: candidate.currentDistSha256 ?? "",
|
||||
localStatus: localVerification.localStatus,
|
||||
vulnerabilityReport,
|
||||
provenanceAttestation,
|
||||
vulnerabilityTrust: await readTrust(
|
||||
environment.VULNERABILITY_PUBLIC_KEY_PATH,
|
||||
environment.VULNERABILITY_KEY_ID,
|
||||
),
|
||||
provenanceTrust: await readTrust(
|
||||
environment.PROVENANCE_PUBLIC_KEY_PATH,
|
||||
environment.PROVENANCE_KEY_ID,
|
||||
),
|
||||
});
|
||||
const failures = [...candidate.failures, ...result.failures];
|
||||
return Object.freeze({
|
||||
schemaVersion: 1 as const,
|
||||
status:
|
||||
failures.length === 0 && result.status === "PASS"
|
||||
? ("PASS" as const)
|
||||
: ("FAIL_UNVERIFIED" as const),
|
||||
vulnerabilityStatus: result.vulnerabilityStatus,
|
||||
provenanceAttestationStatus: result.provenanceAttestationStatus,
|
||||
lockfileSha256: manifest.lockfileSha256,
|
||||
distSha256: manifest.distSha256,
|
||||
failures: Object.freeze(failures),
|
||||
});
|
||||
}
|
||||
|
||||
async function readTrust(
|
||||
publicKeyPath: string | undefined,
|
||||
keyId: string | undefined,
|
||||
): Promise<ProviderTrust | null> {
|
||||
if (!publicKeyPath || !keyId?.trim()) return null;
|
||||
try {
|
||||
return Object.freeze({
|
||||
keyId,
|
||||
publicKey: createPublicKey(await readFile(publicKeyPath, "utf8")),
|
||||
});
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function optionalJson(file: string | undefined): Promise<unknown> {
|
||||
if (!file) return null;
|
||||
try {
|
||||
return JSON.parse(await readFile(file, "utf8")) as unknown;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function requiredJson(file: string): Promise<Record<string, unknown>> {
|
||||
const value: unknown = JSON.parse(await readFile(file, "utf8"));
|
||||
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
||||
throw new TypeError(`${file} must be a JSON object`);
|
||||
}
|
||||
return value as Record<string, unknown>;
|
||||
}
|
||||
Reference in New Issue
Block a user