ACA Secret Key Vault Reference — Azure Firewall Premium TLS-Inspection Variant (H4g) Lab¶
Cost warning This lab uses Azure Firewall Premium, which is billed at a significantly higher rate than the other H4 variants and is expected to cost several US dollars even for a short 1-2 hour run. Delete the resource group immediately after capturing evidence.
Reproduce the Azure Firewall Premium TLS-inspection failure surface where az containerapp secret set --secrets <name>=keyvaultref:<url>,identityref:system fails only while the Entra-authority application rule for login.microsoftonline.com and login.microsoft.com has terminateTLS=true. This is the scripted equivalent of using a system-assigned managed identity plus a Key Vault reference. The topology contains Azure Firewall Premium, a Firewall Policy Premium with TLS inspection configured through an intermediate CA certificate, and a route table that sends 0.0.0.0/0 from the ACA workload subnet to the firewall private IP. The only controlled variable is the Entra-authority rule's TLS-inspection flag.
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-h4g/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: TLS inspection of the Entra authority FQDNs is the sole controlled variable while app, Key Vault, identity, RBAC, firewall presence, route table, revision, ingress, NSG, and DNS stay constant.
Bounded-scope disclosure: this workflow does not prove a control-plane packet capture, does not directly observe the control-plane TLS chain, does not prove workload and control-plane egress are identical, and does not generalize beyond the two exercised Entra authority FQDNs. It also makes no third-party-NVA claim and no Key Vault data-plane-failure claim. Those confounders are carried explicitly in Gate 17 explicit_drops.
Lab scope: H4g (Azure Firewall Premium TLS-inspection variant)
This lab reproduces H4g only — Azure Firewall Premium is present, Firewall Policy Premium is present, TLS inspection is configured, the ACA subnet has a route table sending 0.0.0.0/0 to the firewall private IP, there is no NSG deny trigger, no custom DNS override, and no Virtual WAN routing intent. The H1→H2 flip is not “remove the rule” and not “change the route.” The flip is the Entra-authority application rule's terminateTLS flag: false -> true -> false.
Lab Metadata¶
| Attribute | Value |
|---|---|
| Difficulty | Advanced |
| Estimated Duration | 45-75 minutes depending on Azure Firewall Premium deployment time and reader-generated openssl capture completion |
| Tier | Workload Profiles (Consumption profile) |
| Failure Mode | az containerapp secret set --secrets <name>=keyvaultref:<url>,identityref:system fails only while Azure Firewall Premium terminates TLS for login.microsoftonline.com / login.microsoft.com, causing a managed-identity / OIDC failure with a TLS or certificate clue before the Key Vault read |
| Skills Practiced | Azure Firewall Premium TLS-inspection reasoning, bounded claim-ceiling writing, route-table anchoring, control-plane vs workload evidence discipline |
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 request to https://login.microsoftonline.com/<tenant>/.well-known/openid-configuration (the client may use login.microsoft.com instead — it picks one host at runtime).
H4g reproduces a TLS-inspection exemption failure. Azure Firewall Premium and the route table both remain present throughout the lab. Key Vault stays public, RBAC stays constant, the app stays on the same revision, ingress keeps serving HTTP 200, and no NSG or DNS variable changes. What changes is whether the Entra-authority application rule terminates TLS:
- [Observed] H0 succeeds when the Entra-authority rule exists with
terminateTLS=false. - [Observed] H1 fails when that same rule exists with
terminateTLS=true. - [Observed] H2 succeeds again when that same rule is restored to
terminateTLS=false. - [Observed] A workload replica can capture the interception CA chain during H1 and its absence during H2 with
openssl s_client. - [Strongly Suggested] The control-plane secret resolver is affected by the same Entra-authority TLS-inspection exemption because the secret-set result flips exactly with the same H0/H1/H2 variable.
- [Not Proven] The lab never directly captures the control-plane TLS chain.
Architecture¶
flowchart TD
A["Operator: az containerapp secret set --secrets <name>=keyvaultref:<url>,identityref:system"] --> B["ACA control-plane worker resolves Key Vault reference"]
B --> C["Managed identity OIDC discovery must reach login.microsoftonline.com / login.microsoft.com"]
C --> D["UDR on snet-aca: 0.0.0.0/0 -> Azure Firewall private IP"]
D --> E["Azure Firewall Premium + Firewall Policy Premium"]
E --> F{"Entra-authority rule terminateTLS?"}
F -->|H0 / H2: false| G["TLS-inspection exemption for the Entra authority\nsecret set succeeds\nrevision unchanged"]
F -->|H1: true| H["Firewall terminates TLS for the Entra authority\nsecret set fails with MI / OIDC + TLS or cert clue\nrevision unchanged, ingress HTTP 200"]
H --> I["Workload openssl capture can show the lab interception CA chain"] Workload openssl evidence is not the same thing as direct control-plane capture
[Observed] A workload replica can directly observe whether the lab interception CA appears on the workload data plane for login.microsoftonline.com:443. [Strongly Suggested] If that workload chain flips together with the control-plane secret-set result in one H0/H1/H2 cohort, the control plane is strongly suggested to be affected by the same Entra-authority TLS-inspection exemption. [Not Proven] The guide does not state that this is direct control-plane TLS-chain proof.
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, Azure Firewall Premium is present with Firewall Policy Premium TLS inspection configured, the ACA subnet has a route table sending 0.0.0.0/0 to the firewall private IP, and the only changed field is the Entra-authority rule's terminateTLS flag, THEN:
- H0 baseline (
terminateTLS=false):az containerapp secret set --secrets kvref-h0=keyvaultref:<url>,identityref:systemsucceeds with exit code 0. The named secretkvref-h0appears inproperties.configuration.secrets.latestReadyRevisionNameis unchanged. - H1 trigger (
terminateTLS=true): after redeploying the same Firewall Policy so the same Entra-authority rule terminates TLS, the command fails with exit code non-zero.stderrcarries a classifier-friendly signature: (managed-identity clue OR OIDC clue) AND (TLS/certificate clue).configuration.secretsdoes not containkvref-h1.latestReadyRevisionNameis still unchanged. Ingress still returns HTTP 200. [Observed] the workloadopensslcapture shows the unexpected lab interception CA chain. - H2 fix (
terminateTLS=false): after redeploying the same Firewall Policy so the same Entra-authority rule again exempts TLS inspection, 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] the workloadopensslcapture no longer shows the lab interception CA chain.
| Variable | H0 | H1 | H2 |
|---|---|---|---|
| Azure Firewall present | Premium | Premium | Premium |
| Firewall Policy present | Premium | Premium | Premium |
| TLS inspection configured | Yes | Yes | Yes |
| Route table attached to ACA subnet | Yes | Yes | Yes |
| Default route next hop | Azure Firewall private IP | Azure Firewall private IP | Azure Firewall private IP |
| NSG deny trigger | No | No | No |
| Custom DNS override | No | No | No |
| Virtual WAN routing intent | No | No | No |
| Entra-authority rule exists | Yes | Yes | Yes |
Entra-authority terminateTLS | false | true | false |
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. - Azure subscription permissions for: resource group deploy, role assignment (
Microsoft.Authorization/roleAssignments/write), Container Apps management, Key Vault management, Azure Firewall / Firewall Policy management, route-table management, and user-assigned managed identity creation. jq,curl, and a local shell that can run the lab scripts.- A password-less PFX intermediate CA certificate stored as a Key Vault secret for Azure Firewall Premium TLS inspection.
- A pre-created user-assigned managed identity that already has Get and List access to the Key Vault that stores that CA secret.
Pre-create the TLS-inspection identity access path¶
Before you deploy main.bicep, complete these reader-owned steps:
- Create a user-assigned managed identity in Azure.
- Grant that identity Get and List access on the Key Vault that stores the TLS-inspection intermediate CA secret.
- Export that identity resource ID as
TLS_INSPECTION_IDENTITY_RESOURCE_IDand pass it into the lab deployment.
Deploy infrastructure¶
export RG="rg-aca-secret-kv-ref-mi-network-path-h4g"
export LOCATION="koreacentral"
export BASE_NAME="acasech4g01"
export TLS_INSPECTION_CA_KEY_VAULT_SECRET_ID="https://<ca-vault>.vault.azure.net/secrets/<ca-secret>/<version>"
export TLS_INSPECTION_CA_CERTIFICATE_NAME="lab-h4g-intermediate-ca"
export TLS_INSPECTION_IDENTITY_RESOURCE_ID="/subscriptions/<subscription-id>/resourceGroups/<identity-rg>/providers/Microsoft.ManagedIdentity/userAssignedIdentities/<identity-name>"
az group create --name "$RG" --location "$LOCATION"
az deployment group create \
--resource-group "$RG" \
--name aca-secret-kv-ref-mi-network-path-h4g \
--template-file labs/aca-secret-kv-ref-mi-network-path-h4g/infra/main.bicep \
--parameters baseName="$BASE_NAME" \
--parameters deploymentPrincipalId="$(az ad signed-in-user show --query id --output tsv)" \
--parameters tlsInspectionCaKeyVaultSecretId="$TLS_INSPECTION_CA_KEY_VAULT_SECRET_ID" \
--parameters tlsInspectionCaCertificateName="$TLS_INSPECTION_CA_CERTIFICATE_NAME" \
--parameters tlsInspectionIdentityResourceId="$TLS_INSPECTION_IDENTITY_RESOURCE_ID"
| 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, empty NSG, route table, Azure Firewall Premium, Firewall Policy Premium with TLS inspection configured, Container Apps environment, Container App, Key Vault, and Log Analytics. |
--resource-group | Target resource group for the deployment. |
--name | Deployment name (aca-secret-kv-ref-mi-network-path-h4g). |
--template-file | Path to the lab Bicep template. |
--parameters | Supplies required Bicep parameters. |
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. |
tlsInspectionIdentityResourceId="$TLS_INSPECTION_IDENTITY_RESOURCE_ID" | Supplies the resource ID of the pre-created user-assigned identity that already has Get/List access to the CA vault. |
Expected output:
- Resource group creation succeeds.
- Deployment
provisioningStateisSucceeded. - Bicep outputs include
azureFirewallPresent=true,azureFirewallSku="Premium",firewallPolicyPresent=true,tlsInspectionConfigured=true,routeTableAttached=true, and the Entra-authority rule identifiers.
Run the H0 baseline (trigger.sh)¶
| Command | Why it is used |
|---|---|
trigger.sh | Reads Bicep outputs, records baseline topology anchors proving Firewall Premium + TLS inspection + route table presence, creates a Key Vault secret out-of-band, runs az containerapp secret set --secrets <name>=keyvaultref:<url>,identityref:system 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.secrets.01-deployment-outputs.jsonshowsazure_firewall_present: true,azure_firewall_sku: "Premium",firewall_policy_present: true,tls_inspection_configured: true,route_table_attached: true,entra_authority_terminate_tls: false,nsg_deny_present: false,dns_override_present: false, andvwan_routing_intent_present: false.
Run the H1 → H2 falsification (falsify.sh)¶
| Command | Why it is used |
|---|---|
falsify.sh | Performs H1 by redeploying the same Bicep template with entraAuthorityTerminateTls=true, then re-runs az containerapp secret set with kvref-h1 while capturing the classifier-friendly failure surface and a reader-filled workload openssl evidence placeholder. It then performs H2 by redeploying the same template with entraAuthorityTerminateTls=false, retries a fresh az containerapp secret set with kvref-h2, and captures the matching H2 workload openssl placeholder proving the lab interception CA disappeared again. Writes raw evidence files 06 through 13. |
Expected output:
06-h1-entra-rule-updated.jsonconfirms the Entra-authority rule exists withterminateTLS: true.07-h1-secret-set-outcome.jsoncontainsexit_codenon-zero and classifier-friendly stderr evidence.08-h1-app-state.jsonshows revision unchanged, ingress HTTP 200, andkvref-h1absent.09-h1-rule-state-and-openssl.jsonincludes the H1 firewall-rule snapshot plus a reader-generated workloadopensslplaceholder that must be completed withstatus: "completed"andreader_asserted_contains_lab_intermediate_ca: true.10-h2-entra-rule-updated.jsonconfirms the Entra-authority rule exists withterminateTLS: false.11-h2-secret-set-outcome.jsoncontainsexit_code: 0.12-h2-app-state.jsonshows revision unchanged, ingress HTTP 200, andkvref-h2present.13-h2-rule-state-and-openssl.jsonincludes the H2 firewall-rule snapshot plus a reader-generated workloadopensslplaceholder that must be completed withstatus: "completed"andreader_asserted_contains_lab_intermediate_ca: false.
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, applies the H1 stderr classifier, validates the workload openssl claim-ceiling evidence, and deterministically writes the four Phase B gate JSONs (14-17). The verifier does not call Azure. |
Expected output:
- 17/17 gate passes on a valid cohort.
evidence/14-cohort-integrity-gate.jsonshows the Firewall Premium / TLS-inspection / route-table anchors and the revision silence invariant.evidence/15-h1-tls-inspection-produces-failure-gate.jsonproves H1terminateTLS=true, the classifier match, and the workload-CA observation.evidence/16-h2-exemption-restores-success-gate.jsonproves H2terminateTLS=false, recovery, and the workload-CA disappearance.evidence/17-bounded-falsification-gate.jsonconcludes only that TLS inspection of the Entra authority FQDNs is necessary and sufficient to reproduce this lab's secret-resolution failure.
Optional: inspect the Firewall Policy rule state manually¶
az network firewall policy rule-collection-group show \
--resource-group "$RG" \
--policy-name "$(jq -r .firewall_policy_name labs/aca-secret-kv-ref-mi-network-path-h4g/evidence/01-deployment-outputs.json)" \
--name "$(jq -r .firewall_rule_collection_group_name labs/aca-secret-kv-ref-mi-network-path-h4g/evidence/01-deployment-outputs.json)"
| Command | Why it is used |
|---|---|
az network firewall policy rule-collection-group show | Reads the Firewall Policy rule collection group so you can inspect the exact Entra-authority rule and confirm whether terminateTLS is true during H1 or false during H0/H2. |
--resource-group | Targets the lab resource group. |
--policy-name | Names the lab Firewall Policy, read from the evidence pack. |
--name | Names the rule collection group, read from the evidence pack. |
Expected interpretation:
- H1 window: [Observed] the Entra-authority application rule exists and has
terminateTLS: true. - H2 window: [Observed] the same rule exists and has
terminateTLS: false. - [Not Proven] This query alone does not prove the control-plane TLS chain. It proves the configured Firewall Policy state.
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; Azure Firewall Premium, Firewall Policy Premium, TLS inspection, and route table are all present; no NSG deny trigger; no custom DNS override | Deployment fails, or app never reaches Healthy, or baseline topology is missing Firewall Premium / TLS inspection / route table, which invalidates H4g isolation |
| 2 | Run trigger.sh (H0 baseline) | 04-h0-secret-set-outcome.json exit_code: 0; kvref-h0 present; latestReadyRevisionName unchanged; Entra-authority rule already exists with terminateTLS=false | H0 secret set fails before any H4g rule mutation, which means the baseline is invalid |
| 3 | Run falsify.sh H1 (redeploy with terminateTLS=true) | 06-h1-entra-rule-updated.json proves the Entra-authority rule now terminates TLS | H1 rule snapshot does not show terminateTLS=true, so the trigger was not actually established |
| 4 | Run the H1 secret set with the same symptom | 07-h1-secret-set-outcome.json exit_code non-zero with the classifier signature; 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 | Complete the H1 workload openssl evidence | 09-h1-rule-state-and-openssl.json is updated by the reader to status: "completed" and reader_asserted_contains_lab_intermediate_ca: true | Reader leaves the placeholder unfilled, or the capture does not show the lab interception CA |
| 6 | Run falsify.sh H2 (redeploy with terminateTLS=false) | 10-h2-entra-rule-updated.json proves the same Entra-authority rule no longer terminates TLS | H2 rule snapshot still shows terminateTLS=true, so the documented exemption was not actually restored |
| 7 | Run the 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 exemption was restored |
| 8 | Complete the H2 workload openssl evidence | 13-h2-rule-state-and-openssl.json is updated by the reader to status: "completed" and reader_asserted_contains_lab_intermediate_ca: false | Reader leaves the placeholder unfilled, or the capture still shows the interception CA |
| 9 | Run verify.sh (hermetic offline) | 17/17 gates pass; Gate 15 proves H1 trigger + classifier + workload CA presence; Gate 16 proves H2 exemption + recovery + workload CA absence; Gate 17 bounds the claim ceiling | Any gate fails, especially if H1/H2 openssl placeholders remain unfilled or the terminateTLS anchors do not match |
5) Verification Queries¶
CLI: inspect the Entra-authority Firewall Policy rule¶
az network firewall policy rule-collection-group show \
--resource-group "$RG" \
--policy-name "$(jq -r .firewall_policy_name labs/aca-secret-kv-ref-mi-network-path-h4g/evidence/01-deployment-outputs.json)" \
--name "$(jq -r .firewall_rule_collection_group_name labs/aca-secret-kv-ref-mi-network-path-h4g/evidence/01-deployment-outputs.json)"
| Command | Why it is used |
|---|---|
az network firewall policy rule-collection-group show | Verifies the exact Entra-authority application rule and whether terminateTLS is enabled or exempted. |
--resource-group | Targets the lab resource group. |
--policy-name | Names the lab Firewall Policy. |
--name | Names the rule collection group that contains the Entra-authority rule. |
Expected interpretation:
- H1 window: [Observed]
terminateTLSistruefor the Entra-authority rule. - H2 window: [Observed]
terminateTLSisfalsefor the same rule. - [Not Proven] This query is stronger than a prose summary but still proves only the configured Firewall Policy state, not the control-plane TLS chain.
CLI: reader-run workload openssl capture¶
az containerapp exec \
--name "$(jq -r .app_name labs/aca-secret-kv-ref-mi-network-path-h4g/evidence/01-deployment-outputs.json)" \
--resource-group "$RG" \
--command "sh -lc 'openssl s_client -connect login.microsoftonline.com:443 -servername login.microsoftonline.com -showcerts < /dev/null 2>&1'"
| Command | Why it is used |
|---|---|
az containerapp exec | Runs a reader-driven command inside a workload replica so the workload data plane can observe whether the lab interception CA chain appears for login.microsoftonline.com:443. |
--name | Names the Container App, read from the evidence pack. |
--resource-group | Targets the lab resource group. |
--command | Runs openssl s_client with SNI and -showcerts to expose the observed certificate chain. |
Expected interpretation:
- H1 window: [Observed] the workload capture shows the lab interception CA chain.
- H2 window: [Observed] the workload capture no longer shows that interception CA.
- [Strongly Suggested] If the workload chain flips together with the secret-set result, the control-plane secret resolver is strongly suggested to be affected by the same Entra-authority exemption.
- [Not Proven] This remains workload evidence, not direct control-plane TLS-chain proof.
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-h4g/ with the exact filenames listed in the 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 the H4g Portal-capture follow-up issue #370. Each reader captures their own evidence against their own subscription after running trigger.sh and falsify.sh.
Recommended capture set¶
| Screenshot | File name | Source |
|---|---|---|
H0 Secrets blade showing kvref-h0 | 01-h0-secrets-blade.png | Container App → Settings → Secrets |
H1 Firewall Policy rule showing terminateTLS=true | 02-h1-firewall-rule-terminate-tls-true.png | Firewall Policy → Rule collection group |
| H1 secret-set failure plus unchanged revision | 03-h1-secret-set-failure-and-revision.png | Container App → Secrets and Revisions |
H2 Firewall Policy rule showing terminateTLS=false | 04-h2-firewall-rule-terminate-tls-false.png | Firewall Policy → Rule collection group |
H2 Secrets blade showing kvref-h2 restored | 05-h2-secrets-restored.png | Container App → Settings → Secrets |
Portal-evidence claim ceiling reminders:
- [Observed] Portal screenshots can prove the configured Firewall Policy rule state, revision continuity, and secret presence/absence.
- [Strongly Suggested] The secret-resolver path is strongly suggested to be affected by the same Entra-authority TLS-inspection exemption because H0/H1/H2 behavior flips with only the
terminateTLSfield. - [Not Proven] Portal screenshots do not directly prove the control-plane TLS chain.
Clean Up¶
| Command | Why it is used |
|---|---|
cleanup.sh | Deletes the resource group and all child resources (async). This variant deploys Azure Firewall Premium, so delete the group immediately after capturing evidence. |
Related Playbook¶
- Secret and Key Vault Reference Failure — H4 Variants: When Base H4 KQL Returns Zero Rows (future H4g row)
See Also¶
- ACA Secret Key Vault Reference — Managed Identity Network Path Lab (H4a base variant)
- ACA Secret Key Vault Reference — NSG Deny Variant (H4c)
- ACA Secret Key Vault Reference — Virtual WAN + Routing Intent Variant (H4d)
- Managed Identity Auth Failure Playbook
- Egress Control