Skip to content

Commit 7cc96bb

Browse files
authored
Merge pull request actions#1796 from actions/bdehamer/attest-issuer
derive default OIDC issuer from current tenant
2 parents f299e8b + fa6cc53 commit 7cc96bb

File tree

5 files changed

+41
-23
lines changed

5 files changed

+41
-23
lines changed

packages/attest/RELEASES.md

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
### 1.4.0
44

55
- Add new `headers` parameter to the `attest` and `attestProvenance` functions.
6+
- Update `buildSLSAProvenancePredicate`/`attestProvenance` to automatically derive default OIDC issuer URL from current execution context.
67

78
### 1.3.1
89

packages/attest/__tests__/__snapshots__/provenance.test.ts.snap

+4-4
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ exports[`provenance functions buildSLSAProvenancePredicate returns a provenance
99
"workflow": {
1010
"path": ".github/workflows/main.yml",
1111
"ref": "main",
12-
"repository": "https://github.com/owner/repo",
12+
"repository": "https://foo.ghe.com/owner/repo",
1313
},
1414
},
1515
"internalParameters": {
@@ -25,16 +25,16 @@ exports[`provenance functions buildSLSAProvenancePredicate returns a provenance
2525
"digest": {
2626
"gitCommit": "babca52ab0c93ae16539e5923cb0d7403b9a093b",
2727
},
28-
"uri": "git+https://github.com/owner/repo@refs/heads/main",
28+
"uri": "git+https://foo.ghe.com/owner/repo@refs/heads/main",
2929
},
3030
],
3131
},
3232
"runDetails": {
3333
"builder": {
34-
"id": "https://github.com/owner/workflows/.github/workflows/publish.yml@main",
34+
"id": "https://foo.ghe.com/owner/workflows/.github/workflows/publish.yml@main",
3535
},
3636
"metadata": {
37-
"invocationId": "https://github.com/owner/repo/actions/runs/run-id/attempts/run-attempt",
37+
"invocationId": "https://foo.ghe.com/owner/repo/actions/runs/run-id/attempts/run-attempt",
3838
},
3939
},
4040
},

packages/attest/__tests__/provenance.test.ts

+10-15
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {attestProvenance, buildSLSAProvenancePredicate} from '../src/provenance'
88

99
describe('provenance functions', () => {
1010
const originalEnv = process.env
11-
const issuer = 'https://example.com'
11+
const issuer = 'https://token.actions.foo.ghe.com'
1212
const audience = 'nobody'
1313
const jwksPath = '/.well-known/jwks.json'
1414
const tokenPath = '/token'
@@ -38,7 +38,7 @@ describe('provenance functions', () => {
3838
...originalEnv,
3939
ACTIONS_ID_TOKEN_REQUEST_URL: `${issuer}${tokenPath}?`,
4040
ACTIONS_ID_TOKEN_REQUEST_TOKEN: 'token',
41-
GITHUB_SERVER_URL: 'https://github.com',
41+
GITHUB_SERVER_URL: 'https://foo.ghe.com',
4242
GITHUB_REPOSITORY: claims.repository
4343
}
4444

@@ -68,7 +68,7 @@ describe('provenance functions', () => {
6868

6969
describe('buildSLSAProvenancePredicate', () => {
7070
it('returns a provenance hydrated from an OIDC token', async () => {
71-
const predicate = await buildSLSAProvenancePredicate(issuer)
71+
const predicate = await buildSLSAProvenancePredicate()
7272
expect(predicate).toMatchSnapshot()
7373
})
7474
})
@@ -96,9 +96,9 @@ describe('provenance functions', () => {
9696
})
9797

9898
describe('when using the github Sigstore instance', () => {
99-
const {fulcioURL, tsaServerURL} = signingEndpoints('github')
100-
10199
beforeEach(async () => {
100+
const {fulcioURL, tsaServerURL} = signingEndpoints('github')
101+
102102
// Mock Sigstore
103103
await mockFulcio({baseURL: fulcioURL, strict: false})
104104
await mockTSA({baseURL: tsaServerURL})
@@ -118,8 +118,7 @@ describe('provenance functions', () => {
118118
subjectName,
119119
subjectDigest,
120120
token: 'token',
121-
sigstore: 'github',
122-
issuer
121+
sigstore: 'github'
123122
})
124123

125124
expect(attestation).toBeDefined()
@@ -146,8 +145,7 @@ describe('provenance functions', () => {
146145
const attestation = await attestProvenance({
147146
subjectName,
148147
subjectDigest,
149-
token: 'token',
150-
issuer
148+
token: 'token'
151149
})
152150

153151
expect(attestation).toBeDefined()
@@ -183,8 +181,7 @@ describe('provenance functions', () => {
183181
subjectName,
184182
subjectDigest,
185183
token: 'token',
186-
sigstore: 'public-good',
187-
issuer
184+
sigstore: 'public-good'
188185
})
189186

190187
expect(attestation).toBeDefined()
@@ -211,8 +208,7 @@ describe('provenance functions', () => {
211208
const attestation = await attestProvenance({
212209
subjectName,
213210
subjectDigest,
214-
token: 'token',
215-
issuer
211+
token: 'token'
216212
})
217213

218214
expect(attestation).toBeDefined()
@@ -238,8 +234,7 @@ describe('provenance functions', () => {
238234
subjectDigest,
239235
token: 'token',
240236
sigstore: 'public-good',
241-
skipWrite: true,
242-
issuer
237+
skipWrite: true
243238
})
244239

245240
expect(attestation).toBeDefined()

packages/attest/src/oidc.ts

+25-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@ import * as jose from 'jose'
44

55
const OIDC_AUDIENCE = 'nobody'
66

7+
const VALID_SERVER_URLS = [
8+
'https://github.com',
9+
new RegExp('^https://[a-z0-9-]+\\.ghe\\.com$')
10+
] as const
11+
712
const REQUIRED_CLAIMS = [
813
'iss',
914
'ref',
@@ -25,7 +30,8 @@ type OIDCConfig = {
2530
jwks_uri: string
2631
}
2732

28-
export const getIDTokenClaims = async (issuer: string): Promise<ClaimSet> => {
33+
export const getIDTokenClaims = async (issuer?: string): Promise<ClaimSet> => {
34+
issuer = issuer || getIssuer()
2935
try {
3036
const token = await getIDToken(OIDC_AUDIENCE)
3137
const claims = await decodeOIDCToken(token, issuer)
@@ -82,3 +88,21 @@ function assertClaimSet(claims: jose.JWTPayload): asserts claims is ClaimSet {
8288
throw new Error(`Missing claims: ${missingClaims.join(', ')}`)
8389
}
8490
}
91+
92+
// Derive the current OIDC issuer based on the server URL
93+
function getIssuer(): string {
94+
const serverURL = process.env.GITHUB_SERVER_URL || 'https://github.com'
95+
96+
// Ensure the server URL is a valid GitHub server URL
97+
if (!VALID_SERVER_URLS.some(valid_url => serverURL.match(valid_url))) {
98+
throw new Error(`Invalid server URL: ${serverURL}`)
99+
}
100+
101+
let host = new URL(serverURL).hostname
102+
103+
if (host === 'github.com') {
104+
host = 'githubusercontent.com'
105+
}
106+
107+
return `https://token.actions.${host}`
108+
}

packages/attest/src/provenance.ts

+1-3
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@ import type {Attestation, Predicate} from './shared.types'
55
const SLSA_PREDICATE_V1_TYPE = 'https://slsa.dev/provenance/v1'
66
const GITHUB_BUILD_TYPE = 'https://actions.github.io/buildtypes/workflow/v1'
77

8-
const DEFAULT_ISSUER = 'https://token.actions.githubusercontent.com'
9-
108
export type AttestProvenanceOptions = Omit<
119
AttestOptions,
1210
'predicate' | 'predicateType'
@@ -24,7 +22,7 @@ export type AttestProvenanceOptions = Omit<
2422
* @returns The SLSA provenance predicate.
2523
*/
2624
export const buildSLSAProvenancePredicate = async (
27-
issuer: string = DEFAULT_ISSUER
25+
issuer?: string
2826
): Promise<Predicate> => {
2927
const serverURL = process.env.GITHUB_SERVER_URL
3028
const claims = await getIDTokenClaims(issuer)

0 commit comments

Comments
 (0)