Skip to content

[CFS] Route NuGet and Cargo restores through Azure DevOps feeds - #7268

Draft
danieljurek wants to merge 9 commits into
mainfrom
djurek/cfs-compliance
Draft

[CFS] Route NuGet and Cargo restores through Azure DevOps feeds#7268
danieljurek wants to merge 9 commits into
mainfrom
djurek/cfs-compliance

Conversation

@danieljurek

@danieljurek danieljurek commented Jul 30, 2026

Copy link
Copy Markdown
Member

Summary

Routes CI package restores through Azure DevOps feeds so builds satisfy the CFSClean 1ES Network Isolation policy, instead of reaching the public internet at api.nuget.org and crates.io.

networkIsolationPolicy is also set to Permissive, CFSClean in archetype-sdk-client.yml so the policy is actually applied.

Result

CFSClean is compliant in aggregate across all jobs, verified on three full pipeline runs (162 jobs total):

Definition Build Jobs Blocked connections Non-compliant policies
cpp - core 6647208 54 0 Default Deny(58), CFSClean2(48), CFSClean3(8)
cpp - keyvault 6647120 54 0 CFSClean2(48), Default Deny(10)
cpp - storage 6647119 54 0 CFSClean2(48), Default Deny(10)

CFSClean does not appear for any build, and every one of the 141 measured jobs reports CFSClean=PASS.

Domain Policy Before After
api.nuget.org CFSClean 3175 hits / 37 jobs 0
index.crates.io CFSClean 364 hits / 34 jobs 0
static.crates.io CFSClean (same cluster) 0
static.rust-lang.org CFSClean (same cluster) 0

static.rust-lang.org needed no separate fix. It only ever appeared because it shares Fastly IPs with crates.io, so redirecting crates.io removed it. Confirmed by a run where the rustup toolchain download still occurred and the domain reported zero violations.

Note: those measurements were collected with a temporary local workaround applied to sdk/core/azure-core-amqp/Test-Setup.ps1, described under Known issues below. That workaround is not part of this PR.

Changes

Cargo

  • eng/templates/config.toml.template — declares the azure-sdk-for-rust registry and a [source.crates-io] replace-with entry pointing at it.
  • eng/pipelines/templates/steps/cmake-build.yml — copies that template to $CARGO_HOME/config.toml before CMake/Corrosion invokes cargo, then runs CargoAuthenticate@0 against it.

An on-disk config is required because cargo does not honor environment variables for the [source] table, and only reads config from $CARGO_HOME or ancestors of its working directory.

NuGet

  • eng/templates/NuGet.config.template — clears inherited package sources and adds the azure-sdk-for-net Azure Artifacts feed.
  • eng/pipelines/templates/steps/nuget-config.yml — installs that config at the user level (%APPDATA%\NuGet on Windows, ~/.nuget/NuGet otherwise), prints the resulting source list, then runs NuGetAuthenticate@1.
  • eng/pipelines/templates/jobs/ci.tests.yml, live.tests.yml — invoke the step early, ahead of every NuGet consumer. This replaces the previous standalone NuGetAuthenticate@1 tasks, which were scoped to code coverage only.

Two details worth calling out for review:

  • The config is installed at the user level rather than in the repo because each consumer restores from a different working directory. Coverage tools install globally, test-proxy installs to a tool path, and TestAmqpBroker builds from a temporary clone, so a repo-local NuGet.config would not apply to any of them.
  • NuGetAuthenticate@1 does not register package sources. It only installs the Azure Artifacts Credential Provider and binds it to feeds under pkgs.dev.azure.com/azure-sdk. Clearing sources without adding a replacement fails every restore with NU1100, so the explicit <add> is required rather than optional.

Known issues owned by azure-core-amqp

sdk/core/azure-core-amqp/Test-Setup.ps1 is intentionally not modified by this PR. It has two problems that should be resolved by the owners of the azure-amqp dependency uptake.

1. The step is already failing in the pipelines, independently of this PR. Azure/azure-amqp master now declares <TargetFrameworks>net48;net10.0</TargetFrameworks>. The net8.0 target that Test-Setup.ps1 builds and launches no longer exists, so the step fails regardless of network isolation. Targeting net10.0 was used as a temporary local workaround to get a fully green core run for the compliance measurements above; it has been reverted and is not proposed here, because which framework to target should be a deliberate decision by the owners.

2. It will still reach api.nuget.org once that is fixed. The cloned Azure/azure-amqp repository ships its own nuget.config listing api.nuget.org and dotnet-public. A repository level config takes precedence over the user level config installed by this PR, so the clone reintroduces the blocked source and the restore fails under CFSClean with:

error NU1301: Unable to load the service index for source https://api.nuget.org/v3/index.json.
error NU1301:   Permission denied (api.nuget.org:443)

A verified fix is to rewrite the cloned config after git clone, swapping the nuget.org source for the Azure SDK feed while leaving dotnet-public in place:

$clonedNuGetConfig = Join-Path $WorkingDirectory "azure-amqp/nuget.config"
$azureSdkFeed = "https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-net/nuget/v3/index.json"

dotnet nuget remove source "NuGet official package source" --configfile $clonedNuGetConfig
dotnet nuget add source $azureSdkFeed --name azure-sdk-for-net --configfile $clonedNuGetConfig

Keeping dotnet-public matters: it supplies Microsoft.CodeAnalysis.PublicApiAnalyzers and Microsoft.NETFramework.ReferenceAssemblies.net48, which the azure-sdk-for-net feed has not cached and would otherwise require an authenticated upstream pull. This was validated from a clean clone with an empty package cache, where TestAmqpBroker builds with no credentials.

Out of scope

These remain non-compliant. All are blocked=0 (audit only) and none are addressable by feed redirection:

Domain Policy Hits / Jobs Source
www.powershellgallery.com CFSClean2 48 / 16 shared eng/common PowerShell bootstrap; identical in all three pipelines, so one fix covers all
anglesharp.azurewebsites.net Default Deny 26 / 26 an azure-core unit test calling a live internet endpoint; needs a test-proxy recording or local fixture
docs.oasis-open.org, curl.se, en.cppreference.com, www.doxygen.org, datatracker.ietf.org Default Deny / CFSClean3 34 / 1 documentation links fetched by GenerateReleaseArtifacts

macOS jobs run on Microsoft-hosted agents rather than azsdk-pool, so they are not instrumented by 1ES Network Isolation and could not be assessed.

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 2 pipeline(s).
8 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

danieljurek and others added 3 commits July 31, 2026 12:01
Azure/azure-amqp master moved to <TargetFrameworks>net48;net10.0</TargetFrameworks>,
so the net8.0 output directory referenced by Test-Setup.ps1 no longer exists and
every Validate/ValidateLive job in "cpp - core" failed at "Test-Setup for
azure-core-amqp".

The build command was already bumped to net10.0; this aligns the Set-Location
launch path with it so the broker can actually start. Both changes are marked
with TODO comments to be reverted before merge - this exists only to push the
build far enough forward to validate network isolation / CFS compliance.

Verified locally: builds clean against azure-amqp master and produces
bin/Debug/TestAmqpBroker/net10.0/TestAmqpBroker.dll, which dotnet exec loads and
runs.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 56d86b9d-f4b6-4b43-b37c-758f1e6ac9a3
Under the CFSClean network isolation policy api.nuget.org is blocked, which
fails Test-Setup for azure-core-amqp, the coverage tools install, and the
test-proxy install with NU1301 "Permission denied (api.nuget.org:443)".

NuGetAuthenticate@1 does not register package sources, it only installs the
Azure Artifacts Credential Provider, so authentication alone does not redirect
traffic. Mirroring the cargo config fix, add an XML template that clears the
inherited sources and adds the azure-sdk-for-net feed, and copy it over the
user level NuGet config before NuGetAuthenticate@1 runs.

The config is installed at the user level rather than in the repo because each
consumer restores from a different working directory, so a repo local
NuGet.config would not apply to any of them.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 56d86b9d-f4b6-4b43-b37c-758f1e6ac9a3
danieljurek and others added 3 commits July 31, 2026 15:49
Azure/azure-amqp ships a nuget.config that adds api.nuget.org. A repository
level config takes precedence over the agent's user level config, so the clone
reintroduced the source that CFSClean blocks and Test-Setup kept failing with
NU1301 "Permission denied (api.nuget.org:443)" even after the user level config
was pointed at the Azure Artifacts feed.

Overwrite the clone's nuget.config with the same template. Verified locally by
cloning azure-amqp, replacing the config, and building TestAmqpBroker against
only the azure-sdk-for-net feed with a cold package cache: the feed's nuget.org
upstream supplies Microsoft.CodeAnalysis.PublicApiAnalyzers and
Microsoft.NETFramework.ReferenceAssemblies.net48, and the build succeeds.

Note the upstream requires an authenticated identity to pull packages it has not
cached yet, which NuGetAuthenticate@1 already provides in CI.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 56d86b9d-f4b6-4b43-b37c-758f1e6ac9a3
Azure/azure-amqp ships a nuget.config listing api.nuget.org and dotnet-public. A
repository level config takes precedence over the agent's user level config, so
the clone reintroduced the source CFSClean blocks and Test-Setup failed with
NU1301 "Permission denied (api.nuget.org:443)".

Use dotnet nuget to remove just the nuget.org source and add the Azure SDK
public feed, leaving dotnet-public in place, rather than replacing the file.

Verified from a clean clone with an empty package cache: the resulting sources
are dotnet-public plus azure-sdk-for-net, and TestAmqpBroker builds with no
credentials. Keeping dotnet-public matters because it supplies
Microsoft.CodeAnalysis.PublicApiAnalyzers and
Microsoft.NETFramework.ReferenceAssemblies.net48, which the azure-sdk-for-net
feed has not cached and would otherwise require an authenticated upstream pull.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 56d86b9d-f4b6-4b43-b37c-758f1e6ac9a3
The net10.0 bump was only a workaround so the core pipeline could get far
enough to measure network isolation compliance. Restore the original net8.0
target and drop the TODO markers, keeping only the NuGet source change.

TestAmqpBroker still fails to build because Azure/azure-amqp master targets
net48 and net10.0, so net8.0 no longer exists. That break predates this branch
and belongs to the owners of the azure-amqp dependency uptake.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 56d86b9d-f4b6-4b43-b37c-758f1e6ac9a3
@danieljurek danieljurek changed the title [CFS] Use DevOps feeds for NuGet and Cargo [CFS] Route NuGet and Cargo restores through Azure DevOps feeds Aug 2, 2026
Drop the NuGet source change from Test-Setup.ps1 so this PR does not touch the
file at all. Restoring TestAmqpBroker still reaches api.nuget.org through the
nuget.config shipped by the cloned Azure/azure-amqp repository, which needs to
be addressed alongside the framework target by the owners of that dependency.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 56d86b9d-f4b6-4b43-b37c-758f1e6ac9a3
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant