Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
a9ee69b
add logs
EyalDelarea Apr 29, 2025
799aeb9
add logs
EyalDelarea Apr 29, 2025
a3ecdb4
Default audience
EyalDelarea Apr 29, 2025
a765ad8
dont pass audience to exchange command
EyalDelarea Apr 29, 2025
cc0efe9
Change audience default value
EyalDelarea Apr 30, 2025
119f08f
Test
EyalDelarea Apr 30, 2025
5f4b8e1
Test
EyalDelarea Apr 30, 2025
44c0113
Test
EyalDelarea May 4, 2025
c5a21ca
Test
EyalDelarea May 4, 2025
bbafc02
Test
EyalDelarea May 4, 2025
c49d377
Test
EyalDelarea May 4, 2025
7640918
Extract audience values
EyalDelarea May 4, 2025
abe4d6d
Extract audience values
EyalDelarea May 4, 2025
f48402a
test
EyalDelarea May 4, 2025
7cf343a
test
EyalDelarea May 4, 2025
308692b
test
EyalDelarea May 4, 2025
e6c5b6f
Update test workflow
EyalDelarea May 4, 2025
394b82e
Update workflow
EyalDelarea May 4, 2025
10390a3
Update description
EyalDelarea May 4, 2025
0969cbd
Run local tests
EyalDelarea May 4, 2025
74a5010
Run local tests
EyalDelarea May 4, 2025
ae3fcd9
Test
EyalDelarea May 4, 2025
1b779a5
Test
EyalDelarea May 4, 2025
9173db9
Ready to merge
EyalDelarea May 4, 2025
ca3e7a8
CR
EyalDelarea May 4, 2025
84750a1
CR
EyalDelarea May 4, 2025
e9c6e84
Fix matrix
EyalDelarea May 4, 2025
8a0f100
Fix matrix
EyalDelarea May 4, 2025
e58752c
Remove unneeded step
EyalDelarea May 4, 2025
4e87fea
Test change default
EyalDelarea May 4, 2025
8a6cf1c
Test change default
EyalDelarea May 4, 2025
6b1493b
Test
EyalDelarea May 4, 2025
41b1c2d
Test
EyalDelarea May 4, 2025
b7bf892
Test
EyalDelarea May 4, 2025
e3d8977
Test
EyalDelarea May 4, 2025
7ee09c3
Test
EyalDelarea May 4, 2025
a8703c2
local test
EyalDelarea May 4, 2025
8341c09
case sensitive default github
EyalDelarea May 4, 2025
c123c18
Test all cases locally
EyalDelarea May 4, 2025
b634528
Fix image name
EyalDelarea May 4, 2025
0f899df
Prepare workflow for merge
EyalDelarea May 4, 2025
8d6dd55
test locally
EyalDelarea May 4, 2025
6175f37
Edit comments
EyalDelarea May 5, 2025
99d04f2
Test renames
EyalDelarea May 5, 2025
48afeaa
Prepare action for merge
EyalDelarea May 5, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
121 changes: 85 additions & 36 deletions .github/workflows/oidc-integration-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ name: OIDC Integration Test
# - 2.74.1: Does not support `jf eot` command, validates manual fallback logic.
# - 2.75.0: Introduced native OIDC token exchange.
# - Latest: Ensures ongoing compatibility with the most recent CLI build.

on:
push:
branches:
Expand All @@ -23,54 +22,51 @@ permissions:
contents: read

jobs:
oidc-test:
generate-platform-oidc-integration:
strategy:
fail-fast: false
# Using "include" instead of a matrix of arrays gives us fine-grained control over test combinations.
# This is needed because some audience values (e.g., URLs) contain characters not valid in matrix keys or job names.
#
# Each scenario represents a real-world case:
# - "default": No audience is set in the action or the platform integration.
# - "test": A custom audience is explicitly set in both the action and the platform integration.
# - "github-implicit-default": The platform integration is explicitly configured with GitHub's default audience,
# but the action does not pass any audience.
# This tests CLI behavior in case of mismatches — see https://github.com/jfrog/setup-jfrog-cli/issues/270
matrix:
os: [ ubuntu, macos, windows ]
cli-version: [ '2.74.1', '2.75.0','latest' ]
runs-on: ${{ matrix.os }}-latest
name: OIDC Test - ${{ matrix.cli-version }} on ${{ matrix.os }}
env:
JFROG_CLI_LOG_LEVEL: DEBUG

include:
- audience_id: default
audience_value: ''
- audience_id: test
audience_value: 'audience-value'
- audience_id: github-implicit-default
audience_value: 'https://github.com/jfrog'
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha }}

# Setup OIDC platform integration
- name: Generate unique OIDC provider name
id: gen-oidc
shell: bash
run: |
cli_version="${{ matrix.cli-version }}" && cli_version="${cli_version//./-}"
echo "oidc_provider_name=oidc-integration-${cli_version}-${{ matrix.os }}-$(date +%s)" >> "$GITHUB_OUTPUT"

- name: Create OpenID Connect integration
shell: bash
run: |
curl -X POST "${{ secrets.JFROG_PLATFORM_URL }}/access/api/v1/oidc" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ${{ secrets.JFROG_PLATFORM_RT_TOKEN }}" \
-d '{
"name": "${{ steps.gen-oidc.outputs.oidc_provider_name }}",
"name": "oidc-integration-${{ matrix.audience_id }}-${{ github.run_id }}",
"issuer_url": "https://token.actions.githubusercontent.com",
"provider_type": "GitHub",
"enable_permissive_configuration": "true",
"description": "Test configuration for CLI version ${{ matrix.cli-version }}"
"audience": "${{ matrix.audience_value }}",
"enable_permissive_configuration": true,
"description": "Temp integration for testing OIDC with audience value: ${{ matrix.audience_value }}"
}'

- name: Create OIDC Identity Mapping
shell: bash
run: |
curl -X POST "${{ secrets.JFROG_PLATFORM_URL }}/access/api/v1/oidc/${{ steps.gen-oidc.outputs.oidc_provider_name }}/identity_mappings" \
-H 'Content-Type: application/json' \
curl -X POST "${{ secrets.JFROG_PLATFORM_URL }}/access/api/v1/oidc/oidc-integration-${{ matrix.audience_id }}-${{ github.run_id }}/identity_mappings" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ${{ secrets.JFROG_PLATFORM_RT_TOKEN }}" \
-d '{
"name": "oidc-test-mapping",
"priority": "1",
"priority": 1,
"claims": {
"repository": "${{ github.repository_owner }}/setup-jfrog-cli"
},
Expand All @@ -80,21 +76,65 @@ jobs:
}
}'

# Setup
oidc-test:
needs: generate-platform-oidc-integration
strategy:
fail-fast: false
# Using include allows exact combinations of CLI version and audience ID to ensure coverage of edge cases.
# This avoids invalid audience strings in identifiers and ensures fallback logic is tested selectively.
matrix:
include:
- cli-version: '2.74.1'
audience_id: default
audience_value: ''
- cli-version: '2.75.0'
audience_id: default
audience_value: ''
- cli-version: latest
audience_id: default
audience_value: ''
- cli-version: '2.74.1'
audience_id: test
audience_value: 'audience-value'
- cli-version: '2.75.0'
audience_id: test
audience_value: 'audience-value'
- cli-version: latest
audience_id: test
audience_value: 'audience-value'
# GitHub default audience value is resolved implicitly when omitted.
# These tests verify that the CLI handles an empty value correctly while GitHub sets the expected audience on its backend.
- cli-version: '2.74.1'
audience_id: github-implicit-default
audience_value: ''
- cli-version: '2.75.0'
audience_id: github-implicit-default
audience_value: ''
- cli-version: latest
audience_id: github-implicit-default
audience_value: ''
runs-on: ubuntu-latest
env:
JFROG_CLI_LOG_LEVEL: DEBUG
steps:
- name: Checkout Repository
uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha }}

- name: Setup JFrog CLI
id: setup-jfrog-cli
uses: ./
env:
JF_URL: ${{ secrets.JFROG_PLATFORM_URL }}
with:
version: ${{ matrix.cli-version }}
oidc-provider-name: ${{ steps.gen-oidc.outputs.oidc_provider_name }}
oidc-provider-name: oidc-integration-${{ matrix.audience_id }}-${{ github.run_id }}
oidc-audience: ${{ matrix.audience_value }}

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

# Validate step outputs
- name: Validate user output
shell: bash
run: test -n "${{ steps.setup-jfrog-cli.outputs.oidc-user }}"
Expand All @@ -103,10 +143,19 @@ jobs:
shell: bash
run: test -n "${{ steps.setup-jfrog-cli.outputs.oidc-token }}"

# Cleanup
cleanup-oidc-integration:
needs: oidc-test
if: always()
strategy:
matrix:
include:
- audience_id: default
- audience_id: test
- audience_id: github-implicit-default
runs-on: ubuntu-latest
steps:
- name: Delete OIDC integration
shell: bash
if: always()
run: |
curl -X DELETE "${{ secrets.JFROG_PLATFORM_URL }}/access/api/v1/oidc/${{ steps.gen-oidc.outputs.oidc_provider_name }}" \
curl -X DELETE "${{ secrets.JFROG_PLATFORM_URL }}/access/api/v1/oidc/oidc-integration-${{ matrix.audience_id }}-${{ github.run_id }}" \
-H "Authorization: Bearer ${{ secrets.JFROG_PLATFORM_RT_TOKEN }}"
32 changes: 13 additions & 19 deletions lib/oidc-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,13 @@ class OidcUtils {
throw new Error(`JF_URL must be provided when oidc-provider-name is specified`);
}
// Get OIDC token ID from GitHub
jfrogCredentials.oidcTokenId = yield this.getIdToken(jfrogCredentials.oidcAudience || '');
try {
core.debug('Attempting to fetch JSON Web Token (JWT) ID token with audience value: ' + jfrogCredentials.oidcAudience);
jfrogCredentials.oidcTokenId = yield core.getIDToken(jfrogCredentials.oidcAudience);
}
catch (error) {
throw new Error(`Failed to fetch OpenID Connect JSON Web Token: ${error.message}`);
}
// Version should be more than min version
// If CLI_REMOTE_ARG specified, we have to fetch token before we can download the CLI.
if (this.isCLIVersionOidcSupported() && !core.getInput(utils_1.Utils.CLI_REMOTE_ARG)) {
Expand All @@ -90,7 +96,12 @@ class OidcUtils {
if (creds.oidcProviderName === undefined || creds.oidcTokenId === undefined || creds.jfrogUrl === undefined) {
throw new Error('Missing one or more required fields: OIDC provider name, token ID, or JFrog Platform URL.');
}
output = yield utils_1.Utils.runCliAndGetOutput(['eot', creds.oidcProviderName, creds.oidcTokenId, '--url', creds.jfrogUrl, '--oidc-audience', creds.oidcAudience || 'jfrog-github'], { silent: true });
const args = ['eot', creds.oidcProviderName, creds.oidcTokenId, '--url', creds.jfrogUrl];
if (creds.oidcAudience !== "") {
args.push('--oidc-audience', creds.oidcAudience);
}
core.debug('Running CLI command: ' + args.join(' '));
output = yield utils_1.Utils.runCliAndGetOutput(args, { silent: true });
const { accessToken, username } = this.extractValuesFromOIDCToken(output);
this.setOidcStepOutputs(username, accessToken);
return accessToken;
Expand Down Expand Up @@ -284,23 +295,6 @@ class OidcUtils {
return yield fs_1.promises.readFile(configRelativePath, 'utf-8');
});
}
/**
* Fetches a JSON Web Token (JWT) ID token from GitHub's OIDC provider.
* @param audience - The intended audience for the token.
* @returns A promise that resolves to the JWT ID token as a string.
* @throws An error if fetching the token fails.
*/
static getIdToken(audience) {
return __awaiter(this, void 0, void 0, function* () {
core.debug('Attempting to fetch JSON Web Token (JWT) ID token...');
try {
return yield core.getIDToken(audience);
}
catch (error) {
throw new Error(`Failed to fetch OpenID Connect JSON Web Token: ${error.message}`);
}
});
}
static isCLIVersionOidcSupported() {
const version = core.getInput(utils_1.Utils.CLI_VERSION_ARG) || '';
if (version === '') {
Expand Down
4 changes: 1 addition & 3 deletions lib/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ class Utils {
username: process.env.JF_USER,
password: process.env.JF_PASSWORD,
oidcProviderName: core.getInput(Utils.OIDC_INTEGRATION_PROVIDER_NAME),
oidcAudience: core.getInput(Utils.OIDC_AUDIENCE_ARG) || Utils.DEFAULT_OIDC_AUDIENCE,
oidcAudience: core.getInput(Utils.OIDC_AUDIENCE_ARG) || '',
oidcTokenId: '',
};
if (jfrogCredentials.password && !jfrogCredentials.username) {
Expand Down Expand Up @@ -184,7 +184,6 @@ class Utils {
* @name password - JFrog Platform basic authentication
* @name accessToken - Jfrog Platform access token
* @name oidcProviderName - OpenID Connect provider name defined in the JFrog Platform
* @name oidcAudience - JFrog Platform OpenID Connect audience
*/
let url = jfrogCredentials.jfrogUrl;
let user = jfrogCredentials.username;
Expand Down Expand Up @@ -474,4 +473,3 @@ Utils.JOB_SUMMARY_DISABLE = 'disable-job-summary';
Utils.AUTO_BUILD_PUBLISH_DISABLE = 'disable-auto-build-publish';
// Custom server ID input
Utils.CUSTOM_SERVER_ID = 'custom-server-id';
Utils.DEFAULT_OIDC_AUDIENCE = 'jfrog-github';
32 changes: 12 additions & 20 deletions src/oidc-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,12 @@ export class OidcUtils {
throw new Error(`JF_URL must be provided when oidc-provider-name is specified`);
}
// Get OIDC token ID from GitHub
jfrogCredentials.oidcTokenId = await this.getIdToken(jfrogCredentials.oidcAudience || '');
try {
core.debug('Attempting to fetch JSON Web Token (JWT) ID token with audience value: ' + jfrogCredentials.oidcAudience);
jfrogCredentials.oidcTokenId = await core.getIDToken(jfrogCredentials.oidcAudience);
} catch (error: any) {
throw new Error(`Failed to fetch OpenID Connect JSON Web Token: ${error.message}`);
}

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

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

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

/**
* Fetches a JSON Web Token (JWT) ID token from GitHub's OIDC provider.
* @param audience - The intended audience for the token.
* @returns A promise that resolves to the JWT ID token as a string.
* @throws An error if fetching the token fails.
*/
private static async getIdToken(audience: string): Promise<string> {
core.debug('Attempting to fetch JSON Web Token (JWT) ID token...');
try {
return await core.getIDToken(audience);
} catch (error: any) {
throw new Error(`Failed to fetch OpenID Connect JSON Web Token: ${error.message}`);
}
}

public static isCLIVersionOidcSupported(): boolean {
const version: string = core.getInput(Utils.CLI_VERSION_ARG) || '';
if (version === '') {
Expand Down
2 changes: 1 addition & 1 deletion src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export interface JfrogCredentials {
accessToken?: string;
oidcProviderName?: string;
oidcTokenId?: string;
oidcAudience?: string;
oidcAudience : string;
}

/**
Expand Down
4 changes: 1 addition & 3 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@ export class Utils {
public static readonly AUTO_BUILD_PUBLISH_DISABLE: string = 'disable-auto-build-publish';
// Custom server ID input
private static readonly CUSTOM_SERVER_ID: string = 'custom-server-id';
private static DEFAULT_OIDC_AUDIENCE: string = 'jfrog-github';

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

Expand Down Expand Up @@ -204,7 +203,6 @@ export class Utils {
* @name password - JFrog Platform basic authentication
* @name accessToken - Jfrog Platform access token
* @name oidcProviderName - OpenID Connect provider name defined in the JFrog Platform
* @name oidcAudience - JFrog Platform OpenID Connect audience
*/
let url: string | undefined = jfrogCredentials.jfrogUrl;
let user: string | undefined = jfrogCredentials.username;
Expand Down
18 changes: 18 additions & 0 deletions test/main.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,24 @@ describe('Collect JFrog Credentials from env vars exceptions', () => {
process.env['JF_PASSWORD'] = password;
expect(() => Utils.collectJfrogCredentialsFromEnvVars()).toThrow(new Error(exception));
});

test('collectJfrogCredentialsFromEnvVars should return default values when no environment variables are set', () => {
// Ensure no relevant environment variables are set
delete process.env['JF_URL'];
delete process.env['JF_ACCESS_TOKEN'];
delete process.env['JF_USER'];
delete process.env['JF_PASSWORD'];

// Call the function
const jfrogCredentials: JfrogCredentials = Utils.collectJfrogCredentialsFromEnvVars();

// Verify default values
expect(jfrogCredentials.jfrogUrl).toBeUndefined();
expect(jfrogCredentials.accessToken).toBeUndefined();
expect(jfrogCredentials.username).toBeUndefined();
expect(jfrogCredentials.password).toBeUndefined();
expect(jfrogCredentials.oidcAudience).toEqual("")
});
});

async function testConfigCommand(expectedServerId: string) {
Expand Down
1 change: 1 addition & 0 deletions test/oidc-utils.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ describe('OidcUtils', (): void => {
it('should throw if creds are missing required fields', async (): Promise<void> => {
const incompleteCreds: JfrogCredentials = {
jfrogUrl: 'https://example.jfrog.io',
oidcAudience: ''
// missing provider and token ID
};

Expand Down
Loading