Skip to content

Commit 36dcc74

Browse files
authored
Route npm package restore through the Central Feed Service (#1892)
SFI Network Isolation requires build pipelines to stop restoring packages directly from public feeds. These pipelines resolve everything from registry.npmjs.org today, so they cannot run on a network isolated pool and are flagged by the MountainPass SR21 campaign. Point npm at the CFS feed mseng/VSJava/vscjava instead. The redirect is carried by npm_config_registry, declared as a pipeline variable. npm resolves configuration in the order cli > environment > project .npmrc > user .npmrc, so writing the registry only into a generated user config would leave it outranked by anything the agent image already configures -- Microsoft hosted images do ship a user level .npmrc pointing at an internal proxy. An environment variable sits above every .npmrc file, so it is the one lever that reaches all of these pipelines. npm matches npm_config_* case insensitively, which matters because restore is not driven by a single task here: the Npm tasks, npx json, npx @vscode/vsce and the vsce invocation inside AzureCLI@2 all inherit the agent environment rather than reading a task input. A .npmrc is still generated into the agent temp directory, because NpmAuthenticate discovers the registries to authenticate by reading it. npm now takes the URL from the environment and the matching credential from that file. Nothing is committed, so open source contributors and the GitHub Actions workflows keep restoring from the public registry, and the credential never lands inside the workspace. Assert the result rather than assuming it. A silent fallback to the public registry is the failure this change exists to prevent and it leaves no trace in the build log -- npm never reports which registry it used, and the package counts look identical either way. The pipelines now fail if npm is not pointed at the CFS feed by the time restore begins. The steps template is referenced as an absolute path anchored to @self. A relative path is resolved against the file doing the including, which for these pipelines is the 1ES extends template in another repository, so the unqualified form is looked up in 1ESPipelineTemplates and fails YAML compilation.
1 parent c4e88de commit 36dcc74

7 files changed

Lines changed: 96 additions & 0 deletions

File tree

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# Variables required to route npm package restore through the Central Feed Service
2+
# (CFS). Consumed by every pipeline in this directory alongside the npm-cfs.yml steps
3+
# template, which is where these values are actually applied.
4+
#
5+
# Both are declared here rather than in each pipeline so the feed URL exists in
6+
# exactly one place.
7+
#
8+
# npm_config_registry is not redundant with the registry written into the generated
9+
# .npmrc. npm resolves configuration in the order cli > environment > project .npmrc
10+
# > user .npmrc, so a registry supplied only through the user config is outranked by
11+
# anything the agent image already configures -- Microsoft hosted images ship a user
12+
# level .npmrc pointing at an internal proxy, and a pool that exports
13+
# npm_config_registry would win outright. Restore would then quietly resolve from
14+
# somewhere other than CFS while the build still reported success. Declaring the
15+
# variable here puts the redirect at environment precedence, where only an explicit
16+
# command line flag can override it.
17+
#
18+
# npm matches npm_config_* environment variables case insensitively, so the
19+
# uppercased form that Azure Pipelines exports applies to every step on every OS.
20+
# That matters because package restore here is not driven by a single task: the Npm
21+
# tasks, `npx json`, `npx @vscode/vsce` and the vsce invocation inside AzureCLI@2
22+
# all inherit the agent environment rather than reading a task input.
23+
24+
variables:
25+
- name: npm_config_registry
26+
value: https://pkgs.dev.azure.com/mseng/VSJava/_packaging/vscjava/npm/registry/
27+
- name: npm_config_userconfig
28+
value: $(Agent.TempDirectory)/.npmrc

.azure-pipelines/npm-cfs.yml

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# Routes npm package restore through the Central Feed Service (CFS), as required by
2+
# SFI Network Isolation. Consumed by every build pipeline in this directory.
3+
#
4+
# Pipelines must also include the companion variables template:
5+
# variables:
6+
# - template: /.azure-pipelines/npm-cfs-variables.yml@self
7+
# which declares the feed URL and the generated .npmrc path. The redirect itself is
8+
# carried by the npm_config_registry environment variable that template exports; see
9+
# its header for why the generated .npmrc alone is not enough.
10+
#
11+
# The .npmrc is generated at build time into the agent temp directory rather than
12+
# being committed to the repository, so that:
13+
# * open source contributors and the GitHub Actions workflows keep restoring from
14+
# the public npm registry -- npm rewrites the host of every `resolved` URL in
15+
# package-lock.json to the configured registry, so a single lockfile serves both;
16+
# * the credential that NpmAuthenticate injects never lands inside the workspace;
17+
# * the configuration does not depend on the repository being checked out, so
18+
# release jobs consuming a prebuilt artifact work the same way as build jobs.
19+
#
20+
# The registry is still written into that file because NpmAuthenticate discovers the
21+
# registries to authenticate by reading it. npm then takes the URL from the
22+
# environment and the matching credential from this file.
23+
#
24+
# The file is written with `npm config set` rather than a shell redirect because
25+
# these pipelines span both Linux and Windows pools. `script:` maps to CmdLine@2,
26+
# which runs on both, and the npm invocation itself is shell agnostic. PowerShell@2
27+
# is avoided because it resolves `pwsh` before `powershell` and hard fails when
28+
# neither is on PATH, which is not guaranteed on a custom Linux image.
29+
#
30+
# This template must run after the Node install task, and before any step that
31+
# restores packages -- including `npx`, which resolves downloads through the
32+
# configured registry.
33+
#
34+
# Consumers must reference this file as `/.azure-pipelines/npm-cfs.yml@self`. A
35+
# relative path is resolved against the file doing the including, which for these
36+
# pipelines is the 1ES extends template in another repository, so the unqualified
37+
# form is looked up in 1ESPipelineTemplates and fails YAML compilation.
38+
39+
steps:
40+
- script: npm config set registry $(npm_config_registry) --location=user --userconfig="$(npm_config_userconfig)"
41+
displayName: Configure CFS npm registry
42+
43+
# Appends `//pkgs.dev.azure.com/.../registry/:_authToken=<token>` for every
44+
# registry it finds in the file above. `always-auth` is deliberately not written:
45+
# it is not read by this task and is rejected outright by the npm 10 shipped with
46+
# Node 20.
47+
- task: NpmAuthenticate@0
48+
displayName: Authenticate to CFS feed
49+
inputs:
50+
workingFile: $(npm_config_userconfig)
51+
52+
# Restore silently falling back to the public registry is the failure mode this
53+
# whole template exists to prevent, and it leaves no trace in the build log, so it
54+
# is asserted rather than assumed. Written in node, which the agent already
55+
# provides, to avoid shell differences between the Linux and Windows pools.
56+
- script: >-
57+
node -e "const cp=require('child_process');const r=cp.execSync('npm config get registry').toString().trim();console.log('npm registry -> '+r);if(!r.startsWith('https://pkgs.dev.azure.com/')){console.error('##vso[task.logissue type=error]npm is not configured against the CFS feed');process.exit(1);}"
58+
displayName: Verify CFS npm registry

.azure-pipelines/release-nightly.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ name: $(Date:yyyyMMdd).$(Rev:r) # Use the current date and a revision number for
88
variables:
99
- name: Codeql.Enabled
1010
value: true
11+
- template: /.azure-pipelines/npm-cfs-variables.yml@self
1112
resources:
1213
repositories:
1314
- repository: self
@@ -46,6 +47,7 @@ extends:
4647
displayName: 'Use Node.js 20.x'
4748
inputs:
4849
version: '20.x'
50+
- template: /.azure-pipelines/npm-cfs.yml@self
4951
- task: AzureCLI@2
5052
displayName: 'Publish Extension'
5153
inputs:

.azure-pipelines/release.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ name: $(Date:yyyyMMdd).$(Rev:r) # Use the current date and a revision number for
88
variables:
99
- name: Codeql.Enabled
1010
value: true
11+
- template: /.azure-pipelines/npm-cfs-variables.yml@self
1112
resources:
1213
repositories:
1314
- repository: self
@@ -46,6 +47,7 @@ extends:
4647
displayName: 'Use Node.js 20.x'
4748
inputs:
4849
version: '20.x'
50+
- template: /.azure-pipelines/npm-cfs.yml@self
4951
- task: AzureCLI@2
5052
displayName: 'Publish Extension'
5153
inputs:

.azure-pipelines/vscode-java-test-ci.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ name: $(Date:yyyyMMdd).$(Rev:r)
22
variables:
33
- name: Codeql.Enabled
44
value: true
5+
- template: /.azure-pipelines/npm-cfs-variables.yml@self
56
resources:
67
repositories:
78
- repository: self
@@ -49,6 +50,7 @@ extends:
4950
versionSpec: "21"
5051
jdkArchitectureOption: x64
5152
jdkSourceOption: PreInstalled
53+
- template: /.azure-pipelines/npm-cfs.yml@self
5254
- task: Npm@1
5355
displayName: npm install
5456
inputs:

.azure-pipelines/vscode-java-test-nightly.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ name: $(Date:yyyyMMdd).$(Rev:r)
22
variables:
33
- name: Codeql.Enabled
44
value: true
5+
- template: /.azure-pipelines/npm-cfs-variables.yml@self
56
schedules:
67
- cron: 0 2 * * *
78
branches:
@@ -65,6 +66,7 @@ extends:
6566
jdkDestinationDirectory: $(Agent.ToolsDirectory)/ms-jdk21
6667
- script: java --version
6768
displayName: 'Check Java installation'
69+
- template: /.azure-pipelines/npm-cfs.yml@self
6870
- task: Npm@1
6971
displayName: npm install
7072
inputs:

.azure-pipelines/vscode-java-test-rc.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ name: $(Date:yyyyMMdd).$(Rev:r)
22
variables:
33
- name: Codeql.Enabled
44
value: true
5+
- template: /.azure-pipelines/npm-cfs-variables.yml@self
56
resources:
67
repositories:
78
- repository: self
@@ -60,6 +61,7 @@ extends:
6061
jdkDestinationDirectory: $(Agent.ToolsDirectory)/ms-jdk21
6162
- script: java --version
6263
displayName: 'Check Java installation'
64+
- template: /.azure-pipelines/npm-cfs.yml@self
6365
- task: Npm@1
6466
displayName: npm install
6567
inputs:

0 commit comments

Comments
 (0)