Skip to content

Commit

Permalink
Merge branch 'actions:main' into main
Browse files Browse the repository at this point in the history
  • Loading branch information
anhtai9710 authored Sep 2, 2024
2 parents e7711ef + 6c4e082 commit b9aebb9
Show file tree
Hide file tree
Showing 29 changed files with 242 additions and 108 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/audit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ jobs:
run: npm run bootstrap

- name: audit tools (without allow-list)
run: npm audit --audit-level=moderate
run: npm audit --audit-level=moderate --omit dev

- name: audit packages
run: npm run audit-all
2 changes: 2 additions & 0 deletions .github/workflows/releases.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
name: Publish NPM

run-name: Publish NPM - ${{ github.event.inputs.package }}

on:
workflow_dispatch:
inputs:
Expand Down
9 changes: 5 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions packages/attest/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ export type AttestOptions = {
// Sigstore instance to use for signing. Must be one of "public-good" or
// "github".
sigstore?: 'public-good' | 'github'
// HTTP headers to include in request to attestations API.
headers?: {[header: string]: string | number | undefined}
// Whether to skip writing the attestation to the GH attestations API.
skipWrite?: boolean
}
Expand Down Expand Up @@ -113,6 +115,8 @@ export type AttestProvenanceOptions = {
// Sigstore instance to use for signing. Must be one of "public-good" or
// "github".
sigstore?: 'public-good' | 'github'
// HTTP headers to include in request to attestations API.
headers?: {[header: string]: string | number | undefined}
// Whether to skip writing the attestation to the GH attestations API.
skipWrite?: boolean
// Issuer URL responsible for minting the OIDC token from which the
Expand Down
33 changes: 21 additions & 12 deletions packages/attest/RELEASES.md
Original file line number Diff line number Diff line change
@@ -1,31 +1,40 @@
# @actions/attest Releases

### 1.4.1

- Bump @actions/http-client from 2.2.1 to 2.2.3 [#1805](https://github.com/actions/toolkit/pull/1805)

### 1.4.0

- Add new `headers` parameter to the `attest` and `attestProvenance` functions [#1790](https://github.com/actions/toolkit/pull/1790)
- Update `buildSLSAProvenancePredicate`/`attestProvenance` to automatically derive default OIDC issuer URL from current execution context [#1796](https://github.com/actions/toolkit/pull/1796)

### 1.3.1

- Fix bug with proxy support when retrieving JWKS for OIDC issuer
- Fix bug with proxy support when retrieving JWKS for OIDC issuer [#1776](https://github.com/actions/toolkit/pull/1776)

### 1.3.0

- Dynamic construction of Sigstore API URLs
- Switch to new GH provenance build type
- Fetch existing Rekor entry on 409 conflict error
- Bump @sigstore/bundle from 2.3.0 to 2.3.2
- Bump @sigstore/sign from 2.3.0 to 2.3.2
- Dynamic construction of Sigstore API URLs [#1735](https://github.com/actions/toolkit/pull/1735)
- Switch to new GH provenance build type [#1745](https://github.com/actions/toolkit/pull/1745)
- Fetch existing Rekor entry on 409 conflict error [#1759](https://github.com/actions/toolkit/pull/1759)
- Bump @sigstore/bundle from 2.3.0 to 2.3.2 [#1738](https://github.com/actions/toolkit/pull/1738)
- Bump @sigstore/sign from 2.3.0 to 2.3.2 [#1738](https://github.com/actions/toolkit/pull/1738)

### 1.2.1

- Retry request on attestation persistence failure
- Retry request on attestation persistence failure [#1725](https://github.com/actions/toolkit/pull/1725)

### 1.2.0

- Generate attestations using the v0.3 Sigstore bundle format.
- Bump @sigstore/bundle from 2.2.0 to 2.3.0.
- Bump @sigstore/sign from 2.2.3 to 2.3.0.
- Remove dependency on make-fetch-happen
- Generate attestations using the v0.3 Sigstore bundle format [#1701](https://github.com/actions/toolkit/pull/1701)
- Bump @sigstore/bundle from 2.2.0 to 2.3.0 [#1701](https://github.com/actions/toolkit/pull/1701)
- Bump @sigstore/sign from 2.2.3 to 2.3.0 [#1701](https://github.com/actions/toolkit/pull/1701)
- Remove dependency on make-fetch-happen [#1714](https://github.com/actions/toolkit/pull/1714)

### 1.1.0

- Updates the `attestProvenance` function to retrieve a token from the GitHub OIDC provider and use the token claims to populate the provenance statement.
- Updates the `attestProvenance` function to retrieve a token from the GitHub OIDC provider and use the token claims to populate the provenance statement [#1693](https://github.com/actions/toolkit/pull/1693)

### 1.0.0

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ exports[`provenance functions buildSLSAProvenancePredicate returns a provenance
"workflow": {
"path": ".github/workflows/main.yml",
"ref": "main",
"repository": "https://github.com/owner/repo",
"repository": "https://foo.ghe.com/owner/repo",
},
},
"internalParameters": {
Expand All @@ -25,16 +25,16 @@ exports[`provenance functions buildSLSAProvenancePredicate returns a provenance
"digest": {
"gitCommit": "babca52ab0c93ae16539e5923cb0d7403b9a093b",
},
"uri": "git+https://github.com/owner/repo@refs/heads/main",
"uri": "git+https://foo.ghe.com/owner/repo@refs/heads/main",
},
],
},
"runDetails": {
"builder": {
"id": "https://github.com/owner/workflows/.github/workflows/publish.yml@main",
"id": "https://foo.ghe.com/owner/workflows/.github/workflows/publish.yml@main",
},
"metadata": {
"invocationId": "https://github.com/owner/repo/actions/runs/run-id/attempts/run-attempt",
"invocationId": "https://foo.ghe.com/owner/repo/actions/runs/run-id/attempts/run-attempt",
},
},
},
Expand Down
25 changes: 10 additions & 15 deletions packages/attest/__tests__/provenance.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {attestProvenance, buildSLSAProvenancePredicate} from '../src/provenance'

describe('provenance functions', () => {
const originalEnv = process.env
const issuer = 'https://example.com'
const issuer = 'https://token.actions.foo.ghe.com'
const audience = 'nobody'
const jwksPath = '/.well-known/jwks.json'
const tokenPath = '/token'
Expand Down Expand Up @@ -38,7 +38,7 @@ describe('provenance functions', () => {
...originalEnv,
ACTIONS_ID_TOKEN_REQUEST_URL: `${issuer}${tokenPath}?`,
ACTIONS_ID_TOKEN_REQUEST_TOKEN: 'token',
GITHUB_SERVER_URL: 'https://github.com',
GITHUB_SERVER_URL: 'https://foo.ghe.com',
GITHUB_REPOSITORY: claims.repository
}

Expand Down Expand Up @@ -68,7 +68,7 @@ describe('provenance functions', () => {

describe('buildSLSAProvenancePredicate', () => {
it('returns a provenance hydrated from an OIDC token', async () => {
const predicate = await buildSLSAProvenancePredicate(issuer)
const predicate = await buildSLSAProvenancePredicate()
expect(predicate).toMatchSnapshot()
})
})
Expand Down Expand Up @@ -96,9 +96,9 @@ describe('provenance functions', () => {
})

describe('when using the github Sigstore instance', () => {
const {fulcioURL, tsaServerURL} = signingEndpoints('github')

beforeEach(async () => {
const {fulcioURL, tsaServerURL} = signingEndpoints('github')

// Mock Sigstore
await mockFulcio({baseURL: fulcioURL, strict: false})
await mockTSA({baseURL: tsaServerURL})
Expand All @@ -118,8 +118,7 @@ describe('provenance functions', () => {
subjectName,
subjectDigest,
token: 'token',
sigstore: 'github',
issuer
sigstore: 'github'
})

expect(attestation).toBeDefined()
Expand All @@ -146,8 +145,7 @@ describe('provenance functions', () => {
const attestation = await attestProvenance({
subjectName,
subjectDigest,
token: 'token',
issuer
token: 'token'
})

expect(attestation).toBeDefined()
Expand Down Expand Up @@ -183,8 +181,7 @@ describe('provenance functions', () => {
subjectName,
subjectDigest,
token: 'token',
sigstore: 'public-good',
issuer
sigstore: 'public-good'
})

expect(attestation).toBeDefined()
Expand All @@ -211,8 +208,7 @@ describe('provenance functions', () => {
const attestation = await attestProvenance({
subjectName,
subjectDigest,
token: 'token',
issuer
token: 'token'
})

expect(attestation).toBeDefined()
Expand All @@ -238,8 +234,7 @@ describe('provenance functions', () => {
subjectDigest,
token: 'token',
sigstore: 'public-good',
skipWrite: true,
issuer
skipWrite: true
})

expect(attestation).toBeDefined()
Expand Down
7 changes: 5 additions & 2 deletions packages/attest/__tests__/store.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ describe('writeAttestation', () => {
const originalEnv = process.env
const attestation = {foo: 'bar '}
const token = 'token'
const headers = {'X-GitHub-Foo': 'true'}

const mockAgent = new MockAgent()
setGlobalDispatcher(mockAgent)
Expand All @@ -27,14 +28,16 @@ describe('writeAttestation', () => {
.intercept({
path: '/repos/foo/bar/attestations',
method: 'POST',
headers: {authorization: `token ${token}`},
headers: {authorization: `token ${token}`, ...headers},
body: JSON.stringify({bundle: attestation})
})
.reply(201, {id: '123'})
})

it('persists the attestation', async () => {
await expect(writeAttestation(attestation, token)).resolves.toEqual('123')
await expect(
writeAttestation(attestation, token, {headers})
).resolves.toEqual('123')
})
})

Expand Down
18 changes: 9 additions & 9 deletions packages/attest/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions packages/attest/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@actions/attest",
"version": "1.3.1",
"version": "1.4.1",
"description": "Actions attestation lib",
"keywords": [
"github",
Expand Down Expand Up @@ -44,7 +44,7 @@
"dependencies": {
"@actions/core": "^1.10.1",
"@actions/github": "^6.0.0",
"@actions/http-client": "^2.2.1",
"@actions/http-client": "^2.2.3",
"@octokit/plugin-retry": "^6.0.1",
"@sigstore/bundle": "^2.3.2",
"@sigstore/sign": "^2.3.2",
Expand Down
8 changes: 7 additions & 1 deletion packages/attest/src/attest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ export type AttestOptions = {
// Sigstore instance to use for signing. Must be one of "public-good" or
// "github".
sigstore?: SigstoreInstance
// HTTP headers to include in request to attestations API.
headers?: {[header: string]: string | number | undefined}
// Whether to skip writing the attestation to the GH attestations API.
skipWrite?: boolean
}
Expand Down Expand Up @@ -61,7 +63,11 @@ export async function attest(options: AttestOptions): Promise<Attestation> {
// Store the attestation
let attestationID: string | undefined
if (options.skipWrite !== true) {
attestationID = await writeAttestation(bundleToJSON(bundle), options.token)
attestationID = await writeAttestation(
bundleToJSON(bundle),
options.token,
{headers: options.headers}
)
}

return toAttestation(bundle, attestationID)
Expand Down
26 changes: 25 additions & 1 deletion packages/attest/src/oidc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ import * as jose from 'jose'

const OIDC_AUDIENCE = 'nobody'

const VALID_SERVER_URLS = [
'https://github.com',
new RegExp('^https://[a-z0-9-]+\\.ghe\\.com$')
] as const

const REQUIRED_CLAIMS = [
'iss',
'ref',
Expand All @@ -25,7 +30,8 @@ type OIDCConfig = {
jwks_uri: string
}

export const getIDTokenClaims = async (issuer: string): Promise<ClaimSet> => {
export const getIDTokenClaims = async (issuer?: string): Promise<ClaimSet> => {
issuer = issuer || getIssuer()
try {
const token = await getIDToken(OIDC_AUDIENCE)
const claims = await decodeOIDCToken(token, issuer)
Expand Down Expand Up @@ -82,3 +88,21 @@ function assertClaimSet(claims: jose.JWTPayload): asserts claims is ClaimSet {
throw new Error(`Missing claims: ${missingClaims.join(', ')}`)
}
}

// Derive the current OIDC issuer based on the server URL
function getIssuer(): string {
const serverURL = process.env.GITHUB_SERVER_URL || 'https://github.com'

// Ensure the server URL is a valid GitHub server URL
if (!VALID_SERVER_URLS.some(valid_url => serverURL.match(valid_url))) {
throw new Error(`Invalid server URL: ${serverURL}`)
}

let host = new URL(serverURL).hostname

if (host === 'github.com') {
host = 'githubusercontent.com'
}

return `https://token.actions.${host}`
}
4 changes: 1 addition & 3 deletions packages/attest/src/provenance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ import type {Attestation, Predicate} from './shared.types'
const SLSA_PREDICATE_V1_TYPE = 'https://slsa.dev/provenance/v1'
const GITHUB_BUILD_TYPE = 'https://actions.github.io/buildtypes/workflow/v1'

const DEFAULT_ISSUER = 'https://token.actions.githubusercontent.com'

export type AttestProvenanceOptions = Omit<
AttestOptions,
'predicate' | 'predicateType'
Expand All @@ -24,7 +22,7 @@ export type AttestProvenanceOptions = Omit<
* @returns The SLSA provenance predicate.
*/
export const buildSLSAProvenancePredicate = async (
issuer: string = DEFAULT_ISSUER
issuer?: string
): Promise<Predicate> => {
const serverURL = process.env.GITHUB_SERVER_URL
const claims = await getIDTokenClaims(issuer)
Expand Down
Loading

0 comments on commit b9aebb9

Please sign in to comment.