Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Adding OpenID Connect (OIDC) implementation for Service Connections. #520

Open
wants to merge 3 commits into
base: dev
Choose a base branch
from
Open
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
617 changes: 390 additions & 227 deletions README.md

Large diffs are not rendered by default.

Binary file added images/oidc-integration.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/oidc-json-mapping.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/oidc-service-connection.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 2 additions & 1 deletion jfrog-tasks-utils/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
"azure-pipelines-task-lib": "4.5.0",
"azure-pipelines-tool-lib": "2.0.6",
"azure-pipelines-tasks-java-common": "^2.219.1",
"typed-rest-client": "^1.8.11"
"typed-rest-client": "^1.8.11",
"sync-request": "^6.1.0"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
Expand Down
77 changes: 76 additions & 1 deletion jfrog-tasks-utils/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ const buildAgent = 'jfrog-azure-devops-extension';
const customFolderPath = encodePath(join(jfrogFolderPath, 'current'));
const customCliPath = encodePath(join(customFolderPath, fileName)); // Optional - Customized jfrog-cli path.
const jfrogCliReleasesUrl = 'https://releases.jfrog.io/artifactory/jfrog-cli/v2-jf';

const request = require('sync-request');
// Set by Tools Installer Task. This JFrog CLI version will be used in all tasks unless manual installation is used,
// or a specific version was requested in a task. If not set, use the default CLI version.
const pipelineRequestedCliVersionEnv = 'JFROG_CLI_PIPELINE_REQUESTED_VERSION_AZURE';
Expand Down Expand Up @@ -252,15 +252,90 @@ function configureDistributionCliServer(distributionService, serverId, cliPath,
function configureXrayCliServer(xrayService, serverId, cliPath, buildDir) {
return configureSpecificCliServer(xrayService, '--xray-url', serverId, cliPath, buildDir);
}
function logIDToken(oidcToken) {
const oidcClaims = JSON.parse(Buffer.from(oidcToken.split('.')[1], 'base64').toString());
console.log('OIDC Token Subject: ', oidcClaims.sub);
console.log(`OIDC Token Claims: {"sub": "${oidcClaims.sub}"}`);
console.log('OIDC Token Issuer (Provider URL): ', oidcClaims.iss);
console.log('OIDC Token Audience: ', oidcClaims.aud);
}

function getADOIdToken(serviceConnectionID) {
const uri = tl.getVariable('System.CollectionUri');
const teamPrjID = tl.getVariable('System.TeamProjectId');
const hub = tl.getVariable('System.HostType');
const planID = tl.getVariable('System.PlanId');
const jobID = tl.getVariable('System.JobId');
const apiVersion = '7.1-preview.1';

const url = `${uri}${teamPrjID}/_apis/distributedtask/hubs/${hub}/plans/${planID}/jobs/${jobID}/oidctoken?api-version=${apiVersion}&serviceConnectionId=${serviceConnectionID}`;

try {
const response = request('POST', url, {
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${tl.getVariable('System.AccessToken')}`,
},
});

if (response.statusCode !== 200) {
throw new Error(`HTTP request failed with status code ${response.statusCode}`);
}

const parsedResponse = JSON.parse(response.getBody('utf8'));
const idToken = parsedResponse.oidcToken;
logIDToken(idToken);
return idToken;
} catch (error) {
throw new Error(`Failed to get or parse response: ${error.message}`);
}
}

function getArtifactoryAccessToken(idToken, oidcProviderName, jfrogPlatformUrl) {
const payload = {
grant_type: 'urn:ietf:params:oauth:grant-type:token-exchange',
subject_token_type: 'urn:ietf:params:oauth:token-type:id_token',
subject_token: idToken,
provider_name: oidcProviderName,
};

const url = `${jfrogPlatformUrl}/access/api/v1/oidc/token`;

try {
const response = request('POST', url, {
headers: {
'Content-Type': 'application/json',
},
json: payload,
});

if (response.statusCode !== 200) {
throw new Error(`HTTP request failed with status code ${response.statusCode}: ${response.getBody('utf8')}`);
}

const parsedResponse = JSON.parse(response.getBody('utf8'));
return parsedResponse.access_token;
} catch (error) {
throw new Error(`Failed to get or parse response: ${error.message}`);
}
}

function configureSpecificCliServer(service, urlFlag, serverId, cliPath, buildDir) {
let serviceUrl = tl.getEndpointUrl(service, false);
let serviceUser = tl.getEndpointAuthorizationParameter(service, 'username', true);
let servicePassword = tl.getEndpointAuthorizationParameter(service, 'password', true);
let serviceAccessToken = tl.getEndpointAuthorizationParameter(service, 'apitoken', true);
let oidcProviderName = tl.getEndpointAuthorizationParameter(service, 'oidcProviderName', true);
let jfrogPlatformUrl = tl.getEndpointAuthorizationParameter(service, 'jfrogPlatformUrl', true);
let cliCommand = cliJoin(cliPath, jfrogCliConfigAddCommand, quote(serverId), urlFlag + '=' + quote(serviceUrl), '--interactive=false');
let stdinSecret;
let secretInStdinSupported = isStdinSecretSupported();

if (oidcProviderName) {
const idToken = getADOIdToken(service);
serviceAccessToken = getArtifactoryAccessToken(idToken, oidcProviderName, jfrogPlatformUrl);
}

if (serviceAccessToken) {
// Add access-token if required.
cliCommand = cliJoin(cliCommand, secretInStdinSupported ? '--access-token-stdin' : '--access-token=' + quote(serviceAccessToken));
Expand Down
132 changes: 128 additions & 4 deletions vss-extension.json
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,37 @@
}
}
]
},
{
"type": "ms.vss-endpoint.endpoint-auth-scheme-none",
"displayName": "OpenID Connect Integration",
"properties": {
"isVerifiable": "False"
},
"inputDescriptors": [
{
"id": "oidcProviderName",
"name": "OpenID Connect Provider Name",
"description": "The OpenID Connect \"Provider Name\" configured in JFrog Platform. Click <a href=\"https://github.com/jfrog/jfrog-azure-devops-extension/tree/v2?tab=readme-ov-file#using-openid-connect-oidc-authentication\" target=\"_blank\">here</a> for information about how to configure OpenID Connect.",
"inputMode": "textbox",
"isConfidential": false,
"validation": {
"isRequired": true,
"dataType": "string"
}
},
{
"id": "jfrogPlatformUrl",
"name": "Platform URL",
"description": "The access token will be obtained from this URL. For example, https://my.jfrog.io/",
"inputMode": "textbox",
"isConfidential": false,
"validation": {
"isRequired": true,
"dataType": "string"
}
}
]
}
]
}
Expand All @@ -165,7 +196,7 @@
"displayName": "JFrog Artifactory V2",
"url": {
"displayName": "Server URL",
"helpText": "Specify the root URL of your Artifactory installation. For example, https://repo.jfrog.org/artifactory"
"helpText": "Specify the root URL of your Artifactory installation. For example, https://my.jfrog.io/artifactory"
},
"icon": "images/artifactory.png",
"dataSources": [
Expand Down Expand Up @@ -234,6 +265,37 @@
}
}
]
},
{
"type": "ms.vss-endpoint.endpoint-auth-scheme-none",
"displayName": "OpenID Connect Integration",
"properties": {
"isVerifiable": "False"
},
"inputDescriptors": [
{
"id": "oidcProviderName",
"name": "OpenID Connect Provider Name",
"description": "The OpenID Connect \"Provider Name\" configured in JFrog Platform. Click <a href=\"https://github.com/jfrog/jfrog-azure-devops-extension/tree/v2?tab=readme-ov-file#using-openid-connect-oidc-authentication\" target=\"_blank\">here</a> for information about how to configure OpenID Connect.",
"inputMode": "textbox",
"isConfidential": false,
"validation": {
"isRequired": true,
"dataType": "string"
}
},
{
"id": "jfrogPlatformUrl",
"name": "Platform URL",
"description": "The access token will be obtained from this URL. For example, https://my.jfrog.io/",
"inputMode": "textbox",
"isConfidential": false,
"validation": {
"isRequired": true,
"dataType": "string"
}
}
]
}
]
}
Expand All @@ -250,7 +312,7 @@
"displayName": "JFrog Distribution V2",
"url": {
"displayName": "Server URL",
"helpText": "Specify the root URL of your Distribution installation. For example, https://repo.jfrog.org/distribution"
"helpText": "Specify the root URL of your Distribution installation. For example, https://my.jfrog.io/distribution"
},
"icon": "images/distribution.png",
"dataSources": [
Expand Down Expand Up @@ -309,6 +371,37 @@
}
}
]
},
{
"type": "ms.vss-endpoint.endpoint-auth-scheme-none",
"displayName": "OpenID Connect Integration",
"properties": {
"isVerifiable": "False"
},
"inputDescriptors": [
{
"id": "oidcProviderName",
"name": "OpenID Connect Provider Name",
"description": "The OpenID Connect \"Provider Name\" configured in JFrog Platform. Click <a href=\"https://github.com/jfrog/jfrog-azure-devops-extension/tree/v2?tab=readme-ov-file#using-openid-connect-oidc-authentication\" target=\"_blank\">here</a> for information about how to configure OpenID Connect.",
"inputMode": "textbox",
"isConfidential": false,
"validation": {
"isRequired": true,
"dataType": "string"
}
},
{
"id": "jfrogPlatformUrl",
"name": "Platform URL",
"description": "The access token will be obtained from this URL. For example, https://my.jfrog.io/",
"inputMode": "textbox",
"isConfidential": false,
"validation": {
"isRequired": true,
"dataType": "string"
}
}
]
}
]
}
Expand All @@ -325,7 +418,7 @@
"displayName": "JFrog Xray V2",
"url": {
"displayName": "Server URL",
"helpText": "Specify the root URL of your Xray installation. For example, https://repo.jfrog.org/xray"
"helpText": "Specify the root URL of your Xray installation. For example, https://my.jfrog.io/xray"
},
"icon": "images/xray.png",
"dataSources": [
Expand Down Expand Up @@ -384,6 +477,37 @@
}
}
]
},
{
"type": "ms.vss-endpoint.endpoint-auth-scheme-none",
"displayName": "OpenID Connect Integration",
"properties": {
"isVerifiable": "False"
},
"inputDescriptors": [
{
"id": "oidcProviderName",
"name": "OpenID Connect Provider Name",
"description": "The OpenID Connect \"Provider Name\" configured in JFrog Platform. Click <a href=\"https://github.com/jfrog/jfrog-azure-devops-extension/tree/v2?tab=readme-ov-file#using-openid-connect-oidc-authentication\" target=\"_blank\">here</a> for information about how to configure OpenID Connect.",
"inputMode": "textbox",
"isConfidential": false,
"validation": {
"isRequired": true,
"dataType": "string"
}
},
{
"id": "jfrogPlatformUrl",
"name": "Platform URL",
"description": "The access token will be obtained from this URL. For example, https://my.jfrog.io/",
"inputMode": "textbox",
"isConfidential": false,
"validation": {
"isRequired": true,
"dataType": "string"
}
}
]
}
]
}
Expand Down Expand Up @@ -908,4 +1032,4 @@
"path": "tasks/JFrogGenericArtifacts"
}
]
}
}
Loading