← Azure Failure Labs
10 / 12 Technical field note

The Certificate You Stopped Trusting: The TLS Lab

How TLS trust-chain failures fail closed and quietly, and how to rehearse them with a controlled certificate swap.

AzureTLSSecurityChaos Engineering

TLS failures are quiet and absolute. A certificate expires, or a dependency starts presenting a chain your app does not trust, and every call to it fails closed with no graceful degradation and no partial service. The connection simply refuses to complete. Worse, the error often surfaces as a generic 500, so the team chases an application bug while the real cause is a trust-chain mismatch in the transport layer. The TLS lab reproduces that exact failure on demand.

What the lab does

It stands up a dedicated Linux VM that serves an HTTPS dependency with two certificate modes: a trusted certificate signed by a pinned demo certificate authority, and an untrusted self-signed certificate. A shared-plan App Service calls that dependency while pinning the demo CA. When the Logic App controller switches the VM to the invalid certificate, the app’s TLS validation fails and the calls collapse. Recovery switches the VM back to the trusted certificate.

Logic App tls-controller App Service pins demo CA Trusted cert signed by CA Invalid cert self-signed VM admin switch recover invalid-cert

The VM serves the HTTPS stub on one port and a plain HTTP admin endpoint on another, which is how the controller flips the active certificate without touching the app.

The inject contract

{ "scenario": "invalid-cert", "durationSeconds": 300 }

There are two scenarios: invalid-cert switches the stub to the untrusted certificate, and recover switches it back. The simplicity is the point. There is exactly one fault here, and it is total.

From POST to symptom

sequenceDiagram participant Op as Operator participant LA as Logic App participant VM as TLS stub VM participant APP as Primary app participant M as Azure Monitor Op->>LA: POST invalid-cert LA->>VM: Switch to self-signed cert APP->>VM: HTTPS call, validates chain VM-->>APP: Untrusted certificate APP-->>M: TLS validation fails, app returns 500 Note over APP: Fails closed, no partial service Op->>LA: POST recover LA->>VM: Switch back to trusted cert

The signal that proves it

The primary app’s 5xx scheduled query catches the failure.

AppServiceHTTPLogs
| where TimeGenerated > ago(5m)
| where _ResourceId =~ "<tls-app-resource-id>"
| where ScStatus >= 500
| summarize FailureCount = count() by _ResourceId
| where FailureCount > 5

The 5xx tells you the app is down. The application logs are what tell you why, with the certificate verification or trust-chain error named explicitly. As with the auth lab, that log line is the fingerprint, and seeing it in a drill is what stops you from suspecting your own code first when it shows up for real.

Recovery

recover switches the stub back to the trusted certificate, the app’s TLS validation passes again, and health returns. Baseline is the app returning 200 and validating the dependency’s chain against the pinned CA.

The lesson

TLS fails closed, which is exactly what you want for security and exactly what makes it hard to diagnose. There is no degraded mode, no half-working state, just a clean refusal that bubbles up as a generic 500. The two defenses are knowing what the error looks like in your logs before you ever see it in production, and monitoring certificate validity ahead of time so an expiring chain warns you on its own schedule instead of at the moment every call starts failing at once.


Next: The Queue That Kept Growing, the Service Bus lab.

Architecture depth + engineering leadership

Building platforms enterprises trust at scale.