Skip to content

Commit 89627e4

Browse files
embettenAlexVTor
andauthored
NpmAuthenticateV0 - Enable WIF for internal and external feeds (#20587)
* init * bump version * some refactoring * reprioritize the service connection auth * Add new strings * fix npm auth * remove unused warning * Update Tasks/NpmAuthenticateV0/Strings/resources.resjson/en-US/resources.resjson Co-authored-by: Alex Torres <[email protected]> * update strings --------- Co-authored-by: Alex Torres <[email protected]>
1 parent 5e5c166 commit 89627e4

File tree

23 files changed

+544
-542
lines changed

23 files changed

+544
-542
lines changed

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
"loc.input.label.customEndpoint": "Credentials for registries outside this organization/collection",
99
"loc.input.help.customEndpoint": "Credentials to use for external registries located in the project's .npmrc. For registries in this organization/collection, leave this blank; the build’s credentials are used automatically.",
1010
"loc.input.label.workloadIdentityServiceConnection": "'Azure DevOps' Service Connection",
11-
"loc.input.help.workloadIdentityServiceConnection": "If this is set, feedUrl is required. Service Connections for external organizations/collection and custom endpoints are not compatible.",
11+
"loc.input.help.workloadIdentityServiceConnection": "Credentials to use for registries located a project's .npmrc file. Use with feedUrl to specify credentials used for the single registry in a .npmrc file. Not compatible with customEndpoint.",
1212
"loc.input.label.feedUrl": "Azure Artifacts URL",
1313
"loc.input.help.feedUrl": "If this is set, azureDevOpsServiceConnection is required. Not compatible with customEndpoint. Feed Url should be in the npm registry format, e.g. https://pkgs.dev.azure.com/{ORG_NAME}/{PROJECT}/_packaging/{FEED_NAME}/npm/registry/",
1414
"loc.messages.FoundBuildCredentials": "Found build credentials",
@@ -34,7 +34,7 @@
3434
"loc.messages.Info_AddingFederatedFeedAuth": "Adding auth information from service connection %s for feed %s",
3535
"loc.messages.Info_SuccessAddingFederatedFeedAuth": "Successfully added auth for feed %s.",
3636
"loc.messages.FailedToGetServiceConnectionAuth": "Unable to get federated credentials from service connection: %s.",
37-
"loc.messages.MissingFeedUrlOrServiceConnection": "Both feed url and service connection need to be set and cannot be empty.",
37+
"loc.messages.MissingFeedUrlOrServiceConnection": "If feed url is provided, the 'Azure DevOps' service connection must be provided and cannot be empty.",
3838
"loc.messages.SkippingParsingNpmrc": "Skipping parsing npmrc",
3939
"loc.messages.DuplicateCredentials": "Auth for the registry '%s' was previously set. Overwriting with new configuration.",
4040
"loc.messages.FoundEndpointCredentials": "Found set credentials for the '%s' service connection."

Tasks/NpmAuthenticateV0/npmauth.ts

Lines changed: 61 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -83,48 +83,6 @@ async function main(): Promise<void> {
8383
let LocalNpmRegistries = await npmutil.getLocalNpmRegistries(workingDirectory, packagingLocation.PackagingUris);
8484
let npmrcFile = fs.readFileSync(npmrc, 'utf8').split(os.EOL);
8585

86-
#if WIF
87-
const feedUrl = npmrcparser.NormalizeRegistry(tl.getInput("feedUrl"));
88-
const entraWifServiceConnectionName = tl.getInput("workloadIdentityServiceConnection");
89-
90-
// Skip npmrc parsing if we are using feed url and wif service connection
91-
if (feedUrl && entraWifServiceConnectionName) {
92-
tl.debug(tl.loc("Info_AddingFederatedFeedAuth", entraWifServiceConnectionName, feedUrl));
93-
const feedTenant = await getFeedTenantId(feedUrl);
94-
let token = await getFederatedWorkloadIdentityCredentials(entraWifServiceConnectionName, feedTenant);
95-
if (token)
96-
{
97-
const nerfed = util.toNerfDart(feedUrl);
98-
const auth = `${nerfed}:_authToken=${token}`;
99-
tl.debug(tl.loc('AddingAuthRegistry', feedUrl));
100-
npmutil.appendToNpmrc(npmrc, os.EOL + auth + os.EOL);
101-
tl.debug(tl.loc('SuccessfulAppend'));
102-
npmrcFile.push(os.EOL + auth + os.EOL);
103-
federatedFeedAuthSuccessCount++;
104-
tl.debug(tl.loc('SuccessfulPush'));
105-
console.log(tl.loc("Info_SuccessAddingFederatedFeedAuth", feedUrl));
106-
console.log(tl.loc("SkippingParsingNpmrc"));
107-
108-
if (endpointsArray.includes(feedUrl)){
109-
tl.warning(tl.loc('DuplicateCredentials', feedUrl));
110-
}
111-
112-
else {
113-
endpointsArray.push(feedUrl);
114-
tl.setVariable('EXISTING_ENDPOINTS', endpointsArray.join(','), false);
115-
}
116-
}
117-
else
118-
{
119-
throw new Error(tl.loc("FailedToGetServiceConnectionAuth", entraWifServiceConnectionName));
120-
}
121-
return;
122-
}
123-
else if (feedUrl || entraWifServiceConnectionName) {
124-
throw new Error(tl.loc("MissingFeedUrlOrServiceConnection"));
125-
}
126-
#endif
127-
12886
let endpointRegistries: npmregistry.INpmRegistry[] = [];
12987
let endpointIds = tl.getDelimitedInput(constants.NpmAuthenticateTaskInput.CustomEndpoint, ',');
13088
if (endpointIds && endpointIds.length > 0) {
@@ -144,10 +102,52 @@ async function main(): Promise<void> {
144102
}
145103

146104
let addedRegistry = [];
147-
for (let RegistryURLString of npmrcparser.GetRegistries(npmrc, /* saveNormalizedRegistries */ true)) {
105+
let npmrcRegistries = npmrcparser.GetRegistries(npmrc, /* saveNormalizedRegistries */ true);
106+
107+
#if WIF
108+
const entraWifServiceConnectionName = tl.getInput("workloadIdentityServiceConnection");
109+
const federatedAuthToken = await getAzureDevOpsServiceConnectionCredentials(entraWifServiceConnectionName)
110+
111+
const feedUrl = tl.getInput("feedUrl");
112+
if (feedUrl && !entraWifServiceConnectionName) {
113+
throw new Error(tl.loc("MissingFeedUrlOrServiceConnection"));
114+
}
115+
116+
if(feedUrl){
117+
npmrcRegistries = npmrcRegistries.filter(x=> util.toNerfDart(x) == util.toNerfDart(npmrcparser.NormalizeRegistry(feedUrl)));
118+
if(npmrcRegistries.length == 0){
119+
throw new Error(tl.loc("IgnoringRegistry", feedUrl));
120+
}
121+
}
122+
#endif
123+
124+
for (let RegistryURLString of npmrcRegistries) {
148125
let registryURL = URL.parse(RegistryURLString);
149126
let registry: npmregistry.NpmRegistry;
150-
if (endpointRegistries && endpointRegistries.length > 0) {
127+
128+
#if WIF
129+
if (feedUrl && entraWifServiceConnectionName){
130+
if (util.toNerfDart(npmrcparser.NormalizeRegistry(feedUrl)) == util.toNerfDart(RegistryURLString)) {
131+
console.log(tl.loc("AddingEndpointCredentials", entraWifServiceConnectionName));
132+
registry = new npmregistry.NpmRegistry(RegistryURLString, `${util.toNerfDart(RegistryURLString)}:_authToken=${federatedAuthToken}`, true);
133+
let url = URL.parse(RegistryURLString);
134+
addedRegistry.push(url);
135+
npmrcFile = clearFileOfReferences(npmrc, npmrcFile, url, addedRegistry);
136+
federatedFeedAuthSuccessCount++;
137+
console.log(tl.loc("Info_SuccessAddingFederatedFeedAuth", RegistryURLString));
138+
}
139+
} else if (!feedUrl && entraWifServiceConnectionName){
140+
console.log(tl.loc("AddingEndpointCredentials", entraWifServiceConnectionName));
141+
registry = new npmregistry.NpmRegistry(RegistryURLString, `${util.toNerfDart(RegistryURLString)}:_authToken=${federatedAuthToken}`, true)
142+
let url = URL.parse(RegistryURLString);
143+
addedRegistry.push(url);
144+
npmrcFile = clearFileOfReferences(npmrc, npmrcFile, url, addedRegistry);
145+
federatedFeedAuthSuccessCount++;
146+
console.log(tl.loc("Info_SuccessAddingFederatedFeedAuth", RegistryURLString));
147+
}
148+
#endif
149+
150+
if (!registry && endpointRegistries && endpointRegistries.length > 0) {
151151
for (let serviceEndpoint of endpointRegistries) {
152152
if (util.toNerfDart(serviceEndpoint.url) == util.toNerfDart(RegistryURLString)) {
153153
let serviceURL = URL.parse(serviceEndpoint.url);
@@ -160,10 +160,11 @@ async function main(): Promise<void> {
160160
}
161161
}
162162
}
163+
163164
if (!registry) {
164165
for (let localRegistry of LocalNpmRegistries) {
165166
if (util.toNerfDart(localRegistry.url) == util.toNerfDart(RegistryURLString)) {
166-
// If a registry is found, but we previously added credentials for it, skip it
167+
// If a registry is found, but we previously added credentials for it warn and overwrite
167168
if (endpointsArray.includes(localRegistry.url)) {
168169
tl.warning(tl.loc('DuplicateCredentials', localRegistry.url));
169170
tl.warning(tl.loc('FoundEndpointCredentials', registryURL.host));
@@ -206,6 +207,7 @@ main().catch(error => {
206207
"FederatedFeedAuthCount": federatedFeedAuthSuccessCount
207208
});
208209
});
210+
209211
function clearFileOfReferences(npmrc: string, file: string[], url: URL.Url, addedRegistry: URL.Url[]) {
210212
let redoneFile = file;
211213
let warned = false;
@@ -224,3 +226,17 @@ function clearFileOfReferences(npmrc: string, file: string[], url: URL.Url, adde
224226
return redoneFile;
225227
}
226228

229+
#if WIF
230+
async function getAzureDevOpsServiceConnectionCredentials(adoServiceConnection: string){
231+
if(!adoServiceConnection){
232+
return undefined;
233+
}
234+
235+
let federatedAuthToken = await getFederatedWorkloadIdentityCredentials(adoServiceConnection);
236+
if(!federatedAuthToken){
237+
throw new Error(tl.loc("FailedToGetServiceConnectionAuth", adoServiceConnection));
238+
}
239+
240+
return federatedAuthToken;
241+
}
242+
#endif

0 commit comments

Comments
 (0)