From b6ed484713677908c161f3568ddc317ae6f0b63e Mon Sep 17 00:00:00 2001 From: David Winslow Date: Thu, 31 Oct 2024 10:43:45 +0100 Subject: [PATCH] initial working solution with sync-fetch --- buildScripts/publish-extension.sh | 45 +++++ jfrog-tasks-utils/package.json | 3 +- jfrog-tasks-utils/utils.js | 85 +++++++++- vss-extension.json | 263 ++++++++++++++++++------------ 4 files changed, 287 insertions(+), 109 deletions(-) create mode 100755 buildScripts/publish-extension.sh diff --git a/buildScripts/publish-extension.sh b/buildScripts/publish-extension.sh new file mode 100755 index 00000000..8145b2c3 --- /dev/null +++ b/buildScripts/publish-extension.sh @@ -0,0 +1,45 @@ +#!/usr/bin/env bash +set -eu +rm -f *.vsix + +PUBLISHER="winslowtech" +ORGANIZATION="winslowtech" +EXTENSION_ID="novo-artifactory-setup" +EXTENSION_NAME="Novo Artifactory Setup" + +OVERRIDE_JSON=$( + cat </dev/null || true +tfx extension unpublish -t "$WINSLOWTECH_TOKEN" --extension-id $EXTENSION_ID --publisher "$PUBLISHER" || true + +# Create the VSIX package +tfx extension create --manifests vss-extension.json --publisher "$PUBLISHER" --rev-version --override $OVERRIDE_JSON + +vsixSize=$(du -k *.vsix | awk '{sum+=$1} END {print int(sum/1024)}') +echo "Extension VSIX size is ${vsixSize}MB" + +# Publish the extension +tfx extension publish -t "$WINSLOWTECH_TOKEN" \ + --publisher "$PUBLISHER" \ + --manifests vss-extension.json \ + --share-with "$ORGANIZATION" \ + --override $OVERRIDE_JSON + +# Install the extension to your organization +tfx extension install \ + --publisher "$PUBLISHER" \ + --extension-id $EXTENSION_ID \ + --service-url "https://${PUBLISHER}.visualstudio.com" \ + -t "$WINSLOWTECH_TOKEN" + +rm *.vsix diff --git a/jfrog-tasks-utils/package.json b/jfrog-tasks-utils/package.json index 441564f6..fa4e91ba 100644 --- a/jfrog-tasks-utils/package.json +++ b/jfrog-tasks-utils/package.json @@ -9,8 +9,9 @@ "typings": "utils.d.ts", "dependencies": { "azure-pipelines-task-lib": "4.5.0", - "azure-pipelines-tool-lib": "2.0.6", "azure-pipelines-tasks-java-common": "^2.219.1", + "azure-pipelines-tool-lib": "2.0.6", + "sync-fetch": "^0.5.2", "typed-rest-client": "^1.8.11" }, "scripts": { diff --git a/jfrog-tasks-utils/utils.js b/jfrog-tasks-utils/utils.js index c68093b0..88dd9d40 100644 --- a/jfrog-tasks-utils/utils.js +++ b/jfrog-tasks-utils/utils.js @@ -5,7 +5,6 @@ const execSync = require('child_process').execSync; const toolLib = require('azure-pipelines-tool-lib/tool'); const credentialsHandler = require('typed-rest-client/Handlers'); const findJavaHome = require('azure-pipelines-tasks-java-common/java-common').findJavaHome; - const fileName = getCliExecutableName(); const jfrogCliToolName = 'jf'; const cliPackage = 'jfrog-cli-' + getArchitecture(); @@ -19,6 +18,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 syncFetch = require('sync-fetch'); // 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. @@ -160,6 +160,13 @@ function createAuthHandlers(serviceConnection) { let artifactoryUser = tl.getEndpointAuthorizationParameter(serviceConnection, 'username', true); let artifactoryPassword = tl.getEndpointAuthorizationParameter(serviceConnection, 'password', true); let artifactoryAccessToken = tl.getEndpointAuthorizationParameter(serviceConnection, 'apitoken', true); + let oidcProviderName = tl.getEndpointAuthorizationParameter(service, 'oidcProviderName', true); + let jfrogPlatformUrl = tl.getEndpointAuthorizationParameter(service, 'jfrogPlatformUrl', true); + + if (oidcProviderName) { + const adoJWT = getADOJWT(serviceConnection); + serviceAccessToken = getArtifactoryAccessToken(adoJWT, oidcProviderName, jfrogPlatformUrl); + } // Check if Artifactory should be accessed using access-token. if (artifactoryAccessToken) { @@ -252,15 +259,91 @@ 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 getADOJWT(serviceConnectionID) { + const uri = getValue('System.CollectionUri'); + const teamPrjID = getValue('System.TeamProjectId'); + const hub = getValue('System.HostType'); + const planID = getValue('System.PlanId'); + const jobID = getValue('System.JobId'); + + const url = `${uri}${teamPrjID}/_apis/distributedtask/hubs/${hub}/plans/${planID}/jobs/${jobID}/oidctoken?api-version=7.1-preview.1&serviceConnectionId=${serviceConnectionID}`; + + try { + const response = syncFetch(url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${getValue('System.AccessToken')}`, + }, + }); + + jfrogAccessToken = response.json().oidcToken; + logIDToken(jfrogAccessToken); + return jfrogAccessToken; + } catch (error) { + throw new Error(`Failed to get or parse response: ${error.message}`); + } +} + +function getArtifactoryAccessToken(adoJWT, 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: adoJWT, + provider_name: oidcProviderName, + }; + + const url = `${jfrogPlatformUrl}/access/api/v1/oidc/token`; + + try { + const res = syncFetch(url, { + method: 'POST', + body: JSON.stringify(payload), + headers: { 'Content-Type': 'application/json' }, + }); + + if (!res.ok) { + throw new Error(`Failed to get the Artifactory access token: ${res.statusText}`); + } + return res.json().access_token; + } catch (err) { + throw new Error(`Failed to get or parse response: ${err.message}`); + } +} + +function getValue(key) { + const variable = tl.getVariable(key); + if (!variable) { + throw new Error(`Required variable '${key}' returned undefined!`); + } + + return variable; +} 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 adoJWT = getADOJWT(service); + serviceAccessToken = getArtifactoryAccessToken(adoJWT, oidcProviderName, jfrogPlatformUrl); + } + if (serviceAccessToken) { // Add access-token if required. cliCommand = cliJoin(cliCommand, secretInStdinSupported ? '--access-token-stdin' : '--access-token=' + quote(serviceAccessToken)); diff --git a/vss-extension.json b/vss-extension.json index 1c101b5f..c00513ea 100644 --- a/vss-extension.json +++ b/vss-extension.json @@ -2,7 +2,7 @@ "manifestVersion": 1, "public": true, "id": "jfrog-azure-devops-extension", - "version": "2.10.1", + "version": "2.10.4", "name": "JFrog", "description": "Integrate your JFrog Platform with Visual Studio Team Services.", "publisher": "JFrog", @@ -14,26 +14,9 @@ "icons": { "default": "images/jfrog-logo-200.png" }, - "scopes": [ - "vso.build", - "vso.build_execute" - ], - "categories": [ - "Azure Pipelines" - ], - "tags": [ - "JFrog", - "Artifactory", - "Distribution", - "Xray", - "Artifact", - "Build", - "Release", - "Promote", - "Upload", - "Download", - "Repository" - ], + "scopes": ["vso.build", "vso.build_execute"], + "categories": ["Azure Pipelines"], + "tags": ["JFrog", "Artifactory", "Distribution", "Xray", "Artifact", "Build", "Release", "Promote", "Upload", "Download", "Repository"], "content": { "details": { "path": "overview.md" @@ -72,9 +55,7 @@ "id": "jfrog-platform-service", "description": "Service Endpoint type for JFrog Platform connections. Currently only used by the 'JFrog CLI V2' task.", "type": "ms.vss-endpoint.service-endpoint-type", - "targets": [ - "ms.vss-endpoint.endpoint-types" - ], + "targets": ["ms.vss-endpoint.endpoint-types"], "properties": { "name": "jfrogPlatformService", "displayName": "JFrog Platform V2", @@ -149,6 +130,37 @@ } } ] + }, + { + "type": "ms.vss-endpoint.endpoint-auth-scheme-none", + "displayName": "Azure DevOps OIDC (Services only)", + "properties": { + "isVerifiable": "False" + }, + "inputDescriptors": [ + { + "id": "oidcProviderName", + "name": "OIDC Provider Name", + "description": "The OIDC \"Provider Name\" configured in JFrog Platform.", + "inputMode": "textbox", + "isConfidential": false, + "validation": { + "isRequired": true, + "dataType": "string" + } + }, + { + "id": "jfrogPlatformUrl", + "name": "Platform URL", + "description": "The access token will be obtained from this URL (e.g. https://my.jfrog.com)", + "inputMode": "textbox", + "isConfidential": false, + "validation": { + "isRequired": true, + "dataType": "string" + } + } + ] } ] } @@ -157,9 +169,7 @@ "id": "jfrog-artifactory-service", "description": "Service Endpoint type for Artifactory connections", "type": "ms.vss-endpoint.service-endpoint-type", - "targets": [ - "ms.vss-endpoint.endpoint-types" - ], + "targets": ["ms.vss-endpoint.endpoint-types"], "properties": { "name": "jfrogArtifactoryService", "displayName": "JFrog Artifactory V2", @@ -234,6 +244,37 @@ } } ] + }, + { + "type": "ms.vss-endpoint.endpoint-auth-scheme-none", + "displayName": "Azure DevOps OIDC (Services only)", + "properties": { + "isVerifiable": "False" + }, + "inputDescriptors": [ + { + "id": "oidcProviderName", + "name": "OIDC Provider Name", + "description": "The OIDC \"Provider Name\" configured in JFrog Platform.", + "inputMode": "textbox", + "isConfidential": false, + "validation": { + "isRequired": true, + "dataType": "string" + } + }, + { + "id": "jfrogPlatformUrl", + "name": "Platform URL", + "description": "The access token will be obtained from this URL (e.g. https://my.jfrog.com)", + "inputMode": "textbox", + "isConfidential": false, + "validation": { + "isRequired": true, + "dataType": "string" + } + } + ] } ] } @@ -242,9 +283,7 @@ "id": "jfrog-distribution-service", "description": "Service Endpoint type for Distribution connections", "type": "ms.vss-endpoint.service-endpoint-type", - "targets": [ - "ms.vss-endpoint.endpoint-types" - ], + "targets": ["ms.vss-endpoint.endpoint-types"], "properties": { "name": "jfrogDistributionService", "displayName": "JFrog Distribution V2", @@ -309,6 +348,37 @@ } } ] + }, + { + "type": "ms.vss-endpoint.endpoint-auth-scheme-none", + "displayName": "Azure DevOps OIDC (Services only)", + "properties": { + "isVerifiable": "False" + }, + "inputDescriptors": [ + { + "id": "oidcProviderName", + "name": "OIDC Provider Name", + "description": "The OIDC \"Provider Name\" configured in JFrog Platform.", + "inputMode": "textbox", + "isConfidential": false, + "validation": { + "isRequired": true, + "dataType": "string" + } + }, + { + "id": "jfrogPlatformUrl", + "name": "Platform URL", + "description": "The access token will be obtained from this URL (e.g. https://my.jfrog.com)", + "inputMode": "textbox", + "isConfidential": false, + "validation": { + "isRequired": true, + "dataType": "string" + } + } + ] } ] } @@ -317,9 +387,7 @@ "id": "jfrog-xray-service", "description": "Service Endpoint type for Xray connections", "type": "ms.vss-endpoint.service-endpoint-type", - "targets": [ - "ms.vss-endpoint.endpoint-types" - ], + "targets": ["ms.vss-endpoint.endpoint-types"], "properties": { "name": "jfrogXrayService", "displayName": "JFrog Xray V2", @@ -384,6 +452,37 @@ } } ] + }, + { + "type": "ms.vss-endpoint.endpoint-auth-scheme-none", + "displayName": "Azure DevOps OIDC (Services only)", + "properties": { + "isVerifiable": "False" + }, + "inputDescriptors": [ + { + "id": "oidcProviderName", + "name": "OIDC Provider Name", + "description": "The OIDC \"Provider Name\" configured in JFrog Platform.", + "inputMode": "textbox", + "isConfidential": false, + "validation": { + "isRequired": true, + "dataType": "string" + } + }, + { + "id": "jfrogPlatformUrl", + "name": "Platform URL", + "description": "The access token will be obtained from this URL (e.g. https://my.jfrog.com)", + "inputMode": "textbox", + "isConfidential": false, + "validation": { + "isRequired": true, + "dataType": "string" + } + } + ] } ] } @@ -392,9 +491,7 @@ "id": "jfrog-artifactory-release-artifact-type", "description": "Artifactory", "type": "ms.vss-releaseartifact.release-artifact-type", - "targets": [ - "ms.vss-releaseartifact.artifact-types" - ], + "targets": ["ms.vss-releaseartifact.artifact-types"], "properties": { "name": "JFrogArtifactory", "displayName": "Artifactory V2", @@ -433,10 +530,7 @@ "hasDynamicValueInformation": true, "inputMode": "Combo", "isConfidential": false, - "dependencyInputIds": [ - "connection", - "projectKey" - ], + "dependencyInputIds": ["connection", "projectKey"], "validation": { "isRequired": true, "dataType": "string", @@ -590,12 +684,7 @@ "properties": { "visibleRule": "defaultVersionType == specificVersionType" }, - "dependencyInputIds": [ - "connection", - "definition", - "defaultVersionType", - "projectKey" - ], + "dependencyInputIds": ["connection", "definition", "defaultVersionType", "projectKey"], "validation": { "isRequired": true, "dataType": "string" @@ -634,9 +723,7 @@ "id": "artifactory-build-info-tab", "type": "ms.vss-build-web.build-results-tab", "description": "A tab in build result to enforce Artifactory integration", - "targets": [ - "ms.vss-build-web.build-results-view" - ], + "targets": ["ms.vss-build-web.build-results-view"], "properties": { "name": "Artifactory", "uri": "artifactory-build-info.html" @@ -645,9 +732,7 @@ { "id": "jfrog-promote-build-task", "type": "ms.vss-distributed-task.task", - "targets": [ - "ms.vss-distributed-task.tasks" - ], + "targets": ["ms.vss-distributed-task.tasks"], "properties": { "name": "tasks/JFrogBuildPromotion" } @@ -655,9 +740,7 @@ { "id": "jfrog-maven", "type": "ms.vss-distributed-task.task", - "targets": [ - "ms.vss-distributed-task.tasks" - ], + "targets": ["ms.vss-distributed-task.tasks"], "properties": { "name": "tasks/JFrogMaven" } @@ -665,9 +748,7 @@ { "id": "jfrog-gradle", "type": "ms.vss-distributed-task.task", - "targets": [ - "ms.vss-distributed-task.tasks" - ], + "targets": ["ms.vss-distributed-task.tasks"], "properties": { "name": "tasks/JFrogGradle" } @@ -675,9 +756,7 @@ { "id": "jfrog-npm", "type": "ms.vss-distributed-task.task", - "targets": [ - "ms.vss-distributed-task.tasks" - ], + "targets": ["ms.vss-distributed-task.tasks"], "properties": { "name": "tasks/JFrogNpm" } @@ -685,9 +764,7 @@ { "id": "jfrog-nuget", "type": "ms.vss-distributed-task.task", - "targets": [ - "ms.vss-distributed-task.tasks" - ], + "targets": ["ms.vss-distributed-task.tasks"], "properties": { "name": "tasks/JFrogNuget" } @@ -695,9 +772,7 @@ { "id": "jfrog-dotnet", "type": "ms.vss-distributed-task.task", - "targets": [ - "ms.vss-distributed-task.tasks" - ], + "targets": ["ms.vss-distributed-task.tasks"], "properties": { "name": "tasks/JFrogDotnet" } @@ -705,9 +780,7 @@ { "id": "jfrog-publish-build-info", "type": "ms.vss-distributed-task.task", - "targets": [ - "ms.vss-distributed-task.tasks" - ], + "targets": ["ms.vss-distributed-task.tasks"], "properties": { "name": "tasks/JFrogPublishBuildInfo" } @@ -715,9 +788,7 @@ { "id": "jfrog-conan-build-task", "type": "ms.vss-distributed-task.task", - "targets": [ - "ms.vss-distributed-task.tasks" - ], + "targets": ["ms.vss-distributed-task.tasks"], "properties": { "name": "tasks/JFrogConan" } @@ -725,9 +796,7 @@ { "id": "jfrog-build-scan-task", "type": "ms.vss-distributed-task.task", - "targets": [ - "ms.vss-distributed-task.tasks" - ], + "targets": ["ms.vss-distributed-task.tasks"], "properties": { "name": "tasks/JFrogBuildScan" } @@ -735,9 +804,7 @@ { "id": "jfrog-audit-task", "type": "ms.vss-distributed-task.task", - "targets": [ - "ms.vss-distributed-task.tasks" - ], + "targets": ["ms.vss-distributed-task.tasks"], "properties": { "name": "tasks/JFrogAudit" } @@ -745,9 +812,7 @@ { "id": "jfrog-docker-task", "type": "ms.vss-distributed-task.task", - "targets": [ - "ms.vss-distributed-task.tasks" - ], + "targets": ["ms.vss-distributed-task.tasks"], "properties": { "name": "tasks/JFrogDocker" } @@ -755,9 +820,7 @@ { "id": "jfrog-discard-builds-task", "type": "ms.vss-distributed-task.task", - "targets": [ - "ms.vss-distributed-task.tasks" - ], + "targets": ["ms.vss-distributed-task.tasks"], "properties": { "name": "tasks/JFrogDiscardBuilds" } @@ -765,9 +828,7 @@ { "id": "jfrog-tools-installer-task", "type": "ms.vss-distributed-task.task", - "targets": [ - "ms.vss-distributed-task.tasks" - ], + "targets": ["ms.vss-distributed-task.tasks"], "properties": { "name": "tasks/JFrogToolsInstaller" } @@ -775,9 +836,7 @@ { "id": "jfrog-go", "type": "ms.vss-distributed-task.task", - "targets": [ - "ms.vss-distributed-task.tasks" - ], + "targets": ["ms.vss-distributed-task.tasks"], "properties": { "name": "tasks/JFrogGo" } @@ -785,9 +844,7 @@ { "id": "jfrog-pip", "type": "ms.vss-distributed-task.task", - "targets": [ - "ms.vss-distributed-task.tasks" - ], + "targets": ["ms.vss-distributed-task.tasks"], "properties": { "name": "tasks/JFrogPip" } @@ -795,9 +852,7 @@ { "id": "jfrog-distribution", "type": "ms.vss-distributed-task.task", - "targets": [ - "ms.vss-distributed-task.tasks" - ], + "targets": ["ms.vss-distributed-task.tasks"], "properties": { "name": "tasks/JFrogDistribution" } @@ -805,9 +860,7 @@ { "id": "jfrog-collect-issues", "type": "ms.vss-distributed-task.task", - "targets": [ - "ms.vss-distributed-task.tasks" - ], + "targets": ["ms.vss-distributed-task.tasks"], "properties": { "name": "tasks/JFrogCollectIssues" } @@ -815,9 +868,7 @@ { "id": "jfrog-cli-v2", "type": "ms.vss-distributed-task.task", - "targets": [ - "ms.vss-distributed-task.tasks" - ], + "targets": ["ms.vss-distributed-task.tasks"], "properties": { "name": "tasks/JFrogCliV2" } @@ -825,9 +876,7 @@ { "id": "jfrog-generic-artifacts", "type": "ms.vss-distributed-task.task", - "targets": [ - "ms.vss-distributed-task.tasks" - ], + "targets": ["ms.vss-distributed-task.tasks"], "properties": { "name": "tasks/JFrogGenericArtifacts" } @@ -908,4 +957,4 @@ "path": "tasks/JFrogGenericArtifacts" } ] -} \ No newline at end of file +}