Skip to content

Commit

Permalink
Fix authentication flows (#122)
Browse files Browse the repository at this point in the history
  • Loading branch information
yahavi committed Jan 18, 2024
1 parent 37b5700 commit 69c1043
Show file tree
Hide file tree
Showing 4 changed files with 98 additions and 73 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/oidc-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ jobs:
"name": "oidc-test-identity-mapping",
"priority": "1",
"claims": {
"sub": "repo:jfrog/setup-jfrog-cli:ref:refs/heads/main",
"sub": "repo:${{ github.repository_owner }}/setup-jfrog-cli:ref:${{ github.ref }}",
"iss": "https://token.actions.githubusercontent.com"
},
"token_spec": {
Expand All @@ -72,7 +72,7 @@ jobs:

- name: Test JFrog CLI
run: |
jf rt ping
jf rt s "some-repo/"
# Removing the OIDC integration will remove the Identity Mapping as well
- name: Delete OIDC integration
Expand Down
58 changes: 38 additions & 20 deletions lib/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,14 +51,11 @@ class Utils {
static getJfrogCredentials() {
return __awaiter(this, void 0, void 0, function* () {
let jfrogCredentials = this.collectJfrogCredentialsFromEnvVars();
if (!jfrogCredentials.jfrogUrl) {
return jfrogCredentials;
}
// If the required credentials, such as the access token or a combination of username and password, are available, the process terminates without triggering the OIDC flow
if (jfrogCredentials.accessToken || (jfrogCredentials.username && jfrogCredentials.password)) {
if (!this.shouldUseOpenIDConnect(jfrogCredentials)) {
// Use JF_ENV or the credentials found in the environment variables
return jfrogCredentials;
}
core.info("JF_ACCESS_TOKEN and JF_USER + JF_PASSWORD weren't found. Getting access token using OpenID Connect");
core.info('The JFrog platform credentials were not configured. Obtaining an access token through OpenID Connect.');
const audience = core.getInput(Utils.OIDC_AUDIENCE_ARG);
let jsonWebToken;
try {
Expand All @@ -76,26 +73,44 @@ class Utils {
}
});
}
/**
* Returns true if OpenID Connect authentication should be used.
* @param jfrogCredentials - Credentials retrieved from the environment variables
* @returns true if OpenID Connect authentication should be used
*/
static shouldUseOpenIDConnect(jfrogCredentials) {
if (!process.env.ACTIONS_ID_TOKEN_REQUEST_URL) {
// To enable OpenIDConnect authentication, users must configure the 'id-token: write' permission, which sets the ACTIONS_ID_TOKEN_REQUEST_URL environment variable.
// If this variable is empty, it indicates that OIDC should not be utilized.
return false;
}
if (!jfrogCredentials.jfrogUrl) {
// If no JFrog URL is specified, we can't use OpenID Connect
return false;
}
if (jfrogCredentials.password || jfrogCredentials.accessToken) {
// If credentials are specified - use them instead
return false;
}
return true;
}
/**
* Gathers JFrog's credentials from environment variables and delivers them in a JfrogCredentials structure
* @returns JfrogCredentials struct with all credentials found in environment variables
* @throws Error if a password provided without a username
*/
static collectJfrogCredentialsFromEnvVars() {
let jfrogCredentials = {};
core.debug('Searching for JF_URL');
if (process.env.JF_URL) {
core.debug('JF_URL found');
jfrogCredentials.jfrogUrl = process.env.JF_URL;
}
core.debug('Searching for JF_ACCESS_TOKEN, JF_USER and JF_PASSWORD');
if (process.env.JF_ACCESS_TOKEN) {
core.debug('JF_ACCESS_TOKEN found');
jfrogCredentials.accessToken = process.env.JF_ACCESS_TOKEN;
let jfrogCredentials = {
jfrogUrl: process.env.JF_URL,
accessToken: process.env.JF_ACCESS_TOKEN,
username: process.env.JF_USER,
password: process.env.JF_PASSWORD,
};
if (jfrogCredentials.password && !jfrogCredentials.username) {
throw new Error('JF_PASSWORD is configured, but the JF_USER environment variable was not set.');
}
if (process.env.JF_USER && process.env.JF_PASSWORD) {
core.debug('JF_USER and JF_PASSWORD found');
jfrogCredentials.username = process.env.JF_USER;
jfrogCredentials.password = process.env.JF_PASSWORD;
if (jfrogCredentials.username && !jfrogCredentials.accessToken && !jfrogCredentials.password) {
throw new Error('JF_USER is configured, but the JF_PASSWORD or JF_ACCESS_TOKEN environment variables were not set.');
}
return jfrogCredentials;
}
Expand Down Expand Up @@ -125,6 +140,9 @@ class Utils {
const responseString = yield response.readBody();
const responseJson = JSON.parse(responseString);
jfrogCredentials.accessToken = responseJson.access_token;
if (jfrogCredentials.accessToken) {
core.setSecret(jfrogCredentials.accessToken);
}
return jfrogCredentials;
});
}
Expand Down
63 changes: 40 additions & 23 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,16 +50,12 @@ export class Utils {
*/
public static async getJfrogCredentials(): Promise<JfrogCredentials> {
let jfrogCredentials: JfrogCredentials = this.collectJfrogCredentialsFromEnvVars();
if (!jfrogCredentials.jfrogUrl) {
if (!this.shouldUseOpenIDConnect(jfrogCredentials)) {
// Use JF_ENV or the credentials found in the environment variables
return jfrogCredentials;
}

// If the required credentials, such as the access token or a combination of username and password, are available, the process terminates without triggering the OIDC flow
if (jfrogCredentials.accessToken || (jfrogCredentials.username && jfrogCredentials.password)) {
return jfrogCredentials;
}

core.info("JF_ACCESS_TOKEN and JF_USER + JF_PASSWORD weren't found. Getting access token using OpenID Connect");
core.info('The JFrog platform credentials were not configured. Obtaining an access token through OpenID Connect.');
const audience: string = core.getInput(Utils.OIDC_AUDIENCE_ARG);
let jsonWebToken: string | undefined;
try {
Expand All @@ -76,28 +72,46 @@ export class Utils {
}
}

/**
* Returns true if OpenID Connect authentication should be used.
* @param jfrogCredentials - Credentials retrieved from the environment variables
* @returns true if OpenID Connect authentication should be used
*/
private static shouldUseOpenIDConnect(jfrogCredentials: JfrogCredentials): boolean {
if (!process.env.ACTIONS_ID_TOKEN_REQUEST_URL) {
// To enable OpenIDConnect authentication, users must configure the 'id-token: write' permission, which sets the ACTIONS_ID_TOKEN_REQUEST_URL environment variable.
// If this variable is empty, it indicates that OIDC should not be utilized.
return false;
}
if (!jfrogCredentials.jfrogUrl) {
// If no JFrog URL is specified, we can't use OpenID Connect
return false;
}
if (jfrogCredentials.password || jfrogCredentials.accessToken) {
// If credentials are specified - use them instead
return false;
}
return true;
}

/**
* Gathers JFrog's credentials from environment variables and delivers them in a JfrogCredentials structure
* @returns JfrogCredentials struct with all credentials found in environment variables
* @throws Error if a password provided without a username
*/
public static collectJfrogCredentialsFromEnvVars(): JfrogCredentials {
let jfrogCredentials: JfrogCredentials = {} as JfrogCredentials;
core.debug('Searching for JF_URL');
if (process.env.JF_URL) {
core.debug('JF_URL found');
jfrogCredentials.jfrogUrl = process.env.JF_URL;
let jfrogCredentials: JfrogCredentials = {
jfrogUrl: process.env.JF_URL,
accessToken: process.env.JF_ACCESS_TOKEN,
username: process.env.JF_USER,
password: process.env.JF_PASSWORD,
} as JfrogCredentials;

if (jfrogCredentials.password && !jfrogCredentials.username) {
throw new Error('JF_PASSWORD is configured, but the JF_USER environment variable was not set.');
}

core.debug('Searching for JF_ACCESS_TOKEN, JF_USER and JF_PASSWORD');
if (process.env.JF_ACCESS_TOKEN) {
core.debug('JF_ACCESS_TOKEN found');
jfrogCredentials.accessToken = process.env.JF_ACCESS_TOKEN;
}

if (process.env.JF_USER && process.env.JF_PASSWORD) {
core.debug('JF_USER and JF_PASSWORD found');
jfrogCredentials.username = process.env.JF_USER;
jfrogCredentials.password = process.env.JF_PASSWORD;
if (jfrogCredentials.username && !jfrogCredentials.accessToken && !jfrogCredentials.password) {
throw new Error('JF_USER is configured, but the JF_PASSWORD or JF_ACCESS_TOKEN environment variables were not set.');
}
return jfrogCredentials;
}
Expand Down Expand Up @@ -130,6 +144,9 @@ export class Utils {
const responseString: string = await response.readBody();
const responseJson: TokenExchangeResponseData = JSON.parse(responseString);
jfrogCredentials.accessToken = responseJson.access_token;
if (jfrogCredentials.accessToken) {
core.setSecret(jfrogCredentials.accessToken);
}
return jfrogCredentials;
}

Expand Down
46 changes: 18 additions & 28 deletions test/main.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ describe('Collect credentials from environment variables test', () => {
];

test.each(cases)(
'Checking Jfrog credentials struct for url: %s, access token %s, username: %s, password: %s',
'Checking JFrog credentials struct for url: %s, access token %s, username: %s, password: %s',
(jfrogUrl, accessToken, username, password) => {
process.env['JF_URL'] = jfrogUrl;
process.env['JF_ACCESS_TOKEN'] = accessToken;
Expand All @@ -93,53 +93,43 @@ describe('Collect credentials from environment variables test', () => {
if (jfrogUrl) {
expect(jfrogCredentials.jfrogUrl).toEqual(jfrogUrl);
} else {
expect(jfrogCredentials.jfrogUrl).toBeUndefined();
expect(jfrogCredentials.jfrogUrl).toBeFalsy();
}

if (accessToken) {
expect(jfrogCredentials.accessToken).toEqual(accessToken);
} else {
expect(jfrogCredentials.accessToken).toBeUndefined();
expect(jfrogCredentials.accessToken).toBeFalsy();
}

if (username) {
expect(jfrogCredentials.username).toEqual(username);
} else {
expect(jfrogCredentials.username).toBeUndefined();
expect(jfrogCredentials.username).toBeFalsy();
}

if (password) {
expect(jfrogCredentials.password).toEqual(password);
} else {
expect(jfrogCredentials.password).toBeUndefined();
expect(jfrogCredentials.password).toBeFalsy();
}
},
);
});

test('Collect JFrog Credentials from env vars', async () => {
process.env['JF_URL'] = '';
let jfrogCredentials: JfrogCredentials = Utils.collectJfrogCredentialsFromEnvVars();
expect(jfrogCredentials.jfrogUrl).toBeUndefined();
expect(jfrogCredentials.username).toBeUndefined();
expect(jfrogCredentials.password).toBeUndefined();
expect(jfrogCredentials.accessToken).toBeUndefined();

process.env['JF_URL'] = 'https://my-server.io';
process.env['JF_ACCESS_TOKEN'] = 'my-access-token';
jfrogCredentials = Utils.collectJfrogCredentialsFromEnvVars();
expect(jfrogCredentials.jfrogUrl).toEqual('https://my-server.io');
expect(jfrogCredentials.username).toBeUndefined();
expect(jfrogCredentials.password).toBeUndefined();
expect(jfrogCredentials.accessToken).toEqual('my-access-token');

process.env['JF_USER'] = 'user';
process.env['JF_PASSWORD'] = 'password';
jfrogCredentials = Utils.collectJfrogCredentialsFromEnvVars();
expect(jfrogCredentials.jfrogUrl).toEqual('https://my-server.io');
expect(jfrogCredentials.username).toEqual('user');
expect(jfrogCredentials.password).toEqual('password');
expect(jfrogCredentials.accessToken).toEqual('my-access-token');
describe('Collect JFrog Credentials from env vars exceptions', () => {
let cases: string[][] = [
// [JF_USER, JF_PASSWORD, EXCEPTION]
['', 'password', 'JF_PASSWORD is configured, but the JF_USER environment variable was not set.'],
['user', '', 'JF_USER is configured, but the JF_PASSWORD or JF_ACCESS_TOKEN environment variables were not set.'],
];

test.each(cases)('Checking JFrog credentials struct for username: %s, password: %s', (username, password, exception) => {
process.env['JF_ACCESS_TOKEN'] = '';
process.env['JF_USER'] = username;
process.env['JF_PASSWORD'] = password;
expect(() => Utils.collectJfrogCredentialsFromEnvVars()).toThrow(new Error(exception));
});
});

test('Get separate env config', async () => {
Expand Down

0 comments on commit 69c1043

Please sign in to comment.