sigstore: always set TSA server endpoint to provide trusted timestamping
Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com>
This commit is contained in:
@@ -14,7 +14,7 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {describe, expect, it} from '@jest/globals';
|
import {describe, expect, jest, it} from '@jest/globals';
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
|
|
||||||
@@ -24,6 +24,9 @@ const fixturesDir = path.join(__dirname, '..', '.fixtures');
|
|||||||
|
|
||||||
const maybe = process.env.GITHUB_ACTIONS && process.env.GITHUB_ACTIONS === 'true' && process.env.ACTIONS_ID_TOKEN_REQUEST_URL && process.env.ImageOS && process.env.ImageOS.startsWith('ubuntu') ? describe : describe.skip;
|
const maybe = process.env.GITHUB_ACTIONS && process.env.GITHUB_ACTIONS === 'true' && process.env.ACTIONS_ID_TOKEN_REQUEST_URL && process.env.ImageOS && process.env.ImageOS.startsWith('ubuntu') ? describe : describe.skip;
|
||||||
|
|
||||||
|
// needs current GitHub repo info
|
||||||
|
jest.unmock('@actions/github');
|
||||||
|
|
||||||
maybe('signProvenanceBlobs', () => {
|
maybe('signProvenanceBlobs', () => {
|
||||||
it('single platform', async () => {
|
it('single platform', async () => {
|
||||||
const sigstore = new Sigstore();
|
const sigstore = new Sigstore();
|
||||||
|
|||||||
@@ -18,18 +18,22 @@ import {X509Certificate} from 'crypto';
|
|||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
|
||||||
import {signingEndpoints, SigstoreInstance} from '@actions/attest/lib/endpoints';
|
import {Endpoints} from '@actions/attest/lib/endpoints';
|
||||||
import * as core from '@actions/core';
|
import * as core from '@actions/core';
|
||||||
import {signPayload} from '@actions/attest/lib/sign';
|
import {signPayload} from '@actions/attest/lib/sign';
|
||||||
import {bundleToJSON} from '@sigstore/bundle';
|
import {bundleToJSON} from '@sigstore/bundle';
|
||||||
import {Attestation} from '@actions/attest';
|
import {Attestation} from '@actions/attest';
|
||||||
import {Bundle} from '@sigstore/sign';
|
import {Bundle} from '@sigstore/sign';
|
||||||
|
|
||||||
import {Subject} from '../types/intoto/intoto';
|
import {GitHub} from '../github';
|
||||||
|
|
||||||
|
import {MEDIATYPE_PAYLOAD as intotoMediatypePayload, Subject} from '../types/intoto/intoto';
|
||||||
|
import {FULCIO_URL, REKOR_URL, SEARCH_URL, TSASERVER_URL} from '../types/sigstore/sigstore';
|
||||||
|
|
||||||
export interface SignProvenanceBlobsOpts {
|
export interface SignProvenanceBlobsOpts {
|
||||||
localExportDir: string;
|
localExportDir: string;
|
||||||
name?: string;
|
name?: string;
|
||||||
|
noTransparencyLog?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SignProvenanceBlobsResult extends Attestation {
|
export interface SignProvenanceBlobsResult extends Attestation {
|
||||||
@@ -38,9 +42,6 @@ export interface SignProvenanceBlobsResult extends Attestation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class Sigstore {
|
export class Sigstore {
|
||||||
private intotoPayloadType = 'application/vnd.in-toto+json';
|
|
||||||
private searchSigstoreURL = 'https://search.sigstore.dev';
|
|
||||||
|
|
||||||
public async signProvenanceBlobs(opts: SignProvenanceBlobsOpts): Promise<Record<string, SignProvenanceBlobsResult>> {
|
public async signProvenanceBlobs(opts: SignProvenanceBlobsOpts): Promise<Record<string, SignProvenanceBlobsResult>> {
|
||||||
const result: Record<string, SignProvenanceBlobsResult> = {};
|
const result: Record<string, SignProvenanceBlobsResult> = {};
|
||||||
try {
|
try {
|
||||||
@@ -48,8 +49,7 @@ export class Sigstore {
|
|||||||
throw new Error('missing "id-token" permission. Please add "permissions: id-token: write" to your workflow.');
|
throw new Error('missing "id-token" permission. Please add "permissions: id-token: write" to your workflow.');
|
||||||
}
|
}
|
||||||
|
|
||||||
const sigstoreInstance: SigstoreInstance = 'public-good';
|
const endpoints = this.signingEndpoints(opts);
|
||||||
const endpoints = signingEndpoints(sigstoreInstance);
|
|
||||||
core.info(`Using Sigstore signing endpoint: ${endpoints.fulcioURL}`);
|
core.info(`Using Sigstore signing endpoint: ${endpoints.fulcioURL}`);
|
||||||
|
|
||||||
const provenanceBlobs = Sigstore.getProvenanceBlobs(opts);
|
const provenanceBlobs = Sigstore.getProvenanceBlobs(opts);
|
||||||
@@ -65,7 +65,7 @@ export class Sigstore {
|
|||||||
const bundle = await signPayload(
|
const bundle = await signPayload(
|
||||||
{
|
{
|
||||||
body: blob,
|
body: blob,
|
||||||
type: this.intotoPayloadType
|
type: intotoMediatypePayload
|
||||||
},
|
},
|
||||||
endpoints
|
endpoints
|
||||||
);
|
);
|
||||||
@@ -76,7 +76,7 @@ export class Sigstore {
|
|||||||
core.info(` - ${subject.name} (${digestAlg}:${digestValue})`);
|
core.info(` - ${subject.name} (${digestAlg}:${digestValue})`);
|
||||||
}
|
}
|
||||||
if (attest.tlogID) {
|
if (attest.tlogID) {
|
||||||
core.info(`Attestation signature uploaded to Rekor transparency log: ${this.searchSigstoreURL}?logIndex=${attest.tlogID}`);
|
core.info(`Attestation signature uploaded to Rekor transparency log: ${SEARCH_URL}?logIndex=${attest.tlogID}`);
|
||||||
}
|
}
|
||||||
core.info(`Writing Sigstore bundle to: ${bundlePath}`);
|
core.info(`Writing Sigstore bundle to: ${bundlePath}`);
|
||||||
fs.writeFileSync(bundlePath, JSON.stringify(attest.bundle, null, 2), {
|
fs.writeFileSync(bundlePath, JSON.stringify(attest.bundle, null, 2), {
|
||||||
@@ -95,6 +95,16 @@ export class Sigstore {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private signingEndpoints(opts: SignProvenanceBlobsOpts): Endpoints {
|
||||||
|
const noTransparencyLog = opts.noTransparencyLog ?? GitHub.context.payload.repository?.private;
|
||||||
|
core.info(`Upload to transparency log: ${noTransparencyLog ? 'disabled' : 'enabled'}`);
|
||||||
|
return {
|
||||||
|
fulcioURL: FULCIO_URL,
|
||||||
|
rekorURL: noTransparencyLog ? undefined : REKOR_URL,
|
||||||
|
tsaServerURL: TSASERVER_URL
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private static getProvenanceBlobs(opts: SignProvenanceBlobsOpts): Record<string, Buffer> {
|
private static getProvenanceBlobs(opts: SignProvenanceBlobsOpts): Record<string, Buffer> {
|
||||||
// For single platform build
|
// For single platform build
|
||||||
const singleProvenance = path.join(opts.localExportDir, 'provenance.json');
|
const singleProvenance = path.join(opts.localExportDir, 'provenance.json');
|
||||||
|
|||||||
20
src/types/sigstore/sigstore.ts
Normal file
20
src/types/sigstore/sigstore.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2025 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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';
|
||||||
Reference in New Issue
Block a user