Skip to content

Commit 7d404f9

Browse files
authored
Fix OIDC audience default value (#271)
1 parent 45d6045 commit 7d404f9

File tree

8 files changed

+132
-82
lines changed

8 files changed

+132
-82
lines changed

.github/workflows/oidc-integration-test.yml

Lines changed: 85 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ name: OIDC Integration Test
55
# - 2.74.1: Does not support `jf eot` command, validates manual fallback logic.
66
# - 2.75.0: Introduced native OIDC token exchange.
77
# - Latest: Ensures ongoing compatibility with the most recent CLI build.
8-
98
on:
109
push:
1110
branches:
@@ -23,54 +22,51 @@ permissions:
2322
contents: read
2423

2524
jobs:
26-
oidc-test:
25+
generate-platform-oidc-integration:
2726
strategy:
28-
fail-fast: false
27+
# Using "include" instead of a matrix of arrays gives us fine-grained control over test combinations.
28+
# This is needed because some audience values (e.g., URLs) contain characters not valid in matrix keys or job names.
29+
#
30+
# Each scenario represents a real-world case:
31+
# - "default": No audience is set in the action or the platform integration.
32+
# - "test": A custom audience is explicitly set in both the action and the platform integration.
33+
# - "github-implicit-default": The platform integration is explicitly configured with GitHub's default audience,
34+
# but the action does not pass any audience.
35+
# This tests CLI behavior in case of mismatches — see https://github.com/jfrog/setup-jfrog-cli/issues/270
2936
matrix:
30-
os: [ ubuntu, macos, windows ]
31-
cli-version: [ '2.74.1', '2.75.0','latest' ]
32-
runs-on: ${{ matrix.os }}-latest
33-
name: OIDC Test - ${{ matrix.cli-version }} on ${{ matrix.os }}
34-
env:
35-
JFROG_CLI_LOG_LEVEL: DEBUG
36-
37+
include:
38+
- audience_id: default
39+
audience_value: ''
40+
- audience_id: test
41+
audience_value: 'audience-value'
42+
- audience_id: github-implicit-default
43+
audience_value: 'https://github.com/jfrog'
44+
runs-on: ubuntu-latest
3745
steps:
38-
- name: Checkout Repository
39-
uses: actions/checkout@v4
40-
with:
41-
ref: ${{ github.event.pull_request.head.sha }}
42-
43-
# Setup OIDC platform integration
44-
- name: Generate unique OIDC provider name
45-
id: gen-oidc
46-
shell: bash
47-
run: |
48-
cli_version="${{ matrix.cli-version }}" && cli_version="${cli_version//./-}"
49-
echo "oidc_provider_name=oidc-integration-${cli_version}-${{ matrix.os }}-$(date +%s)" >> "$GITHUB_OUTPUT"
50-
5146
- name: Create OpenID Connect integration
5247
shell: bash
5348
run: |
5449
curl -X POST "${{ secrets.JFROG_PLATFORM_URL }}/access/api/v1/oidc" \
5550
-H "Content-Type: application/json" \
5651
-H "Authorization: Bearer ${{ secrets.JFROG_PLATFORM_RT_TOKEN }}" \
5752
-d '{
58-
"name": "${{ steps.gen-oidc.outputs.oidc_provider_name }}",
53+
"name": "oidc-integration-${{ matrix.audience_id }}-${{ github.run_id }}",
5954
"issuer_url": "https://token.actions.githubusercontent.com",
6055
"provider_type": "GitHub",
61-
"enable_permissive_configuration": "true",
62-
"description": "Test configuration for CLI version ${{ matrix.cli-version }}"
56+
"audience": "${{ matrix.audience_value }}",
57+
"enable_permissive_configuration": true,
58+
"description": "Temp integration for testing OIDC with audience value: ${{ matrix.audience_value }}"
6359
}'
6460
6561
- name: Create OIDC Identity Mapping
6662
shell: bash
6763
run: |
68-
curl -X POST "${{ secrets.JFROG_PLATFORM_URL }}/access/api/v1/oidc/${{ steps.gen-oidc.outputs.oidc_provider_name }}/identity_mappings" \
69-
-H 'Content-Type: application/json' \
64+
curl -X POST "${{ secrets.JFROG_PLATFORM_URL }}/access/api/v1/oidc/oidc-integration-${{ matrix.audience_id }}-${{ github.run_id }}/identity_mappings" \
65+
-H "Content-Type: application/json" \
7066
-H "Authorization: Bearer ${{ secrets.JFROG_PLATFORM_RT_TOKEN }}" \
7167
-d '{
7268
"name": "oidc-test-mapping",
73-
"priority": "1",
69+
"priority": 1,
7470
"claims": {
7571
"repository": "${{ github.repository_owner }}/setup-jfrog-cli"
7672
},
@@ -80,21 +76,65 @@ jobs:
8076
}
8177
}'
8278
83-
# Setup
79+
oidc-test:
80+
needs: generate-platform-oidc-integration
81+
strategy:
82+
fail-fast: false
83+
# Using include allows exact combinations of CLI version and audience ID to ensure coverage of edge cases.
84+
# This avoids invalid audience strings in identifiers and ensures fallback logic is tested selectively.
85+
matrix:
86+
include:
87+
- cli-version: '2.74.1'
88+
audience_id: default
89+
audience_value: ''
90+
- cli-version: '2.75.0'
91+
audience_id: default
92+
audience_value: ''
93+
- cli-version: latest
94+
audience_id: default
95+
audience_value: ''
96+
- cli-version: '2.74.1'
97+
audience_id: test
98+
audience_value: 'audience-value'
99+
- cli-version: '2.75.0'
100+
audience_id: test
101+
audience_value: 'audience-value'
102+
- cli-version: latest
103+
audience_id: test
104+
audience_value: 'audience-value'
105+
# GitHub default audience value is resolved implicitly when omitted.
106+
# These tests verify that the CLI handles an empty value correctly while GitHub sets the expected audience on its backend.
107+
- cli-version: '2.74.1'
108+
audience_id: github-implicit-default
109+
audience_value: ''
110+
- cli-version: '2.75.0'
111+
audience_id: github-implicit-default
112+
audience_value: ''
113+
- cli-version: latest
114+
audience_id: github-implicit-default
115+
audience_value: ''
116+
runs-on: ubuntu-latest
117+
env:
118+
JFROG_CLI_LOG_LEVEL: DEBUG
119+
steps:
120+
- name: Checkout Repository
121+
uses: actions/checkout@v4
122+
with:
123+
ref: ${{ github.event.pull_request.head.sha }}
124+
84125
- name: Setup JFrog CLI
85126
id: setup-jfrog-cli
86127
uses: ./
87128
env:
88129
JF_URL: ${{ secrets.JFROG_PLATFORM_URL }}
89130
with:
90131
version: ${{ matrix.cli-version }}
91-
oidc-provider-name: ${{ steps.gen-oidc.outputs.oidc_provider_name }}
132+
oidc-provider-name: oidc-integration-${{ matrix.audience_id }}-${{ github.run_id }}
133+
oidc-audience: ${{ matrix.audience_value }}
92134

93-
# validate successful OIDC configuration
94135
- name: Test JFrog CLI connectivity
95136
run: jf rt ping
96137

97-
# Validate step outputs
98138
- name: Validate user output
99139
shell: bash
100140
run: test -n "${{ steps.setup-jfrog-cli.outputs.oidc-user }}"
@@ -103,10 +143,19 @@ jobs:
103143
shell: bash
104144
run: test -n "${{ steps.setup-jfrog-cli.outputs.oidc-token }}"
105145

106-
# Cleanup
146+
cleanup-oidc-integration:
147+
needs: oidc-test
148+
if: always()
149+
strategy:
150+
matrix:
151+
include:
152+
- audience_id: default
153+
- audience_id: test
154+
- audience_id: github-implicit-default
155+
runs-on: ubuntu-latest
156+
steps:
107157
- name: Delete OIDC integration
108158
shell: bash
109-
if: always()
110159
run: |
111-
curl -X DELETE "${{ secrets.JFROG_PLATFORM_URL }}/access/api/v1/oidc/${{ steps.gen-oidc.outputs.oidc_provider_name }}" \
160+
curl -X DELETE "${{ secrets.JFROG_PLATFORM_URL }}/access/api/v1/oidc/oidc-integration-${{ matrix.audience_id }}-${{ github.run_id }}" \
112161
-H "Authorization: Bearer ${{ secrets.JFROG_PLATFORM_RT_TOKEN }}"

lib/oidc-utils.js

Lines changed: 13 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,13 @@ class OidcUtils {
6464
throw new Error(`JF_URL must be provided when oidc-provider-name is specified`);
6565
}
6666
// Get OIDC token ID from GitHub
67-
jfrogCredentials.oidcTokenId = yield this.getIdToken(jfrogCredentials.oidcAudience || '');
67+
try {
68+
core.debug('Attempting to fetch JSON Web Token (JWT) ID token with audience value: ' + jfrogCredentials.oidcAudience);
69+
jfrogCredentials.oidcTokenId = yield core.getIDToken(jfrogCredentials.oidcAudience);
70+
}
71+
catch (error) {
72+
throw new Error(`Failed to fetch OpenID Connect JSON Web Token: ${error.message}`);
73+
}
6874
// Version should be more than min version
6975
// If CLI_REMOTE_ARG specified, we have to fetch token before we can download the CLI.
7076
if (this.isCLIVersionOidcSupported() && !core.getInput(utils_1.Utils.CLI_REMOTE_ARG)) {
@@ -90,7 +96,12 @@ class OidcUtils {
9096
if (creds.oidcProviderName === undefined || creds.oidcTokenId === undefined || creds.jfrogUrl === undefined) {
9197
throw new Error('Missing one or more required fields: OIDC provider name, token ID, or JFrog Platform URL.');
9298
}
93-
output = yield utils_1.Utils.runCliAndGetOutput(['eot', creds.oidcProviderName, creds.oidcTokenId, '--url', creds.jfrogUrl, '--oidc-audience', creds.oidcAudience || 'jfrog-github'], { silent: true });
99+
const args = ['eot', creds.oidcProviderName, creds.oidcTokenId, '--url', creds.jfrogUrl];
100+
if (creds.oidcAudience !== "") {
101+
args.push('--oidc-audience', creds.oidcAudience);
102+
}
103+
core.debug('Running CLI command: ' + args.join(' '));
104+
output = yield utils_1.Utils.runCliAndGetOutput(args, { silent: true });
94105
const { accessToken, username } = this.extractValuesFromOIDCToken(output);
95106
this.setOidcStepOutputs(username, accessToken);
96107
return accessToken;
@@ -284,23 +295,6 @@ class OidcUtils {
284295
return yield fs_1.promises.readFile(configRelativePath, 'utf-8');
285296
});
286297
}
287-
/**
288-
* Fetches a JSON Web Token (JWT) ID token from GitHub's OIDC provider.
289-
* @param audience - The intended audience for the token.
290-
* @returns A promise that resolves to the JWT ID token as a string.
291-
* @throws An error if fetching the token fails.
292-
*/
293-
static getIdToken(audience) {
294-
return __awaiter(this, void 0, void 0, function* () {
295-
core.debug('Attempting to fetch JSON Web Token (JWT) ID token...');
296-
try {
297-
return yield core.getIDToken(audience);
298-
}
299-
catch (error) {
300-
throw new Error(`Failed to fetch OpenID Connect JSON Web Token: ${error.message}`);
301-
}
302-
});
303-
}
304298
static isCLIVersionOidcSupported() {
305299
const version = core.getInput(utils_1.Utils.CLI_VERSION_ARG) || '';
306300
if (version === '') {

lib/utils.js

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ class Utils {
5555
username: process.env.JF_USER,
5656
password: process.env.JF_PASSWORD,
5757
oidcProviderName: core.getInput(Utils.OIDC_INTEGRATION_PROVIDER_NAME),
58-
oidcAudience: core.getInput(Utils.OIDC_AUDIENCE_ARG) || Utils.DEFAULT_OIDC_AUDIENCE,
58+
oidcAudience: core.getInput(Utils.OIDC_AUDIENCE_ARG) || '',
5959
oidcTokenId: '',
6060
};
6161
if (jfrogCredentials.password && !jfrogCredentials.username) {
@@ -184,7 +184,6 @@ class Utils {
184184
* @name password - JFrog Platform basic authentication
185185
* @name accessToken - Jfrog Platform access token
186186
* @name oidcProviderName - OpenID Connect provider name defined in the JFrog Platform
187-
* @name oidcAudience - JFrog Platform OpenID Connect audience
188187
*/
189188
let url = jfrogCredentials.jfrogUrl;
190189
let user = jfrogCredentials.username;
@@ -474,4 +473,3 @@ Utils.JOB_SUMMARY_DISABLE = 'disable-job-summary';
474473
Utils.AUTO_BUILD_PUBLISH_DISABLE = 'disable-auto-build-publish';
475474
// Custom server ID input
476475
Utils.CUSTOM_SERVER_ID = 'custom-server-id';
477-
Utils.DEFAULT_OIDC_AUDIENCE = 'jfrog-github';

src/oidc-utils.ts

Lines changed: 12 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,12 @@ export class OidcUtils {
4141
throw new Error(`JF_URL must be provided when oidc-provider-name is specified`);
4242
}
4343
// Get OIDC token ID from GitHub
44-
jfrogCredentials.oidcTokenId = await this.getIdToken(jfrogCredentials.oidcAudience || '');
44+
try {
45+
core.debug('Attempting to fetch JSON Web Token (JWT) ID token with audience value: ' + jfrogCredentials.oidcAudience);
46+
jfrogCredentials.oidcTokenId = await core.getIDToken(jfrogCredentials.oidcAudience);
47+
} catch (error: any) {
48+
throw new Error(`Failed to fetch OpenID Connect JSON Web Token: ${error.message}`);
49+
}
4550

4651
// Version should be more than min version
4752
// If CLI_REMOTE_ARG specified, we have to fetch token before we can download the CLI.
@@ -69,10 +74,12 @@ export class OidcUtils {
6974
throw new Error('Missing one or more required fields: OIDC provider name, token ID, or JFrog Platform URL.');
7075
}
7176

72-
output = await Utils.runCliAndGetOutput(
73-
['eot', creds.oidcProviderName, creds.oidcTokenId, '--url', creds.jfrogUrl, '--oidc-audience', creds.oidcAudience || 'jfrog-github'],
74-
{ silent: true },
75-
);
77+
const args = ['eot', creds.oidcProviderName, creds.oidcTokenId, '--url', creds.jfrogUrl];
78+
if (creds.oidcAudience !== "") {
79+
args.push('--oidc-audience', creds.oidcAudience);
80+
}
81+
core.debug('Running CLI command: ' + args.join(' '));
82+
output = await Utils.runCliAndGetOutput(args, { silent: true });
7683

7784
const { accessToken, username }: CliExchangeTokenResponse = this.extractValuesFromOIDCToken(output);
7885
this.setOidcStepOutputs(username, accessToken);
@@ -276,21 +283,6 @@ export class OidcUtils {
276283
return await fs.readFile(configRelativePath, 'utf-8');
277284
}
278285

279-
/**
280-
* Fetches a JSON Web Token (JWT) ID token from GitHub's OIDC provider.
281-
* @param audience - The intended audience for the token.
282-
* @returns A promise that resolves to the JWT ID token as a string.
283-
* @throws An error if fetching the token fails.
284-
*/
285-
private static async getIdToken(audience: string): Promise<string> {
286-
core.debug('Attempting to fetch JSON Web Token (JWT) ID token...');
287-
try {
288-
return await core.getIDToken(audience);
289-
} catch (error: any) {
290-
throw new Error(`Failed to fetch OpenID Connect JSON Web Token: ${error.message}`);
291-
}
292-
}
293-
294286
public static isCLIVersionOidcSupported(): boolean {
295287
const version: string = core.getInput(Utils.CLI_VERSION_ARG) || '';
296288
if (version === '') {

src/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ export interface JfrogCredentials {
1212
accessToken?: string;
1313
oidcProviderName?: string;
1414
oidcTokenId?: string;
15-
oidcAudience?: string;
15+
oidcAudience : string;
1616
}
1717

1818
/**

src/utils.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,6 @@ export class Utils {
5252
public static readonly AUTO_BUILD_PUBLISH_DISABLE: string = 'disable-auto-build-publish';
5353
// Custom server ID input
5454
private static readonly CUSTOM_SERVER_ID: string = 'custom-server-id';
55-
private static DEFAULT_OIDC_AUDIENCE: string = 'jfrog-github';
5655

5756
/**
5857
* Gathers JFrog's credentials from environment variables and delivers them in a JfrogCredentials structure
@@ -66,7 +65,7 @@ export class Utils {
6665
username: process.env.JF_USER,
6766
password: process.env.JF_PASSWORD,
6867
oidcProviderName: core.getInput(Utils.OIDC_INTEGRATION_PROVIDER_NAME),
69-
oidcAudience: core.getInput(Utils.OIDC_AUDIENCE_ARG) || Utils.DEFAULT_OIDC_AUDIENCE,
68+
oidcAudience: core.getInput(Utils.OIDC_AUDIENCE_ARG) || '',
7069
oidcTokenId: '',
7170
} as JfrogCredentials;
7271

@@ -204,7 +203,6 @@ export class Utils {
204203
* @name password - JFrog Platform basic authentication
205204
* @name accessToken - Jfrog Platform access token
206205
* @name oidcProviderName - OpenID Connect provider name defined in the JFrog Platform
207-
* @name oidcAudience - JFrog Platform OpenID Connect audience
208206
*/
209207
let url: string | undefined = jfrogCredentials.jfrogUrl;
210208
let user: string | undefined = jfrogCredentials.username;

test/main.spec.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,24 @@ describe('Collect JFrog Credentials from env vars exceptions', () => {
111111
process.env['JF_PASSWORD'] = password;
112112
expect(() => Utils.collectJfrogCredentialsFromEnvVars()).toThrow(new Error(exception));
113113
});
114+
115+
test('collectJfrogCredentialsFromEnvVars should return default values when no environment variables are set', () => {
116+
// Ensure no relevant environment variables are set
117+
delete process.env['JF_URL'];
118+
delete process.env['JF_ACCESS_TOKEN'];
119+
delete process.env['JF_USER'];
120+
delete process.env['JF_PASSWORD'];
121+
122+
// Call the function
123+
const jfrogCredentials: JfrogCredentials = Utils.collectJfrogCredentialsFromEnvVars();
124+
125+
// Verify default values
126+
expect(jfrogCredentials.jfrogUrl).toBeUndefined();
127+
expect(jfrogCredentials.accessToken).toBeUndefined();
128+
expect(jfrogCredentials.username).toBeUndefined();
129+
expect(jfrogCredentials.password).toBeUndefined();
130+
expect(jfrogCredentials.oidcAudience).toEqual("")
131+
});
114132
});
115133

116134
async function testConfigCommand(expectedServerId: string) {

test/oidc-utils.spec.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ describe('OidcUtils', (): void => {
8888
it('should throw if creds are missing required fields', async (): Promise<void> => {
8989
const incompleteCreds: JfrogCredentials = {
9090
jfrogUrl: 'https://example.jfrog.io',
91+
oidcAudience: ''
9192
// missing provider and token ID
9293
};
9394

0 commit comments

Comments
 (0)