Skip to content

Commit 882907c

Browse files
authored
Merge pull request #931 from docker/sigstore-signing-config
sigstore: use signing config with cosign
2 parents d5a1a96 + c47fbe6 commit 882907c

File tree

3 files changed

+130
-5
lines changed

3 files changed

+130
-5
lines changed
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
/**
2+
* Copyright 2026 actions-toolkit authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import {beforeAll, describe, expect, jest, it} from '@jest/globals';
18+
import * as path from 'path';
19+
20+
import {Buildx} from '../../src/buildx/buildx';
21+
import {Build} from '../../src/buildx/build';
22+
import {Install as CosignInstall} from '../../src/cosign/install';
23+
import {Docker} from '../../src/docker/docker';
24+
import {Exec} from '../../src/exec';
25+
import {Sigstore} from '../../src/sigstore/sigstore';
26+
27+
const fixturesDir = path.join(__dirname, '..', '.fixtures');
28+
29+
const runTest = process.env.GITHUB_ACTIONS && process.env.GITHUB_ACTIONS === 'true' && process.env.ImageOS && process.env.ImageOS.startsWith('ubuntu');
30+
31+
const maybeIdToken = runTest && process.env.ACTIONS_ID_TOKEN_REQUEST_URL ? describe : describe.skip;
32+
33+
// needs current GitHub repo info
34+
jest.unmock('@actions/github');
35+
36+
beforeAll(async () => {
37+
const cosignInstall = new CosignInstall();
38+
const cosignBinPath = await cosignInstall.download({
39+
version: 'v3.0.2'
40+
});
41+
await cosignInstall.install(cosignBinPath);
42+
}, 100000);
43+
44+
maybeIdToken('signAttestationManifests', () => {
45+
it('build, sign and verify', async () => {
46+
const buildx = new Buildx();
47+
const build = new Build({buildx: buildx});
48+
const imageName = 'ghcr.io/docker/actions-toolkit/test';
49+
50+
await expect(
51+
(async () => {
52+
await Docker.getExecOutput(['login', '--password-stdin', '--username', process.env.GITHUB_REPOSITORY_OWNER || 'docker', 'ghcr.io'], {
53+
input: Buffer.from(process.env.GITHUB_TOKEN || '')
54+
});
55+
})()
56+
).resolves.not.toThrow();
57+
58+
await expect(
59+
(async () => {
60+
// prettier-ignore
61+
const buildCmd = await buildx.getCommand([
62+
'--builder', process.env.CTN_BUILDER_NAME ?? 'default',
63+
'build',
64+
'-f', path.join(fixturesDir, 'hello.Dockerfile'),
65+
'--provenance=mode=max',
66+
'--tag', `${imageName}:sigstore-itg`,
67+
'--platform', 'linux/amd64,linux/arm64',
68+
'--push',
69+
'--metadata-file', build.getMetadataFilePath(),
70+
fixturesDir
71+
]);
72+
await Exec.exec(buildCmd.command, buildCmd.args);
73+
})()
74+
).resolves.not.toThrow();
75+
76+
const metadata = build.resolveMetadata();
77+
expect(metadata).toBeDefined();
78+
const buildDigest = build.resolveDigest(metadata);
79+
expect(buildDigest).toBeDefined();
80+
81+
const sigstore = new Sigstore();
82+
const signResults = await sigstore.signAttestationManifests({
83+
imageNames: [imageName],
84+
imageDigest: buildDigest!
85+
});
86+
expect(Object.keys(signResults).length).toEqual(2);
87+
88+
const verifyResults = await sigstore.verifySignedManifests(signResults, {
89+
certificateIdentityRegexp: `^https://github.com/docker/actions-toolkit/.github/workflows/test.yml.*$`
90+
});
91+
expect(Object.keys(verifyResults).length).toEqual(2);
92+
}, 100000);
93+
});

__tests__/sigstore/sigstore.test.itg.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ jest.unmock('@actions/github');
3939
beforeAll(async () => {
4040
const cosignInstall = new CosignInstall();
4141
const cosignBinPath = await cosignInstall.download({
42-
version: 'v3.0.2'
42+
version: 'v3.0.4'
4343
});
4444
await cosignInstall.install(cosignBinPath);
4545
}, 100000);

src/sigstore/sigstore.ts

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import * as core from '@actions/core';
2222
import {bundleFromJSON, bundleToJSON} from '@sigstore/bundle';
2323
import {Artifact, Bundle, CIContextProvider, DSSEBundleBuilder, FulcioSigner, RekorWitness, TSAWitness, Witness} from '@sigstore/sign';
2424

25+
import {Context} from '../context';
2526
import {Cosign} from '../cosign/cosign';
2627
import {Exec} from '../exec';
2728
import {GitHub} from '../github';
@@ -73,6 +74,40 @@ export class Sigstore {
7374
core.info(`Using Sigstore signing endpoint: ${endpoints.fulcioURL}`);
7475
const noTransparencyLog = Sigstore.noTransparencyLog(opts.noTransparencyLog);
7576

77+
const cosignExtraArgs: string[] = [];
78+
if (await this.cosign.versionSatisfies('>=3.0.4')) {
79+
await core.group(`Creating Sigstore protobuf signing config`, async () => {
80+
const signingConfig = Context.tmpName({
81+
template: 'signing-config-XXXXXX.json',
82+
tmpdir: Context.tmpDir()
83+
});
84+
// prettier-ignore
85+
const createConfigArgs = [
86+
'signing-config',
87+
'create',
88+
'--with-default-services=true',
89+
`--out=${signingConfig}`
90+
];
91+
if (noTransparencyLog) {
92+
createConfigArgs.push('--no-default-rekor=true');
93+
}
94+
await Exec.exec('cosign', createConfigArgs, {
95+
env: Object.assign({}, process.env, {
96+
COSIGN_EXPERIMENTAL: '1'
97+
}) as {
98+
[key: string]: string;
99+
}
100+
});
101+
core.info(JSON.stringify(JSON.parse(fs.readFileSync(signingConfig, {encoding: 'utf-8'})), null, 2));
102+
cosignExtraArgs.push(`--signing-config=${signingConfig}`);
103+
});
104+
} else {
105+
cosignExtraArgs.push('--use-signing-config');
106+
if (noTransparencyLog) {
107+
cosignExtraArgs.push('--tlog-upload=false');
108+
}
109+
}
110+
76111
for (const imageName of opts.imageNames) {
77112
const attestationDigests = await this.imageTools.attestationDigests(`${imageName}@${opts.imageDigest}`);
78113
for (const attestationDigest of attestationDigests) {
@@ -85,11 +120,8 @@ export class Sigstore {
85120
'--oidc-provider', 'github-actions',
86121
'--registry-referrers-mode', 'oci-1-1',
87122
'--new-bundle-format',
88-
'--use-signing-config'
123+
...cosignExtraArgs
89124
];
90-
if (noTransparencyLog) {
91-
cosignArgs.push('--tlog-upload=false');
92-
}
93125
core.info(`[command]cosign ${[...cosignArgs, attestationRef].join(' ')}`);
94126
const execRes = await Exec.getExecOutput('cosign', ['--verbose', ...cosignArgs, attestationRef], {
95127
ignoreReturnCode: true,

0 commit comments

Comments
 (0)