reference @actions/attest package (#10)

Signed-off-by: Brian DeHamer <bdehamer@github.com>
This commit is contained in:
Brian DeHamer
2024-02-26 14:49:08 -08:00
committed by GitHub
parent 96a4c7ef30
commit d6e451253f
27 changed files with 1396 additions and 3168 deletions

View File

@@ -2,4 +2,3 @@ lib/
dist/
node_modules/
coverage/
packages/

View File

@@ -41,9 +41,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

View File

@@ -34,9 +34,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

View File

@@ -36,9 +36,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@v6

2
.gitignore vendored
View File

@@ -101,5 +101,3 @@ __tests__/runner/*
.idea
.vscode
*.code-workspace
packages/attest/dist

883
dist/index.js generated vendored

File diff suppressed because it is too large Load Diff

12
dist/licenses.txt generated vendored
View File

@@ -1,5 +1,15 @@
@actions/attest
Apache-2.0
MIT
The MIT License (MIT)
Copyright 2024 GitHub
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@actions/core
MIT

2821
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -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",
@@ -67,6 +66,7 @@
]
},
"dependencies": {
"@actions/attest": "^1.0.0",
"@actions/core": "^1.10.1"
},
"devDependencies": {
@@ -86,8 +86,5 @@
"prettier-eslint": "^16.3.0",
"ts-jest": "^29.1.2",
"typescript": "^5.3.3"
},
"workspaces": [
"./packages/*"
]
}
}

View File

@@ -1,5 +0,0 @@
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
testMatch: ['**/__tests__/*.test.ts'],
};

View File

@@ -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"
}
}

View File

@@ -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",
},
],
}
`;

View File

@@ -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()
})
})
})
})

View File

@@ -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()
})

View File

@@ -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()
})
})

View File

@@ -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)
})
})
})

View File

@@ -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/)
})
})
})

View File

@@ -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<string, string>
token: string
skipWrite?: boolean
}
export type AttestOptions = AttestBaseOptions & {
predicateType: string
predicate: object
}
export type AttestProvenanceOptions = AttestBaseOptions
export async function attest(options: AttestOptions): Promise<Attestation> {
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<Attestation> {
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
}
}

View File

@@ -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'

View File

@@ -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
}
}

View File

@@ -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
}
}

View File

@@ -1,22 +0,0 @@
import type { SerializedBundle } from '@sigstore/bundle'
export type Subject = {
name: string
digest: Record<string, string>
}
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
}

View File

@@ -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<Bundle> => {
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 })
}

View File

@@ -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<string> => {
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}`)
}
}

View File

@@ -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__"
]
}

View File

@@ -1,4 +1,4 @@
import { generateProvenancePredicate } from '@actions/attest'
import { buildSLSAProvenancePredicate } from '@actions/attest'
import * as core from '@actions/core'
/**
@@ -8,7 +8,7 @@ import * as core from '@actions/core'
export async function run(): Promise<void> {
try {
// Calculate subject from inputs and generate provenance
const predicate = generateProvenancePredicate(process.env)
const predicate = buildSLSAProvenancePredicate(process.env)
core.setOutput('predicate', predicate.params)
core.setOutput('predicate-type', predicate.type)

View File

@@ -15,9 +15,5 @@
"skipLibCheck": true,
"newLine": "lf"
},
"include": [ "/src/*" ],
"exclude": ["./dist", "./node_modules", "./__tests__", "./coverage"],
"references": [
{ "path": "./packages/attest" }
]
"exclude": ["./dist", "./node_modules", "./__tests__", "./coverage"]
}