Skip to content
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
4 changes: 3 additions & 1 deletion jfrog-tasks-utils/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@
"main": "utils",
"typings": "utils.d.ts",
"dependencies": {
"azure-devops-node-api": "^13.0.0",
"azure-pipelines-task-lib": "4.5.0",
"azure-pipelines-tool-lib": "2.0.6",
"azure-pipelines-tasks-java-common": "^2.219.1",
"node-fetch": "2.0.0",
"typed-rest-client": "^1.8.11"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
}
}
}
101 changes: 90 additions & 11 deletions jfrog-tasks-utils/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ const tl = require('azure-pipelines-task-lib/task');
const { join, sep, isAbsolute } = require('path');
const execSync = require('child_process').execSync;
const toolLib = require('azure-pipelines-tool-lib/tool');
const azNodeLib = require('azure-devops-node-api');
const fetch = require('node-fetch');
const credentialsHandler = require('typed-rest-client/Handlers');
const findJavaHome = require('azure-pipelines-tasks-java-common/java-common').findJavaHome;

Expand Down Expand Up @@ -156,10 +158,19 @@ function getCliExePathInArtifactory(cliVersion) {
return cliVersion + '/' + cliPackage + '/' + fileName;
}

function createAuthHandlers(serviceConnection) {
async function createAuthHandlers(serviceConnection) {
let serviceUrl = tl.getEndpointUrl(serviceConnection, false);
let artifactoryUser = tl.getEndpointAuthorizationParameter(serviceConnection, 'username', true);
let artifactoryPassword = tl.getEndpointAuthorizationParameter(serviceConnection, 'password', true);
let artifactoryAccessToken = tl.getEndpointAuthorizationParameter(serviceConnection, 'apitoken', true);
let artifactoryProviderName = tl.getEndpointAuthorizationParameter(serviceConnection, 'providerName', true);
let artifactoryAuthServer = tl.getEndpointAuthorizationParameter(serviceConnection, 'authServer', true);

// Check if Artifactory should be accessed using Azure DevOps OIDC Token
if (artifactoryProviderName) {
const accessToken = await getAccessTokenWithOIDForEndpoint(serviceConnection, artifactoryProviderName, serviceUrl, artifactoryAuthServer);
return [new credentialsHandler.BearerCredentialHandler(accessToken, false)];
}

// Check if Artifactory should be accessed using access-token.
if (artifactoryAccessToken) {
Expand All @@ -175,6 +186,67 @@ function createAuthHandlers(serviceConnection) {
return [new credentialsHandler.BasicCredentialHandler(artifactoryUser, artifactoryPassword, false)];
}

async function getAccessTokenWithOIDForEndpoint(endpoint_name, provider_name, endpoint_server, auth_server) {
const adoToken = await getOidcTokenForEndpoint(endpoint_name);
const oidcTokenParts = adoToken.split('.');
if (oidcTokenParts.length !== 3) {
throw new Error('Invalid oidc token');
}
const oidcClaims = JSON.parse(Buffer.from(oidcTokenParts[1], 'base64').toString());

// Log the OIDC token claims so users know how to configure AWS
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);

const artifactoryTokenRequestBoth = {
grant_type: 'urn:ietf:params:oauth:grant-type:token-exchange',
subject_token_type: 'urn:ietf:params:oauth:token-type:id_token',
subject_token: adoToken,
provider_name: provider_name,
};

let token_server = auth_server ? auth_server : endpoint_server;

let response = await fetch(`${token_server}/access/api/v1/oidc/token`, {
method: 'post',
body: JSON.stringify(artifactoryTokenRequestBoth),
headers: { 'Content-Type': 'application/json' },
});
const data = await response.json();
console.log(`JFrog access token acquired, expires in ${(data.expires_in / 60).toFixed(2)} minutes.`);

return data.access_token;
}

async function getOidcTokenForEndpoint(endpoint_name) {
const jobId = getVariableRequired('System.JobId');
const planId = getVariableRequired('System.PlanId');
const projectId = getVariableRequired('System.TeamProjectId');
const hub = getVariableRequired('System.HostType');
const uri = getVariableRequired('System.CollectionUri');
const token = getVariableRequired('System.AccessToken');

const auth = azNodeLib.getBasicHandler('', token);
const connection = new azNodeLib.WebApi(uri, auth);
const api = await connection.getTaskApi();
const response = await api.createOidcToken({}, projectId, hub, planId, jobId, endpoint_name);
if (!response.oidcToken) {
throw new Error('Invalid createOidcToken response, no oidcToken.');
}
return response.oidcToken;
}

function getVariableRequired(name) {
const variable = tl.getVariable(name);
if (!variable) {
throw new Error(`Required variable '${name}' returned undefined!`);
}

return variable;
}

function generateDownloadCliErrorMessage(downloadUrl, cliVersion) {
let errMsg = 'Failed while attempting to download JFrog CLI from ' + downloadUrl;
if (downloadUrl === buildReleasesDownloadUrl(cliVersion)) {
Expand Down Expand Up @@ -237,31 +309,38 @@ function maskSecrets(str) {
.replace(/--access-token='.*?'/g, '--access-token=***');
}

function configureJfrogCliServer(jfrogService, serverId, cliPath, buildDir) {
return configureSpecificCliServer(jfrogService, '--url', serverId, cliPath, buildDir);
async function configureJfrogCliServer(jfrogService, serverId, cliPath, buildDir) {
return await configureSpecificCliServer(jfrogService, '--url', serverId, cliPath, buildDir);
}

function configureArtifactoryCliServer(artifactoryService, serverId, cliPath, buildDir) {
return configureSpecificCliServer(artifactoryService, '--artifactory-url', serverId, cliPath, buildDir);
async function configureArtifactoryCliServer(artifactoryService, serverId, cliPath, buildDir) {
return await configureSpecificCliServer(artifactoryService, '--artifactory-url', serverId, cliPath, buildDir);
}

function configureDistributionCliServer(distributionService, serverId, cliPath, buildDir) {
return configureSpecificCliServer(distributionService, '--distribution-url', serverId, cliPath, buildDir);
async function configureDistributionCliServer(distributionService, serverId, cliPath, buildDir) {
return await configureSpecificCliServer(distributionService, '--distribution-url', serverId, cliPath, buildDir);
}

function configureXrayCliServer(xrayService, serverId, cliPath, buildDir) {
return configureSpecificCliServer(xrayService, '--xray-url', serverId, cliPath, buildDir);
async function configureXrayCliServer(xrayService, serverId, cliPath, buildDir) {
return await configureSpecificCliServer(xrayService, '--xray-url', serverId, cliPath, buildDir);
}

function configureSpecificCliServer(service, urlFlag, serverId, cliPath, buildDir) {
async 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 artifactoryProviderName = tl.getEndpointAuthorizationParameter(service, 'providerName', true);
let artifactoryAuthServer = tl.getEndpointAuthorizationParameter(service, 'authServer', true);

let cliCommand = cliJoin(cliPath, jfrogCliConfigAddCommand, quote(serverId), urlFlag + '=' + quote(serviceUrl), '--interactive=false');
let stdinSecret;
let secretInStdinSupported = isStdinSecretSupported();
if (serviceAccessToken) {
if (artifactoryProviderName) {
const serviceAccessToken = await getAccessTokenWithOIDForEndpoint(service, artifactoryProviderName, serviceUrl, artifactoryAuthServer);
cliCommand = cliJoin(cliCommand, secretInStdinSupported ? '--access-token-stdin' : '--access-token=' + quote(serviceAccessToken));
stdinSecret = secretInStdinSupported ? serviceAccessToken : undefined;
} else if (serviceAccessToken) {
// Add access-token if required.
cliCommand = cliJoin(cliCommand, secretInStdinSupported ? '--access-token-stdin' : '--access-token=' + quote(serviceAccessToken));
stdinSecret = secretInStdinSupported ? serviceAccessToken : undefined;
Expand Down
4 changes: 2 additions & 2 deletions tasks/JFrogAudit/task.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@
],
"version": {
"Major": "1",
"Minor": "9",
"Patch": "4"
"Minor": "10",
"Patch": "0"
},
"demands": [],
"minimumAgentVersion": "1.89.0",
Expand Down
4 changes: 2 additions & 2 deletions tasks/JFrogBuildPromotion/task.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@
"demands": [],
"version": {
"Major": "1",
"Minor": "9",
"Patch": "4"
"Minor": "10",
"Patch": "0"
},
"minimumAgentVersion": "1.83.0",
"instanceNameFormat": "JFrog Build Promotion",
Expand Down
4 changes: 2 additions & 2 deletions tasks/JFrogBuildScan/task.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@
"demands": [],
"version": {
"Major": "1",
"Minor": "9",
"Patch": "4"
"Minor": "10",
"Patch": "0"
},
"minimumAgentVersion": "1.83.0",
"instanceNameFormat": "JFrog Build Scan",
Expand Down
4 changes: 2 additions & 2 deletions tasks/JFrogCliV2/task.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@
],
"version": {
"Major": "1",
"Minor": "9",
"Patch": "4"
"Minor": "10",
"Patch": "0"
},
"demands": [],
"minimumAgentVersion": "1.89.0",
Expand Down
4 changes: 2 additions & 2 deletions tasks/JFrogCollectIssues/task.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@
"demands": [],
"version": {
"Major": "1",
"Minor": "9",
"Patch": "4"
"Minor": "10",
"Patch": "0"
},
"minimumAgentVersion": "1.83.0",
"instanceNameFormat": "JFrog Collect Build Issues",
Expand Down
4 changes: 2 additions & 2 deletions tasks/JFrogConan/task.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@
"demands": [],
"version": {
"Major": "1",
"Minor": "9",
"Patch": "4"
"Minor": "10",
"Patch": "0"
},
"minimumAgentVersion": "1.83.0",
"instanceNameFormat": "Conan $(conanCommand)",
Expand Down
4 changes: 2 additions & 2 deletions tasks/JFrogDiscardBuilds/task.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@
"demands": [],
"version": {
"Major": "1",
"Minor": "9",
"Patch": "4"
"Minor": "10",
"Patch": "0"
},
"minimumAgentVersion": "1.83.0",
"instanceNameFormat": "JFrog Discard Builds",
Expand Down
4 changes: 2 additions & 2 deletions tasks/JFrogDistribution/task.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@
],
"version": {
"Major": "1",
"Minor": "9",
"Patch": "4"
"Minor": "10",
"Patch": "0"
},
"demands": [],
"minimumAgentVersion": "1.83.0",
Expand Down
4 changes: 2 additions & 2 deletions tasks/JFrogDocker/task.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@
],
"version": {
"Major": "1",
"Minor": "9",
"Patch": "4"
"Minor": "10",
"Patch": "0"
},
"demands": [],
"minimumAgentVersion": "1.89.0",
Expand Down
4 changes: 2 additions & 2 deletions tasks/JFrogDotnet/task.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@
],
"version": {
"Major": "1",
"Minor": "9",
"Patch": "4"
"Minor": "10",
"Patch": "0"
},
"demands": [],
"minimumAgentVersion": "2.115.0",
Expand Down
4 changes: 2 additions & 2 deletions tasks/JFrogGenericArtifacts/runGenericArtifacts.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const cliCopyCommand = 'rt cp';
const cliDeleteArtifactsCommand = 'rt del';
let serverId;

function RunTaskCbk(cliPath) {
async function RunTaskCbk(cliPath) {
let defaultWorkDir = tl.getVariable('System.DefaultWorkingDirectory');
if (!defaultWorkDir) {
tl.setResult(tl.TaskResult.Failed, 'Failed getting default working directory.');
Expand All @@ -29,7 +29,7 @@ function RunTaskCbk(cliPath) {
// The 'connection' input parameter is used by Artifact Source Download and cannot be renamed due to Azure limitations.
let artifactoryService = tl.getInput('connection', true);
serverId = utils.assembleUniqueServerId('generic');
utils.configureArtifactoryCliServer(artifactoryService, serverId, cliPath, workDir);
await utils.configureArtifactoryCliServer(artifactoryService, serverId, cliPath, workDir);

// Decide if the task runs as generic or artifact-source download.
let definition = tl.getInput('definition', false);
Expand Down
4 changes: 2 additions & 2 deletions tasks/JFrogGenericArtifacts/task.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@
"demands": [],
"version": {
"Major": "1",
"Minor": "9",
"Patch": "4"
"Minor": "10",
"Patch": "0"
},
"minimumAgentVersion": "1.83.0",
"instanceNameFormat": "JFrog Generic Artifacts $(command)",
Expand Down
4 changes: 2 additions & 2 deletions tasks/JFrogGo/task.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@
],
"version": {
"Major": "1",
"Minor": "9",
"Patch": "4"
"Minor": "10",
"Patch": "0"
},
"demands": [],
"minimumAgentVersion": "1.91.0",
Expand Down
10 changes: 5 additions & 5 deletions tasks/JFrogGradle/gradleBuild.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,15 @@ let serverIdResolver;

utils.executeCliTask(RunTaskCbk);

function RunTaskCbk(cliPath) {
async function RunTaskCbk(cliPath) {
utils.setJdkHomeForJavaTasks();
let workDir = getWorkDir();
try {
if (!workDir) {
tl.setResult(tl.TaskResult.Failed, 'Failed getting default working directory.');
return;
}
executeGradleConfig(cliPath, workDir);
await executeGradleConfig(cliPath, workDir);
executeGradle(cliPath, workDir);
} catch (ex) {
tl.setResult(tl.TaskResult.Failed, ex);
Expand Down Expand Up @@ -45,15 +45,15 @@ function getWorkDir() {
* @param cliPath - Path to JFrog CLI
* @param workDir - Gradle project directory
*/
function executeGradleConfig(cliPath, workDir) {
async function executeGradleConfig(cliPath, workDir) {
// Build the cli config command.
let cliCommand = utils.cliJoin(cliPath, gradleConfigCommand);

// Configure resolver server, throws on failure.
let artifactoryResolver = tl.getInput('artifactoryResolverService');
if (artifactoryResolver) {
serverIdResolver = utils.assembleUniqueServerId('gradle_resolver');
utils.configureArtifactoryCliServer(artifactoryResolver, serverIdResolver, cliPath, workDir);
await utils.configureArtifactoryCliServer(artifactoryResolver, serverIdResolver, cliPath, workDir);
cliCommand = utils.cliJoin(cliCommand, '--server-id-resolve=' + utils.quote(serverIdResolver));
} else {
console.log('Resolution from Artifactory is not configured');
Expand All @@ -63,7 +63,7 @@ function executeGradleConfig(cliPath, workDir) {
let artifactoryDeployer = tl.getInput('artifactoryDeployerService');
if (artifactoryDeployer) {
serverIdDeployer = utils.assembleUniqueServerId('gradle_deployer');
utils.configureArtifactoryCliServer(artifactoryDeployer, serverIdDeployer, cliPath, workDir);
await utils.configureArtifactoryCliServer(artifactoryDeployer, serverIdDeployer, cliPath, workDir);
cliCommand = utils.cliJoin(cliCommand, '--server-id-deploy=' + utils.quote(serverIdDeployer));
}

Expand Down
4 changes: 2 additions & 2 deletions tasks/JFrogGradle/task.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@
],
"version": {
"Major": "1",
"Minor": "9",
"Patch": "4"
"Minor": "10",
"Patch": "0"
},
"demands": [],
"minimumAgentVersion": "1.89.0",
Expand Down
Loading