Skip to content

Commit a160844

Browse files
authored
[NotationV0] feat: azure-kv plugin workload identity support (#19840)
* feat: add notation akv plugin v1.1.0 support Signed-off-by: Junjie Gao <[email protected]> * fix: update version Signed-off-by: Junjie Gao <[email protected]> * feat: support workload identity Signed-off-by: Junjie Gao <[email protected]> * fix: update Signed-off-by: Junjie Gao <[email protected]> * fix: update Signed-off-by: Junjie Gao <[email protected]> * fix: bump update to version 0.240.0 Signed-off-by: Junjie Gao <[email protected]> --------- Signed-off-by: Junjie Gao <[email protected]>
1 parent f319813 commit a160844

File tree

7 files changed

+261
-35
lines changed

7 files changed

+261
-35
lines changed

Tasks/NotationV0/Strings/resources.resjson/en-US/resources.resjson

+3-2
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,13 @@
3939
"loc.messages.ArtifactRefs": "Artifact references: %s.",
4040
"loc.messages.ArtifactRefsNotSpecified": "Artifact references are not specified.",
4141
"loc.messages.APPDATANotSet": "APPDATA is not set.",
42-
"loc.messages.AzureKVPluginAlreadyInstalled": "Azure Key Vault plugin is already installed.",
42+
"loc.messages.AzureKVPluginAlreadyInstalled": "Azure Key Vault plugin v%s is already installed.",
4343
"loc.messages.CannotFindTrustStore": "Cannot find trust store directory: %s.",
4444
"loc.messages.ChecksumValidated": "Checksum validated: %s.",
4545
"loc.messages.ChecksumValidationFailed": "Checksum validation failed. Expected: %s, Actual: %s.",
4646
"loc.messages.FailedArtifacts": "Failed artifacts: %s.",
4747
"loc.messages.FailedToAddCertToTrustStore": "Failed to add a certificate to trust store: %s.",
48+
"loc.messages.FailedToGetWorkloadIdToken": "Failed to get workload identity token: %s.",
4849
"loc.messages.FailedToImportTrustPolicy": "Failed to import trust policy: %s.",
4950
"loc.messages.FailedToListTrustStore": "Failed to list the trust store.",
5051
"loc.messages.FailedToShowTrustPolicy": "Failed to show trust policy.",
@@ -65,7 +66,7 @@
6566
"loc.messages.UnsupportedFileExtension": "Unsupported file extension: %s.",
6667
"loc.messages.UnsupportedPlatform": "Unsupported platform: %s.",
6768
"loc.messages.UnsupportedVersion": "Unsupported version: %s.",
68-
"loc.messages.UseManagedIdentity": "Use managed identity to access Azure Key Vault.",
69+
"loc.messages.UseAuthenticationMethod": "Use %s to access Azure Key Vault.",
6970
"loc.messages.UnknownCommand": "Unknown command: %s.",
7071
"loc.messages.UnknownError": "An unknown error occurred.",
7172
"loc.messages.UnknownPlugin": "Unknown plugin: %s.",

Tasks/NotationV0/package-lock.json

+94-10
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Tasks/NotationV0/package.json

+3-1
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,11 @@
1010
"author": "",
1111
"license": "MIT",
1212
"dependencies": {
13-
"@types/node": "^16.11.39",
1413
"@types/mocha": "^5.2.7",
14+
"@types/node": "^16.11.39",
15+
"azure-devops-node-api": "^13.0.0",
1516
"azure-pipelines-task-lib": "4.6.1",
17+
"azure-pipelines-tasks-artifacts-common": "^2.230.0",
1618
"azure-pipelines-tool-lib": "2.0.7"
1719
},
1820
"devDependencies": {

Tasks/NotationV0/src/lib/credentials.ts

+79-6
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,37 @@
11
import * as taskLib from 'azure-pipelines-task-lib/task';
22
import * as fs from 'fs';
33
import * as path from 'path';
4+
import { getSystemAccessToken } from 'azure-pipelines-tasks-artifacts-common/webapi';
5+
import { getHandlerFromToken, WebApi } from "azure-devops-node-api";
6+
import { ITaskApi } from "azure-devops-node-api/TaskApi";
47

5-
export async function getVaultCredentials(): Promise<{ [key: string]: string }> {
8+
export async function getVaultCredentials(): Promise<[{ [key: string]: string }, String]> {
69
let connectedService = taskLib.getInput("azurekvServiceConection", true);
710
if (!connectedService) {
811
console.log(taskLib.loc('NoServiceConnection'));
9-
return {};
12+
return [{}, ""];
1013
}
14+
1115
var authScheme = taskLib.getEndpointAuthorizationScheme(connectedService, true);
1216
if (!authScheme) {
1317
console.log(taskLib.loc('NoAuthScheme'));
14-
return {};
18+
return [{}, ""];
1519
}
1620

1721
let envVariables = {};
22+
let credentialType = "";
1823
switch (authScheme.toLocaleLowerCase()) {
1924
case "managedserviceidentity":
2025
// azure key vault plugin will automatially try managed idenitty
21-
console.log(taskLib.loc('UseManagedIdentity'));
26+
console.log(taskLib.loc('UseAuthenticationMethod', 'Managed Identity'));
27+
credentialType = "managedid";
2228
break;
2329
case "serviceprincipal":
30+
console.log(taskLib.loc('UseAuthenticationMethod', 'Service Principal'));
2431
let authType = taskLib.getEndpointAuthorizationParameter(connectedService, 'authenticationType', true);
2532
let cliPassword: string | undefined = "";
2633
var servicePrincipalId = taskLib.getEndpointAuthorizationParameter(connectedService, "serviceprincipalid", false);
2734
var tenantId = taskLib.getEndpointAuthorizationParameter(connectedService, "tenantid", false);
28-
2935
if (authType == "spnCertificate") {
3036
taskLib.debug('certificate based endpoint');
3137
let certificateContent = taskLib.getEndpointAuthorizationParameter(connectedService, "servicePrincipalCertificate", false) ?? '';
@@ -50,9 +56,76 @@ export async function getVaultCredentials(): Promise<{ [key: string]: string }>
5056
"AZURE_CLIENT_SECRET": cliPassword,
5157
}
5258
}
59+
credentialType = "environment"
60+
break;
61+
case "workloadidentityfederation":
62+
console.log(taskLib.loc('UseAuthenticationMethod', 'Workload Identity Federation'));
63+
var servicePrincipalId = taskLib.getEndpointAuthorizationParameter(connectedService, "serviceprincipalid", false);
64+
var tenantId = taskLib.getEndpointAuthorizationParameter(connectedService, "tenantid", false);
65+
const federatedToken = await getWorkloadIdToken(connectedService);
66+
const extractPath = taskLib.getVariable('Agent.TempDirectory');
67+
if (!extractPath) {
68+
throw new Error(taskLib.loc('TempDirectoryNotSet'));
69+
}
70+
71+
const tokenFile = path.join(extractPath, 'oidcToken');
72+
fs.writeFileSync(tokenFile, federatedToken);
73+
envVariables = {
74+
"AZURE_TENANT_ID": tenantId,
75+
"AZURE_CLIENT_ID": servicePrincipalId,
76+
"AZURE_FEDERATED_TOKEN_FILE": tokenFile,
77+
}
78+
credentialType = "workloadid"
5379
break;
5480
default:
5581
throw new Error(taskLib.loc('UnsupportedAuthScheme', authScheme));
5682
}
57-
return envVariables;
83+
84+
return [envVariables, credentialType];
5885
}
86+
87+
async function getWorkloadIdToken(connectedService: string): Promise<string> {
88+
const jobId = taskLib.getVariable("System.JobId");
89+
if (!jobId) {
90+
throw new Error(taskLib.loc('FailedToGetWorkloadIdToken', 'JobId not set'));
91+
}
92+
93+
const planId = taskLib.getVariable("System.PlanId");
94+
if (!planId) {
95+
throw new Error(taskLib.loc('FailedToGetWorkloadIdToken', 'PlanId not set'));
96+
}
97+
98+
const projectId = taskLib.getVariable("System.TeamProjectId");
99+
if (!projectId) {
100+
throw new Error(taskLib.loc('FailedToGetWorkloadIdToken', 'ProjectId not set'));
101+
}
102+
103+
const hub = taskLib.getVariable("System.HostType");
104+
if (!hub) {
105+
throw new Error(taskLib.loc('FailedToGetWorkloadIdToken', 'Hub not set'));
106+
}
107+
108+
const uri = taskLib.getVariable("System.CollectionUri");
109+
if (!uri) {
110+
throw new Error(taskLib.loc('FailedToGetWorkloadIdToken', 'CollectionUri not set'));
111+
}
112+
113+
const token = getSystemAccessToken();
114+
if (!token) {
115+
throw new Error(taskLib.loc('FailedToGetWorkloadIdToken', 'Token not set'));
116+
}
117+
118+
const authHandler = getHandlerFromToken(token);
119+
const connection = new WebApi(uri, authHandler);
120+
const api: ITaskApi = await connection.getTaskApi();
121+
const response = await api.createOidcToken({}, projectId, hub, planId, jobId, connectedService);
122+
if (response == null) {
123+
throw new Error(taskLib.loc('FailedToGetWorkloadIdToken', 'Response is null'));
124+
}
125+
126+
if (!response.oidcToken) {
127+
throw new Error(taskLib.loc('FailedToGetWorkloadIdToken', 'Token not set'));
128+
}
129+
130+
return response.oidcToken;
131+
}

0 commit comments

Comments
 (0)