329 lines
10 KiB
TypeScript
329 lines
10 KiB
TypeScript
import { createHash, generateKeyPairSync, sign } from "node:crypto";
|
|
import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
|
import { tmpdir } from "node:os";
|
|
import path from "node:path";
|
|
|
|
import {
|
|
providerEvidenceSignaturePayload,
|
|
providerPublicKeyFingerprint,
|
|
} from "./lib/provider-evidence.ts";
|
|
import { localEvidenceAssessmentArtifactSchema } from "./contracts/release-artifacts.ts";
|
|
import { verifyPromotionInputs } from "./lib/promotion-verifier.ts";
|
|
import {
|
|
createReleaseCandidateManifest,
|
|
LOCAL_EVIDENCE_ASSESSMENT_PATH,
|
|
RELEASE_CANDIDATE_EVIDENCE_PATHS,
|
|
RELEASE_CANDIDATE_MANIFEST_PATH,
|
|
releaseCandidateManifestSchema,
|
|
} from "./lib/release-candidate.ts";
|
|
|
|
const NOW = Date.parse("2026-08-02T01:00:00.000Z");
|
|
const FIXTURE_SOURCE = Object.freeze({
|
|
revision: "a".repeat(40),
|
|
sourceSetSha256: "b".repeat(64),
|
|
});
|
|
const FIXTURE_LOCAL_IDENTITY = Object.freeze({
|
|
sourceRevision: FIXTURE_SOURCE.revision,
|
|
sourceSetSha256: FIXTURE_SOURCE.sourceSetSha256,
|
|
assessmentSha256: "c".repeat(64),
|
|
});
|
|
|
|
const fixtureRoot = await mkdtemp(
|
|
path.join(tmpdir(), "supply-chain-provider-fixture-"),
|
|
);
|
|
try {
|
|
const repositoryRoot = process.cwd();
|
|
const actualCandidate = releaseCandidateManifestSchema.parse(
|
|
JSON.parse(
|
|
await readFile(
|
|
path.join(repositoryRoot, RELEASE_CANDIDATE_MANIFEST_PATH),
|
|
"utf8",
|
|
),
|
|
) as unknown,
|
|
);
|
|
const actualAssessment = localEvidenceAssessmentArtifactSchema.parse(
|
|
JSON.parse(
|
|
await readFile(path.join(repositoryRoot, LOCAL_EVIDENCE_ASSESSMENT_PATH), "utf8"),
|
|
) as unknown,
|
|
);
|
|
const actualProviderEnvironment = absoluteProviderEnvironment(
|
|
fixtureRoot,
|
|
await writeProviderEnvironment(
|
|
fixtureRoot,
|
|
"actual",
|
|
actualCandidate,
|
|
{
|
|
revision: actualAssessment.source.revision,
|
|
sourceSetSha256: actualAssessment.source.sourceSetSha256,
|
|
},
|
|
),
|
|
);
|
|
const actualDefaultVerifier = await verifyPromotionInputs({
|
|
artifactType: "provider-verification",
|
|
environment: actualProviderEnvironment,
|
|
nowEpochMs: () => NOW,
|
|
});
|
|
|
|
const rawLockfile = "lockfileVersion: '9.0'\n";
|
|
const lockfileSha256 = createHash("sha256")
|
|
.update(rawLockfile)
|
|
.digest("hex");
|
|
await mkdir(path.join(fixtureRoot, "dist"), { recursive: true });
|
|
await writeFile(path.join(fixtureRoot, "dist/app.js"), "immutable\n");
|
|
await writeFile(path.join(fixtureRoot, "pnpm-lock.yaml"), rawLockfile);
|
|
for (const file of RELEASE_CANDIDATE_EVIDENCE_PATHS) {
|
|
if (file === "pnpm-lock.yaml") continue;
|
|
await mkdir(path.dirname(path.join(fixtureRoot, file)), {
|
|
recursive: true,
|
|
});
|
|
const value =
|
|
file === "artifacts/release/dependency-inventory.json"
|
|
? { lockfileSha256 }
|
|
: { fixture: file };
|
|
await writeFile(
|
|
path.join(fixtureRoot, file),
|
|
`${JSON.stringify(value)}\n`,
|
|
);
|
|
}
|
|
const candidate = await createReleaseCandidateManifest(fixtureRoot);
|
|
await writeFile(
|
|
path.join(fixtureRoot, RELEASE_CANDIDATE_MANIFEST_PATH),
|
|
`${JSON.stringify(candidate)}\n`,
|
|
);
|
|
|
|
const validEnvironment = await writeProviderEnvironment(
|
|
fixtureRoot,
|
|
"valid",
|
|
candidate,
|
|
FIXTURE_SOURCE,
|
|
);
|
|
const wrongEnvironment = await writeProviderEnvironment(
|
|
fixtureRoot,
|
|
"wrong",
|
|
candidate,
|
|
FIXTURE_SOURCE,
|
|
{ distSha256: "3".repeat(64) },
|
|
);
|
|
const acceptLocalEvidence = async () => ({
|
|
status: "PASS" as const,
|
|
identity: FIXTURE_LOCAL_IDENTITY,
|
|
failures: [] as const,
|
|
});
|
|
const fixtures = {
|
|
absent: await verifyPromotionInputs({
|
|
artifactType: "provider-verification",
|
|
repositoryRoot: fixtureRoot,
|
|
environment: {},
|
|
verifyLocalEvidence: acceptLocalEvidence,
|
|
nowEpochMs: () => NOW,
|
|
}),
|
|
validImmutable: await verifyPromotionInputs({
|
|
artifactType: "provider-verification",
|
|
repositoryRoot: fixtureRoot,
|
|
environment: validEnvironment,
|
|
verifyLocalEvidence: acceptLocalEvidence,
|
|
nowEpochMs: () => NOW,
|
|
}),
|
|
wrongDigest: await verifyPromotionInputs({
|
|
artifactType: "provider-verification",
|
|
repositoryRoot: fixtureRoot,
|
|
environment: wrongEnvironment,
|
|
verifyLocalEvidence: acceptLocalEvidence,
|
|
nowEpochMs: () => NOW,
|
|
}),
|
|
postAttestationMutation: null as Awaited<
|
|
ReturnType<typeof verifyPromotionInputs>
|
|
> | null,
|
|
};
|
|
await writeFile(path.join(fixtureRoot, "dist/app.js"), "mutated\n");
|
|
fixtures.postAttestationMutation = await verifyPromotionInputs({
|
|
artifactType: "provider-verification",
|
|
repositoryRoot: fixtureRoot,
|
|
environment: validEnvironment,
|
|
verifyLocalEvidence: acceptLocalEvidence,
|
|
nowEpochMs: () => NOW,
|
|
});
|
|
|
|
const passed =
|
|
actualDefaultVerifier.status === "PASS" &&
|
|
fixtures.validImmutable.status === "PASS" &&
|
|
fixtures.absent.status === "FAIL_UNVERIFIED" &&
|
|
fixtures.wrongDigest.status === "FAIL_UNVERIFIED" &&
|
|
fixtures.postAttestationMutation.status === "FAIL_UNVERIFIED";
|
|
await mkdir("artifacts/security", { recursive: true });
|
|
await writeFile(
|
|
"artifacts/security/supply-chain-provider-fixtures.json",
|
|
`${JSON.stringify(
|
|
{
|
|
schemaVersion: 1,
|
|
actualDefaultVerifier: {
|
|
status: actualDefaultVerifier.status,
|
|
failures: actualDefaultVerifier.failures,
|
|
},
|
|
fixtures: Object.fromEntries(
|
|
Object.entries(fixtures).map(([name, result]) => [
|
|
name,
|
|
{ status: result?.status, failures: result?.failures },
|
|
]),
|
|
),
|
|
passingFixtureCount: Object.values(fixtures).filter(
|
|
(result) => result?.status === "PASS",
|
|
).length,
|
|
status: passed ? "PASS" : "FAIL",
|
|
},
|
|
null,
|
|
2,
|
|
)}\n`,
|
|
);
|
|
if (!passed) {
|
|
process.stderr.write(
|
|
"Supply-chain provider fixtures failed closed incorrectly\n",
|
|
);
|
|
process.exitCode = 1;
|
|
} else {
|
|
process.stdout.write(
|
|
"Supply-chain provider fixtures: actual default verifier and valid immutable fixture PASS\n",
|
|
);
|
|
}
|
|
} finally {
|
|
await rm(fixtureRoot, { recursive: true, force: true });
|
|
}
|
|
|
|
function absoluteProviderEnvironment(
|
|
repositoryRoot: string,
|
|
environment: NodeJS.ProcessEnv,
|
|
): NodeJS.ProcessEnv {
|
|
const absolute = { ...environment };
|
|
for (const key of [
|
|
"CANDIDATE_ARCHIVE_PATH",
|
|
"VULNERABILITY_REPORT_PATH",
|
|
"PROVENANCE_ATTESTATION_PATH",
|
|
"VULNERABILITY_PUBLIC_KEY_PATH",
|
|
"PROVENANCE_PUBLIC_KEY_PATH",
|
|
] as const) {
|
|
const value = absolute[key];
|
|
if (value) absolute[key] = path.join(repositoryRoot, value);
|
|
}
|
|
return absolute;
|
|
}
|
|
|
|
async function writeProviderEnvironment(
|
|
repositoryRoot: string,
|
|
name: string,
|
|
candidate: Awaited<ReturnType<typeof createReleaseCandidateManifest>>,
|
|
source: Readonly<{ revision: string; sourceSetSha256: string }>,
|
|
overrides: Readonly<{ distSha256?: string }> = {},
|
|
): Promise<NodeJS.ProcessEnv> {
|
|
const vulnerabilityKeys = generateKeyPairSync("ed25519");
|
|
const provenanceKeys = generateKeyPairSync("ed25519");
|
|
const directory = `provider/${name}`;
|
|
const archiveBytes = `fixture archive ${name}\n`;
|
|
const archiveSha256 = createHash("sha256").update(archiveBytes).digest("hex");
|
|
const candidateIdentity = {
|
|
archiveSha256,
|
|
bundleSha256: candidate.bundleSha256,
|
|
distSha256: overrides.distSha256 ?? candidate.distSha256,
|
|
lockfileSha256: candidate.lockfileSha256,
|
|
};
|
|
const sourceIdentity = {
|
|
revision: source.revision,
|
|
sourceSetSha256: source.sourceSetSha256,
|
|
};
|
|
await mkdir(path.join(repositoryRoot, directory), { recursive: true });
|
|
const vulnerability = signedEvidence(
|
|
{
|
|
schemaVersion: 2,
|
|
evidenceType: "vulnerability-report",
|
|
provider: "fixture-vulnerability-provider",
|
|
issuedAt: "2026-08-02T01:00:00.000Z",
|
|
expiresAt: "2026-08-02T02:00:00.000Z",
|
|
run: { id: "fixture-run", attempt: 1, invocationNonce: "1".repeat(64) },
|
|
source: sourceIdentity,
|
|
candidate: candidateIdentity,
|
|
findings: [],
|
|
},
|
|
"fixture-vulnerability-key",
|
|
vulnerabilityKeys.publicKey,
|
|
vulnerabilityKeys.privateKey,
|
|
);
|
|
const provenance = signedEvidence(
|
|
{
|
|
schemaVersion: 2,
|
|
evidenceType: "provenance-attestation",
|
|
provider: "fixture-provenance-provider",
|
|
signer: "fixture-workload-identity",
|
|
issuedAt: "2026-08-02T01:00:00.000Z",
|
|
expiresAt: "2026-08-02T02:00:00.000Z",
|
|
run: { id: "fixture-run", attempt: 1, invocationNonce: "2".repeat(64) },
|
|
source: sourceIdentity,
|
|
candidate: candidateIdentity,
|
|
subject: { name: "dist", digest: { sha256: candidateIdentity.distSha256 } },
|
|
},
|
|
"fixture-provenance-key",
|
|
provenanceKeys.publicKey,
|
|
provenanceKeys.privateKey,
|
|
);
|
|
await Promise.all([
|
|
writeFile(
|
|
path.join(repositoryRoot, directory, "candidate.tar.gz"),
|
|
archiveBytes,
|
|
),
|
|
writeFile(
|
|
path.join(repositoryRoot, directory, "vulnerability.json"),
|
|
`${JSON.stringify(vulnerability)}\n`,
|
|
),
|
|
writeFile(
|
|
path.join(repositoryRoot, directory, "provenance.json"),
|
|
`${JSON.stringify(provenance)}\n`,
|
|
),
|
|
writeFile(
|
|
path.join(repositoryRoot, directory, "vulnerability.pem"),
|
|
vulnerabilityKeys.publicKey
|
|
.export({ type: "spki", format: "pem" })
|
|
.toString(),
|
|
),
|
|
writeFile(
|
|
path.join(repositoryRoot, directory, "provenance.pem"),
|
|
provenanceKeys.publicKey
|
|
.export({ type: "spki", format: "pem" })
|
|
.toString(),
|
|
),
|
|
]);
|
|
return {
|
|
CANDIDATE_ARCHIVE_PATH: `${directory}/candidate.tar.gz`,
|
|
CANDIDATE_ARCHIVE_SHA256: archiveSha256,
|
|
CI_RUN_ID: "fixture-run",
|
|
CI_RUN_ATTEMPT: "1",
|
|
EXPECTED_SOURCE_REVISION: source.revision,
|
|
VULNERABILITY_INVOCATION_NONCE: "1".repeat(64),
|
|
PROVENANCE_INVOCATION_NONCE: "2".repeat(64),
|
|
VULNERABILITY_REPORT_PATH: `${directory}/vulnerability.json`,
|
|
PROVENANCE_ATTESTATION_PATH: `${directory}/provenance.json`,
|
|
VULNERABILITY_PUBLIC_KEY_PATH: `${directory}/vulnerability.pem`,
|
|
VULNERABILITY_KEY_ID: "fixture-vulnerability-key",
|
|
PROVENANCE_PUBLIC_KEY_PATH: `${directory}/provenance.pem`,
|
|
PROVENANCE_KEY_ID: "fixture-provenance-key",
|
|
};
|
|
}
|
|
|
|
function signedEvidence(
|
|
value: Record<string, unknown>,
|
|
keyId: string,
|
|
publicKey: ReturnType<typeof generateKeyPairSync>["publicKey"],
|
|
privateKey: ReturnType<typeof generateKeyPairSync>["privateKey"],
|
|
) {
|
|
return {
|
|
...value,
|
|
signature: {
|
|
algorithm: "Ed25519",
|
|
keyId,
|
|
publicKeyFingerprint: providerPublicKeyFingerprint(publicKey),
|
|
value: sign(
|
|
null,
|
|
providerEvidenceSignaturePayload(value),
|
|
privateKey,
|
|
).toString("base64"),
|
|
},
|
|
};
|
|
}
|