Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion __tests__/cosign/install.test.itg.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,10 @@ describe('download', () => {
'install cosign %s', async (version) => {
await expect((async () => {
const install = new Install();
const toolPath = await install.download(version);
const toolPath = await install.download({
version: version,
verifySignature: true
});
if (!fs.existsSync(toolPath)) {
throw new Error('toolPath does not exist');
}
Expand Down
13 changes: 9 additions & 4 deletions __tests__/cosign/install.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ describe('download', () => {
])(
'acquires %p of cosign', async (version) => {
const install = new Install();
const toolPath = await install.download(version);
const toolPath = await install.download({version});
expect(fs.existsSync(toolPath)).toBe(true);
const cosignBin = await install.install(toolPath, tmpDir);
expect(fs.existsSync(cosignBin)).toBe(true);
Expand All @@ -52,7 +52,7 @@ describe('download', () => {
])(
'acquires %p of cosign with cache', async (version) => {
const install = new Install();
const toolPath = await install.download(version);
const toolPath = await install.download({version});
expect(fs.existsSync(toolPath)).toBe(true);
}, 100000);

Expand All @@ -63,7 +63,10 @@ describe('download', () => {
])(
'acquires %p of cosign without cache', async (version) => {
const install = new Install();
const toolPath = await install.download(version, true);
const toolPath = await install.download({
version: version,
ghaNoCache: true
});
expect(fs.existsSync(toolPath)).toBe(true);
}, 100000);

Expand All @@ -80,7 +83,9 @@ describe('download', () => {
jest.spyOn(osm, 'platform').mockImplementation(() => os as NodeJS.Platform);
jest.spyOn(osm, 'arch').mockImplementation(() => arch);
const install = new Install();
const cosignBin = await install.download('latest');
const cosignBin = await install.download({
version: 'latest'
});
expect(fs.existsSync(cosignBin)).toBe(true);
}, 100000);
});
Expand Down
4 changes: 3 additions & 1 deletion __tests__/sigstore/sigstore.test.itg.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@ jest.unmock('@actions/github');

beforeAll(async () => {
const cosignInstall = new CosignInstall();
const cosignBinPath = await cosignInstall.download('v3.0.2', true);
const cosignBinPath = await cosignInstall.download({
version: 'v3.0.2'
});
await cosignInstall.install(cosignBinPath);
}, 100000);

Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@
"@octokit/plugin-rest-endpoint-methods": "^10.4.1",
"@sigstore/bundle": "^4.0.0",
"@sigstore/sign": "^4.0.1",
"@sigstore/tuf": "^4.0.0",
"@sigstore/verify": "^3.0.0",
"async-retry": "^1.3.3",
"csv-parse": "^6.1.0",
"gunzip-maybe": "^1.4.2",
Expand Down
51 changes: 47 additions & 4 deletions src/cosign/install.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ import os from 'os';
import path from 'path';
import * as core from '@actions/core';
import * as tc from '@actions/tool-cache';
import {bundleFromJSON, SerializedBundle} from '@sigstore/bundle';
import * as tuf from '@sigstore/tuf';
import {toSignedEntity, toTrustMaterial, Verifier} from '@sigstore/verify';
import * as semver from 'semver';
import * as util from 'util';

Expand All @@ -34,6 +37,13 @@ import {DownloadVersion} from '../types/cosign/cosign';
import {GitHubRelease} from '../types/github';
import {dockerfileContent} from './dockerfile';

export interface DownloadOpts {
version: string;
ghaNoCache?: boolean;
skipState?: boolean;
verifySignature?: boolean;
}

export interface InstallOpts {
githubToken?: string;
buildx?: Buildx;
Expand All @@ -48,8 +58,8 @@ export class Install {
this.buildx = opts?.buildx || new Buildx();
}

public async download(v: string, ghaNoCache?: boolean, skipState?: boolean): Promise<string> {
const version: DownloadVersion = await Install.getDownloadVersion(v);
public async download(opts: DownloadOpts): Promise<string> {
const version: DownloadVersion = await Install.getDownloadVersion(opts.version);
core.debug(`Install.download version: ${version.version}`);

const release: GitHubRelease = await Install.getRelease(version, this.githubToken);
Expand All @@ -68,7 +78,7 @@ export class Install {
htcVersion: vspec,
baseCacheDir: path.join(os.homedir(), '.bin'),
cacheFile: os.platform() == 'win32' ? 'cosign.exe' : 'cosign',
ghaNoCache: ghaNoCache
ghaNoCache: opts.ghaNoCache
});

const cacheFoundPath = await installCache.find();
Expand All @@ -83,7 +93,11 @@ export class Install {
const htcDownloadPath = await tc.downloadTool(downloadURL, undefined, this.githubToken);
core.debug(`Install.download htcDownloadPath: ${htcDownloadPath}`);

const cacheSavePath = await installCache.save(htcDownloadPath, skipState);
if (opts.verifySignature && semver.satisfies(vspec, '>=3.0.1')) {
await this.verifySignature(htcDownloadPath, downloadURL);
}

const cacheSavePath = await installCache.save(htcDownloadPath, opts.skipState);
core.info(`Cached to ${cacheSavePath}`);
return cacheSavePath;
}
Expand Down Expand Up @@ -176,6 +190,35 @@ export class Install {
return await new Buildx({standalone: buildStandalone}).getCommand(args);
}

private async verifySignature(cosignBinPath: string, downloadURL: string): Promise<void> {
const bundleURL = `${downloadURL}.sigstore.json`;
core.info(`Downloading keyless verification bundle at ${bundleURL}`);
const bundlePath = await tc.downloadTool(bundleURL, undefined, this.githubToken);
core.debug(`Install.verifySignature bundlePath: ${bundlePath}`);

core.info(`Verifying keyless verification bundle signature`);
const parsedBundle = JSON.parse(fs.readFileSync(bundlePath, 'utf-8')) as SerializedBundle;
const bundle = bundleFromJSON(parsedBundle);

core.info(`Fetching Sigstore TUF trusted root metadata`);
const trustedRoot = await tuf.getTrustedRoot();
const trustMaterial = toTrustMaterial(trustedRoot);

try {
core.info(`Verifying cosign binary signature`);
const signedEntity = toSignedEntity(bundle, fs.readFileSync(cosignBinPath));
const verifier = new Verifier(trustMaterial);
const signer = verifier.verify(signedEntity, {
subjectAlternativeName: 'keyless@projectsigstore.iam.gserviceaccount.com',
extensions: {issuer: 'https://accounts.google.com'}
});
core.debug(`Install.verifySignature signer: ${JSON.stringify(signer)}`);
core.info(`Cosign binary signature verified!`);
} catch (err) {
throw new Error(`Failed to verify cosign binary signature: ${err}`);
}
}

private filename(): string {
let arch: string;
switch (os.arch()) {
Expand Down
124 changes: 123 additions & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1185,6 +1185,8 @@ __metadata:
"@sigstore/bundle": "npm:^4.0.0"
"@sigstore/rekor-types": "npm:^3.0.0"
"@sigstore/sign": "npm:^4.0.1"
"@sigstore/tuf": "npm:^4.0.0"
"@sigstore/verify": "npm:^3.0.0"
"@types/gunzip-maybe": "npm:^1.4.3"
"@types/he": "npm:^1.2.3"
"@types/js-yaml": "npm:^4.0.9"
Expand Down Expand Up @@ -2273,6 +2275,27 @@ __metadata:
languageName: node
linkType: hard

"@sigstore/tuf@npm:^4.0.0":
version: 4.0.0
resolution: "@sigstore/tuf@npm:4.0.0"
dependencies:
"@sigstore/protobuf-specs": "npm:^0.5.0"
tuf-js: "npm:^4.0.0"
checksum: 10/8f47a0bc814a8ee1ef59bc90eb7954e0bb33734a913c77c04bdbf08fce2622d406feb0b243191154453a046224fcc512e916c1c919563fab902070b66837ad5e
languageName: node
linkType: hard

"@sigstore/verify@npm:^3.0.0":
version: 3.0.0
resolution: "@sigstore/verify@npm:3.0.0"
dependencies:
"@sigstore/bundle": "npm:^4.0.0"
"@sigstore/core": "npm:^3.0.0"
"@sigstore/protobuf-specs": "npm:^0.5.0"
checksum: 10/c5b4891f42586a4c68fb22f127f19dd16b0bda0388ae8a40727cedd2443919006df3ec1ac4d6c3bd2786cff4c3f8d987135e87979262790e718bcc53e8a3a6c1
languageName: node
linkType: hard

"@sinclair/typebox@npm:^0.34.0":
version: 0.34.41
resolution: "@sinclair/typebox@npm:0.34.41"
Expand Down Expand Up @@ -2333,6 +2356,23 @@ __metadata:
languageName: node
linkType: hard

"@tufjs/canonical-json@npm:2.0.0":
version: 2.0.0
resolution: "@tufjs/canonical-json@npm:2.0.0"
checksum: 10/cc719a1d0d0ae1aa1ba551a82c87dcbefac088e433c03a3d8a1d547ea721350e47dab4ab5b0fca40d5c7ab1f4882e72edc39c9eae15bf47c45c43bcb6ee39f4f
languageName: node
linkType: hard

"@tufjs/models@npm:4.0.0":
version: 4.0.0
resolution: "@tufjs/models@npm:4.0.0"
dependencies:
"@tufjs/canonical-json": "npm:2.0.0"
minimatch: "npm:^9.0.5"
checksum: 10/1b8d119b4144018d92237aa0dfcf4ac85ee609dd0062d15817736cfd0d0d594761e9179dd7b580894a6e7f67dd06d4421f16534756b66441c8838e8644e77632
languageName: node
linkType: hard

"@tybys/wasm-util@npm:^0.10.0":
version: 0.10.1
resolution: "@tybys/wasm-util@npm:0.10.1"
Expand Down Expand Up @@ -3947,6 +3987,18 @@ __metadata:
languageName: node
linkType: hard

"debug@npm:^4.4.1":
version: 4.4.3
resolution: "debug@npm:4.4.3"
dependencies:
ms: "npm:^2.1.3"
peerDependenciesMeta:
supports-color:
optional: true
checksum: 10/9ada3434ea2993800bd9a1e320bd4aa7af69659fb51cca685d390949434bc0a8873c21ed7c9b852af6f2455a55c6d050aa3937d52b3c69f796dab666f762acad
languageName: node
linkType: hard

"dedent@npm:^1.6.0":
version: 1.7.0
resolution: "dedent@npm:1.7.0"
Expand Down Expand Up @@ -7062,6 +7114,25 @@ __metadata:
languageName: node
linkType: hard

"make-fetch-happen@npm:^15.0.0":
version: 15.0.3
resolution: "make-fetch-happen@npm:15.0.3"
dependencies:
"@npmcli/agent": "npm:^4.0.0"
cacache: "npm:^20.0.1"
http-cache-semantics: "npm:^4.1.1"
minipass: "npm:^7.0.2"
minipass-fetch: "npm:^5.0.0"
minipass-flush: "npm:^1.0.5"
minipass-pipeline: "npm:^1.2.4"
negotiator: "npm:^1.0.0"
proc-log: "npm:^6.0.0"
promise-retry: "npm:^2.0.1"
ssri: "npm:^13.0.0"
checksum: 10/78da4fc1df83cb596e2bae25aa0653b8a9c6cbdd6674a104894e03be3acfcd08c70b78f06ef6407fbd6b173f6a60672480d78641e693d05eb71c09c13ee35278
languageName: node
linkType: hard

"make-fetch-happen@npm:^15.0.2":
version: 15.0.2
resolution: "make-fetch-happen@npm:15.0.2"
Expand Down Expand Up @@ -7175,6 +7246,15 @@ __metadata:
languageName: node
linkType: hard

"minimatch@npm:^9.0.5":
version: 9.0.5
resolution: "minimatch@npm:9.0.5"
dependencies:
brace-expansion: "npm:^2.0.1"
checksum: 10/dd6a8927b063aca6d910b119e1f2df6d2ce7d36eab91de83167dd136bb85e1ebff97b0d3de1cb08bd1f7e018ca170b4962479fefab5b2a69e2ae12cb2edc8348
languageName: node
linkType: hard

"minimist@npm:^1.2.0, minimist@npm:^1.2.6":
version: 1.2.7
resolution: "minimist@npm:1.2.7"
Expand Down Expand Up @@ -7237,6 +7317,21 @@ __metadata:
languageName: node
linkType: hard

"minipass-fetch@npm:^5.0.0":
version: 5.0.0
resolution: "minipass-fetch@npm:5.0.0"
dependencies:
encoding: "npm:^0.1.13"
minipass: "npm:^7.0.3"
minipass-sized: "npm:^1.0.3"
minizlib: "npm:^3.0.1"
dependenciesMeta:
encoding:
optional: true
checksum: 10/4fb7dca630a64e6970a8211dade505bfe260d0b8d60beb348dcdfb95fe35ef91d977b29963929c9017ae0805686aa3f413107dc6bc5deac9b9e26b0b41c3b86c
languageName: node
linkType: hard

"minipass-flush@npm:^1.0.5":
version: 1.0.5
resolution: "minipass-flush@npm:1.0.5"
Expand Down Expand Up @@ -7347,7 +7442,7 @@ __metadata:
languageName: node
linkType: hard

"ms@npm:^2.0.0, ms@npm:^2.1.1":
"ms@npm:^2.0.0, ms@npm:^2.1.1, ms@npm:^2.1.3":
version: 2.1.3
resolution: "ms@npm:2.1.3"
checksum: 10/aa92de608021b242401676e35cfa5aa42dd70cbdc082b916da7fb925c542173e36bce97ea3e804923fe92c0ad991434e4a38327e15a1b5b5f945d66df615ae6d
Expand Down Expand Up @@ -7879,6 +7974,13 @@ __metadata:
languageName: node
linkType: hard

"proc-log@npm:^6.0.0":
version: 6.1.0
resolution: "proc-log@npm:6.1.0"
checksum: 10/9033f30f168ed5a0991b773d0c50ff88384c4738e9a0a67d341de36bf7293771eed648ab6a0562f62276da12fde91f3bbfc75ffff6e71ad49aafd74fc646be66
languageName: node
linkType: hard

"process-nextick-args@npm:~2.0.0":
version: 2.0.1
resolution: "process-nextick-args@npm:2.0.1"
Expand Down Expand Up @@ -8552,6 +8654,15 @@ __metadata:
languageName: node
linkType: hard

"ssri@npm:^13.0.0":
version: 13.0.0
resolution: "ssri@npm:13.0.0"
dependencies:
minipass: "npm:^7.0.3"
checksum: 10/fd59bfedf0659c1b83f6e15459162da021f08ec0f5834dd9163296f8b77ee82f9656aa1d415c3d3848484293e0e6aefdd482e863e52ddb53d520bb73da1eeec1
languageName: node
linkType: hard

"ssri@npm:^9.0.0":
version: 9.0.1
resolution: "ssri@npm:9.0.1"
Expand Down Expand Up @@ -9067,6 +9178,17 @@ __metadata:
languageName: node
linkType: hard

"tuf-js@npm:^4.0.0":
version: 4.0.0
resolution: "tuf-js@npm:4.0.0"
dependencies:
"@tufjs/models": "npm:4.0.0"
debug: "npm:^4.4.1"
make-fetch-happen: "npm:^15.0.0"
checksum: 10/7de216e39578f7abd449b2eaed7977b9e99f3b66bcc7ff24f4f4a4a4bcca032a1c180e2a3fd20019ed820d898010fcd9f2654446c87dbf93a9b13f163bb99422
languageName: node
linkType: hard

"tunnel@npm:^0.0.6":
version: 0.0.6
resolution: "tunnel@npm:0.0.6"
Expand Down
Loading