ACA Secret Key Vault Reference — NSG Deny Variant (H4c) Lab¶
Reproduce the NSG deny failure surface where az containerapp secret set --identity system --key-vault-url ... fails with Unable to get value using Managed identity → Get https://login.microsoftonline.com/<tenant>/.well-known/openid-configuration: EOF, without any Azure Firewall or UDR in path. This lab proves that an outbound NSG rule on the ACA workload subnet that denies AzureActiveDirectory:443 can break Entra authority reachability in a topology that contains no Azure Firewall or UDR at all, and that adding the documented higher-priority Allow restores success with the same Key Vault, identity, and RBAC state.
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-h4c/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: the ACA workload-subnet NSG rule set is the sole controlled variable, and flipping it changes whether the managed-identity OIDC discovery step can reach the public Entra authority.
Bounded-scope disclosure: the workflow does not prove NSG-before-Azure-Firewall ordering, NSG flow-log behavior, exact Entra IP resolution at failure time, or that every ACA control-plane call uses the same subnet-governed egress path. NSG rule enumeration is a direct configuration-state observation; the claim that the ACA-managed control-plane secret resolver follows that same subnet-governed egress path is only an inference justified by the H0/H1/H2 secret-set behavior. Those confounders are carried explicitly in Gate 17 explicit_drops.
Lab scope: H4c (NSG deny variant)
This lab reproduces H4c only — Azure-provided DNS on the VNet, no Azure Firewall, no UDR, no custom dhcpOptions.dnsServers, and an NSG attached to the ACA workload subnet. It is deliberately the opposite of H4a: H4a proves an egress firewall path, while H4c proves a subnet-NSG deny path in a topology that contains no firewall at all.
The diagnostic signature is therefore different from H4a/H4b/H4e: the key smoking gun is az network nsg rule list showing outbound rule deny-aad-443-h4c with priority 200, direction Outbound, access Deny, protocol Tcp, destination AzureActiveDirectory, and destination port 443, followed by az containerapp secret set failure. H2 then adds allow-aad-443-h4c at priority 100 while leaving the deny in place, proving the documented remediation by direct rule precedence. Reader-generated Portal captures for this guide are deferred to follow-up issue #368. Follow-up reproducer labs for the remaining H4 variants remain tracked in issue #307.
Lab Metadata¶
| Attribute | Value |
|---|---|
| Difficulty | Advanced |
| Estimated Duration | 20-30 minutes (includes 6-10 min ACA/KV deploy, one short H1 NSG settle, and one short H2 retry loop; no Azure Firewall deploy) |
| 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 the ACA workload-subnet NSG denies outbound AzureActiveDirectory:443 before the OIDC discovery TCP connection can reach the public Microsoft endpoint |
| Skills Practiced | NSG rule precedence diagnosis, service-tag reasoning, control-plane-vs-subnet-path evidence discipline, silence-gate reasoning for control-plane vs data-plane failures |
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 call Key Vault it must acquire a token, and managed identity token acquisition requires an OIDC discovery step against the Entra authority — a plain HTTPS GET https://login.microsoftonline.com/<tenant>/.well-known/openid-configuration (the client may use login.microsoft.com instead — it picks one host at runtime).
H4c reproduces a subnet-NSG failure in a topology where no outbound firewall exists at all. A Network Security Group attached to the ACA workload subnet filters outbound flows directly; because this lab has no hub firewall or downstream appliance in the path, a firewall-deny explanation is structurally impossible here. This lab therefore does not assert anything about how an NSG and a firewall are evaluated relative to each other in topologies that do contain a firewall. If the subnet NSG contains a rule that denies outbound Tcp/443 to the AzureActiveDirectory service tag, the managed-identity OIDC discovery call can fail even when Key Vault, RBAC, revisions, ingress, and DNS stay healthy. Adding a higher-priority Allow for the same service tag and port restores the exact same secret-set operation without changing any other lab variable.
This is why the evidence discipline matters:
- [Observed] NSG rule enumeration directly captures the configured outbound rule set on the workload subnet.
- [Observed]
07-h1-secret-set-outcome.jsonfails while11-h2-secret-set-outcome.jsonsucceeds. - [Observed]
08-h1-app-state.jsonand12-h2-app-state.jsonshow the same revision, ingress HTTP 200, and only the secret-reference presence flipping. - [Inferred] The ACA-managed control-plane secret resolver is strongly suggested to use a subnet-governed egress path affected by the NSG because H0 succeeds, H1 fails after only the Deny is added, and H2 succeeds after only the higher-priority Allow is added.
- [Not Proven] Azure does not formally document that every ACA control-plane secret-resolution call must use the exact same subnet-governed path as implied by this flip. That remains an explicit Gate 17 drop.
Architecture¶
flowchart TD
A["Operator: az containerapp secret set --identity system --key-vault-url"] --> B["ACA control-plane worker resolves KV URL"]
B --> C["MI token acquisition begins:<br/>GET https://login.microsoftonline.com/{tenant}/.well-known/openid-configuration"]
C --> D["Subnet NSG on snet-aca"]
D --> E{"Custom outbound rule for<br/>AzureActiveDirectory:443?"}
E -->|H0: No| F["Azure default rules only<br/>OIDC discovery succeeds<br/>secret set succeeds<br/>revision unchanged"]
E -->|H1: deny-aad-443-h4c priority 200| G["Outbound Tcp/443 to AzureActiveDirectory denied<br/>OIDC discovery fails before Key Vault read<br/>secret set fails<br/>revision unchanged, ingress HTTP 200"]
E -->|H2: allow-aad-443-h4c priority 100 + deny 200 retained| H["Higher-priority Allow overrides Deny<br/>OIDC discovery succeeds again<br/>secret set succeeds<br/>revision unchanged"] The subnet NSG configuration is not the same thing as a proven control-plane path contract
The az network nsg rule list output is the rule set attached to the delegated ACA workload subnet. [Observed] That is valid evidence for the configured network policy. [Inferred] The ACA-managed control-plane secret resolver likely used a subnet-governed egress path affected by that policy because H0 succeeded before the Deny existed, H1 failed after the Deny was added, and H2 succeeded after the higher-priority Allow was added. However, Azure does not document that every ACA control-plane secret-resolution call must share an identical subnet-governed path, so the guide never states that equivalence as proven.
Why H4c is explicitly not H4a
The baseline topology in this lab has an NSG attached to the workload subnet, but no Azure Firewall resource, no route table on the ACA subnet, and no custom VNet DNS servers. H0 and H2 both succeed with the identical Key Vault, identity, and RBAC state. That isolates the H1→H2 controlling variable to the NSG rule set and rules out the H4a firewall path by construction.
2) Hypothesis¶
IF an Azure Container Apps environment uses Azure-provided DNS on its infrastructure VNet, and the app has a system-assigned managed identity granted Key Vault Secrets User at the target Key Vault scope, and there is no Azure Firewall and no UDR on the workload path, THEN:
- H0 baseline (NSG attached, no custom AAD rule):
az containerapp secret set --identity system --key-vault-url ...succeeds with exit code 0. The named secretkvref-h0appears inproperties.configuration.secretswith a populatedkeyVaultUrlfield.latestReadyRevisionNameis unchanged. - H1 (NSG deny present): After creating outbound rule
deny-aad-443-h4cwith priority200, directionOutbound, accessDeny, protocolTcp, destinationAzureActiveDirectory, and destination port443, the command fails with exit code non-zero.stderrcarries the marker phrasesFailed to update secrets,Unable to get value using Managed identity, and includes the substringopenid-configuration.configuration.secretsdoes not containkvref-h1.latestReadyRevisionNameis still unchanged. Ingress still returns HTTP 200. [Observed] NSG rule enumeration shows the Deny exists and no higher-priority matching Allow exists. - H2 (higher-priority Allow added while Deny remains): After creating outbound rule
allow-aad-443-h4cwith priority100for the same service tag and port while keeping the Deny in place, a new secret-set attempt succeeds with exit code 0.kvref-h2appears inconfiguration.secrets.latestReadyRevisionNameis still unchanged from baseline. Ingress still returns HTTP 200. [Observed] NSG rule enumeration shows the Allow exists with numerically lower priority than the Deny and therefore governs. [Inferred] The control-plane resolver is no longer blocked by the subnet NSG path because the secret-set operation succeeds again without any other topology change.
| Variable | Control state (H0) | H1 (NSG deny present) | H2 (Allow added above Deny) |
|---|---|---|---|
| Azure Firewall in topology | Absent | Absent | Absent |
| UDR on ACA subnet | Absent | Absent | Absent |
VNet dhcpOptions.dnsServers | Azure-provided DNS ([]) | Azure-provided DNS ([]) | Azure-provided DNS ([]) |
| NSG attached to ACA subnet | Present | Present | Present |
| Custom outbound AAD rule | None | deny-aad-443-h4c priority 200 | allow-aad-443-h4c priority 100 + Deny 200 retained |
| Destination tuple | n/a | AzureActiveDirectory / Tcp / 443 | AzureActiveDirectory / Tcp / 443 |
az containerapp secret set exit code | 0 | Non-zero | 0 |
Secret in configuration.secrets | kvref-h0 present | kvref-h1 absent | kvref-h2 present |
| NSG rule enumeration | No H4c custom rule | Deny present, no higher-priority Allow | Allow present with priority 100 < 200 |
latestReadyRevisionName | Baseline | Unchanged (silence gate) | Unchanged (silence gate) |
| Ingress HTTP status | 200 | 200 | 200 |
3) Runbook¶
Prerequisites¶
- Azure CLI 2.80+ with the
containerappextension. - Azure subscription permissions for: resource group deploy, role assignment (
Microsoft.Authorization/roleAssignments/write), Container Apps management, Key Vault management, and NSG rule management. jq,curl, and a local shell that can run the lab scripts.
Deploy infrastructure¶
export RG="rg-aca-secret-kv-ref-mi-network-path-h4c"
export LOCATION="koreacentral"
export BASE_NAME="acasech4c01"
az group create --name "$RG" --location "$LOCATION"
az deployment group create \
--resource-group "$RG" \
--name aca-secret-kv-ref-mi-network-path-h4c \
--template-file labs/aca-secret-kv-ref-mi-network-path-h4c/infra/main.bicep \
--parameters baseName="$BASE_NAME" \
--parameters deploymentPrincipalId="$(az ad signed-in-user show --query id --output tsv)"
| Command | Why it is used |
|---|---|
az group create | Creates the resource group that scopes all lab resources. |
--name | Name of the resource group ($RG). |
--location | Azure region for the resource group ($LOCATION). |
az deployment group create | Deploys the Bicep template that provisions the VNet with a delegated ACA subnet, NSG attached inline to that subnet, Container Apps environment with workload-profile networking, Container App with system-assigned managed identity, Key Vault with RBAC authorization mode, and Log Analytics. No Azure Firewall or route table is deployed. |
--resource-group | Target resource group for the deployment. |
--name | Deployment name (aca-secret-kv-ref-mi-network-path-h4c). |
--template-file | Path to the lab Bicep template. |
--parameters baseName | Base name used to derive child resource names. |
--parameters deploymentPrincipalId | Object ID of the deploying principal, resolved inline with az ad signed-in-user show, so the template can grant the correct RBAC assignments. |
az ad signed-in-user show | Resolves the signed-in principal's object ID for the deploymentPrincipalId parameter. |
--query id | Projects only the id field from the signed-in user object. |
--output tsv | Emits the object ID as a bare string for inline substitution. |
Expected output:
- Resource group creation succeeds.
- Deployment
provisioningStateisSucceeded. - Bicep outputs include
appName,environmentName,keyVaultName,keyVaultUri,vnetName,acaSubnetPrefix,nsgName, andlogAnalyticsCustomerId. trigger.shlater records that the VNet uses Azure-provided DNS, the ACA subnet has no route table, the subnet NSG is attached, and no custom H4c deny/allow rule exists yet.
Run the H0 baseline (trigger.sh)¶
| Command | Why it is used |
|---|---|
trigger.sh | Reads Bicep outputs, records baseline topology anchors proving NSG attached / no firewall / no UDR / Azure-provided DNS, enumerates the baseline NSG rules to prove deny-aad-443-h4c and allow-aad-443-h4c do not exist yet, creates a Key Vault secret out-of-band, runs az containerapp secret set --identity system --key-vault-url ... against the healthy configuration, captures baseline app state before and after, and writes raw evidence files 01 through 05. |
Expected output:
04-h0-secret-set-outcome.jsoncontainsexit_code: 0.05-h0-app-state-after.jsonshows the secretkvref-h0present inconfiguration.secretswith a populatedkeyVaultUrlfield.05-h0-app-state-after.jsonlatestReadyRevisionNamematches the value in02-h0-app-state-before.json(secret set does not create a new revision).01-deployment-outputs.jsonshowsnsg_attached: true,azure_firewall_present: false,route_table_attached: false,uses_azure_provided_dns: true, and bothbaseline_deny_aad_443_h4c_present: falseandbaseline_allow_aad_443_h4c_present: false.
Run the H1 → H2 falsification (falsify.sh)¶
| Command | Why it is used |
|---|---|
falsify.sh | Performs H1 by creating outbound NSG rule deny-aad-443-h4c (priority 200, Outbound, Deny, Tcp, destination AzureActiveDirectory, port 443), waiting briefly for rule convergence, and then re-running az containerapp secret set with kvref-h1 while capturing the failure surface and NSG rule enumeration proving no higher-priority matching Allow exists. It then performs H2 by creating outbound NSG rule allow-aad-443-h4c (priority 100) for the same tuple while leaving the Deny in place, retries a fresh az containerapp secret set with kvref-h2 until the short settle window clears, and captures a new NSG rule enumeration proving the Allow now governs. Writes raw evidence files 06 through 13. |
Expected output:
06-h1-nsg-deny-created.jsonconfirmsdeny-aad-443-h4cexists with priority200andhigher_priority_matching_allow_count: 0.07-h1-secret-set-outcome.jsoncontainsexit_codenon-zero andstderrwithFailed to update secrets,Unable to get value using Managed identity, andopenid-configuration.08-h1-app-state.jsonshows revision name unchanged, ingress HTTP 200, andkvref-h1absent fromconfiguration.secrets.09-h1-nsg-effective-rules.jsonshowsgoverning_rule_name: "deny-aad-443-h4c"andhigher_priority_matching_allow_count: 0.10-h2-nsg-allow-created.jsonconfirmsallow-aad-443-h4cexists with priority100while the Deny remains at200.11-h2-secret-set-outcome.jsoncontainsexit_code: 0.12-h2-app-state.jsonshows revision name still unchanged from baseline, ingress HTTP 200, andkvref-h2present inconfiguration.secrets.13-h2-nsg-effective-rules.jsonshowsgoverning_rule_name: "allow-aad-443-h4c"andhighest_priority_matching_allow.name: "allow-aad-443-h4c".
Run the offline verifier over your locally generated pack¶
| Command | Why it is used |
|---|---|
verify.sh | Reads only the local evidence files 01-13 that trigger.sh and falsify.sh wrote into evidence/, runs the prerequisite/schema gates, then deterministically writes the four Phase B gate JSONs (14 cohort integrity, 15 H1 NSG deny, 16 H2 allow remediation, 17 bounded falsification). The verifier does not call Azure — you can re-run it offline after cleanup.sh has deleted the resource group, provided the local evidence/ files remain in place. |
Expected output:
- 17/17 gate passes on a valid cohort.
evidence/14-cohort-integrity-gate.jsonshows the revision silence invariant, the NSG-attached / no-firewall / no-UDR / Azure-provided-DNS anchors, and the absence of storage-account / flow-log artifacts.evidence/15-h1-nsg-deny-produces-failure-gate.jsonshows the H1 sub-gates pass, including the deny-rule tuple and absence of a higher-priority matching Allow.evidence/16-h2-allow-remediation-restores-success-gate.jsonshows the H2 sub-gates pass, including the Allow priority100 < 200andkvref-h2presence.evidence/17-bounded-falsification-gate.jsonenumerates the explicit drops for NSG-before-firewall ordering, flow-log behavior, control-plane path uniformity, exact Entra IP resolution, and Key Vault firewall/RBAC failure.
Optional: inspect the NSG rules manually during H1 and H2¶
az network nsg rule list \
--resource-group "$RG" \
--nsg-name "$(jq -r .nsg_name labs/aca-secret-kv-ref-mi-network-path-h4c/evidence/01-deployment-outputs.json)"
az containerapp show \
--name "$(jq -r .app_name labs/aca-secret-kv-ref-mi-network-path-h4c/evidence/01-deployment-outputs.json)" \
--resource-group "$RG" \
--query "properties.configuration.secrets"
| Command | Why it is used |
|---|---|
az network nsg rule list | Enumerates the custom outbound rules on the workload-subnet NSG so you can confirm the exact H1 Deny tuple and the H2 Allow-over-Deny precedence directly. |
--resource-group | Limits the query to the lab resource group. |
--nsg-name | Names the lab NSG, read from the evidence pack. |
az containerapp show | Reads the current Container App configuration so you can inspect whether kvref-h1 is absent during H1 and kvref-h2 is present after H2. |
--name | Names the Container App, read from the evidence pack. |
--resource-group | Targets the lab resource group. |
--query | Projects only properties.configuration.secrets for a tight configuration-state view. |
Expected output:
- [Observed] During H1,
az network nsg rule listincludesdeny-aad-443-h4cat priority200and no higher-priority matching Allow forAzureActiveDirectory:443. - [Observed] During H2,
az network nsg rule listincludesallow-aad-443-h4cat priority100above the retained Deny at200. - [Observed] During H1,
az containerapp show --query "properties.configuration.secrets"does not includekvref-h1; during H2 it includeskvref-h2. - [Inferred] Because H0 succeeds, H1 fails, and H2 succeeds with no firewall, DNS, UDR, Key Vault, identity, or RBAC change, the control-plane secret resolver is strongly suggested to be affected by the workload-subnet NSG path.
- [Not Proven] These CLI queries alone do not prove all ACA control-plane calls use the same subnet-governed egress path.
4) Experiment Log¶
| Step | Action | Expected | Falsification |
|---|---|---|---|
| 1 | Deploy baseline infrastructure via az deployment group create | Deployment succeeds; app is Healthy/Running; ingress FQDN returns HTTP 200; NSG attached to ACA subnet; no Azure Firewall exists; no route table attached to the ACA subnet; VNet uses Azure-provided DNS | Deployment fails, or app never reaches Healthy, or the baseline topology includes a firewall / UDR / custom VNet DNS, which would invalidate the H4c isolation |
| 2 | Run trigger.sh (H0 baseline) | 04-h0-secret-set-outcome.json exit_code: 0; kvref-h0 present in configuration.secrets; latestReadyRevisionName unchanged; baseline NSG rule list proves no custom H4c deny/allow rule exists | H0 secret set fails without the H4c rule mutation (indicates something other than the controlled variable is broken — invalidates the baseline) |
| 3 | Run falsify.sh phase H1 (create NSG deny → attempt secret set) | 07-h1-secret-set-outcome.json exit_code non-zero with the OIDC markers; 08-h1-app-state.json shows revision name unchanged, ingress HTTP 200, kvref-h1 absent | H1 secret set still succeeds (NSG deny did not control the failure), OR revision name changes (violates the silence-gate invariant), OR ingress goes down (indicates a data-plane failure instead of the claimed control-plane failure) |
| 4 | Capture H1 NSG rule enumeration | 09-h1-nsg-effective-rules.json shows deny-aad-443-h4c governs AzureActiveDirectory:443 and no higher-priority matching Allow exists | Rule enumeration does not show the Deny, or shows a higher-priority matching Allow already existed, which means H1 did not isolate the intended variable |
| 5 | Run falsify.sh phase H2 (add higher-priority Allow, keep Deny, attempt fresh secret set) | 11-h2-secret-set-outcome.json exit_code: 0; 12-h2-app-state.json shows revision name still unchanged from baseline, ingress HTTP 200, kvref-h2 present | H2 secret set still fails after the higher-priority Allow is added (indicates the deny/allow precedence alone is not sufficient to explain recovery) |
| 6 | Capture H2 NSG rule enumeration | 13-h2-nsg-effective-rules.json shows allow-aad-443-h4c priority 100 now governs above Deny priority 200 | Rule enumeration still shows the Deny governing, or the Allow is absent, which means the documented remediation was not actually applied |
| 7 | Run verify.sh (hermetic offline) | 17/17 gates pass; Gate 14 anchors the non-H4a topology; Gate 15 proves H1 deny + failure; Gate 16 proves H2 allow remediation + recovery; Gate 17 enumerates the bounded explicit drops | Any gate fails, especially if H1 lacks the deny-governs predicate or H2 lacks the allow-governs predicate |
Evidence discipline for H4c¶
This lab intentionally separates what is directly observed from what is only inferred:
- [Observed]
06-h1-nsg-deny-created.json,09-h1-nsg-effective-rules.json,10-h2-nsg-allow-created.json, and13-h2-nsg-effective-rules.jsoncapture the configured NSG rule state and rule-precedence evidence. - [Observed]
07-h1-secret-set-outcome.jsonfails, while11-h2-secret-set-outcome.jsonsucceeds. - [Observed]
08-h1-app-state.jsonand12-h2-app-state.jsonkeep the same revision and ingress HTTP 200 while only the secret-reference presence flips. - [Strongly Suggested] The ACA-managed control-plane secret resolver follows a subnet-governed path affected by the NSG because the secret-set result flips exactly with the deny/allow mutation and no firewall/UDR/DNS/RBAC variable changes.
- [Not Proven] Azure does not document that all ACA control-plane secret-resolution calls must share the same subnet-governed egress path. That equivalence is intentionally left as an explicit drop in Gate 17.
Gate 17 carries these explicit drops verbatim:
- Does NOT prove NSG-before-Azure-Firewall ordering (no Azure Firewall is deployed in this lab).
- Does NOT prove NSG flow-log behavior (flow logs and storage accounts are intentionally out of scope).
- Does NOT prove all ACA control-plane calls use the same subnet-governed egress path.
- Does NOT prove exact Entra IP resolution at failure time (it proves the NSG rule targets the Microsoft-managed
AzureActiveDirectoryservice tag). - Does NOT prove Key Vault firewall/RBAC failure (KV, identity, RBAC, revision, ingress held constant).
Why this is not a firewall lab¶
The baseline topology is deliberately silent on firewall evidence because there is no firewall to inspect. 01-deployment-outputs.json captures four direct anchors:
nsg_attached: trueazure_firewall_present: falseroute_table_attached: falseuses_azure_provided_dns: true
That means the H1 failure cannot be explained by H4a/H4b-style firewall denial. With no firewall anywhere in the topology, the subnet NSG itself denies outbound AzureActiveDirectory:443, so no firewall theory is needed to explain the failure. This does not prove how an NSG and a firewall would be ordered relative to each other when both are present. This is the point of the H4c variant: the same customer-facing OIDC discovery error can originate from an NSG rule even when the usual AzFW-deny hypothesis is structurally impossible.
5) Verification Queries¶
CLI: inspect the workload-subnet NSG rule set¶
az network nsg rule list \
--resource-group "$RG" \
--nsg-name "$(jq -r .nsg_name labs/aca-secret-kv-ref-mi-network-path-h4c/evidence/01-deployment-outputs.json)"
| Command | Why it is used |
|---|---|
az network nsg rule list | Enumerates the custom outbound rules on the workload-subnet NSG so you can directly observe H1 Deny presence and H2 Allow-over-Deny precedence for AzureActiveDirectory:443. |
--resource-group | Targets the lab resource group. |
--nsg-name | Names the lab NSG, read from the evidence pack. |
Expected interpretation:
- H1 window: [Observed] the output includes
deny-aad-443-h4cwith priority200, directionOutbound, accessDeny, protocolTcp, destinationAzureActiveDirectory, and port443. - H2 window: [Observed] the output includes
allow-aad-443-h4cwith priority100, which is numerically lower than the retained Deny priority200. - [Observed] This query is stronger than a replica DNS probe for H4c because it directly captures the configured network-policy state on the subnet.
- [Not Proven] This query alone does not prove all ACA control-plane secret-resolution calls use the same subnet-governed egress path.
CLI: inspect the Container App secret configuration¶
az containerapp show \
--name "$(jq -r .app_name labs/aca-secret-kv-ref-mi-network-path-h4c/evidence/01-deployment-outputs.json)" \
--resource-group "$RG" \
--query "properties.configuration.secrets"
| Command | Why it is used |
|---|---|
az containerapp show | Reads the current Container App configuration so you can inspect whether the secret-reference set succeeded or failed without creating a new revision. |
--name | Names the Container App, read from the evidence pack. |
--resource-group | Targets the lab resource group. |
--query | Projects only properties.configuration.secrets for a tight configuration-state view. |
Expected interpretation:
- H1 window: [Observed]
kvref-h1is absent. - H2 window: [Observed]
kvref-h2is present. - All phases: [Observed] the configuration flip is limited to secret-reference presence;
08-h1-app-state.jsonand12-h2-app-state.jsonseparately anchor the revision silence invariant and ingress HTTP 200. - [Inferred] Combined with the NSG rule mutation, the presence/absence flip strongly suggests the control-plane secret resolver is what the subnet policy is gating.
6) Portal Evidence (reader-generated captures)¶
Azure Portal screenshots to collect after running the H0 → H1 → H2 sequence. Save to docs/assets/troubleshooting/aca-secret-kv-ref-mi-network-path-h4c/ with the exact filenames listed in the Screenshot capture checklist below.
Portal captures are reader-generated
The captures listed below are not shipped with this lab guide; real Portal PNG capture is a deferred follow-up tracked in issue #368. Each reader captures their own evidence against their own subscription after running trigger.sh and falsify.sh.
Container App — Secrets blade (H0 baseline)¶
Portal evidence — Container App Secrets blade after H0
The Container App → Secrets blade after trigger.sh completes shows one row: kvref-h0 with Source: Key Vault reference and a Key Vault URL. This proves the H0 baseline succeeded before any custom H4c NSG deny existed.
Target filename: 01-h0-secrets-blade.png
Network Security Group — H1 outbound Deny rule¶
Portal evidence — H1 NSG Outbound security rules
The Network Security Group → Outbound security rules blade during H1 shows deny-aad-443-h4c with priority 200, access Deny, protocol Tcp, destination AzureActiveDirectory, and destination port 443. This is the visual proof of the H1 trigger itself.
Target filename: 02-h1-nsg-deny-rule.png
Container App — failed H1 secret-set surface¶
Portal evidence — H1 secret-set failure
The Container App → Secrets blade during H1 still lacks kvref-h1, while your terminal or Cloud Shell history shows the failed az containerapp secret set command with the managed-identity OIDC discovery error surface. This visually supports the H1 failure without implying a new revision.
Target filename: 03-h1-secret-set-failure.png
Container App — Revisions blade (silence gate)¶
Portal evidence — Revisions blade during H1/H2
The Container App → Revisions and replicas blade during H1 and H2 shows the same active revision name, Running state, Healthy health state, and no new revision created by the secret-set attempts. This is the silence-gate invariant.
Target filename: 04-h1h2-revisions-unchanged.png
Network Security Group + Secrets blade — H2 remediation and recovery¶
Portal evidence — H2 Allow rule above Deny and secret restored
After H2, the Network Security Group → Outbound security rules blade shows allow-aad-443-h4c at priority 100 above the retained Deny at 200, and the Container App → Secrets blade shows kvref-h2 present. This is the visual proof that the documented higher-priority Allow restores success without any firewall change.
Target filename: 05-h2-nsg-allow-rule-and-secret-restored.png
Screenshot capture checklist¶
| Screenshot | File name | Source |
|---|---|---|
| H0 Secrets blade | 01-h0-secrets-blade.png | Container App → Settings → Secrets |
| H1 NSG deny rule | 02-h1-nsg-deny-rule.png | Network Security Group → Outbound security rules |
| H1 secret-set failure | 03-h1-secret-set-failure.png | Container App Secrets blade + terminal / Cloud Shell evidence |
| H1/H2 revisions unchanged | 04-h1h2-revisions-unchanged.png | Container App → Revisions and replicas |
H2 Allow rule + kvref-h2 restored | 05-h2-nsg-allow-rule-and-secret-restored.png | NSG Outbound rules + Container App Secrets blades |
Portal-evidence claim ceiling reminders:
- [Observed] Portal screenshots can prove the configured NSG rules, revision continuity, and secret presence/absence.
- [Inferred] The secret-resolver path is strongly suggested to be gated by the subnet NSG because the H0/H1/H2 behavior flips with only the rule mutation.
- [Not Proven] Does NOT prove all ACA control-plane calls use the same subnet-governed egress path.
Clean Up¶
| Command | Why it is used |
|---|---|
cleanup.sh | Deletes the resource group and all child resources (async). This variant is much cheaper than the Firewall-based H4a/H4b labs, but the group should still be deleted promptly. |
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 — DNS Override Variant (H4e)
- Managed Identity Auth Failure Playbook
- Egress Control