import path from "node:path"; import { readdir } from "node:fs/promises"; import { PROMOTED_FILE_NAMES } from "./contracts/promotion-artifacts.ts"; import { readBoundedRegularFile } from "./lib/ci-artifact-validator.ts"; import { verifyExactPromotionBundle } from "./lib/exact-promotion-bundle.ts"; import { readProviderTrust } from "./lib/provider-trust.ts"; const required = (name: string): string => { const value = process.env[name]; if (!value) throw new TypeError(`exact promotion verification environment is missing ${name}`); return value; }; const rootArgument = process.argv.indexOf("--root"); const bundleRoot = path.resolve( rootArgument >= 0 ? process.argv[rootArgument + 1] ?? "" : required("PROMOTION_BUNDLE_ROOT"), ); if (rootArgument >= 0 && !process.argv[rootArgument + 1]) { throw new TypeError("--root requires a promotion bundle directory"); } const trustRoot = process.cwd(); const [vulnerabilityTrust, provenanceTrust] = await Promise.all([ readProviderTrust( trustRoot, required("VULNERABILITY_PUBLIC_KEY_PATH"), required("VULNERABILITY_KEY_ID"), ), readProviderTrust( trustRoot, required("PROVENANCE_PUBLIC_KEY_PATH"), required("PROVENANCE_KEY_ID"), ), ]); if (!vulnerabilityTrust || !provenanceTrust) { throw new TypeError("exact promotion verification trust keys are invalid"); } const names = (await readdir(bundleRoot)).sort(asciiCompare); if ( JSON.stringify(names) !== JSON.stringify([...PROMOTED_FILE_NAMES].sort(asciiCompare)) ) { throw new TypeError("promotion bundle directory must contain exactly the canonical five files"); } const files = Object.fromEntries( await Promise.all( PROMOTED_FILE_NAMES.map(async (name) => [ name, await readBoundedRegularFile({ root: bundleRoot, relativePath: name, maxBytes: name === "release-candidate.tar.gz" ? 268_435_456 : 16_777_216, }), ] as const), ), ); await verifyExactPromotionBundle(files, { vulnerabilityTrust, provenanceTrust, expected: { run: { id: required("EXPECTED_PROMOTION_RUN_ID"), attempt: requiredPositiveInteger("EXPECTED_PROMOTION_RUN_ATTEMPT"), }, sourceRevision: required("EXPECTED_PROMOTION_SOURCE_REVISION"), archiveSha256: required("EXPECTED_PROMOTION_ARCHIVE_SHA256"), ...optionalDigest("sourceSetSha256", "EXPECTED_PROMOTION_SOURCE_SET_SHA256"), ...optionalDigest("bundleSha256", "EXPECTED_PROMOTION_BUNDLE_SHA256"), ...optionalDigest("distSha256", "EXPECTED_PROMOTION_DIST_SHA256"), ...optionalDigest("lockfileSha256", "EXPECTED_PROMOTION_LOCKFILE_SHA256"), }, }); process.stdout.write("Exact promotion bundle verification: PASS\n"); function asciiCompare(left: string, right: string): number { return left < right ? -1 : left > right ? 1 : 0; } function requiredPositiveInteger(name: string): number { const value = required(name); if (!/^[1-9][0-9]*$/u.test(value) || !Number.isSafeInteger(Number(value))) { throw new TypeError(`${name} must be a positive safe integer`); } return Number(value); } function optionalDigest( property: "sourceSetSha256" | "bundleSha256" | "distSha256" | "lockfileSha256", environmentName: string, ): Readonly> { const value = process.env[environmentName]; return value ? { [property]: value } : {}; }