sigstore: remove @actions/attest dependency

Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com>
This commit is contained in:
CrazyMax
2025-11-05 11:06:17 +01:00
parent 5c04d3904d
commit 85dfc7a573
5 changed files with 127 additions and 417 deletions

View File

@@ -18,12 +18,9 @@ import {X509Certificate} from 'crypto';
import fs from 'fs';
import path from 'path';
import {Endpoints} from '@actions/attest/lib/endpoints';
import * as core from '@actions/core';
import {signPayload} from '@actions/attest/lib/sign';
import {bundleFromJSON, bundleToJSON} from '@sigstore/bundle';
import {Attestation} from '@actions/attest';
import {Bundle} from '@sigstore/sign';
import {Artifact, Bundle, CIContextProvider, DSSEBundleBuilder, FulcioSigner, RekorWitness, TSAWitness, Witness} from '@sigstore/sign';
import {Cosign} from '../cosign/cosign';
import {Exec} from '../exec';
@@ -31,47 +28,22 @@ import {GitHub} from '../github';
import {ImageTools} from '../buildx/imagetools';
import {MEDIATYPE_PAYLOAD as INTOTO_MEDIATYPE_PAYLOAD, Subject} from '../types/intoto/intoto';
import {FULCIO_URL, REKOR_URL, SEARCH_URL, TSASERVER_URL} from '../types/sigstore/sigstore';
export interface SignAttestationManifestsOpts {
imageNames: Array<string>;
imageDigest: string;
noTransparencyLog?: boolean;
}
export interface SignAttestationManifestsResult extends Attestation {
imageName: string;
}
export interface VerifySignedManifestsOpts {
certificateIdentityRegexp: string;
retries?: number;
}
export interface VerifySignedManifestsResult {
cosignArgs: Array<string>;
signatureManifestDigest: string;
}
export interface SignProvenanceBlobsOpts {
localExportDir: string;
name?: string;
noTransparencyLog?: boolean;
}
export interface SignProvenanceBlobsResult extends Attestation {
bundlePath: string;
subjects: Array<Subject>;
}
export interface VerifySignedArtifactsOpts {
certificateIdentityRegexp: string;
}
export interface VerifySignedArtifactsResult {
bundlePath: string;
cosignArgs: Array<string>;
}
import {
Endpoints,
FULCIO_URL,
ParsedBundle,
REKOR_URL,
SEARCH_URL,
SignAttestationManifestsOpts,
SignAttestationManifestsResult,
SignProvenanceBlobsOpts,
SignProvenanceBlobsResult,
TSASERVER_URL,
VerifySignedArtifactsOpts,
VerifySignedArtifactsResult,
VerifySignedManifestsOpts,
VerifySignedManifestsResult
} from '../types/sigstore/sigstore';
export interface SigstoreOpts {
cosign?: Cosign;
@@ -138,13 +110,13 @@ export class Sigstore {
throw new Error(`Cosign sign command failed with exit code ${execRes.exitCode}`);
}
}
const attest = Sigstore.toAttestation(bundleFromJSON(signResult.bundle));
if (attest.tlogID) {
core.info(`Uploaded to Rekor transparency log: ${SEARCH_URL}?logIndex=${attest.tlogID}`);
const parsedBundle = Sigstore.parseBundle(bundleFromJSON(signResult.bundle));
if (parsedBundle.tlogID) {
core.info(`Uploaded to Rekor transparency log: ${SEARCH_URL}?logIndex=${parsedBundle.tlogID}`);
}
core.info(`Signature manifest pushed: https://oci.dag.dev/?referrers=${attestationRef}`);
result[attestationRef] = {
...attest,
...parsedBundle,
imageName: imageName
};
});
@@ -242,28 +214,28 @@ export class Sigstore {
core.warning(`No subjects found in provenance ${p}, skip signing.`);
return;
}
const bundle = await signPayload(
const bundle = await Sigstore.signPayload(
{
body: blob,
data: blob,
type: INTOTO_MEDIATYPE_PAYLOAD
},
endpoints
);
const attest = Sigstore.toAttestation(bundle);
const parsedBundle = Sigstore.parseBundle(bundle);
core.info(`Provenance blob signed for:`);
for (const subject of subjects) {
const [digestAlg, digestValue] = Object.entries(subject.digest)[0] || [];
core.info(` - ${subject.name} (${digestAlg}:${digestValue})`);
}
if (attest.tlogID) {
core.info(`Attestation signature uploaded to Rekor transparency log: ${SEARCH_URL}?logIndex=${attest.tlogID}`);
if (parsedBundle.tlogID) {
core.info(`Attestation signature uploaded to Rekor transparency log: ${SEARCH_URL}?logIndex=${parsedBundle.tlogID}`);
}
core.info(`Writing Sigstore bundle to: ${bundlePath}`);
fs.writeFileSync(bundlePath, JSON.stringify(attest.bundle, null, 2), {
fs.writeFileSync(bundlePath, JSON.stringify(parsedBundle.payload, null, 2), {
encoding: 'utf-8'
});
result[p] = {
...attest,
...parsedBundle,
bundlePath: bundlePath,
subjects: subjects
};
@@ -359,8 +331,41 @@ export class Sigstore {
}));
}
// https://github.com/actions/toolkit/blob/d3ab50471b4ff1d1274dffb90ef9c5d9949b4886/packages/attest/src/attest.ts#L90
private static toAttestation(bundle: Bundle): Attestation {
private static async signPayload(artifact: Artifact, endpoints: Endpoints, timeout?: number, retries?: number): Promise<Bundle> {
const witnesses: Witness[] = [];
const signer = new FulcioSigner({
identityProvider: new CIContextProvider('sigstore'),
fulcioBaseURL: endpoints.fulcioURL,
timeout: timeout,
retry: retries
});
if (endpoints.rekorURL) {
witnesses.push(
new RekorWitness({
rekorBaseURL: endpoints.rekorURL,
fetchOnConflict: true,
timeout: timeout,
retry: retries
})
);
}
if (endpoints.tsaServerURL) {
witnesses.push(
new TSAWitness({
tsaBaseURL: endpoints.tsaServerURL,
timeout: timeout,
retry: retries
})
);
}
return new DSSEBundleBuilder({signer, witnesses}).create(artifact);
}
private static parseBundle(bundle: Bundle): ParsedBundle {
let certBytes: Buffer;
switch (bundle.verificationMaterial.content.$case) {
case 'x509CertificateChain':
@@ -375,12 +380,12 @@ export class Sigstore {
const signingCert = new X509Certificate(certBytes);
// Collect transparency log ID if available
// collect transparency log ID if available
const tlogEntries = bundle.verificationMaterial.tlogEntries;
const tlogID = tlogEntries.length > 0 ? tlogEntries[0].logIndex : undefined;
return {
bundle: bundleToJSON(bundle),
payload: bundleToJSON(bundle),
certificate: signingCert.toString(),
tlogID: tlogID
};

View File

@@ -14,7 +14,63 @@
* limitations under the License.
*/
import type {SerializedBundle} from '@sigstore/bundle';
import {Subject} from '../intoto/intoto';
export const FULCIO_URL = 'https://fulcio.sigstore.dev';
export const REKOR_URL = 'https://rekor.sigstore.dev';
export const TSASERVER_URL = 'https://timestamp.sigstore.dev';
export const SEARCH_URL = 'https://search.sigstore.dev';
export interface Endpoints {
fulcioURL: string;
rekorURL?: string;
tsaServerURL?: string;
}
export interface ParsedBundle {
payload: SerializedBundle;
certificate: string;
tlogID?: string;
}
export interface SignAttestationManifestsOpts {
imageNames: Array<string>;
imageDigest: string;
noTransparencyLog?: boolean;
}
export interface SignAttestationManifestsResult extends ParsedBundle {
imageName: string;
}
export interface VerifySignedManifestsOpts {
certificateIdentityRegexp: string;
retries?: number;
}
export interface VerifySignedManifestsResult {
cosignArgs: Array<string>;
signatureManifestDigest: string;
}
export interface SignProvenanceBlobsOpts {
localExportDir: string;
name?: string;
noTransparencyLog?: boolean;
}
export interface SignProvenanceBlobsResult extends ParsedBundle {
bundlePath: string;
subjects: Array<Subject>;
}
export interface VerifySignedArtifactsOpts {
certificateIdentityRegexp: string;
}
export interface VerifySignedArtifactsResult {
bundlePath: string;
cosignArgs: Array<string>;
}