ACA Secret Key Vault Reference — Virtual WAN + Routing Intent Variant (H4d) Lab¶
Cost warning Virtual WAN secured-hub deployments are expensive. Prefer existing secured-hub mode whenever possible. Use full synthetic mode only with explicit opt-in (
DEPLOY_VIRTUAL_WAN=true) and delete the resource group immediately after the run.
Reproduce the Virtual WAN + Routing Intent failure surface where az containerapp secret set --identity system --key-vault-url ... fails with Unable to get value using Managed identity and an openid-configuration error only while Routing Intent forces the ACA infrastructure VNet's egress through a secured-hub Azure Firewall. The firewall policy stays intentionally restrictive throughout: it allows *.vault.azure.net, but it does not allow the Entra authority FQDNs login.microsoftonline.com and login.microsoft.com.
This lab is a reader-generated 17-gate Phase B falsification workflow. You run trigger.sh and falsify.sh against your own Azure subscription to capture one live H0 → H1 → H2 cohort (files 01-13) into labs/aca-secret-kv-ref-mi-network-path-h4d/evidence/. You then run verify.sh, which reads only those local files (no Azure API calls) and deterministically emits the four Phase B gate JSONs (14-17) that validate the narrow claim: Routing Intent is the controlled route-state variable while workload, identity, Key Vault, and firewall policy stay constant.
Bounded-scope disclosure: this workflow does not prove packet capture, dataplane bypass, regional-hub propagation bugs, or perfect route-table/dataplane synchronization at the failure instant. It also does not use az containerapp exec as the primary egress-path proof; the reproducer uses the same az containerapp secret set symptom in H0, H1, and H2. Those confounders are carried explicitly in Gate 17 explicit_drops.
Lab scope: H4d (Virtual WAN + Routing Intent variant)
This lab reproduces H4d only — the ACTUAL ACA infrastructure VNet is connected to a Virtual WAN Hub, Routing Intent is toggled on/off, and the secured-hub firewall policy remains unchanged. The H1→H2 flip is not “add the missing Entra Application Rule.” The flip is Routing Intent ON → OFF.
The diagnostic signature is therefore different from H4a and H4b. The positive pass condition is route-state evidence: az network vhub routing-intent show proves the policy is enabled, and az network vhub get-effective-routes --resource-type HubVirtualNetworkConnection --resource-id $ACA_VNET_CONNECTION_ID ... shows whether 0.0.0.0/0 targets the hub firewall. A zero-row AZFWApplicationRule result is a real-world escalation clue, not the deterministic pass condition for this lab.
Lab Metadata¶
| Attribute | Value |
|---|---|
| Difficulty | Advanced |
| Estimated Duration | 45-90 minutes depending on Virtual WAN convergence and whether an existing secured hub is reused |
| Tier | Workload Profiles (Consumption profile) |
| Failure Mode | az containerapp secret set --identity system --key-vault-url ... fails with Unable to get value using Managed identity because Routing Intent forces egress through a secured-hub Azure Firewall that still allows *.vault.azure.net but does not allow the Entra authority FQDNs |
| Skills Practiced | Virtual WAN route-state inspection, Routing Intent convergence validation, control-plane evidence discipline, bounded falsification with silence gates |
1) Background¶
Azure Container Apps supports Key Vault references in the secret manifest: the app declares a reference of the form --key-vault-url https://<vault>.vault.azure.net/secrets/<name> and the platform resolves it using a managed identity. Before the platform can read Key Vault, it must acquire a token, and managed identity token acquisition requires an OIDC discovery step against the Entra authority — an HTTPS request to https://login.microsoftonline.com/<tenant>/.well-known/openid-configuration (or the related login.microsoft.com authority host).
H4d reproduces a route-state failure. The ACA environment itself stays simple: Azure-provided DNS, no custom DNS servers, no Key Vault private endpoint, and no UDR attached to the ACA subnet. The secured-hub firewall policy is also stable: Key Vault public FQDNs stay allowed, while the Entra authority FQDNs stay blocked. What changes is whether Routing Intent on the Virtual Hub redirects internet/private traffic through that firewall.
That evidence discipline matters:
- [Observed]
az network vhub routing-intent showreports whether the Routing Intent policy exists and where it points. - [Observed]
az network vhub get-effective-routes --resource-type HubVirtualNetworkConnection --resource-id $ACA_VNET_CONNECTION_ID ...reports whether0.0.0.0/0targets the hub firewall from the Virtual Hub control-plane perspective. - [Observed] H0 succeeds, H1 fails, and H2 succeeds again using the same
az containerapp secret setsymptom. - [Not Proven] Effective routes are control-plane evidence, not packet capture.
- [Inferred] Routing Intent is the causal variable only because the symptom flips while the app, identity, Key Vault, and firewall policy stay constant.
Architecture¶
flowchart TD
A["Operator: az containerapp secret set --identity system --key-vault-url"] --> B["ACA control-plane worker resolves the Key Vault reference"]
B --> C["Managed identity OIDC discovery must reach login.microsoftonline.com / login.microsoft.com"]
C --> D["ACA infrastructure VNet"]
D --> E{"Routing Intent active on the Virtual Hub?"}
E -->|H0 / H2: No| F["No active secured-hub internet route on this path\nOIDC discovery reaches public Entra authority\nsecret set succeeds\nrevision unchanged"]
E -->|H1: Yes| G["InternetTraffic + PrivateTraffic -> secured-hub Azure Firewall\nFirewall policy still allows *.vault.azure.net only\nEntra authority remains blocked\nsecret set fails before Key Vault read\nrevision unchanged, ingress HTTP 200"]
G --> H["Virtual Hub effective routes show 0.0.0.0/0 -> hub firewall"] Effective routes are not packet capture
[Observed] The effective-route output is the Virtual Hub control-plane view. [Not Proven] It does not prove that every packet followed the route exactly as shown at every instant. This lab therefore treats route-state plus H0/H1/H2 symptom flips as bounded evidence, not as universal proof of dataplane behavior.
Why Azure Firewall log silence is not the pass condition
In real incidents, zero AZFWApplicationRule Deny rows can be a useful H4d escalation clue. But it is not deterministic proof of bypass: diagnostics latency, disabled categories, wrong workspaces, or querying the wrong firewall can all produce silence. If diagnostics are enabled and the traffic reaches Azure Firewall, AZFWApplicationRule may show Deny rows for login.microsoftonline.com or login.microsoft.com. This lab therefore gates on Routing Intent + effective-route state, not on log silence.
2) Hypothesis¶
IF an Azure Container Apps environment uses Azure-provided DNS on its infrastructure VNet, the app has a system-assigned managed identity granted Key Vault Secrets User at the target Key Vault scope, Key Vault remains public, and the secured-hub firewall policy continues to allow *.vault.azure.net but continues to block login.microsoftonline.com and login.microsoft.com, THEN:
- H0 baseline (no active Routing Intent path):
az containerapp secret set --identity system --key-vault-url ...succeeds with exit code 0. The named secretkvref-h0appears inproperties.configuration.secrets.latestReadyRevisionNameis unchanged. - H1 (Routing Intent ON): after connecting the ACTUAL ACA infrastructure VNet to the Virtual Hub and enabling Routing Intent so both
InternetTrafficandPrivateTraffictarget the hub Azure Firewall, the command fails with exit code non-zero.stderrcarriesUnable to get value using Managed identityand includesopenid-configuration.configuration.secretsdoes not containkvref-h1.latestReadyRevisionNameis still unchanged. Ingress still returns HTTP 200. [Observed]az network vhub get-effective-routes --resource-type HubVirtualNetworkConnection --resource-id $ACA_VNET_CONNECTION_ID ...shows0.0.0.0/0targeting the hub firewall. - H2 (Routing Intent OFF): after removing Routing Intent while leaving the firewall policy unchanged, the effective routes no longer show
0.0.0.0/0targeting the hub firewall, and a new secret-set attempt succeeds with exit code 0.kvref-h2appears inconfiguration.secrets.latestReadyRevisionNameis still unchanged from baseline. Ingress still returns HTTP 200.
| Variable | H0 | H1 | H2 |
|---|---|---|---|
| ACA subnet UDR | None | None | None |
| VNet DNS servers | Azure-provided DNS ([]) | Azure-provided DNS ([]) | Azure-provided DNS ([]) |
| Key Vault endpoint | Public | Public | Public |
| Firewall policy | Restrictive, unchanged | Restrictive, unchanged | Restrictive, unchanged |
| Key Vault public FQDN allow | Present | Present | Present |
| Entra authority allow | Absent | Absent | Absent |
| Hub connection to ACTUAL ACA VNet | No active test path | Present | Present |
| Routing Intent | Off / absent for this test path | On | Off / removed |
Effective route for 0.0.0.0/0 | Not targeting hub firewall | Targets hub firewall | No longer targets hub firewall |
az containerapp secret set exit code | 0 | Non-zero | 0 |
Secret in configuration.secrets | kvref-h0 present | kvref-h1 absent | kvref-h2 present |
latestReadyRevisionName | Baseline | Unchanged (silence gate) | Unchanged (silence gate) |
| Ingress HTTP status | 200 | 200 | 200 |
3) Runbook¶
Prerequisites¶
- Azure CLI 2.80+ with the
containerappextension and the Virtual WAN extension commands available. - Azure subscription permissions for: resource group deploy, role assignment (
Microsoft.Authorization/roleAssignments/write), Container Apps management, Key Vault management, and Virtual WAN / Virtual Hub / Routing Intent management. jq,curl, and a local shell that can run the lab scripts.- Either an existing secured hub (preferred) or explicit willingness to pay for a synthetic secured hub.
Deploy infrastructure¶
Use one of the two supported modes below.
export RG="rg-aca-secret-kv-ref-mi-network-path-h4d"
export LOCATION="koreacentral"
export BASE_NAME="acasech4d01"
export EXISTING_VIRTUAL_HUB_RESOURCE_ID="/subscriptions/<subscription-id>/resourceGroups/<vwan-rg>/providers/Microsoft.Network/virtualHubs/<vhub-name>"
export EXISTING_AZURE_FIREWALL_RESOURCE_ID="/subscriptions/<subscription-id>/resourceGroups/<vwan-rg>/providers/Microsoft.Network/azureFirewalls/<azfw-name>"
export EXISTING_FIREWALL_POLICY_RESOURCE_ID="/subscriptions/<subscription-id>/resourceGroups/<vwan-rg>/providers/Microsoft.Network/firewallPolicies/<policy-name>"
export EXISTING_FIREWALL_LOG_ANALYTICS_CUSTOMER_ID="00000000-0000-0000-0000-000000000000"
bash labs/aca-secret-kv-ref-mi-network-path-h4d/trigger.sh
| Command | Why it is used |
|---|---|
trigger.sh | Creates the resource group, deploys the H4d Bicep template, records workload-plane topology anchors, and runs the H0 baseline. In existing secured-hub mode it consumes the supplied hub/firewall/policy resource IDs instead of deploying Virtual WAN resources. |
export RG=... | Sets the resource group name for the run. |
export LOCATION=... | Sets the Azure region. |
export BASE_NAME=... | Sets the naming seed used by the Bicep template. |
export EXISTING_VIRTUAL_HUB_RESOURCE_ID=... | Supplies the existing Virtual Hub resource ID for cost-conscious mode. |
export EXISTING_AZURE_FIREWALL_RESOURCE_ID=... | Supplies the existing secured-hub Azure Firewall resource ID. |
export EXISTING_FIREWALL_POLICY_RESOURCE_ID=... | Supplies the existing Firewall Policy resource ID. |
export EXISTING_FIREWALL_LOG_ANALYTICS_CUSTOMER_ID=... | Optionally supplies the firewall workspace customer ID so best-effort clue files can query the correct workspace. |
If you need the fully synthetic mode instead, add export DEPLOY_VIRTUAL_WAN="true" before running trigger.sh and omit the three EXISTING_*_RESOURCE_ID variables.
Run the H1 → H2 falsification (falsify.sh)¶
Expected output:
06-h1-routing-intent-enabled.jsonconfirms the ACTUAL ACA infrastructure VNet is connected to the Virtual Hub, Routing Intent converged, and effective routes show0.0.0.0/0targeting the hub firewall.07-h1-secret-set-outcome.jsoncontains a non-zeroexit_codeandstderrwithUnable to get value using Managed identityplusopenid-configuration.08-h1-app-state.jsonshows revision name unchanged, ingress HTTP 200, andkvref-h1absent.10-h2-routing-intent-removed.jsonconfirms effective routes no longer show0.0.0.0/0targeting the hub firewall.11-h2-secret-set-outcome.jsoncontainsexit_code: 0.12-h2-app-state.jsonshows revision name still unchanged from baseline, ingress HTTP 200, andkvref-h2present.
Run the offline verifier over your locally generated pack¶
Expected output:
- 17/17 gate passes on a valid cohort.
evidence/14-cohort-integrity-gate.jsonshows the HubVirtualNetworkConnection anchor and revision silence invariant.evidence/15-h1-routing-intent-produces-failure-gate.jsonproves H1 route-state plus failure markers.evidence/16-h2-routing-intent-removal-restores-success-gate.jsonproves H2 route-state removal plus recovery.evidence/17-bounded-falsification-gate.jsonenumerates the explicit drops for Azure Firewall log silence, effective-route limits, DNS-override non-coverage, Key Vault private endpoint non-coverage, and lack of packet-capture proof.
Optional: inspect the Virtual WAN route state manually¶
az network vhub routing-intent show \
--name "defaultRoutingIntent" \
--resource-group "<vwan-rg>" \
--vhub "<vhub-name>"
az network vhub get-effective-routes \
--resource-type HubVirtualNetworkConnection \
--resource-id "/subscriptions/<subscription-id>/resourceGroups/<vwan-rg>/providers/Microsoft.Network/virtualHubs/<vhub-name>/hubVirtualNetworkConnections/<connection-name>" \
--name "<vhub-name>" \
--resource-group "<vwan-rg>"
| Command | Why it is used |
|---|---|
az network vhub routing-intent show | Reads the Routing Intent policy on the Virtual Hub so the InternetTraffic and PrivateTraffic targets can be verified. |
--name | Names the Routing Intent resource (defaultRoutingIntent in this lab). |
--resource-group | Targets the Virtual WAN resource group. |
--vhub | Names the Virtual Hub that holds the Routing Intent policy. |
az network vhub get-effective-routes | Retrieves the effective route table from the Virtual Hub perspective for the HubVirtualNetworkConnection used by the ACA infrastructure VNet. |
--resource-type | Declares that the --resource-id belongs to a HubVirtualNetworkConnection. |
--resource-id | Supplies the HubVirtualNetworkConnection resource ID. Do not pass the VNet ID. |
--name | Names the Virtual Hub whose effective routes are being requested. |
--resource-group | Targets the Virtual WAN resource group that owns the Virtual Hub. |
Expected interpretation:
- H1 window: [Observed] the Routing Intent policy exists and the effective route table includes
0.0.0.0/0targeting the hub firewall. - H2 window: [Observed] the effective route table no longer shows
0.0.0.0/0targeting the hub firewall. - [Not Proven] This query alone does not prove dataplane packet forwarding, only Virtual Hub control-plane route state.
4) Experiment Log¶
| Step | Action | Expected | Falsification |
|---|---|---|---|
| 1 | Deploy the workload plane via trigger.sh | Deployment succeeds; app is Healthy; ingress FQDN returns HTTP 200; VNet uses Azure-provided DNS; ACA subnet has no route table; Key Vault is public | Deployment fails, or app never reaches Healthy, or baseline topology adds custom DNS / a UDR / a Key Vault private endpoint, which invalidates the H4d isolation |
| 2 | Run trigger.sh H0 baseline | 04-h0-secret-set-outcome.json exit_code: 0; kvref-h0 present in configuration.secrets; latestReadyRevisionName unchanged | H0 secret set fails before any Routing Intent change, which means the baseline is invalid |
| 3 | Run falsify.sh H1 (connect ACTUAL ACA VNet to hub + enable Routing Intent + poll for convergence) | 06-h1-routing-intent-enabled.json proves Routing Intent Succeeded and effective routes show 0.0.0.0/0 targeting the hub firewall | Routing Intent never converges, or the effective route table never targets the firewall, so the H1 trigger was not actually established |
| 4 | Run H1 secret set with the same symptom | 07-h1-secret-set-outcome.json exit_code non-zero with the MI / openid-configuration markers; 08-h1-app-state.json shows revision unchanged, ingress HTTP 200, kvref-h1 absent | H1 secret set still succeeds, or the revision changes, or ingress goes down, which breaks the silence-gate invariant |
| 5 | Run falsify.sh H2 (remove Routing Intent, poll until effective routes no longer target the firewall) | 10-h2-routing-intent-removed.json proves 0.0.0.0/0 no longer targets the hub firewall | Effective routes still point to the firewall after Routing Intent removal |
| 6 | Run H2 secret set as a new attempt | 11-h2-secret-set-outcome.json exit_code: 0; 12-h2-app-state.json shows revision unchanged, ingress HTTP 200, kvref-h2 present | H2 still fails even though the route-state flip was removed |
| 7 | Run verify.sh (hermetic offline) | 17/17 gates pass; Gate 15 proves H1 route-state + failure; Gate 16 proves H2 route-state removal + recovery; Gate 17 enumerates the bounded explicit drops | Any gate fails, especially if H1 lacks the route-state trigger or H2 lacks the recovery flip |
Evidence discipline for H4d¶
- [Observed]
06-h1-routing-intent-enabled.jsonand10-h2-routing-intent-removed.jsoncapture the Virtual Hub control-plane route state. - [Observed]
07-h1-secret-set-outcome.jsonfails, while11-h2-secret-set-outcome.jsonsucceeds. - [Observed]
08-h1-app-state.jsonand12-h2-app-state.jsonprove the silence gates: the same revision keeps serving ingress HTTP 200. - [Inferred] Routing Intent is the causal variable because the symptom flips while the app, identity, Key Vault, and firewall policy stay constant.
- [Not Proven] Effective routes do not prove packet forwarding or that Azure Firewall never received the traffic.
5) Verification Queries¶
CLI: inspect the Virtual WAN route state¶
az network vhub routing-intent show \
--name "defaultRoutingIntent" \
--resource-group "<vwan-rg>" \
--vhub "<vhub-name>"
az network vhub get-effective-routes \
--resource-type HubVirtualNetworkConnection \
--resource-id "/subscriptions/<subscription-id>/resourceGroups/<vwan-rg>/providers/Microsoft.Network/virtualHubs/<vhub-name>/hubVirtualNetworkConnections/<connection-name>" \
--name "<vhub-name>" \
--resource-group "<vwan-rg>"
| Command | Why it is used |
|---|---|
az network vhub routing-intent show | Verifies whether Routing Intent exists and which next-hop resource each policy targets. |
--name | Names the Routing Intent resource. |
--resource-group | Targets the Virtual WAN resource group. |
--vhub | Names the Virtual Hub. |
az network vhub get-effective-routes | Verifies the effective route table for the HubVirtualNetworkConnection that represents the ACA infrastructure VNet. |
--resource-type | Declares HubVirtualNetworkConnection as the resource type. |
--resource-id | Supplies the HubVirtualNetworkConnection resource ID captured by falsify.sh. |
--name | Names the Virtual Hub. |
--resource-group | Targets the Virtual WAN resource group. |
Expected interpretation:
- H1 window: [Observed]
0.0.0.0/0targets the hub firewall. - H2 window: [Observed]
0.0.0.0/0no longer targets the hub firewall. - [Not Proven] This is control-plane route evidence, not packet capture.
CLI: optional Azure Firewall clue (never the pass condition)¶
az monitor log-analytics query \
--workspace "00000000-0000-0000-0000-000000000000" \
--analytics-query "AZFWApplicationRule | where Fqdn has 'login.microsoftonline.com' or Fqdn has 'login.microsoft.com' | order by TimeGenerated desc | take 20"
| Command | Why it is used |
|---|---|
az monitor log-analytics query | Captures a best-effort Azure Firewall diagnostic clue from the workspace configured for the firewall. |
--workspace | Supplies the Log Analytics workspace customer ID (workspace GUID). |
--analytics-query | Queries recent Azure Firewall application-rule rows for the Entra authority FQDNs. |
Expected interpretation:
- [Observed] If diagnostics are enabled and the flow reaches Azure Firewall, rows may appear for the Entra authority FQDNs.
- [Not Proven] A zero-row result does not prove bypass; it may reflect diagnostics latency, disabled categories, the wrong workspace, or the wrong firewall.
- [Inferred] Use this only as a clue in real investigations. The reproducer itself gates on Routing Intent + effective-route state, not on log silence.
6) Portal Evidence¶
Reader-generated deferral note: Portal captures for this lab are tracked in issue #366.
Portal captures are reader-generated
Capture your own Portal evidence after running H0 → H1 → H2. The recommended capture set is: (1) the Virtual Hub Routing Intent blade showing the H1 policy targeting Azure Firewall, (2) the Virtual Hub effective-routes view for the ACA HubVirtualNetworkConnection showing 0.0.0.0/0 to the firewall in H1, (3) the same effective-routes view showing the route no longer targeting the firewall in H2, and (4) the Container App Secrets / Revisions blades proving the silence gates.
Clean Up¶
Related Playbook¶
See Also¶
- ACA Secret Key Vault Reference — Managed Identity Network Path Lab (H4a base variant)
- ACA Secret Key Vault Reference — Logging Gap Variant (H4b)
- ACA Secret Key Vault Reference — Custom DNS Override Variant (H4e)
- Managed Identity Auth Failure Playbook
- Egress Control