Skip to content

Commit 85dfc7a

Browse files
committed
sigstore: remove @actions/attest dependency
Signed-off-by: CrazyMax <[email protected]>
1 parent 5c04d39 commit 85dfc7a

File tree

5 files changed

+127
-417
lines changed

5 files changed

+127
-417
lines changed

__tests__/sigstore/sigstore.test.itg.ts

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,10 @@ maybe('signProvenanceBlobs', () => {
4444
const provenancePath = Object.keys(results)[0];
4545
expect(provenancePath).toEqual(path.join(fixturesDir, 'sigstore', 'single', 'provenance.json'));
4646
expect(fs.existsSync(results[provenancePath].bundlePath)).toBe(true);
47-
expect(results[provenancePath].bundle).toBeDefined();
47+
expect(results[provenancePath].payload).toBeDefined();
4848
expect(results[provenancePath].certificate).toBeDefined();
4949
expect(results[provenancePath].tlogID).toBeDefined();
50-
expect(results[provenancePath].attestationID).not.toBeDefined();
51-
console.log(provenancePath, JSON.stringify(results[provenancePath].bundle, null, 2));
50+
console.log(provenancePath, JSON.stringify(results[provenancePath].payload, null, 2));
5251
});
5352
it('multi-platform', async () => {
5453
const sigstore = new Sigstore();
@@ -59,11 +58,10 @@ maybe('signProvenanceBlobs', () => {
5958
for (const [provenancePath, res] of Object.entries(results)) {
6059
expect(provenancePath).toMatch(/linux_(amd64|arm64)\/provenance.json/);
6160
expect(fs.existsSync(res.bundlePath)).toBe(true);
62-
expect(res.bundle).toBeDefined();
61+
expect(res.payload).toBeDefined();
6362
expect(res.certificate).toBeDefined();
6463
expect(res.tlogID).toBeDefined();
65-
expect(res.attestationID).not.toBeDefined();
66-
console.log(provenancePath, JSON.stringify(res.bundle, null, 2));
64+
console.log(provenancePath, JSON.stringify(res.payload, null, 2));
6765
}
6866
});
6967
});

package.json

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,6 @@
4646
},
4747
"dependencies": {
4848
"@actions/artifact": "^4.0.0",
49-
"@actions/attest": "^2.0.0",
5049
"@actions/cache": "^4.1.0",
5150
"@actions/core": "^1.11.1",
5251
"@actions/exec": "^1.1.1",
@@ -71,7 +70,6 @@
7170
"tmp": "^0.2.5"
7271
},
7372
"devDependencies": {
74-
"@sigstore/mock": "^0.10.0",
7573
"@sigstore/rekor-types": "^3.0.0",
7674
"@types/gunzip-maybe": "^1.4.2",
7775
"@types/he": "^1.2.3",

src/sigstore/sigstore.ts

Lines changed: 65 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -18,60 +18,32 @@ import {X509Certificate} from 'crypto';
1818
import fs from 'fs';
1919
import path from 'path';
2020

21-
import {Endpoints} from '@actions/attest/lib/endpoints';
2221
import * as core from '@actions/core';
23-
import {signPayload} from '@actions/attest/lib/sign';
2422
import {bundleFromJSON, bundleToJSON} from '@sigstore/bundle';
25-
import {Attestation} from '@actions/attest';
26-
import {Bundle} from '@sigstore/sign';
23+
import {Artifact, Bundle, CIContextProvider, DSSEBundleBuilder, FulcioSigner, RekorWitness, TSAWitness, Witness} from '@sigstore/sign';
2724

2825
import {Cosign} from '../cosign/cosign';
2926
import {Exec} from '../exec';
3027
import {GitHub} from '../github';
3128
import {ImageTools} from '../buildx/imagetools';
3229

3330
import {MEDIATYPE_PAYLOAD as INTOTO_MEDIATYPE_PAYLOAD, Subject} from '../types/intoto/intoto';
34-
import {FULCIO_URL, REKOR_URL, SEARCH_URL, TSASERVER_URL} from '../types/sigstore/sigstore';
35-
36-
export interface SignAttestationManifestsOpts {
37-
imageNames: Array<string>;
38-
imageDigest: string;
39-
noTransparencyLog?: boolean;
40-
}
41-
42-
export interface SignAttestationManifestsResult extends Attestation {
43-
imageName: string;
44-
}
45-
46-
export interface VerifySignedManifestsOpts {
47-
certificateIdentityRegexp: string;
48-
retries?: number;
49-
}
50-
51-
export interface VerifySignedManifestsResult {
52-
cosignArgs: Array<string>;
53-
signatureManifestDigest: string;
54-
}
55-
56-
export interface SignProvenanceBlobsOpts {
57-
localExportDir: string;
58-
name?: string;
59-
noTransparencyLog?: boolean;
60-
}
61-
62-
export interface SignProvenanceBlobsResult extends Attestation {
63-
bundlePath: string;
64-
subjects: Array<Subject>;
65-
}
66-
67-
export interface VerifySignedArtifactsOpts {
68-
certificateIdentityRegexp: string;
69-
}
70-
71-
export interface VerifySignedArtifactsResult {
72-
bundlePath: string;
73-
cosignArgs: Array<string>;
74-
}
31+
import {
32+
Endpoints,
33+
FULCIO_URL,
34+
ParsedBundle,
35+
REKOR_URL,
36+
SEARCH_URL,
37+
SignAttestationManifestsOpts,
38+
SignAttestationManifestsResult,
39+
SignProvenanceBlobsOpts,
40+
SignProvenanceBlobsResult,
41+
TSASERVER_URL,
42+
VerifySignedArtifactsOpts,
43+
VerifySignedArtifactsResult,
44+
VerifySignedManifestsOpts,
45+
VerifySignedManifestsResult
46+
} from '../types/sigstore/sigstore';
7547

7648
export interface SigstoreOpts {
7749
cosign?: Cosign;
@@ -138,13 +110,13 @@ export class Sigstore {
138110
throw new Error(`Cosign sign command failed with exit code ${execRes.exitCode}`);
139111
}
140112
}
141-
const attest = Sigstore.toAttestation(bundleFromJSON(signResult.bundle));
142-
if (attest.tlogID) {
143-
core.info(`Uploaded to Rekor transparency log: ${SEARCH_URL}?logIndex=${attest.tlogID}`);
113+
const parsedBundle = Sigstore.parseBundle(bundleFromJSON(signResult.bundle));
114+
if (parsedBundle.tlogID) {
115+
core.info(`Uploaded to Rekor transparency log: ${SEARCH_URL}?logIndex=${parsedBundle.tlogID}`);
144116
}
145117
core.info(`Signature manifest pushed: https://oci.dag.dev/?referrers=${attestationRef}`);
146118
result[attestationRef] = {
147-
...attest,
119+
...parsedBundle,
148120
imageName: imageName
149121
};
150122
});
@@ -242,28 +214,28 @@ export class Sigstore {
242214
core.warning(`No subjects found in provenance ${p}, skip signing.`);
243215
return;
244216
}
245-
const bundle = await signPayload(
217+
const bundle = await Sigstore.signPayload(
246218
{
247-
body: blob,
219+
data: blob,
248220
type: INTOTO_MEDIATYPE_PAYLOAD
249221
},
250222
endpoints
251223
);
252-
const attest = Sigstore.toAttestation(bundle);
224+
const parsedBundle = Sigstore.parseBundle(bundle);
253225
core.info(`Provenance blob signed for:`);
254226
for (const subject of subjects) {
255227
const [digestAlg, digestValue] = Object.entries(subject.digest)[0] || [];
256228
core.info(` - ${subject.name} (${digestAlg}:${digestValue})`);
257229
}
258-
if (attest.tlogID) {
259-
core.info(`Attestation signature uploaded to Rekor transparency log: ${SEARCH_URL}?logIndex=${attest.tlogID}`);
230+
if (parsedBundle.tlogID) {
231+
core.info(`Attestation signature uploaded to Rekor transparency log: ${SEARCH_URL}?logIndex=${parsedBundle.tlogID}`);
260232
}
261233
core.info(`Writing Sigstore bundle to: ${bundlePath}`);
262-
fs.writeFileSync(bundlePath, JSON.stringify(attest.bundle, null, 2), {
234+
fs.writeFileSync(bundlePath, JSON.stringify(parsedBundle.payload, null, 2), {
263235
encoding: 'utf-8'
264236
});
265237
result[p] = {
266-
...attest,
238+
...parsedBundle,
267239
bundlePath: bundlePath,
268240
subjects: subjects
269241
};
@@ -359,8 +331,41 @@ export class Sigstore {
359331
}));
360332
}
361333

362-
// https://github.com/actions/toolkit/blob/d3ab50471b4ff1d1274dffb90ef9c5d9949b4886/packages/attest/src/attest.ts#L90
363-
private static toAttestation(bundle: Bundle): Attestation {
334+
private static async signPayload(artifact: Artifact, endpoints: Endpoints, timeout?: number, retries?: number): Promise<Bundle> {
335+
const witnesses: Witness[] = [];
336+
337+
const signer = new FulcioSigner({
338+
identityProvider: new CIContextProvider('sigstore'),
339+
fulcioBaseURL: endpoints.fulcioURL,
340+
timeout: timeout,
341+
retry: retries
342+
});
343+
344+
if (endpoints.rekorURL) {
345+
witnesses.push(
346+
new RekorWitness({
347+
rekorBaseURL: endpoints.rekorURL,
348+
fetchOnConflict: true,
349+
timeout: timeout,
350+
retry: retries
351+
})
352+
);
353+
}
354+
355+
if (endpoints.tsaServerURL) {
356+
witnesses.push(
357+
new TSAWitness({
358+
tsaBaseURL: endpoints.tsaServerURL,
359+
timeout: timeout,
360+
retry: retries
361+
})
362+
);
363+
}
364+
365+
return new DSSEBundleBuilder({signer, witnesses}).create(artifact);
366+
}
367+
368+
private static parseBundle(bundle: Bundle): ParsedBundle {
364369
let certBytes: Buffer;
365370
switch (bundle.verificationMaterial.content.$case) {
366371
case 'x509CertificateChain':
@@ -375,12 +380,12 @@ export class Sigstore {
375380

376381
const signingCert = new X509Certificate(certBytes);
377382

378-
// Collect transparency log ID if available
383+
// collect transparency log ID if available
379384
const tlogEntries = bundle.verificationMaterial.tlogEntries;
380385
const tlogID = tlogEntries.length > 0 ? tlogEntries[0].logIndex : undefined;
381386

382387
return {
383-
bundle: bundleToJSON(bundle),
388+
payload: bundleToJSON(bundle),
384389
certificate: signingCert.toString(),
385390
tlogID: tlogID
386391
};

src/types/sigstore/sigstore.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,63 @@
1414
* limitations under the License.
1515
*/
1616

17+
import type {SerializedBundle} from '@sigstore/bundle';
18+
19+
import {Subject} from '../intoto/intoto';
20+
1721
export const FULCIO_URL = 'https://fulcio.sigstore.dev';
1822
export const REKOR_URL = 'https://rekor.sigstore.dev';
1923
export const TSASERVER_URL = 'https://timestamp.sigstore.dev';
2024
export const SEARCH_URL = 'https://search.sigstore.dev';
25+
26+
export interface Endpoints {
27+
fulcioURL: string;
28+
rekorURL?: string;
29+
tsaServerURL?: string;
30+
}
31+
32+
export interface ParsedBundle {
33+
payload: SerializedBundle;
34+
certificate: string;
35+
tlogID?: string;
36+
}
37+
38+
export interface SignAttestationManifestsOpts {
39+
imageNames: Array<string>;
40+
imageDigest: string;
41+
noTransparencyLog?: boolean;
42+
}
43+
44+
export interface SignAttestationManifestsResult extends ParsedBundle {
45+
imageName: string;
46+
}
47+
48+
export interface VerifySignedManifestsOpts {
49+
certificateIdentityRegexp: string;
50+
retries?: number;
51+
}
52+
53+
export interface VerifySignedManifestsResult {
54+
cosignArgs: Array<string>;
55+
signatureManifestDigest: string;
56+
}
57+
58+
export interface SignProvenanceBlobsOpts {
59+
localExportDir: string;
60+
name?: string;
61+
noTransparencyLog?: boolean;
62+
}
63+
64+
export interface SignProvenanceBlobsResult extends ParsedBundle {
65+
bundlePath: string;
66+
subjects: Array<Subject>;
67+
}
68+
69+
export interface VerifySignedArtifactsOpts {
70+
certificateIdentityRegexp: string;
71+
}
72+
73+
export interface VerifySignedArtifactsResult {
74+
bundlePath: string;
75+
cosignArgs: Array<string>;
76+
}

0 commit comments

Comments
 (0)