Skip to content

build: route CI Maven restore through the CFS feed (SR21 network isolation) - #1895

Draft
wenytang-ms wants to merge 2 commits into
mainfrom
wenyt/cfs-maven-feed
Draft

build: route CI Maven restore through the CFS feed (SR21 network isolation)#1895
wenytang-ms wants to merge 2 commits into
mainfrom
wenyt/cfs-maven-feed

Conversation

@wenytang-ms

@wenytang-ms wenytang-ms commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Why

Part of ICM 840100965 / SFI MountainPass SR21 (1ES Network Isolation). Build pipelines must stop restoring packages from public feeds and go through the Central Feed Service. This repo's pipelines currently pull from repo.maven.apache.org, which blocks the move to a network-isolated pool.

What this does

Redirects CI-only Maven traffic to the CFS feed mseng/VSJava/vscjava. It is fully declarative — no scripts, no generated files, no mutation of the agent's ~/.m2/settings.xml.

File Role
.azure-pipelines/cfs-settings.xml Committed Maven settings: a <mirror> (mirrorOf: central) plus the matching <server>, both id vscjava. The URL and the token are read from the environment via ${env.*}.
.azure-pipelines/maven-cfs-variables.yml Non-secret pipeline variables: CFS_MAVEN_URL, MAVEN_ARGS (-s .../cfs-settings.xml), MVNW_REPOURL, MVNW_USERNAME.
pipeline YAMLs Each step that invokes Maven (npm run build-plugin) gets a scoped env: block supplying SYSTEM_ACCESSTOKEN and MVNW_PASSWORD.
java-extension/mvnw, mvnw.cmd Takari wrapper -> Apache maven-wrapper 3.3.3 only-script. maven-wrapper.jar is deleted.

Local developer experience is unchanged. All of these variables are set only by the pipeline; on a laptop none of them exist, so mvnw uses the committed distributionUrl and Maven uses your own settings.xml. Nothing internal is baked into the repo except the feed URL, which already appears in *-cfs-variables.yml elsewhere in this org.

Two details worth calling out:

  • MVNW_REPOURL replaces everything ahead of /org/apache/maven/ in the distribution URL, so the committed distributionUrl needs no edit — one value works for both contributors and agents.

  • Dropping maven-wrapper.jar removes a 47 KB unreviewable binary from the repo; the only-script distribution is pure shell/batch.

  • scripts/buildJdtlsExt.js: the JaCoCo agent is now staged with dependency:copy instead of being fished out of the local repository by hand.

  • Of the 1207 resolved artifacts the only 5 non-CFS lines are p2/Eclipse update-site URLs, which mirrorOf: central deliberately does not capture — Tycho p2 is not a Maven repository.

Fail-closed

If the variables are ever lost, the build fails rather than silently falling back to Maven Central. Verified: with CFS_MAVEN_URL unset the mirror URL stays the literal ${env.CFS_MAVEN_URL}, Maven issues 0 requests to public central, and the build exits BUILD FAILURE.

Verification

Real ADO build (microsoft.vscode-maven-CI #31839172, exercising this exact template):

Host Hits
pkgs.dev.azure.com 1762
repo.maven.apache.org 0
repo1.maven.org 0
oss.sonatype.org 0
Eclipse mirrors (rafal.ca, cicku, osuosl, ...) 0

BUILD SUCCESS. The only other outbound host in the entire log is download.eclipse.org (p2, DefaultDeny-only, out of SR21 scope).

This repo, locally: ./mvnw clean verify -B -> BUILD SUCCESS, 1207 artifacts served by vscjava. Also verified with a scratch MAVEN_USER_HOME that the wrapper itself fetches the Maven distribution from CFS, and the full authenticated path was re-run on Linux (WSL + Temurin 21), since the agents are Linux.

Server-side pipelines/{id}/preview on every enabled pipeline in this repo: all compile, all carry MAVEN_ARGS / MVNW_REPOURL / MVNW_PASSWORD.

Two bugs that only real CI could surface

  1. maven-wrapper.properties had no trailing newline. The Apache mvnw reads it with while IFS="=" read -r key value; POSIX read discards an unterminated final line, so distributionUrl came back empty and the script died with cannot read distributionUrl property. The old Takari wrapper used a Java properties loader, and mvnw.cmd on Windows doesn't care either — only a Linux agent fails. Reproduced under WSL, then fixed.
  2. System.AccessToken cannot be passed through the variables: block. It is a secret, and Azure Pipelines does not export secret variables into the process environment — not even indirectly, through a variable whose value is $(System.AccessToken). In the failing run MVNW_REPOURL (derived from a non-secret) was exported correctly while MVNW_PASSWORD was empty, so the wrapper fell into its unauthenticated wget branch and got 401. Hence the step-scoped env: block — which is also the tighter posture, since the token is then visible to the Maven steps only rather than to every task in the job.

SFI Network Isolation requires builds to stop reaching public package hosts,
and Maven is the last source of egress from these pipelines now that npm has
moved. Two independent paths reach repo.maven.apache.org: artifact resolution,
and the wrapper's download of the Maven distribution -- which happens before
Maven exists, so no settings file can influence it.

Both are closed with configuration Maven and its wrapper already understand,
declared as pipeline variables so they reach every step in the job. That last
part matters: these builds never name Maven on a command line, they start it
from an npm script, so there is nowhere to add a flag.

MAVEN_ARGS points Maven at a committed settings.xml carrying the mirror and
its credential. The mirror is scoped to `central` rather than `external:*`
because Tycho resolves p2 repositories through the same configuration and the
feed cannot serve p2. MVNW_REPOURL substitutes the host in distributionUrl,
which is why the wrapper properties still name the public host and need no
edit at all.

MVNW_REPOURL requires the Apache wrapper -- the Takari one bundled here reads
the URL only from the properties file and honours no override -- so it is
replaced by the script-only distribution of Apache maven-wrapper 3.3.3, which
also removes maven-wrapper.jar from the tree. 3.3.3 and not 3.3.2 because the
latter selects the mvnd path pattern for non-mvnd builds on Windows and
produces a malformed URL.

This replaces an earlier attempt that had a pipeline step rewrite the user
settings.xml and the tracked wrapper properties. It needed to locate the JVM's
user.home, edit XML that another task had just written, and leave the checkout
dirty. None of that is necessary.

The jacoco agent was fetched with curl straight from repo1.maven.org, which no
settings file can redirect because Maven is not the one making the request. It
now comes from `dependency:copy`, so it follows whatever repository the build
is already pointed at. `-N` keeps that on the parent pom, whose packaging is
`pom`, so Tycho has no module to build and skips a second target platform
resolution.

Nothing here changes a local build: MAVEN_ARGS and MVNW_* are set only on the
agents, so ./mvnw keeps using the developer's own settings and the public
central. If either variable were missing, the build fails against an unusable
repository rather than quietly falling back to the public host.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: ac225268-483d-4498-a08f-fe79a87d4d2e
@wenytang-ms
wenytang-ms force-pushed the wenyt/cfs-maven-feed branch from 9727061 to 6e06969 Compare July 29, 2026 10:21
@wenytang-ms wenytang-ms changed the title build: resolve maven artifacts through the CFS feed build: route CI Maven restore through the CFS feed (SR21 network isolation) Jul 29, 2026
The comment on downloadJacocoAgent pointed at .azure-pipelines/maven-cfs.yml,
which does not exist. The variables live in maven-cfs-variables.yml.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 43342f3b-f3d1-495c-88c4-397a6a56c01b
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant