Merge pull request #931 from docker/sigstore-signing-config

sigstore: use signing config with cosign
This commit is contained in:
CrazyMax
2026-01-15 17:11:42 +01:00
committed by GitHub
3 changed files with 130 additions and 5 deletions

View File

@@ -0,0 +1,93 @@
/**
* Copyright 2026 actions-toolkit authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {beforeAll, describe, expect, jest, it} from '@jest/globals';
import * as path from 'path';
import {Buildx} from '../../src/buildx/buildx';
import {Build} from '../../src/buildx/build';
import {Install as CosignInstall} from '../../src/cosign/install';
import {Docker} from '../../src/docker/docker';
import {Exec} from '../../src/exec';
import {Sigstore} from '../../src/sigstore/sigstore';
const fixturesDir = path.join(__dirname, '..', '.fixtures');
const runTest = process.env.GITHUB_ACTIONS && process.env.GITHUB_ACTIONS === 'true' && process.env.ImageOS && process.env.ImageOS.startsWith('ubuntu');
const maybeIdToken = runTest && process.env.ACTIONS_ID_TOKEN_REQUEST_URL ? describe : describe.skip;
// needs current GitHub repo info
jest.unmock('@actions/github');
beforeAll(async () => {
const cosignInstall = new CosignInstall();
const cosignBinPath = await cosignInstall.download({
version: 'v3.0.2'
});
await cosignInstall.install(cosignBinPath);
}, 100000);
maybeIdToken('signAttestationManifests', () => {
it('build, sign and verify', async () => {
const buildx = new Buildx();
const build = new Build({buildx: buildx});
const imageName = 'ghcr.io/docker/actions-toolkit/test';
await expect(
(async () => {
await Docker.getExecOutput(['login', '--password-stdin', '--username', process.env.GITHUB_REPOSITORY_OWNER || 'docker', 'ghcr.io'], {
input: Buffer.from(process.env.GITHUB_TOKEN || '')
});
})()
).resolves.not.toThrow();
await expect(
(async () => {
// prettier-ignore
const buildCmd = await buildx.getCommand([
'--builder', process.env.CTN_BUILDER_NAME ?? 'default',
'build',
'-f', path.join(fixturesDir, 'hello.Dockerfile'),
'--provenance=mode=max',
'--tag', `${imageName}:sigstore-itg`,
'--platform', 'linux/amd64,linux/arm64',
'--push',
'--metadata-file', build.getMetadataFilePath(),
fixturesDir
]);
await Exec.exec(buildCmd.command, buildCmd.args);
})()
).resolves.not.toThrow();
const metadata = build.resolveMetadata();
expect(metadata).toBeDefined();
const buildDigest = build.resolveDigest(metadata);
expect(buildDigest).toBeDefined();
const sigstore = new Sigstore();
const signResults = await sigstore.signAttestationManifests({
imageNames: [imageName],
imageDigest: buildDigest!
});
expect(Object.keys(signResults).length).toEqual(2);
const verifyResults = await sigstore.verifySignedManifests(signResults, {
certificateIdentityRegexp: `^https://github.com/docker/actions-toolkit/.github/workflows/test.yml.*$`
});
expect(Object.keys(verifyResults).length).toEqual(2);
}, 100000);
});

View File

@@ -39,7 +39,7 @@ jest.unmock('@actions/github');
beforeAll(async () => {
const cosignInstall = new CosignInstall();
const cosignBinPath = await cosignInstall.download({
version: 'v3.0.2'
version: 'v3.0.4'
});
await cosignInstall.install(cosignBinPath);
}, 100000);

View File

@@ -22,6 +22,7 @@ import * as core from '@actions/core';
import {bundleFromJSON, bundleToJSON} from '@sigstore/bundle';
import {Artifact, Bundle, CIContextProvider, DSSEBundleBuilder, FulcioSigner, RekorWitness, TSAWitness, Witness} from '@sigstore/sign';
import {Context} from '../context';
import {Cosign} from '../cosign/cosign';
import {Exec} from '../exec';
import {GitHub} from '../github';
@@ -73,6 +74,40 @@ export class Sigstore {
core.info(`Using Sigstore signing endpoint: ${endpoints.fulcioURL}`);
const noTransparencyLog = Sigstore.noTransparencyLog(opts.noTransparencyLog);
const cosignExtraArgs: string[] = [];
if (await this.cosign.versionSatisfies('>=3.0.4')) {
await core.group(`Creating Sigstore protobuf signing config`, async () => {
const signingConfig = Context.tmpName({
template: 'signing-config-XXXXXX.json',
tmpdir: Context.tmpDir()
});
// prettier-ignore
const createConfigArgs = [
'signing-config',
'create',
'--with-default-services=true',
`--out=${signingConfig}`
];
if (noTransparencyLog) {
createConfigArgs.push('--no-default-rekor=true');
}
await Exec.exec('cosign', createConfigArgs, {
env: Object.assign({}, process.env, {
COSIGN_EXPERIMENTAL: '1'
}) as {
[key: string]: string;
}
});
core.info(JSON.stringify(JSON.parse(fs.readFileSync(signingConfig, {encoding: 'utf-8'})), null, 2));
cosignExtraArgs.push(`--signing-config=${signingConfig}`);
});
} else {
cosignExtraArgs.push('--use-signing-config');
if (noTransparencyLog) {
cosignExtraArgs.push('--tlog-upload=false');
}
}
for (const imageName of opts.imageNames) {
const attestationDigests = await this.imageTools.attestationDigests(`${imageName}@${opts.imageDigest}`);
for (const attestationDigest of attestationDigests) {
@@ -85,11 +120,8 @@ export class Sigstore {
'--oidc-provider', 'github-actions',
'--registry-referrers-mode', 'oci-1-1',
'--new-bundle-format',
'--use-signing-config'
...cosignExtraArgs
];
if (noTransparencyLog) {
cosignArgs.push('--tlog-upload=false');
}
core.info(`[command]cosign ${[...cosignArgs, attestationRef].join(' ')}`);
const execRes = await Exec.getExecOutput('cosign', ['--verbose', ...cosignArgs, attestationRef], {
ignoreReturnCode: true,