Skip to content

Private Azure Monitor path for network-secured Foundry projects - #1

Closed
kseager wants to merge 5 commits into
feat/foundry-agent-default-tracingfrom
feat/foundry-vnet-monitoring
Closed

kseager wants to merge 5 commits into
feat/foundry-agent-default-tracingfrom
feat/foundry-vnet-monitoring

Conversation

@kseager

@kseager kseager commented Nov 3, 2026

Copy link
Copy Markdown
Owner

Private Azure Monitor path for network-secured Foundry projects

Stacked on Azure#10743 (feat/foundry-agent-default-tracing); retarget to main after it merges.

Problem

A project with a network: block (VNet-injected agents) could not send traces: native azd monitoring created public-ingestion App Insights. Azure#10743 skips monitoring in that case. This PR adds a private path so agent traces arrive in App Insights and can be read from inside the network.

What azd now provisions (new project, network: with your own agent subnet, monitoring enabled)

Customer VNet
├─ agent subnet (delegated Microsoft.App/environments)   ← agent containers
└─ pe subnet
     ├─ private endpoint → Foundry account
     ├─ private endpoint → Storage (blob)
     └─ private endpoint "azuremonitor" ──┐
                                          ▼
                    Azure Monitor Private Link Scope (AMPLS)
                      ingestionAccessMode: Open
                      queryAccessMode:     PrivateOnly
                      scoped resources:
                        ├─ Application Insights   public ingestion Disabled, public query Disabled
                        └─ Log Analytics workspace public ingestion Disabled, public query Disabled

Private DNS zones linked to the VNet (Monitor):
  privatelink.monitor.azure.com, privatelink.oms.opinsights.azure.com,
  privatelink.ods.opinsights.azure.com, privatelink.agentsvc.azure-automation.net

Project connection: AppInsights, ApiKey (project-scoped, azd-appinsights-<hash>)

App Insights and Log Analytics are regional PaaS resources, not VNet members. They are reachable from the VNet only through the AMPLS private endpoint. Traces are readable only from a network that reaches the scope (jump host, VPN); a laptop on the public internet cannot query them.

AMPLS access modes (why ingestion is Open)

The AMPLS lists the scoped resources and has two access modes. The private endpoint and DNS send the VNet's Azure Monitor traffic to the scope; the mode decides what happens to traffic for resources that are not scoped. The path to a scoped resource is the same in both modes.

Mode VNet to scoped resource (via PE) VNet to unscoped resource
PrivateOnly Allowed Blocked
Open Allowed Allowed over its public endpoint, if that resource accepts public traffic

Live testing found that PrivateOnly ingestion makes a scoped component reject private ingestion and no traces arrive. This setting is also the default of the bug bash foundry-private-setup skill. The component's own public ingestion stays Disabled, so ingestion still reaches it only through the private endpoint. Query stays PrivateOnly.

Tradeoff: with ingestion Open, the VNet can also send telemetry to unscoped Azure Monitor components that accept public traffic. Why a scoped component rejects PrivateOnly ingestion is not understood and is being raised with the Azure Monitor team.

Test evidence (S2 environment from the Foundry VNet bug bash, westus3, one run each)

Jump VM inside the VNet, public access off on all resources, ApiKey connection unchanged. A control custom event is sent to the private ingestion endpoint, a prompt agent and a hosted agent are invoked, then Application Insights is queried privately for rows newer than the call.

AMPLS ingestion / query Control event Foundry traces read privately
PrivateOnly / PrivateOnly 403 "Component private link id does not match incoming link" none
Open / Open 200 yes (responsesapi, hosted-agent dependency and trace)
Open / PrivateOnly (this PR) 200 yes

A separate private environment configured with an Open AMPLS and an Entra connection also delivered service-side traces. Auth mode (ApiKey vs Entra) was not the blocker.

Differences from the bug bash S2 (foundry-private-setup) Monitor configuration

The Monitor setup matches S2 (workspace-based App Insights and workspace with public ingestion and query Disabled, both scoped to one AMPLS, azuremonitor private endpoint, the same four DNS zones, ApiKey connection) except:

  1. AMPLS access modes: S2 uses PrivateOnly / PrivateOnly, which blocked trace ingestion in testing. This PR uses ingestion Open, query PrivateOnly.
  2. Connection scope: S2 creates the AppInsights connection on the account (shared to all projects). This PR creates it on the project (accounts/projects/connections).

Resource names also differ (for example scoped resources appinsights-scoped and workspace-scoped) with no functional effect. The project-scoped connection was not live-tested; S2 traces worked with the account-scoped one.

Behavior and limits

  • Supported only for a new project with its own agent subnet (BYO egress), azd-created component. Managed egress, an existing component, or a VNet that already has an azuremonitor private endpoint do not block provisioning: monitoring is skipped with a warning (opt-out: monitoring.enabled: false).
  • A VNet can connect to only one AMPLS; a second one overrides Monitor DNS.
  • Users reading traces need Log Analytics Reader (plus Privileged Monitoring Data Reader if tables are protected) and network reach to the scope.

Changes

  • synthesis/templates/modules/monitor-private-link.bicep: AMPLS, scoped resources, azuremonitor private endpoint, four DNS zones (create-and-link or reuse existing), ingestionAccessMode: Open, queryAccessMode: PrivateOnly.
  • Regenerated main.arm.json and existing-project.arm.json.
  • TestMonitorPrivateLinkAccessModes pins the access modes in the Bicep and both ARM templates.
  • README.md and azure.ai.agents/docs/private-networking.md.

Not covered

One region and one run per configuration; Terraform parity and eject for this module follow the same shape but were not live-tested.

Kayla Ames and others added 5 commits November 2, 2026 16:16
Create App Insights with ingestion disabled, an Azure Monitor Private Link
Scope, the azuremonitor private endpoint and DNS zones when a project uses
BYO-egress network isolation. Add a preflight check for an existing
azuremonitor private endpoint on the VNet. Managed egress stays unsupported.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: c1d0003f-40c8-481d-92a5-ecb8d7afb491
Bug bash S2 and the Foundry team require a project-level Agents capability
host for private tracing. Add it for BYO egress, regenerate main.arm.json,
and add a regression test.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: c1d0003f-40c8-481d-92a5-ecb8d7afb491
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: c1d0003f-40c8-481d-92a5-ecb8d7afb491
…oring

Unsupported private-network monitoring cases now skip with a warning.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: c1d0003f-40c8-481d-92a5-ecb8d7afb491
Live testing in a VNet showed PrivateOnly ingestion makes a scoped App Insights
component reject private ingestion (403) and no Foundry traces arrive. Ingestion
Open with query PrivateOnly delivered traces readable from inside the network.
Component public ingestion and query stay disabled.

Adds a regression test pinning the access modes in the Bicep and ARM templates
and updates the README and private networking docs.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: c1d0003f-40c8-481d-92a5-ecb8d7afb491
@kseager kseager closed this Nov 3, 2026
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