refactor: adapter 구현중..
This commit is contained in:
@@ -1,248 +1,161 @@
|
||||
import { createHash, generateKeyPairSync, sign } from "node:crypto";
|
||||
import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
||||
import { spawnSync } from "node:child_process";
|
||||
import {
|
||||
createHash,
|
||||
generateKeyPairSync,
|
||||
sign,
|
||||
type KeyObject,
|
||||
} from "node:crypto";
|
||||
import {
|
||||
cp,
|
||||
mkdir,
|
||||
mkdtemp,
|
||||
readFile,
|
||||
readdir,
|
||||
rm,
|
||||
symlink,
|
||||
writeFile,
|
||||
} from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import path from "node:path";
|
||||
|
||||
import {
|
||||
localEvidenceAssessmentArtifactSchema,
|
||||
supplyChainProviderFixturesArtifactSchema,
|
||||
} from "./contracts/release-artifacts.ts";
|
||||
import { PROMOTED_FILE_NAMES } from "./contracts/promotion-artifacts.ts";
|
||||
import {
|
||||
captureCiCandidateArchive,
|
||||
} from "./lib/ci-candidate-archive.ts";
|
||||
import {
|
||||
cleanupFinalizedPromotion,
|
||||
finalizeVerifiedPromotion,
|
||||
} from "./lib/promotion-stager.ts";
|
||||
import { verifyExactPromotionBundle } from "./lib/exact-promotion-bundle.ts";
|
||||
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";
|
||||
import { writeValidatedJsonArtifact } from "./lib/validated-json-artifact.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-"),
|
||||
);
|
||||
const repositoryRoot = process.cwd();
|
||||
const fixtureRoot = await mkdtemp(path.join(tmpdir(), "provider-exact-five-fixture-"));
|
||||
try {
|
||||
const repositoryRoot = process.cwd();
|
||||
const actualCandidate = releaseCandidateManifestSchema.parse(
|
||||
await cp(repositoryRoot, fixtureRoot, {
|
||||
recursive: true,
|
||||
filter: (source) => {
|
||||
const relative = path.relative(repositoryRoot, source);
|
||||
if (!relative) return true;
|
||||
const first = relative.split(path.sep)[0];
|
||||
return ![".release", "artifacts", "dist", "node_modules"].includes(first ?? "");
|
||||
},
|
||||
});
|
||||
await cp(path.join(repositoryRoot, "artifacts"), path.join(fixtureRoot, "artifacts"), {
|
||||
recursive: true,
|
||||
});
|
||||
await rm(path.join(fixtureRoot, "artifacts/release"), { recursive: true, force: true });
|
||||
await symlink(path.join(repositoryRoot, "node_modules"), path.join(fixtureRoot, "node_modules"), "dir");
|
||||
const git = spawnSync("git", ["show", "-s", "--format=%H%n%ct", "HEAD"], {
|
||||
cwd: repositoryRoot,
|
||||
encoding: "utf8",
|
||||
});
|
||||
if (git.status !== 0) throw new Error(`provider fixture git identity failed: ${git.stderr}`);
|
||||
const [revision, sourceDateEpoch] = git.stdout.trim().split(/\r?\n/u);
|
||||
if (!revision || !sourceDateEpoch) throw new Error("provider fixture git identity is incomplete");
|
||||
const build = spawnSync("corepack", ["pnpm", "build:release-candidate"], {
|
||||
cwd: fixtureRoot,
|
||||
encoding: "utf8",
|
||||
timeout: 120_000,
|
||||
maxBuffer: 32 * 1024 * 1024,
|
||||
env: {
|
||||
...process.env,
|
||||
CI: "true",
|
||||
VITE_BUILD_ID: "provider-exact-five-fixture",
|
||||
VITE_COMMIT_SHA: revision,
|
||||
RELEASE_ID: "provider-exact-five-fixture",
|
||||
SOURCE_DATE_EPOCH: sourceDateEpoch,
|
||||
CI_RUNNER_IMAGE: `fixture@sha256:${"a".repeat(64)}`,
|
||||
},
|
||||
});
|
||||
if (build.status !== 0) throw new Error(`${build.stdout}\n${build.stderr}`);
|
||||
const manifest = releaseCandidateManifestSchema.parse(
|
||||
JSON.parse(
|
||||
await readFile(
|
||||
path.join(repositoryRoot, RELEASE_CANDIDATE_MANIFEST_PATH),
|
||||
"utf8",
|
||||
),
|
||||
await readFile(path.join(fixtureRoot, RELEASE_CANDIDATE_MANIFEST_PATH), "utf8"),
|
||||
) as unknown,
|
||||
);
|
||||
const actualAssessment = localEvidenceAssessmentArtifactSchema.parse(
|
||||
const assessment = localEvidenceAssessmentArtifactSchema.parse(
|
||||
JSON.parse(
|
||||
await readFile(path.join(repositoryRoot, LOCAL_EVIDENCE_ASSESSMENT_PATH), "utf8"),
|
||||
await readFile(path.join(fixtureRoot, 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 archivePath = path.join(fixtureRoot, "candidate.tar.gz");
|
||||
const tar = spawnSync(
|
||||
"/usr/bin/tar",
|
||||
[
|
||||
"--sort=name",
|
||||
"--mtime=@0",
|
||||
"--owner=0",
|
||||
"--group=0",
|
||||
"--numeric-owner",
|
||||
"-czf",
|
||||
archivePath,
|
||||
"dist",
|
||||
...RELEASE_CANDIDATE_EVIDENCE_PATHS,
|
||||
RELEASE_CANDIDATE_MANIFEST_PATH,
|
||||
],
|
||||
{ cwd: fixtureRoot, encoding: "utf8" },
|
||||
);
|
||||
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> {
|
||||
if (tar.status !== 0) throw new Error(`provider fixture real tar failed: ${tar.stderr}`);
|
||||
const archiveBytes = await readFile(archivePath);
|
||||
const archiveSha256 = sha256(archiveBytes);
|
||||
const now = Date.parse("2026-08-02T01:00:00.000Z");
|
||||
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 vulnerabilityKeyId = "fixture-vulnerability-key";
|
||||
const provenanceKeyId = "fixture-provenance-key";
|
||||
const vulnerabilityNonce = "1".repeat(64);
|
||||
const provenanceNonce = "2".repeat(64);
|
||||
const context = {
|
||||
run: { id: "fixture-run", attempt: 1 },
|
||||
source: {
|
||||
revision: assessment.source.revision,
|
||||
sourceSetSha256: assessment.source.sourceSetSha256,
|
||||
},
|
||||
candidate: {
|
||||
archiveSha256,
|
||||
bundleSha256: manifest.bundleSha256,
|
||||
distSha256: manifest.distSha256,
|
||||
lockfileSha256: manifest.lockfileSha256,
|
||||
},
|
||||
} as const;
|
||||
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,
|
||||
issuedAt: new Date(now).toISOString(),
|
||||
expiresAt: new Date(now + 60 * 60 * 1_000).toISOString(),
|
||||
run: { ...context.run, invocationNonce: vulnerabilityNonce },
|
||||
source: context.source,
|
||||
candidate: context.candidate,
|
||||
secretScanAttestation: {
|
||||
status: "PASS",
|
||||
localEvidenceAssessmentSha256: sha256(
|
||||
await readFile(path.join(fixtureRoot, LOCAL_EVIDENCE_ASSESSMENT_PATH)),
|
||||
),
|
||||
sourceSetSha256: assessment.source.sourceSetSha256,
|
||||
policySha256: assessment.secretScan.policySha256,
|
||||
sarifSha256: assessment.secretScan.sarifSha256,
|
||||
scanInputSha256: assessment.secretScan.scanInputSha256,
|
||||
},
|
||||
findings: [],
|
||||
},
|
||||
"fixture-vulnerability-key",
|
||||
vulnerabilityKeyId,
|
||||
vulnerabilityKeys.publicKey,
|
||||
vulnerabilityKeys.privateKey,
|
||||
);
|
||||
@@ -252,65 +165,175 @@ async function writeProviderEnvironment(
|
||||
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 } },
|
||||
issuedAt: new Date(now).toISOString(),
|
||||
expiresAt: new Date(now + 60 * 60 * 1_000).toISOString(),
|
||||
run: { ...context.run, invocationNonce: provenanceNonce },
|
||||
source: context.source,
|
||||
candidate: context.candidate,
|
||||
subject: { name: "dist", digest: { sha256: manifest.distSha256 } },
|
||||
},
|
||||
"fixture-provenance-key",
|
||||
provenanceKeyId,
|
||||
provenanceKeys.publicKey,
|
||||
provenanceKeys.privateKey,
|
||||
);
|
||||
await Promise.all([
|
||||
writeFile(
|
||||
path.join(repositoryRoot, directory, "candidate.tar.gz"),
|
||||
archiveBytes,
|
||||
write(fixtureRoot, "provider/vulnerability.json", `${JSON.stringify(vulnerability)}\n`),
|
||||
write(fixtureRoot, "provider/provenance.json", `${JSON.stringify(provenance)}\n`),
|
||||
write(
|
||||
fixtureRoot,
|
||||
"provider/vulnerability.pem",
|
||||
vulnerabilityKeys.publicKey.export({ type: "spki", format: "pem" }),
|
||||
),
|
||||
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(),
|
||||
write(
|
||||
fixtureRoot,
|
||||
"provider/provenance.pem",
|
||||
provenanceKeys.publicKey.export({ type: "spki", format: "pem" }),
|
||||
),
|
||||
]);
|
||||
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",
|
||||
};
|
||||
const runnerTempRoot = path.join(fixtureRoot, "runner-temp");
|
||||
await mkdir(runnerTempRoot, { mode: 0o700 });
|
||||
const finalized = await finalizeVerifiedPromotion(
|
||||
{
|
||||
repositoryRoot: fixtureRoot,
|
||||
archivePath,
|
||||
expectedArchiveSha256: archiveSha256,
|
||||
vulnerabilityReportPath: "provider/vulnerability.json",
|
||||
provenanceAttestationPath: "provider/provenance.json",
|
||||
vulnerabilityPublicKeyPath: "provider/vulnerability.pem",
|
||||
vulnerabilityKeyId,
|
||||
provenancePublicKeyPath: "provider/provenance.pem",
|
||||
provenanceKeyId,
|
||||
expectedRun: { id: context.run.id, attempt: 1, sourceRevision: revision },
|
||||
vulnerabilityInvocationNonce: vulnerabilityNonce,
|
||||
provenanceInvocationNonce: provenanceNonce,
|
||||
runnerTempRoot,
|
||||
},
|
||||
{
|
||||
nowEpochMs: () => now,
|
||||
randomBytes: (bytes) => Buffer.alloc(bytes, 0x4a),
|
||||
afterCapture: async () => {
|
||||
await writeFile(path.join(fixtureRoot, "dist/index.html"), "contradictory external tree\n");
|
||||
},
|
||||
},
|
||||
);
|
||||
const validImmutable =
|
||||
finalized.files.length === 5 &&
|
||||
(await readdir(finalized.stagingRoot)).length === 5;
|
||||
const exactFiles = Object.fromEntries(
|
||||
await Promise.all(
|
||||
PROMOTED_FILE_NAMES.map(async (name) => [
|
||||
name,
|
||||
await readFile(path.join(finalized.stagingRoot, name)),
|
||||
] as const),
|
||||
),
|
||||
);
|
||||
const bundleTrust = {
|
||||
vulnerabilityTrust: {
|
||||
keyId: vulnerabilityKeyId,
|
||||
publicKey: vulnerabilityKeys.publicKey,
|
||||
publicKeyFingerprint: providerPublicKeyFingerprint(vulnerabilityKeys.publicKey),
|
||||
},
|
||||
provenanceTrust: {
|
||||
keyId: provenanceKeyId,
|
||||
publicKey: provenanceKeys.publicKey,
|
||||
publicKeyFingerprint: providerPublicKeyFingerprint(provenanceKeys.publicKey),
|
||||
},
|
||||
expected: {
|
||||
run: context.run,
|
||||
sourceRevision: context.source.revision,
|
||||
sourceSetSha256: context.source.sourceSetSha256,
|
||||
archiveSha256: context.candidate.archiveSha256,
|
||||
bundleSha256: context.candidate.bundleSha256,
|
||||
distSha256: context.candidate.distSha256,
|
||||
lockfileSha256: context.candidate.lockfileSha256,
|
||||
},
|
||||
nowEpochMs: () => now,
|
||||
} as const;
|
||||
const absentFiles = { ...exactFiles } as Partial<typeof exactFiles>;
|
||||
delete absentFiles["provider-verification.json"];
|
||||
const absent = await captureRejection(async () => {
|
||||
await verifyExactPromotionBundle(absentFiles, bundleTrust);
|
||||
});
|
||||
|
||||
const invalidPath = path.join(fixtureRoot, "invalid.tar.gz");
|
||||
const invalidBytes = Buffer.from("arbitrary non-tar bytes\n");
|
||||
await writeFile(invalidPath, invalidBytes);
|
||||
const invalidTar = await captureRejection(async () => {
|
||||
await finalizeVerifiedPromotion(
|
||||
{
|
||||
repositoryRoot: fixtureRoot,
|
||||
archivePath: invalidPath,
|
||||
expectedArchiveSha256: sha256(invalidBytes),
|
||||
vulnerabilityReportPath: "provider/vulnerability.json",
|
||||
provenanceAttestationPath: "provider/provenance.json",
|
||||
vulnerabilityPublicKeyPath: "provider/vulnerability.pem",
|
||||
vulnerabilityKeyId,
|
||||
provenancePublicKeyPath: "provider/provenance.pem",
|
||||
provenanceKeyId,
|
||||
expectedRun: { id: context.run.id, attempt: 1, sourceRevision: revision },
|
||||
vulnerabilityInvocationNonce: vulnerabilityNonce,
|
||||
provenanceInvocationNonce: provenanceNonce,
|
||||
runnerTempRoot,
|
||||
},
|
||||
{ nowEpochMs: () => now },
|
||||
);
|
||||
});
|
||||
const wrongDigest = await captureRejection(async () => {
|
||||
await captureCiCandidateArchive({ archivePath, expectedSha256: "0".repeat(64) });
|
||||
});
|
||||
const passed =
|
||||
validImmutable && absent.rejected && invalidTar.rejected && wrongDigest.rejected;
|
||||
await cleanupFinalizedPromotion({
|
||||
runnerTempRoot,
|
||||
stagingRoot: finalized.stagingRoot,
|
||||
cleanupToken: finalized.cleanupToken,
|
||||
runnerTempIdentity: finalized.runnerTempIdentity,
|
||||
stagingIdentity: finalized.stagingIdentity,
|
||||
});
|
||||
await mkdir(path.join(repositoryRoot, "artifacts/security"), { recursive: true });
|
||||
await writeValidatedJsonArtifact({
|
||||
path: path.join(repositoryRoot, "artifacts/security/supply-chain-provider-fixtures.json"),
|
||||
schema: supplyChainProviderFixturesArtifactSchema,
|
||||
value: {
|
||||
schemaVersion: 1,
|
||||
actualDefaultVerifier: {
|
||||
status: validImmutable ? "PASS" : "FAIL_UNVERIFIED",
|
||||
failures: validImmutable ? [] : ["canonical exact-five finalizer failed"],
|
||||
},
|
||||
fixtures: {
|
||||
absent: {
|
||||
status: absent.rejected ? "FAIL_UNVERIFIED" : "PASS",
|
||||
failures: [absent.failure],
|
||||
},
|
||||
validImmutable: { status: validImmutable ? "PASS" : "FAIL_UNVERIFIED", failures: [] },
|
||||
wrongDigest: {
|
||||
status: wrongDigest.rejected ? "FAIL_UNVERIFIED" : "PASS",
|
||||
failures: [wrongDigest.failure],
|
||||
},
|
||||
invalidTar: {
|
||||
status: invalidTar.rejected ? "FAIL_UNVERIFIED" : "PASS",
|
||||
failures: [invalidTar.failure],
|
||||
},
|
||||
},
|
||||
externalTreeCanary: {
|
||||
status: validImmutable ? "PASS" : "FAIL_UNVERIFIED",
|
||||
failures: validImmutable ? [] : ["external tree canary influenced captured archive"],
|
||||
},
|
||||
passingFixtureCount: validImmutable ? 1 : 0,
|
||||
status: passed ? "PASS" : "FAIL",
|
||||
},
|
||||
});
|
||||
if (!passed) throw new Error("canonical exact-five provider fixtures failed closed incorrectly");
|
||||
process.stdout.write("Supply-chain provider real-tar signed exact-five fixtures: PASS\n");
|
||||
} finally {
|
||||
await rm(fixtureRoot, { recursive: true, force: true });
|
||||
}
|
||||
|
||||
function signedEvidence(
|
||||
value: Record<string, unknown>,
|
||||
keyId: string,
|
||||
publicKey: ReturnType<typeof generateKeyPairSync>["publicKey"],
|
||||
privateKey: ReturnType<typeof generateKeyPairSync>["privateKey"],
|
||||
publicKey: KeyObject,
|
||||
privateKey: KeyObject,
|
||||
) {
|
||||
return {
|
||||
...value,
|
||||
@@ -326,3 +349,27 @@ function signedEvidence(
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
async function write(root: string, relative: string, value: string | Buffer): Promise<void> {
|
||||
const absolute = path.join(root, relative);
|
||||
await mkdir(path.dirname(absolute), { recursive: true });
|
||||
await writeFile(absolute, value);
|
||||
}
|
||||
|
||||
async function captureRejection(
|
||||
operation: () => Promise<void>,
|
||||
): Promise<Readonly<{ rejected: boolean; failure: string }>> {
|
||||
try {
|
||||
await operation();
|
||||
return Object.freeze({ rejected: false, failure: "fixture unexpectedly passed" });
|
||||
} catch (error) {
|
||||
return Object.freeze({
|
||||
rejected: true,
|
||||
failure: error instanceof Error ? error.message : String(error),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function sha256(bytes: Buffer): string {
|
||||
return createHash("sha256").update(bytes).digest("hex");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user