Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
12 changes: 9 additions & 3 deletions .github/workflows/cli-oidc-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,15 @@ jobs:
JF_URL: ${{ secrets.JFROG_PLATFORM_URL }}
with:
oidc-provider-name: setup-jfrog-cli-test
# This can be removed after the default CLI version will be updated
version: 2.75.0

- name: Test JFrog CLI
run: |
jf rt ping
jf rt ping

- name: Test User Output
shell: bash
run: test -n "${{ steps.setup-jfrog-cli.outputs.oidc-user }}"

- name: Test Token Output
shell: bash
run: test -n "${{ steps.setup-jfrog-cli.outputs.oidc-token }}"
2 changes: 0 additions & 2 deletions .github/workflows/manual-oidc-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,6 @@ jobs:
JF_URL: ${{ secrets.JFROG_PLATFORM_URL }}
with:
oidc-provider-name: ${{ env.OIDC_PROVIDER_NAME }}
# The last version which outputs OIDC params as step outputs
version: '2.74.1'

- name: Test JFrog CLI
run: |
Expand Down
2 changes: 1 addition & 1 deletion action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ author: "JFrog"
inputs:
version:
description: "JFrog CLI Version"
default: "2.74.1"
default: "2.75.0"
required: false
download-repository:
description: "Remote repository in Artifactory pointing to 'https://releases.jfrog.io/artifactory/jfrog-cli'. Use this parameter in case you don't have an Internet access."
Expand Down
105 changes: 69 additions & 36 deletions lib/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Utils = void 0;
exports.parseInput = parseInput;
const core = __importStar(require("@actions/core"));
const exec_1 = require("@actions/exec");
const http_client_1 = require("@actions/http-client");
Expand Down Expand Up @@ -291,42 +292,49 @@ class Utils {
* @param jfrogCredentials existing JFrog credentials - url, access token, username + password
*/
static getSeparateEnvConfigArgs(jfrogCredentials) {
/**
* @name url - JFrog Platform URL
* @name user - JFrog Platform basic authentication
* @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;
let password = jfrogCredentials.password;
let accessToken = jfrogCredentials.accessToken;
let oidcProviderName = jfrogCredentials.oidcProviderName;
let oidcTokenId = jfrogCredentials.oidcTokenId;
let oidcAudience = jfrogCredentials.oidcAudience;
// Url is mandatory for JFrog CLI configuration
if (!url) {
return;
}
const configCmd = [Utils.getServerIdForConfig(), '--url', url, '--interactive=false', '--overwrite=true'];
// OIDC auth
if (this.isCLIVersionOidcSupported() && !!oidcProviderName) {
configCmd.push(`--oidc-provider-name=${oidcProviderName}`);
configCmd.push('--oidc-provider-type=Github');
configCmd.push(`--oidc-token-id=${oidcTokenId}`);
configCmd.push(`--oidc-audience=${oidcAudience}`);
return __awaiter(this, void 0, void 0, function* () {
/**
* @name url - JFrog Platform URL
* @name user - JFrog Platform basic authentication
* @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;
let password = jfrogCredentials.password;
let accessToken = jfrogCredentials.accessToken;
let oidcProviderName = jfrogCredentials.oidcProviderName;
let oidcTokenId = jfrogCredentials.oidcTokenId;
let oidcAudience = jfrogCredentials.oidcAudience;
// Url is mandatory for JFrog CLI configuration
if (!url) {
return;
}
// OIDC
if (!!oidcProviderName && !!oidcTokenId) {
core.info('calling EOT ! ');
let output = yield (0, exec_1.getExecOutput)('jf', ['eot', oidcProviderName, oidcTokenId, '--url', url, '--oidc-audience', oidcAudience], { silent: true, ignoreReturnCode: true });
const { accessToken, username } = parseInput(output.stdout);
// Sets the OIDC token as access token to be used in config.
core.info('setting as secret');
core.setSecret('oidc-token');
core.setOutput('oidc-token', username);
core.setSecret('oidc-user');
core.setOutput('oidc-user', accessToken);
}
const configCmd = [Utils.getServerIdForConfig(), '--url', url, '--interactive=false', '--overwrite=true'];
// Access Token
}
else if (!!accessToken) {
configCmd.push('--access-token', accessToken);
// Basic Auth
}
else if (!!user && !!password) {
configCmd.push('--user', user, '--password', password);
}
return configCmd;
if (!!accessToken) {
configCmd.push('--access-token', accessToken);
// Basic Auth
}
else if (!!user && !!password) {
configCmd.push('--user', user, '--password', password);
}
return configCmd;
});
}
/**
* Get server ID for JFrog CLI configuration. Save the server ID in the servers env var if it doesn't already exist.
Expand Down Expand Up @@ -413,7 +421,7 @@ class Utils {
core.setSecret(configToken);
yield Utils.runCli(cliConfigCmd.concat('import', configToken));
}
let configArgs = Utils.getSeparateEnvConfigArgs(jfrogCredentials);
let configArgs = yield Utils.getSeparateEnvConfigArgs(jfrogCredentials);
if (configArgs) {
yield Utils.runCli(cliConfigCmd.concat('add', ...configArgs));
}
Expand Down Expand Up @@ -1023,3 +1031,28 @@ Utils.METRIC_PARAM_KEY = 'm';
Utils.METRIC_PARAM_VALUE = '1';
Utils.MIN_CLI_OIDC_VERSION = '2.75.0';
Utils.DEFAULT_OIDC_AUDIENCE = 'jfrog-github';
function parseInput(input) {
try {
// Attempt to parse as JSON
const parsed = JSON.parse(input);
if (parsed.AccessToken && parsed.Username) {
return {
accessToken: parsed.AccessToken,
username: parsed.Username,
};
}
throw new Error('JSON does not contain required fields.');
}
catch (_a) {
// Fallback to regex extraction
const regex = /AccessToken:\s*(\S+)\s*Username:\s*(\S+)/;
const match = regex.exec(input);
if (!match) {
throw new Error('Failed to extract values. Input format is invalid.');
}
return {
accessToken: match[1],
username: match[2],
};
}
}
58 changes: 48 additions & 10 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -359,7 +359,7 @@ export class Utils {
* Get separate env config for the URL and connection details and return args to add to the config add command
* @param jfrogCredentials existing JFrog credentials - url, access token, username + password
*/
public static getSeparateEnvConfigArgs(jfrogCredentials: JfrogCredentials): string[] | undefined {
public static async getSeparateEnvConfigArgs(jfrogCredentials: JfrogCredentials): Promise<string[] | undefined> {
/**
* @name url - JFrog Platform URL
* @name user - JFrog Platform basic authentication
Expand All @@ -381,15 +381,26 @@ export class Utils {
return;
}

// OIDC
if (!!oidcProviderName && !!oidcTokenId) {
core.info('calling EOT ! ');
let output: ExecOutput = await getExecOutput(
'jf',
['eot', oidcProviderName, oidcTokenId, '--url', url, '--oidc-audience', oidcAudience],
{ silent: true, ignoreReturnCode: true },
);
const { accessToken, username }: { accessToken: string; username: string } = parseInput(output.stdout);
// Sets the OIDC token as access token to be used in config.
core.info('setting as secret');
core.setSecret('oidc-token');
core.setOutput('oidc-token', username);
core.setSecret('oidc-user');
core.setOutput('oidc-user', accessToken);
}

const configCmd: string[] = [Utils.getServerIdForConfig(), '--url', url, '--interactive=false', '--overwrite=true'];
// OIDC auth
if (this.isCLIVersionOidcSupported() && !!oidcProviderName) {
configCmd.push(`--oidc-provider-name=${oidcProviderName}`);
configCmd.push('--oidc-provider-type=Github');
configCmd.push(`--oidc-token-id=${oidcTokenId}`);
configCmd.push(`--oidc-audience=${oidcAudience}`);
// Access Token
} else if (!!accessToken) {
// Access Token
if (!!accessToken) {
configCmd.push('--access-token', accessToken);
// Basic Auth
} else if (!!user && !!password) {
Expand Down Expand Up @@ -500,7 +511,7 @@ export class Utils {
await Utils.runCli(cliConfigCmd.concat('import', configToken));
}

let configArgs: string[] | undefined = Utils.getSeparateEnvConfigArgs(jfrogCredentials);
let configArgs: string[] | undefined = await Utils.getSeparateEnvConfigArgs(jfrogCredentials);
if (configArgs) {
await Utils.runCli(cliConfigCmd.concat('add', ...configArgs));
}
Expand Down Expand Up @@ -1040,6 +1051,33 @@ export class Utils {
}
}

export function parseInput(input: string): { accessToken: string; username: string } {
try {
// Attempt to parse as JSON
const parsed: { AccessToken?: string; Username?: string } = JSON.parse(input);
if (parsed.AccessToken && parsed.Username) {
return {
accessToken: parsed.AccessToken,
username: parsed.Username,
};
}
throw new Error('JSON does not contain required fields.');
} catch {
// Fallback to regex extraction
const regex: RegExp = /AccessToken:\s*(\S+)\s*Username:\s*(\S+)/;
const match: RegExpMatchArray | null = regex.exec(input);

if (!match) {
throw new Error('Failed to extract values. Input format is invalid.');
}

return {
accessToken: match[1],
username: match[2],
};
}
}

export interface DownloadDetails {
artifactoryUrl: string;
repository: string;
Expand Down
Loading
Loading