diff --git a/packages/attest/RELEASES.md b/packages/attest/RELEASES.md index 4e85ca3878..722fcd46b6 100644 --- a/packages/attest/RELEASES.md +++ b/packages/attest/RELEASES.md @@ -1,5 +1,8 @@ # @actions/attest Releases +### 1.4.2 + +- Fix bug in `buildSLSAProvenancePredicate`/`attestProvenance` when generating provenance statement for enterprise account using customized OIDC issuer value [#1823](https://github.com/actions/toolkit/pull/1823) ### 1.4.1 - Bump @actions/http-client from 2.2.1 to 2.2.3 [#1805](https://github.com/actions/toolkit/pull/1805) @@ -8,7 +11,6 @@ - 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 [#1776](https://github.com/actions/toolkit/pull/1776) diff --git a/packages/attest/__tests__/oidc.test.ts b/packages/attest/__tests__/oidc.test.ts index 69ffa340fa..3922325bfb 100644 --- a/packages/attest/__tests__/oidc.test.ts +++ b/packages/attest/__tests__/oidc.test.ts @@ -68,6 +68,55 @@ describe('getIDTokenClaims', () => { }) }) + describe('when ID token is valid (w/ enterprise slug)', () => { + const claims = { + iss: `${issuer}/foo-bar`, + aud: audience, + ref: 'ref', + sha: 'sha', + repository: 'repo', + event_name: 'push', + job_workflow_ref: 'job_workflow_ref', + workflow_ref: 'workflow', + repository_id: '1', + repository_owner_id: '1', + runner_environment: 'github-hosted', + run_id: '1', + run_attempt: '1' + } + + beforeEach(async () => { + const jwt = await new jose.SignJWT(claims) + .setProtectedHeader({alg: 'PS256'}) + .sign(key.privateKey) + + nock(issuer).get(tokenPath).query({audience}).reply(200, {value: jwt}) + }) + + it('returns the ID token claims', async () => { + const result = await getIDTokenClaims(issuer) + expect(result).toEqual(claims) + }) + }) + + describe('when ID token is missing the "iss" claim', () => { + const claims = { + aud: audience + } + + beforeEach(async () => { + const jwt = await new jose.SignJWT(claims) + .setProtectedHeader({alg: 'PS256'}) + .sign(key.privateKey) + + nock(issuer).get(tokenPath).query({audience}).reply(200, {value: jwt}) + }) + + it('throws an error', async () => { + await expect(getIDTokenClaims(issuer)).rejects.toThrow(/missing "iss"/i) + }) + }) + describe('when ID token is missing required claims', () => { const claims = { iss: issuer, @@ -99,7 +148,9 @@ describe('getIDTokenClaims', () => { }) it('throws an error', async () => { - await expect(getIDTokenClaims(issuer)).rejects.toThrow(/unexpected "iss"/) + await expect(getIDTokenClaims(issuer)).rejects.toThrow( + /unexpected "iss"/i + ) }) }) diff --git a/packages/attest/package-lock.json b/packages/attest/package-lock.json index 17b728496e..2726fbc528 100644 --- a/packages/attest/package-lock.json +++ b/packages/attest/package-lock.json @@ -1,12 +1,12 @@ { "name": "@actions/attest", - "version": "1.4.1", + "version": "1.4.2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@actions/attest", - "version": "1.4.1", + "version": "1.4.2", "license": "MIT", "dependencies": { "@actions/core": "^1.10.1", diff --git a/packages/attest/package.json b/packages/attest/package.json index 224e948ab8..22f01f4d7b 100644 --- a/packages/attest/package.json +++ b/packages/attest/package.json @@ -1,6 +1,6 @@ { "name": "@actions/attest", - "version": "1.4.1", + "version": "1.4.2", "description": "Actions attestation lib", "keywords": [ "github", diff --git a/packages/attest/src/oidc.ts b/packages/attest/src/oidc.ts index f855469cc8..736716beda 100644 --- a/packages/attest/src/oidc.ts +++ b/packages/attest/src/oidc.ts @@ -49,10 +49,19 @@ const decodeOIDCToken = async ( // Verify and decode token const jwks = jose.createLocalJWKSet(await getJWKS(issuer)) const {payload} = await jose.jwtVerify(token, jwks, { - audience: OIDC_AUDIENCE, - issuer + audience: OIDC_AUDIENCE }) + if (!payload.iss) { + throw new Error('Missing "iss" claim') + } + + // Check that the issuer STARTS WITH the expected issuer URL to account for + // the fact that the value may include an enterprise-specific slug + if (!payload.iss.startsWith(issuer)) { + throw new Error(`Unexpected "iss" claim: ${payload.iss}`) + } + return payload }