diff --git a/.github/linters/tsconfig.json b/.github/linters/tsconfig.json index cc87883..a34cf90 100644 --- a/.github/linters/tsconfig.json +++ b/.github/linters/tsconfig.json @@ -5,8 +5,5 @@ "noEmit": true }, "include": ["../../__tests__/**/*", "../../src/**/*"], - "exclude": ["../../dist", "../../node_modules", "../../coverage", "*.json"], - "references": [ - { "path": "./packages/attest" } - ] + "exclude": ["../../dist", "../../node_modules", "../../coverage", "*.json"] } diff --git a/.github/workflows/check-dist.yml b/.github/workflows/check-dist.yml index 74ba562..5798200 100644 --- a/.github/workflows/check-dist.yml +++ b/.github/workflows/check-dist.yml @@ -39,9 +39,6 @@ jobs: id: install run: npm ci - - name: Build @actions/attest - run: npm run build --workspace packages/attest - - name: Build dist/ Directory id: build run: npm run bundle diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4298f19..f140e14 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -32,9 +32,6 @@ jobs: id: npm-ci run: npm ci - - name: Build @actions/attest - run: npm run build --workspace packages/attest - - name: Check Format id: npm-format-check run: npm run format:check diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml index 98bc57a..044e527 100644 --- a/.github/workflows/linter.yml +++ b/.github/workflows/linter.yml @@ -32,9 +32,6 @@ jobs: id: install run: npm ci - - name: Build @actions/attest - run: npm run build --workspace packages/attest - - name: Lint Codebase id: super-linter uses: super-linter/super-linter/slim@v5 diff --git a/.gitignore b/.gitignore index 61cf7ba..f642761 100644 --- a/.gitignore +++ b/.gitignore @@ -100,5 +100,4 @@ __tests__/runner/* # IDE files .idea .vscode -*.code-workspace -packages/attest/dist +*.code-workspace \ No newline at end of file diff --git a/package.json b/package.json index da0657c..8ee744c 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,6 @@ }, "scripts": { "bundle": "npm run format:write && npm run package", - "prepackage": "npm run build --workspace packages/attest", "ci-test": "jest", "coverage": "make-coverage-badge --output-path ./badges/coverage.svg", "format:write": "prettier --write **/*.ts", diff --git a/packages/attest/jest.config.js b/packages/attest/jest.config.js deleted file mode 100644 index c149fcd..0000000 --- a/packages/attest/jest.config.js +++ /dev/null @@ -1,5 +0,0 @@ -module.exports = { - preset: 'ts-jest', - testEnvironment: 'node', - testMatch: ['**/__tests__/*.test.ts'], -}; diff --git a/packages/attest/package.json b/packages/attest/package.json deleted file mode 100644 index 0e155ae..0000000 --- a/packages/attest/package.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "name": "@actions/attest", - "version": "0.0.0", - "description": "Base library for Sigstore", - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "clean": "shx rm -rf dist *.tsbuildinfo", - "build": "tsc --build", - "test": "jest" - }, - "files": [ - "dist" - ], - "author": "bdehamer@github.com", - "license": "Apache-2.0", - "repository": { - "type": "git", - "url": "git+https://github.com/github/attest-js.git" - }, - "bugs": { - "url": "https://github.com/github/attest-js/issues" - }, - "homepage": "https://github.com/github/attest-js/tree/main/packages/core#readme", - "publishConfig": { - "provenance": true - }, - "devDependencies": { - "@sigstore/mock": "^0.6.4", - "@total-typescript/shoehorn": "^0.1.1", - "@tsconfig/node18": "^18.2.2", - "@types/make-fetch-happen": "^10.0.4", - "nock": "^13.5.1" - }, - "dependencies": { - "@actions/github": "^6.0.0", - "@sigstore/bundle": "^2.2.0", - "@sigstore/sign": "^2.2.3", - "make-fetch-happen": "^13.0.0" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } -} diff --git a/packages/attest/src/__tests__/__snapshots__/provenance.test.ts.snap b/packages/attest/src/__tests__/__snapshots__/provenance.test.ts.snap deleted file mode 100644 index bac77b9..0000000 --- a/packages/attest/src/__tests__/__snapshots__/provenance.test.ts.snap +++ /dev/null @@ -1,51 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`generateProvenance returns a provenance hydrated from env vars 1`] = ` -{ - "_type": "https://in-toto.io/Statement/v1", - "predicate": { - "buildDefinition": { - "buildType": "https://slsa-framework.github.io/github-actions-buildtypes/workflow/v1", - "externalParameters": { - "workflow": { - "path": ".github/workflows/main.yml", - "ref": "main", - "repository": "https://github.com/owner/repo", - }, - }, - "internalParameters": { - "github": { - "event_name": "push", - "repository_id": "repo-id", - "repository_owner_id": "owner-id", - }, - }, - "resolvedDependencies": [ - { - "digest": { - "gitCommit": "babca52ab0c93ae16539e5923cb0d7403b9a093b", - }, - "uri": "git+https://github.com/owner/repo@refs/heads/main", - }, - ], - }, - "runDetails": { - "builder": { - "id": "https://github.com/actions/runner/github-hosted", - }, - "metadata": { - "invocationId": "https://github.com/owner/repo/actions/runs/run-id/attempts/run-attempt", - }, - }, - }, - "predicateType": "https://slsa.dev/provenance/v1", - "subject": [ - { - "digest": { - "sha256": "7d070f6b64d9bcc530fe99cc21eaaa4b3c364e0b2d367d7735671fa202a03b32", - }, - "name": "subjecty", - }, - ], -} -`; diff --git a/packages/attest/src/__tests__/attest.test.ts b/packages/attest/src/__tests__/attest.test.ts deleted file mode 100644 index a1d82ed..0000000 --- a/packages/attest/src/__tests__/attest.test.ts +++ /dev/null @@ -1,154 +0,0 @@ -import { mockFulcio, mockRekor, mockTSA } from '@sigstore/mock' -import nock from 'nock' -import { attestProvenance } from '../attest' - -describe('attest functions', () => { - // Capture original environment variables and GitHub context so we can restore - // them after each test - const originalEnv = process.env - - // Fake an OIDC token - const subject = 'foo@bar.com' - const oidcPayload = { sub: subject, iss: '' } - const oidcToken = `.${Buffer.from(JSON.stringify(oidcPayload)).toString( - 'base64' - )}.}` - - const tokenURL = 'https://token.url' - const fulcioURL = 'https://fulcio.url' - const rekorURL = 'https://rekor.url' - const tsaServerURL = 'https://tsa.url' - const attestationID = '1234567890' - - beforeEach(async () => { - jest.clearAllMocks() - - nock(tokenURL) - .get('/') - .query({ audience: 'sigstore' }) - .reply(200, { value: oidcToken }) - - // Mock Fulcio endpoint - await mockFulcio({ baseURL: fulcioURL, strict: false }) - - // Set-up GHA environment variables - process.env = { - ...originalEnv, - ACTIONS_ID_TOKEN_REQUEST_URL: tokenURL, - ACTIONS_ID_TOKEN_REQUEST_TOKEN: 'token' - } - }) - - afterEach(() => { - // Restore the original environment - process.env = originalEnv - }) - - describe('#attestProvenance', () => { - const env = { - GITHUB_REPOSITORY: 'owner/repo', - GITHUB_REF: 'refs/heads/main', - GITHUB_SHA: 'babca52ab0c93ae16539e5923cb0d7403b9a093b', - GITHUB_WORKFLOW_REF: 'owner/repo/.github/workflows/main.yml@main', - GITHUB_SERVER_URL: 'https://github.com', - GITHUB_EVENT_NAME: 'push', - GITHUB_REPOSITORY_ID: 'repo-id', - GITHUB_REPOSITORY_OWNER_ID: 'owner-id', - GITHUB_RUN_ID: 'run-id', - GITHUB_RUN_ATTEMPT: 'run-attempt', - RUNNER_ENVIRONMENT: 'github-hosted' - } - - beforeEach(() => { - process.env = { ...process.env, ...env } - }) - - describe('when the timestamp authority URL is set', () => { - beforeEach(async () => { - await mockTSA({ baseURL: tsaServerURL }) - - // Mock GH attestations API - nock('https://api.github.com') - .post(/^\/repos\/.*\/.*\/attestations$/) - .reply(201, { id: attestationID }) - }) - - it('attests provenance', async () => { - const attestation = await attestProvenance({ - subjectName: 'subjective', - subjectDigest: { - sha256: - '7d070f6b64d9bcc530fe99cc21eaaa4b3c364e0b2d367d7735671fa202a03b32' - }, - token: 'token', - fulcioURL, - tsaServerURL - }) - - expect(attestation).toBeDefined() - expect(attestation.bundle).toBeDefined() - expect(attestation.certificate).toMatch(/-----BEGIN CERTIFICATE-----/) - expect(attestation.tlogID).toBeUndefined() - expect(attestation.attestationID).toBe(attestationID) - }) - }) - - describe('when the transparency log URL is set', () => { - beforeEach(async () => { - await mockRekor({ baseURL: rekorURL }) - - // Mock GH attestations API - nock('https://api.github.com') - .post(/^\/repos\/.*\/.*\/attestations$/) - .reply(201, { id: attestationID }) - }) - - it('attests provenance', async () => { - const attestation = await attestProvenance({ - subjectName: 'subjective', - subjectDigest: { - sha256: - '7d070f6b64d9bcc530fe99cc21eaaa4b3c364e0b2d367d7735671fa202a03b32' - }, - token: 'token', - fulcioURL, - rekorURL - }) - - expect(attestation).toBeDefined() - expect(attestation.bundle).toBeDefined() - expect(attestation.certificate).toMatch(/-----BEGIN CERTIFICATE-----/) - expect(attestation.tlogID).toBeDefined() - expect(attestation.attestationID).toBe(attestationID) - }) - }) - - describe('when skipWrite is set to true', () => { - beforeEach(async () => { - await mockRekor({ baseURL: rekorURL }) - await mockTSA({ baseURL: tsaServerURL }) - }) - - it('attests provenance', async () => { - const attestation = await attestProvenance({ - subjectName: 'subjective', - subjectDigest: { - sha256: - '7d070f6b64d9bcc530fe99cc21eaaa4b3c364e0b2d367d7735671fa202a03b32' - }, - token: 'token', - fulcioURL, - rekorURL, - tsaServerURL, - skipWrite: true - }) - - expect(attestation).toBeDefined() - expect(attestation.bundle).toBeDefined() - expect(attestation.certificate).toMatch(/-----BEGIN CERTIFICATE-----/) - expect(attestation.tlogID).toBeDefined() - expect(attestation.attestationID).toBeUndefined() - }) - }) - }) -}) diff --git a/packages/attest/src/__tests__/index.test.ts b/packages/attest/src/__tests__/index.test.ts deleted file mode 100644 index 8b883e3..0000000 --- a/packages/attest/src/__tests__/index.test.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { fromPartial } from '@total-typescript/shoehorn' -import { - AttestOptions, - AttestProvenanceOptions, - Attestation, - Predicate, - Subject, - attest, - attestProvenance -} from '..' - -it('exports functions', () => { - expect(attestProvenance).toBeInstanceOf(Function) - expect(attest).toBeInstanceOf(Function) -}) - -it('exports types', async () => { - const attestation: Attestation = fromPartial({}) - expect(attestation).toBeDefined() - - const attestOptions: AttestOptions = fromPartial({}) - expect(attestOptions).toBeDefined() - - const attestProvenanceOptions: AttestProvenanceOptions = fromPartial({}) - expect(attestProvenanceOptions).toBeDefined() - - const subject: Subject = fromPartial({}) - expect(subject).toBeDefined() - - const predicate: Predicate = fromPartial({}) - expect(predicate).toBeDefined() -}) diff --git a/packages/attest/src/__tests__/provenance.test.ts b/packages/attest/src/__tests__/provenance.test.ts deleted file mode 100644 index fb32518..0000000 --- a/packages/attest/src/__tests__/provenance.test.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { generateProvenance } from '../provenance' -import type { Subject } from '../shared.types' - -describe('generateProvenance', () => { - const subject: Subject = { - name: 'subjecty', - digest: { - sha256: '7d070f6b64d9bcc530fe99cc21eaaa4b3c364e0b2d367d7735671fa202a03b32' - } - } - - const env = { - GITHUB_REPOSITORY: 'owner/repo', - GITHUB_REF: 'refs/heads/main', - GITHUB_SHA: 'babca52ab0c93ae16539e5923cb0d7403b9a093b', - GITHUB_WORKFLOW_REF: 'owner/repo/.github/workflows/main.yml@main', - GITHUB_SERVER_URL: 'https://github.com', - GITHUB_EVENT_NAME: 'push', - GITHUB_REPOSITORY_ID: 'repo-id', - GITHUB_REPOSITORY_OWNER_ID: 'owner-id', - GITHUB_RUN_ID: 'run-id', - GITHUB_RUN_ATTEMPT: 'run-attempt', - RUNNER_ENVIRONMENT: 'github-hosted' - } - - it('returns a provenance hydrated from env vars', () => { - const provenance = generateProvenance(subject, env) - expect(provenance).toMatchSnapshot() - }) -}) diff --git a/packages/attest/src/__tests__/sign.test.ts b/packages/attest/src/__tests__/sign.test.ts deleted file mode 100644 index 0d2ce23..0000000 --- a/packages/attest/src/__tests__/sign.test.ts +++ /dev/null @@ -1,105 +0,0 @@ -import { mockFulcio, mockRekor, mockTSA } from '@sigstore/mock' -import nock from 'nock' -import { Payload, signPayload } from '../sign' - -describe('signProvenance', () => { - const originalEnv = process.env - - // Fake an OIDC token - const subject = 'foo@bar.com' - const oidcPayload = { sub: subject, iss: '' } - const oidcToken = `.${Buffer.from(JSON.stringify(oidcPayload)).toString( - 'base64' - )}.}` - - // Dummy provenance to be signed - const provenance = { - _type: 'https://in-toto.io/Statement/v1', - subject: { - name: 'subjective', - digest: { - sha256: - '7d070f6b64d9bcc530fe99cc21eaaa4b3c364e0b2d367d7735671fa202a03b32' - } - } - } - - const payload: Payload = { - body: Buffer.from(JSON.stringify(provenance)), - type: 'application/vnd.in-toto+json' - } - - const fulcioURL = 'https://fulcio.url' - const rekorURL = 'https://rekor.url' - const tsaServerURL = 'https://tsa.url' - - beforeEach(() => { - // Mock OIDC token endpoint - const tokenURL = 'https://token.url' - - process.env = { - ...originalEnv, - ACTIONS_ID_TOKEN_REQUEST_URL: tokenURL, - ACTIONS_ID_TOKEN_REQUEST_TOKEN: 'token' - } - - nock(tokenURL) - .get('/') - .query({ audience: 'sigstore' }) - .reply(200, { value: oidcToken }) - }) - - afterEach(() => { - process.env = originalEnv - }) - - describe('when visibility is public', () => { - beforeEach(async () => { - await mockFulcio({ baseURL: fulcioURL, strict: false }) - await mockRekor({ baseURL: rekorURL }) - }) - - it('returns a bundle', async () => { - const att = await signPayload(payload, { fulcioURL, rekorURL }) - - expect(att).toBeDefined() - expect(att.mediaType).toEqual( - 'application/vnd.dev.sigstore.bundle+json;version=0.2' - ) - - expect(att.content.$case).toEqual('dsseEnvelope') - expect(att.verificationMaterial.content.$case).toEqual( - 'x509CertificateChain' - ) - expect(att.verificationMaterial.tlogEntries).toHaveLength(1) - expect( - att.verificationMaterial.timestampVerificationData?.rfc3161Timestamps - ).toHaveLength(0) - }) - }) - - describe('when visibility is private', () => { - beforeEach(async () => { - await mockFulcio({ baseURL: fulcioURL, strict: false }) - await mockTSA({ baseURL: tsaServerURL }) - }) - - it('returns a bundle', async () => { - const att = await signPayload(payload, { fulcioURL, tsaServerURL }) - - expect(att).toBeDefined() - expect(att.mediaType).toEqual( - 'application/vnd.dev.sigstore.bundle+json;version=0.2' - ) - - expect(att.content.$case).toEqual('dsseEnvelope') - expect(att.verificationMaterial.content.$case).toEqual( - 'x509CertificateChain' - ) - expect(att.verificationMaterial.tlogEntries).toHaveLength(0) - expect( - att.verificationMaterial.timestampVerificationData?.rfc3161Timestamps - ).toHaveLength(1) - }) - }) -}) diff --git a/packages/attest/src/__tests__/store.test.ts b/packages/attest/src/__tests__/store.test.ts deleted file mode 100644 index 19db163..0000000 --- a/packages/attest/src/__tests__/store.test.ts +++ /dev/null @@ -1,45 +0,0 @@ -import nock from 'nock' -import { writeAttestation } from '../store' - -describe('writeAttestation', () => { - const originalEnv = process.env - const attestation = { foo: 'bar ' } - const token = 'token' - - beforeEach(() => { - process.env = { - ...originalEnv, - GITHUB_REPOSITORY: 'foo/bar' - } - }) - - afterEach(() => { - process.env = originalEnv - }) - - describe('when the api call is successful', () => { - beforeEach(() => { - nock('https://api.github.com') - .matchHeader('authorization', `token ${token}`) - .post('/repos/foo/bar/attestations', { bundle: attestation }) - .reply(201, { id: '123' }) - }) - - it('persists the attestation', async () => { - await expect(writeAttestation(attestation, token)).resolves.toEqual('123') - }) - }) - - describe('when the api call fails', () => { - beforeEach(() => { - nock('https://api.github.com') - .matchHeader('authorization', `token ${token}`) - .post('/repos/foo/bar/attestations', { bundle: attestation }) - .reply(500, 'oops') - }) - - it('persists the attestation', async () => { - await expect(writeAttestation(attestation, token)).rejects.toThrow(/oops/) - }) - }) -}) diff --git a/packages/attest/src/attest.ts b/packages/attest/src/attest.ts deleted file mode 100644 index 1a3f445..0000000 --- a/packages/attest/src/attest.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { Bundle, bundleToJSON } from '@sigstore/bundle' -import { generateProvenancePredicate } from './provenance' -import { Payload, SignOptions, signPayload } from './sign' -import { writeAttestation } from './store' - -import assert from 'assert' -import { X509Certificate } from 'crypto' -import type { Attestation, Subject } from './shared.types' - -const INTOTO_PAYLOAD_TYPE = 'application/vnd.in-toto+json' -const INTOTO_STATEMENT_V1_TYPE = 'https://in-toto.io/Statement/v1' - -type AttestBaseOptions = SignOptions & { - subjectName: string - subjectDigest: Record - token: string - skipWrite?: boolean -} - -export type AttestOptions = AttestBaseOptions & { - predicateType: string - predicate: object -} - -export type AttestProvenanceOptions = AttestBaseOptions - -export async function attest(options: AttestOptions): Promise { - const subject: Subject = { - name: options.subjectName, - digest: options.subjectDigest - } - - const statement = { - _type: INTOTO_STATEMENT_V1_TYPE, - subject: [subject], - predicateType: options.predicateType, - predicate: options.predicate - } - - // Sign the provenance statement - const payload: Payload = { - body: Buffer.from(JSON.stringify(statement)), - type: INTOTO_PAYLOAD_TYPE - } - const bundle = await signPayload(payload, options) - - // Store the attestation - let attestationID: string | undefined - if (options.skipWrite !== true) { - attestationID = await writeAttestation(bundleToJSON(bundle), options.token) - } - - return toAttestation(bundle, attestationID) -} - -export async function attestProvenance( - options: AttestProvenanceOptions -): Promise { - const predicate = generateProvenancePredicate(process.env) - return attest({ - ...options, - predicateType: predicate.type, - predicate: predicate.params - }) -} - -function toAttestation(bundle: Bundle, attestationID?: string): Attestation { - // Extract the signing certificate from the bundle - assert( - bundle.verificationMaterial.content.$case === 'x509CertificateChain', - 'Bundle must contain an x509 certificate chain' - ) - - const signingCert = new X509Certificate( - bundle.verificationMaterial.content.x509CertificateChain.certificates[0].rawBytes - ) - - // Determine if we can provide a link to the transparency log - const tlogEntries = bundle.verificationMaterial.tlogEntries - const tlogID = tlogEntries.length > 0 ? tlogEntries[0].logIndex : undefined - - return { - bundle: bundleToJSON(bundle), - certificate: signingCert.toString(), - tlogID, - attestationID - } -} diff --git a/packages/attest/src/index.ts b/packages/attest/src/index.ts deleted file mode 100644 index 275a6f2..0000000 --- a/packages/attest/src/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -export { - AttestOptions, - AttestProvenanceOptions, - attest, - attestProvenance -} from './attest' -export { generateProvenancePredicate } from './provenance' -export { generateSBOMPredicate } from './sbom' - -export type { Attestation, Predicate, Subject, SBOM } from './shared.types' diff --git a/packages/attest/src/provenance.ts b/packages/attest/src/provenance.ts deleted file mode 100644 index bcc57dc..0000000 --- a/packages/attest/src/provenance.ts +++ /dev/null @@ -1,72 +0,0 @@ -import type { Predicate, Subject } from './shared.types' - -const INTOTO_STATEMENT_V1_TYPE = 'https://in-toto.io/Statement/v1' -export const SLSA_PREDICATE_V1_TYPE = 'https://slsa.dev/provenance/v1' - -const GITHUB_BUILDER_ID_PREFIX = 'https://github.com/actions/runner' -const GITHUB_BUILD_TYPE = - 'https://slsa-framework.github.io/github-actions-buildtypes/workflow/v1' - -export const generateProvenancePredicate = ( - env: NodeJS.ProcessEnv -): Predicate => { - const workflow = env.GITHUB_WORKFLOW_REF || /* istanbul ignore next */ '' - // Split just the path and ref from the workflow string. - // owner/repo/.github/workflows/main.yml@main => - // .github/workflows/main.yml, main - const [workflowPath, workflowRef] = workflow - .replace(`${env.GITHUB_REPOSITORY}/`, '') - .split('@') - - return { - type: SLSA_PREDICATE_V1_TYPE, - params: { - buildDefinition: { - buildType: GITHUB_BUILD_TYPE, - externalParameters: { - workflow: { - ref: workflowRef, - repository: `${env.GITHUB_SERVER_URL}/${env.GITHUB_REPOSITORY}`, - path: workflowPath - } - }, - internalParameters: { - github: { - event_name: env.GITHUB_EVENT_NAME, - repository_id: env.GITHUB_REPOSITORY_ID, - repository_owner_id: env.GITHUB_REPOSITORY_OWNER_ID - } - }, - resolvedDependencies: [ - { - uri: `git+${env.GITHUB_SERVER_URL}/${env.GITHUB_REPOSITORY}@${env.GITHUB_REF}`, - digest: { - gitCommit: env.GITHUB_SHA - } - } - ] - }, - runDetails: { - builder: { - id: `${GITHUB_BUILDER_ID_PREFIX}/${env.RUNNER_ENVIRONMENT}` - }, - metadata: { - invocationId: `${env.GITHUB_SERVER_URL}/${env.GITHUB_REPOSITORY}/actions/runs/${env.GITHUB_RUN_ID}/attempts/${env.GITHUB_RUN_ATTEMPT}` - } - } - } - } -} - -export const generateProvenance = ( - subject: Subject, - env: NodeJS.ProcessEnv -): object => { - const predicate = generateProvenancePredicate(env) - return { - _type: INTOTO_STATEMENT_V1_TYPE, - subject: [subject], - predicateType: predicate.type, - predicate: predicate.params - } -} diff --git a/packages/attest/src/sbom.ts b/packages/attest/src/sbom.ts deleted file mode 100644 index 5dc996f..0000000 --- a/packages/attest/src/sbom.ts +++ /dev/null @@ -1,34 +0,0 @@ -import type { SBOM, Predicate } from './shared.types' - -export const generateSBOMPredicate = (sbom: SBOM): Predicate => { - if (sbom.type === 'spdx') { - return generateSPDXIntoto(sbom.object) - } - if (sbom.type === 'cyclonedx') { - return generateCycloneDXIntoto(sbom.object) - } - throw new Error('Unsupported SBOM format') -} - -// ref: https://github.com/in-toto/attestation/blob/main/spec/predicates/spdx.md -const generateSPDXIntoto = (sbom: object): Predicate => { - const spdxVersion = (sbom as { spdxVersion?: string })?.['spdxVersion'] - if (!spdxVersion) { - throw new Error('Cannot find spdxVersion in the SBOM') - } - - const version = spdxVersion.split('-')[1] - - return { - type: `https://spdx.dev/Document/v${version}`, - params: sbom - } -} - -// ref: https://github.com/in-toto/attestation/blob/main/spec/predicates/cyclonedx.md -const generateCycloneDXIntoto = (sbom: object): Predicate => { - return { - type: 'https://cyclonedx.org/bom', - params: sbom - } -} diff --git a/packages/attest/src/shared.types.ts b/packages/attest/src/shared.types.ts deleted file mode 100644 index ee629fb..0000000 --- a/packages/attest/src/shared.types.ts +++ /dev/null @@ -1,22 +0,0 @@ -import type { SerializedBundle } from '@sigstore/bundle' -export type Subject = { - name: string - digest: Record -} - -export type Predicate = { - type: string - params: object -} - -export type Attestation = { - bundle: SerializedBundle - certificate: string - tlogID?: string - attestationID?: string -} - -export type SBOM = { - type: 'spdx' | 'cyclonedx' - object: object -} diff --git a/packages/attest/src/sign.ts b/packages/attest/src/sign.ts deleted file mode 100644 index 585bf4d..0000000 --- a/packages/attest/src/sign.ts +++ /dev/null @@ -1,82 +0,0 @@ -import { Bundle } from '@sigstore/bundle' -import { - BundleBuilder, - CIContextProvider, - DSSEBundleBuilder, - FulcioSigner, - IdentityProvider, - RekorWitness, - TSAWitness, - Witness -} from '@sigstore/sign' - -const OIDC_AUDIENCE = 'sigstore' -const DEFAULT_TIMEOUT = 10000 -const DEFAULT_RETRIES = 3 - -export type Payload = { - body: Buffer - type: string -} - -export type SignOptions = { - fulcioURL: string - rekorURL?: string - tsaServerURL?: string - identityProvider?: IdentityProvider - timeout?: number - retry?: number -} - -// Signs the provided payload with Sigstore. -export const signPayload = async ( - payload: Payload, - options: SignOptions -): Promise => { - const artifact = { - data: payload.body, - type: payload.type - } - - // Sign the artifact and build the bundle - return initBundleBuilder(options).create(artifact) -} - -// Assembles the Sigstore bundle builder with the appropriate options -const initBundleBuilder = (opts: SignOptions): BundleBuilder => { - const identityProvider = - opts.identityProvider || new CIContextProvider(OIDC_AUDIENCE) - const timeout = opts.timeout || DEFAULT_TIMEOUT - const retry = opts.retry || DEFAULT_RETRIES - const witnesses: Witness[] = [] - - const signer = new FulcioSigner({ - identityProvider: identityProvider, - fulcioBaseURL: opts.fulcioURL, - timeout: timeout, - retry: retry - }) - - if (opts.rekorURL) { - witnesses.push( - new RekorWitness({ - rekorBaseURL: opts.rekorURL, - entryType: 'dsse', - timeout: timeout, - retry: retry - }) - ) - } - - if (opts.tsaServerURL) { - witnesses.push( - new TSAWitness({ - tsaBaseURL: opts.tsaServerURL, - timeout: timeout, - retry: retry - }) - ) - } - - return new DSSEBundleBuilder({ signer, witnesses }) -} diff --git a/packages/attest/src/store.ts b/packages/attest/src/store.ts deleted file mode 100644 index f6d4148..0000000 --- a/packages/attest/src/store.ts +++ /dev/null @@ -1,27 +0,0 @@ -import * as github from '@actions/github' -import fetch from 'make-fetch-happen' - -const CREATE_ATTESTATION_REQUEST = 'POST /repos/{owner}/{repo}/attestations' - -// Upload the attestation to the repository's attestations endpoint. Returns the -// ID of the uploaded attestation. -export const writeAttestation = async ( - attestation: unknown, - token: string -): Promise => { - const octokit = github.getOctokit(token, { request: { fetch } }) - - try { - const response = await octokit.request(CREATE_ATTESTATION_REQUEST, { - owner: github.context.repo.owner, - repo: github.context.repo.repo, - data: { bundle: attestation } - }) - - return response.data?.id - } catch (err) { - /* istanbul ignore next */ - const message = err instanceof Error ? err.message : err - throw new Error(`Failed to persist attestation: ${message}`) - } -} diff --git a/packages/attest/tsconfig.json b/packages/attest/tsconfig.json deleted file mode 100644 index 81e3154..0000000 --- a/packages/attest/tsconfig.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "extends": "@tsconfig/node18/tsconfig.json", - "compilerOptions": { - "composite": true, - "rootDir": "src", - "outDir": "dist", - "declaration": true, - "noFallthroughCasesInSwitch": true, - "noImplicitOverride": true, - "allowUnreachableCode": false, - "noImplicitReturns": true, - "noUnusedParameters": true - }, - "exclude": [ - "./dist", - "**/__tests__" - ] -} diff --git a/src/sbom.ts b/src/sbom.ts index f7b78cb..ff4b8bc 100644 --- a/src/sbom.ts +++ b/src/sbom.ts @@ -1,5 +1,4 @@ import fs from 'fs' -import { Predicate } from '@actions/attest' import * as path from 'path' export type SBOM = { @@ -7,6 +6,11 @@ export type SBOM = { object: object } +type Predicate = { + type: string + params: object +} + export async function parseSBOMFromPath(filePath: string): Promise { // Read the file content const fileContent = await fs.promises.readFile(filePath, 'utf8') diff --git a/tsconfig.json b/tsconfig.json index 87711a7..79d47e3 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -15,8 +15,5 @@ "skipLibCheck": true, "newLine": "lf" }, - "include": [ "/src/**/*" ], - "references": [ - { "path": "./packages/attest" } - ] + "include": [ "/src/**/*" ] } \ No newline at end of file