cosign(install): verify binary signature with keyless verification bundle
Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com>
This commit is contained in:
@@ -27,7 +27,10 @@ describe('download', () => {
|
|||||||
'install cosign %s', async (version) => {
|
'install cosign %s', async (version) => {
|
||||||
await expect((async () => {
|
await expect((async () => {
|
||||||
const install = new Install();
|
const install = new Install();
|
||||||
const toolPath = await install.download(version);
|
const toolPath = await install.download({
|
||||||
|
version: version,
|
||||||
|
verifySignature: true
|
||||||
|
});
|
||||||
if (!fs.existsSync(toolPath)) {
|
if (!fs.existsSync(toolPath)) {
|
||||||
throw new Error('toolPath does not exist');
|
throw new Error('toolPath does not exist');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ describe('download', () => {
|
|||||||
])(
|
])(
|
||||||
'acquires %p of cosign', async (version) => {
|
'acquires %p of cosign', async (version) => {
|
||||||
const install = new Install();
|
const install = new Install();
|
||||||
const toolPath = await install.download(version);
|
const toolPath = await install.download({version});
|
||||||
expect(fs.existsSync(toolPath)).toBe(true);
|
expect(fs.existsSync(toolPath)).toBe(true);
|
||||||
const cosignBin = await install.install(toolPath, tmpDir);
|
const cosignBin = await install.install(toolPath, tmpDir);
|
||||||
expect(fs.existsSync(cosignBin)).toBe(true);
|
expect(fs.existsSync(cosignBin)).toBe(true);
|
||||||
@@ -52,7 +52,7 @@ describe('download', () => {
|
|||||||
])(
|
])(
|
||||||
'acquires %p of cosign with cache', async (version) => {
|
'acquires %p of cosign with cache', async (version) => {
|
||||||
const install = new Install();
|
const install = new Install();
|
||||||
const toolPath = await install.download(version);
|
const toolPath = await install.download({version});
|
||||||
expect(fs.existsSync(toolPath)).toBe(true);
|
expect(fs.existsSync(toolPath)).toBe(true);
|
||||||
}, 100000);
|
}, 100000);
|
||||||
|
|
||||||
@@ -63,7 +63,10 @@ describe('download', () => {
|
|||||||
])(
|
])(
|
||||||
'acquires %p of cosign without cache', async (version) => {
|
'acquires %p of cosign without cache', async (version) => {
|
||||||
const install = new Install();
|
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);
|
expect(fs.existsSync(toolPath)).toBe(true);
|
||||||
}, 100000);
|
}, 100000);
|
||||||
|
|
||||||
@@ -80,7 +83,9 @@ describe('download', () => {
|
|||||||
jest.spyOn(osm, 'platform').mockImplementation(() => os as NodeJS.Platform);
|
jest.spyOn(osm, 'platform').mockImplementation(() => os as NodeJS.Platform);
|
||||||
jest.spyOn(osm, 'arch').mockImplementation(() => arch);
|
jest.spyOn(osm, 'arch').mockImplementation(() => arch);
|
||||||
const install = new Install();
|
const install = new Install();
|
||||||
const cosignBin = await install.download('latest');
|
const cosignBin = await install.download({
|
||||||
|
version: 'latest'
|
||||||
|
});
|
||||||
expect(fs.existsSync(cosignBin)).toBe(true);
|
expect(fs.existsSync(cosignBin)).toBe(true);
|
||||||
}, 100000);
|
}, 100000);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -30,7 +30,9 @@ jest.unmock('@actions/github');
|
|||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
const cosignInstall = new CosignInstall();
|
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);
|
await cosignInstall.install(cosignBinPath);
|
||||||
}, 100000);
|
}, 100000);
|
||||||
|
|
||||||
|
|||||||
@@ -34,6 +34,13 @@ import {DownloadVersion} from '../types/cosign/cosign';
|
|||||||
import {GitHubRelease} from '../types/github';
|
import {GitHubRelease} from '../types/github';
|
||||||
import {dockerfileContent} from './dockerfile';
|
import {dockerfileContent} from './dockerfile';
|
||||||
|
|
||||||
|
export interface DownloadOpts {
|
||||||
|
version: string;
|
||||||
|
ghaNoCache?: boolean;
|
||||||
|
skipState?: boolean;
|
||||||
|
verifySignature?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
export interface InstallOpts {
|
export interface InstallOpts {
|
||||||
githubToken?: string;
|
githubToken?: string;
|
||||||
buildx?: Buildx;
|
buildx?: Buildx;
|
||||||
@@ -48,8 +55,8 @@ export class Install {
|
|||||||
this.buildx = opts?.buildx || new Buildx();
|
this.buildx = opts?.buildx || new Buildx();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async download(v: string, ghaNoCache?: boolean, skipState?: boolean): Promise<string> {
|
public async download(opts: DownloadOpts): Promise<string> {
|
||||||
const version: DownloadVersion = await Install.getDownloadVersion(v);
|
const version: DownloadVersion = await Install.getDownloadVersion(opts.version);
|
||||||
core.debug(`Install.download version: ${version.version}`);
|
core.debug(`Install.download version: ${version.version}`);
|
||||||
|
|
||||||
const release: GitHubRelease = await Install.getRelease(version, this.githubToken);
|
const release: GitHubRelease = await Install.getRelease(version, this.githubToken);
|
||||||
@@ -68,7 +75,7 @@ export class Install {
|
|||||||
htcVersion: vspec,
|
htcVersion: vspec,
|
||||||
baseCacheDir: path.join(os.homedir(), '.bin'),
|
baseCacheDir: path.join(os.homedir(), '.bin'),
|
||||||
cacheFile: os.platform() == 'win32' ? 'cosign.exe' : 'cosign',
|
cacheFile: os.platform() == 'win32' ? 'cosign.exe' : 'cosign',
|
||||||
ghaNoCache: ghaNoCache
|
ghaNoCache: opts.ghaNoCache
|
||||||
});
|
});
|
||||||
|
|
||||||
const cacheFoundPath = await installCache.find();
|
const cacheFoundPath = await installCache.find();
|
||||||
@@ -83,7 +90,11 @@ export class Install {
|
|||||||
const htcDownloadPath = await tc.downloadTool(downloadURL, undefined, this.githubToken);
|
const htcDownloadPath = await tc.downloadTool(downloadURL, undefined, this.githubToken);
|
||||||
core.debug(`Install.download htcDownloadPath: ${htcDownloadPath}`);
|
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}`);
|
core.info(`Cached to ${cacheSavePath}`);
|
||||||
return cacheSavePath;
|
return cacheSavePath;
|
||||||
}
|
}
|
||||||
@@ -176,6 +187,27 @@ export class Install {
|
|||||||
return await new Buildx({standalone: buildStandalone}).getCommand(args);
|
return await new Buildx({standalone: buildStandalone}).getCommand(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async verifySignature(cosignBinPath: string, downloadURL: string): Promise<void> {
|
||||||
|
const cosignBootstrapPath = path.join(Context.tmpDir(), `cosign-bootstrap${os.platform() == 'win32' ? '.exe' : ''}`);
|
||||||
|
fs.copyFileSync(cosignBinPath, cosignBootstrapPath);
|
||||||
|
fs.chmodSync(cosignBootstrapPath, '0755');
|
||||||
|
|
||||||
|
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 cosign binary signature with keyless verification bundle`);
|
||||||
|
// prettier-ignore
|
||||||
|
await Exec.exec(cosignBootstrapPath, [
|
||||||
|
'verify-blob',
|
||||||
|
'--certificate-identity', 'keyless@projectsigstore.iam.gserviceaccount.com',
|
||||||
|
'--certificate-oidc-issuer', 'https://accounts.google.com',
|
||||||
|
'--bundle', bundlePath,
|
||||||
|
cosignBinPath
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
private filename(): string {
|
private filename(): string {
|
||||||
let arch: string;
|
let arch: string;
|
||||||
switch (os.arch()) {
|
switch (os.arch()) {
|
||||||
|
|||||||
Reference in New Issue
Block a user