top of page
21 hours ago
25 min read

Download SHA-256: 08116a594cd180c0471a1384d49735163144ac5157472a8a32d4273762abc666

azure infrastructure workshop

AI-generated synthwave workshop illustration showing an HTTPS client, Azure Application Gateway WAF, and a private Windows IIS backend.

AI-generated workshop artwork (conceptual illustration). The Azure evidence screenshots below were captured from the live lab.

Release evidencePASSED

Azure Workshopz · Windows IIS · Terraform · PowerShell · Azure CLI

An HTTPS request returns 502. Another returns 403. The backend VM is running, and the deployment succeeded. Which layer actually refused the request?

Build Azure Application Gateway WAF_v2 with a private Windows Server IIS backend. Establish a working HTTPS path, then investigate three deliberately different incidents: an incorrect probe path, a backend certificate-name mismatch, and a legitimate documentation request blocked by managed WAF rules. Repair the smallest owned setting and prove that unrelated protections still work.

Level: intermediate to advanced. Hands-on time: allow 3–5 hours including provisioning, Windows setup, policy propagation, log ingestion and cleanup. Region: West Europe. Platform: Windows Server 2025 IIS, controlled from a Windows PowerShell 7 workstation.

Verification status: PASSED

All three live fault/recovery cycles passed, including first and repeated repair validations. Final trusted HTTPS, DRS 2.2 managed/custom log correlation and Terraform no-drift checks passed. Exact cleanup and repeated cleanup confirmed absence, preserving all 27 pre-existing resources. The sanitized export records complete=true with no missing required checks. This release includes 20 genuine privacy-reviewed captures: 11 Portal and nine CLI. The validation record and capture inventory distinguish actual observations from expected results. This release contains all 18 required images plus two supplemental Portal close-ups: 20 genuine captures with reviewed provenance. A screenshot of configuration alone cannot prove a successful request or an effective repair.

Download the complete workshop ZIP and its separate SHA-256 receipt. The receipt identifies the final archive bytes and is kept outside the ZIP.

Individual Markdown, PowerShell, KQL and checksum receipt downloads are supplied as .txt for viewing. The complete ZIP retains the original filenames and full folder structure; extract the ZIP to run the workshop.

What you will learn

  • Separate gateway probe health, backend TLS validation and WAF decisions.

  • Configure HTTPS on both legs while keeping the backend VM private.

  • Recognize why the same VM can be healthy for one backend setting and unhealthy for another.

  • Combine real HTTP responses, periodic health reasons and request-correlated logs.

  • Tune an exclusion for one legitimate field without turning off WAF inspection for the request.

  • Repeat repair safely, verify the final Terraform state, and remove exact recorded resources.

Workshop blocks

Block 1 — Scenario and architecture

You operate a small documentation site. Application Gateway accepts HTTPS and forwards it to IIS over HTTPS. All content is synthetic and static. The documentation endpoint does not execute or reflect query-string content: its labSnippet parameter represents a code example submitted to an application that is understood to handle that field safely.

A single private IIS VM serves four routes. Each exercise has its own backend settings and probe, so one configuration can fail while /control/index.html still succeeds. This isolates the failing layer without requiring four VMs.

The workshop teaches a single-region troubleshooting method. It does not establish production capacity, application security, regional disaster recovery or VM high availability. A healthy control route is a diagnostic control, not a claim that a one-VM application is highly available.

The VM has no public IP and no inbound administration path. Azure VM Run Command installs and inspects IIS. A VM Agent or Run Command failure is a management prerequisite failure; it is not acceptable evidence of a WAF, probe or TLS incident. NAT Gateway supplies explicit backend egress for the platform and Windows servicing.

Architecture and address plan

Operator: CA-validated HTTPS request to appgw.lab.test
    |
    v
Application Gateway WAF_v2 + Prevention policy
    |-- /control/*      --> control-settings --> private IIS:443
    |-- /probe-fault/*  --> probe-settings   --> private IIS:443
    |-- /tls-fault/*    --> tls-settings     --> private IIS:443
    `-- /docs/*         --> control-settings --> private IIS:443

Backend expected name: backend.lab.test
Private lab CA: trusted explicitly by the client and gateway
Access / firewall logs --> dedicated Log Analytics workspace
VM management         --> Azure VM Agent / Run Command
VM outbound access    --> NAT Gateway

Component

Workshop configuration

Location

West Europe

VNet

10.171.0.0/24; preflight rejects overlap

Subnets

Dedicated gateway subnet and separate private backend subnet

Gateway

WAF_v2, autoscale minimum 0 / maximum 2, HTTPS listener

IIS backend

One Standard_B2s Windows Server 2025 VM, pinned image

Certificates

Fresh workshop CA; separate frontend and backend certificates

WAF

Microsoft Default Rule Set 2.2, Prevention, exact field/rule exclusions

Diagnostics

Resource-specific AGWAccessLogs and AGWFirewallLogs

Public exposure

Gateway HTTPS only; no public VM IP or inbound RDP

The synthetic .test names require no purchased domain. The test client maps appgw.lab.test to the recorded gateway public address for that request, while explicitly trusting the generated CA. It still verifies the certificate chain and hostname. The workshop does not add a root certificate to the operator's Windows trust store or bypass a browser security warning.

Block 2 — Preflight, cost and private state

Use PowerShell 7.6.9, Azure CLI 2.88.0, Terraform 1.15.8 and Windows curl.exe 7.70 or newer for the recorded environment. The package pins AzureRM 5.8.0 and carries its provider lockfile. Follow the README prerequisites rather than upgrading tools automatically during a paid run.

Authenticate through your approved interactive process. The operator needs permission to manage the isolated network, gateway, VM and workspace, invoke VM Run Command, and remove the deployment. The scripts do not silently switch subscriptions, broaden access, register providers or adopt existing resources.

Operating limit: €20 total, with €5 reserved for cleanup. A conservative estimate for six hours of active resources must not exceed €15 before deployment. Rates are refreshed during preflight. The gateway has a fixed charge even when autoscale minimum is zero; VM, disk, NAT, public IP, traffic and log charges are additional. This is an operational limit, not an Azure-enforced spending cap.

Begin cleanup by 4½ hours and verify removal before six hours from the new Preflight. The controller starts this clock before any paid resources are created; it is not reset by rerunning Preflight. Retain enough time for deletion and absence checks. Deallocating the VM does not remove gateway or networking charges.

Extract the complete ZIP. Open PowerShell in its top-level folder. Choose a new protected state path outside the package and any Git repository:

$private = Join-Path $env:LOCALAPPDATA ('AzureWorkshopz-Waf-' + [guid]::NewGuid().ToString('N'))
$state = Join-Path $private 'state.json'

az login
az account set --subscription '<your-subscription-id>'
$subscriptionId = (az account show --query id --output tsv).Trim()
./Invoke-AppGatewayWafWorkshop.ps1 -Stage Preflight -StatePath $state -SubscriptionId $subscriptionId

Preflight checks the exact subscription and tenant, existing resources, permissions, policies/locks, registered providers, image/SKU availability, quota, address overlap and current rates. It records the protected baseline privately without creating Azure resources. The supplied subscription and tenant are recorded in the new run; later stages require the same exact context.

Expected evidence: every prerequisite passes, the planned estimate is within the active allowance, and no Azure resource was created. Resolve a failed prerequisite rather than changing regions, SKUs or budget silently.

Preflight and reviewed plan

Capture 01 · CLI Actual Cloud Shell display of the retained local Preflight and reviewed Plan report. Execution occurred on the operator workstation; the recorded preflight time is Europe/Berlin local time. The display timestamp is UTC. The retained preflight line uses workstation CET (UTC+1); the Cloud Shell display timestamp is UTC.

Block 3 — Plan and deploy

./Invoke-AppGatewayWafWorkshop.ps1 -Stage Plan -StatePath $state

# Read the saved plan and exact owned resource summary before applying it.
./Invoke-AppGatewayWafWorkshop.ps1 -Stage Deploy -StatePath $state -Execute -AcknowledgeCost -ReviewedPlan

Check the new resource group, West Europe location, private VM, subnet ranges, NAT egress, WAF_v2 tier, TLS configuration and dedicated diagnostics. The plan must not change an unrelated resource. A new plan replaces the previous approval target and must be reviewed again.

The execution flags authorize the requested stage and reviewed plan, not a different mutation. Context and ownership checks remain active.

Portal checkpoint: open the recorded resource group and gateway. Verify WAF_v2, the HTTPS listener, private backend address, WAF policy association and diagnostic destination. The gateway can be provisioned before IIS is ready; provisioned is not the same as healthy.

Application Gateway overview

Capture 02 · Portal Actual deployed Application Gateway overview in West Europe: WAF V2 tier and the dedicated gateway subnet. Subscription and public IP values are redacted.

HTTPS listener

Capture 03 · Portal Actual HTTPS listener inspector: public frontend, port 443, and the existing frontend lab certificate.

If deployment stops, preserve the same state and inspect the pending operation. Confirm the original process has ended and reconcile its Azure outcome before using the supported -Resume path. Do not delete the ledger, clear a live lock, or start a second apply over the partially deployed group.

Block 4 — Bootstrap IIS and prove the healthy baseline

./Invoke-AppGatewayWafWorkshop.ps1 -Stage Bootstrap -StatePath $state -Execute
./Invoke-AppGatewayWafWorkshop.ps1 -Stage Validate -StatePath $state -Execute

Bootstrap installs IIS, creates a default HTTPS binding and synthetic static content, imports the backend certificate and prepares the owned routes. All IIS page content is embedded in the downloadable New-IisBootstrapScript.ps1 helper, so learners can inspect and regenerate the exact four-route site from source. The backend certificate includes backend.lab.test in its Subject Alternative Name. Application Gateway trusts the CA and checks the expected backend name.

Each custom probe requests /health.txt and accepts HTTP 200. It derives its hostname from its own backend settings. The control, probe-exercise and TLS-exercise settings are separate even though they target the same VM.

Portal checkpoint: inspect backend settings, trusted root certificate and custom probes, then open Backend health. Use periodic health as the checkpoint; a one-off Test probe can behave differently from the configured periodic probe.

Backend TLS validation and trusted root

Capture 04 · Portal Actual TLS backend settings: HTTPS port 443, Complete validation, a private CA, and the uploaded lab-root-ca certificate. The hostname and probe are shown separately in the supplemental close-up.

Backend hostname and probe close-up

Supplemental capture 19 · Portal Actual lower section of the same TLS backend inspector: backend.lab.test is the hostname override and tls-probe is the selected custom probe.

Healthy baseline

Capture 05 · Portal Live Azure Portal: all three HTTPS backend settings are Healthy and received HTTP 200 before fault injection.

Validation sends fresh requests over trusted TLS. It requires HTTP 200 and the exact synthetic marker belonging to this run. It also verifies that the gateway and VM retain the intended configuration.

To understand the underlying test, supply the exact values from your private report:

$gatewayIp = Read-Host 'Recorded gateway public IP'
$caFile = Read-Host 'Private path to the generated public CA PEM file'
curl.exe --silent --show-error --fail --ssl-revoke-best-effort --noproxy "*" --connect-timeout 20 --max-time 60 --cacert $caFile --resolve ('appgw.lab.test:443:{0}' -f $gatewayIp) 'https://appgw.lab.test/control/index.html'
if ($LASTEXITCODE -ne 0) { throw 'Trusted baseline request failed.' }

No insecure switch is used. The address mapping changes where the request connects; it does not change the name TLS verifies. The wrapper additionally checks the marker and records the result privately.

Private-CA limitation: this disposable CA has no CRL distribution service. Windows Schannel can reject an otherwise trusted certificate when revocation status is unavailable. The Windows-only best-effort option, available since curl 7.70, tolerates missing/offline revocation-list distribution points; CA-chain and hostname verification remain enabled. See the curl option reference. Other TLS backends do not need this Schannel-specific adjustment. This is a client readiness precondition, separate from the deliberate gateway-to-IIS TLS fault in Block 6. Production PKI needs an appropriate revocation service and lifecycle.

Trusted baseline request

Capture 06 · CLI Live Cloud Shell execution at 18:06 UTC: curl trusts the public lab CA, returns HTTP 200 with the run marker, and Azure CLI reports all three HTTPS settings Healthy.

Establish the narrow WAF baseline

Live calibration at 2026-11-06T17:57:01Z: the unexcluded <script>alert(1)</script> payload in labSnippet returned HTTP 403. DRS 2.2 matched XSS rules 941100, 941110 and 941160; aggregation rule 949110 blocked at anomaly score 15. The policy-level exclusion covers only that field for those three XSS rules across all associated paths, not only /docs; 949110 remains enabled and unexcluded. The deliberate WAF fault returned 403; first and repeated repair validations returned 200 at 18:24:53 and 18:26:12 UTC while the nonexcluded field stayed blocked.

A benign code example can resemble an attack signature. The documentation endpoint is static and does not execute or reflect query input. Its known labSnippet field therefore supplies a controlled false-positive exercise.

The released fixture records the exact request, ruleset version, contributing managed rule IDs and group names. The baseline exclusion uses RequestArgValues, operator Equals, selector labSnippet, and only those exact rule IDs. It does not exclude the whole request or disable the anomaly-scoring aggregation rule.

The exclusion belongs to the gateway-associated WAF policy and applies across every path inspected by that policy, not only /docs. Its boundary is the exact field name labSnippet plus the three selected XSS rules; it is not a path restriction or an allowlist of specific snippet contents. Other fields and other rules, including aggregation rule 949110, remain inspected. All workshop routes serve static content and do not execute or reflect query input. Production tuning must account for every route sharing this policy; a separate path/listener policy may be appropriate when the safe-field assumption differs.

The released fixtures/waf-exclusions.json and fixtures/waf-fixture.json carry the observed map and payload. A learner's initial Plan loads these defaults automatically. To inspect the map explicitly, or during a separately reviewed authoring calibration, apply only the owned WAF policy change:

$exclusionMapJson = Get-Content -LiteralPath './fixtures/waf-exclusions.json' -Raw
./Invoke-AppGatewayWafWorkshop.ps1 -Stage Plan -StatePath $state -WafExclusionRulesJson $exclusionMapJson
./Invoke-AppGatewayWafWorkshop.ps1 -Stage Deploy -StatePath $state -Execute -AcknowledgeCost -ReviewedPlan
./Invoke-AppGatewayWafWorkshop.ps1 -Stage Validate -StatePath $state -Execute

Expected healthy boundary: the legitimate labSnippet request returns 200; the same pattern in untrustedSnippet is blocked. Normal content returns 200. The harmless custom marker labMarker=awz-block-me returns 403 independently of managed-rule tuning.

Pinned managed ruleset

Capture 11 · Portal Actual Portal view of Microsoft Default Rule Set 2.2, filtered to XSS rules. Enabled and disabled rows reflect the deployed ruleset defaults before exclusion calibration.

WAF Prevention mode close-up

Supplemental capture 20 · Portal Live Azure Portal overview confirms the WAF policy is Enabled in Prevention mode. Subscription identifier is redacted.

Enforcement controls

Capture 12 · CLI Live Cloud Shell at 18:08 UTC: DRS 2.2 is Enabled in Prevention mode; the trusted normal request returns 200 and the harmless custom marker returns 403, both with curl exit 0.

Block 5 — Diagnose an incorrect probe path

Start with fresh healthy evidence. Change only the owned exercise probe path from /health.txt to the known missing health file.

./Invoke-AppGatewayWafWorkshop.ps1 -Stage Validate -StatePath $state -Execute
./Invoke-AppGatewayWafWorkshop.ps1 -Stage Fault -Scenario Probe -StatePath $state -Execute
./Invoke-AppGatewayWafWorkshop.ps1 -Stage Validate -Scenario Probe -StatePath $state -Execute

Expected failure: the periodic probe receives the missing-path response and marks the probe exercise backend unhealthy. /probe-fault/index.html returns 502 while /control/index.html remains 200 with the correct marker. The VM and its TLS certificate remain healthy.

Portal checkpoint: compare the failing probe settings with the healthy control settings in Backend health. Read the probe response reason before looking for routing or certificate changes.

Probe failure

Capture 07 · Portal Live Azure Portal: only probe-settings is Unhealthy, with the actual HTTP 404 probe diagnostic; control-settings and tls-settings remain Healthy.

./Invoke-AppGatewayWafWorkshop.ps1 -Stage Repair -Scenario Probe -StatePath $state -Execute
./Invoke-AppGatewayWafWorkshop.ps1 -Stage Validate -Scenario Probe -StatePath $state -Execute
./Invoke-AppGatewayWafWorkshop.ps1 -Stage Repair -Scenario Probe -StatePath $state -Execute
./Invoke-AppGatewayWafWorkshop.ps1 -Stage Validate -Scenario Probe -StatePath $state -Execute

Required recovery: both routes return 200, markers match, and periodic health is healthy. A second repair is safe when the original value is already restored. Broadening accepted probe status codes to include 404 would conceal the mistake.

Probe repair and repeated repair

Capture 08 · CLI Genuine Cloud Shell: retained local controller evidence shows the 502 fault, repair, repeated repair and both recovery validations at their original times. Beneath it, newly executed trusted curl requests at 18:21 UTC return 200 and the correct marker for the probe and control routes.

Block 6 — Diagnose a backend TLS hostname mismatch

A valid certificate chain can still be wrong for the expected name. Change only the TLS exercise backend hostname from backend.lab.test to wrong.lab.test. The private IP, port, chain, listener and control settings remain unchanged.

./Invoke-AppGatewayWafWorkshop.ps1 -Stage Validate -StatePath $state -Execute
./Invoke-AppGatewayWafWorkshop.ps1 -Stage Fault -Scenario Tls -StatePath $state -Execute
./Invoke-AppGatewayWafWorkshop.ps1 -Stage Validate -Scenario Tls -StatePath $state -Execute

Expected failure: periodic health identifies the certificate/name problem; /tls-fault/index.html returns 502 while the control route returns 200. IIS continues serving its default backend certificate. A reset, guest-agent failure or unavailable VM is not the intended fault.

Portal checkpoint: compare the hostname override and the certificate SAN. Keep certificate-chain, expiration and hostname validation enabled throughout.

Backend TLS failure

Capture 09 · Portal Live Azure Portal: tls-settings alone is Unhealthy. The actual diagnostic says the backend leaf certificate Common Name does not match the probe or backend-setting hostname; control and probe settings remain Healthy.

./Invoke-AppGatewayWafWorkshop.ps1 -Stage Repair -Scenario Tls -StatePath $state -Execute
./Invoke-AppGatewayWafWorkshop.ps1 -Stage Validate -Scenario Tls -StatePath $state -Execute
./Invoke-AppGatewayWafWorkshop.ps1 -Stage Repair -Scenario Tls -StatePath $state -Execute
./Invoke-AppGatewayWafWorkshop.ps1 -Stage Validate -Scenario Tls -StatePath $state -Execute

Required recovery: restore backend.lab.test and observe healthy periodic results and successful marker responses. Disabling certificate checks would weaken the boundary and fail the exercise.

TLS repair and repeated repair

Capture 10 · CLI Genuine Cloud Shell: retained local controller evidence shows the TLS 502 fault and two successful repair/validation windows. Fresh CA-trusted curl requests at 18:23 UTC return 200 with the run marker for the restored TLS route and control.

Block 7 — Diagnose a legitimate request blocked by WAF

Remove only the owned narrow exclusion from the healthy baseline. Keep WAF in Prevention mode and retain all managed rules.

./Invoke-AppGatewayWafWorkshop.ps1 -Stage Validate -StatePath $state -Execute
./Invoke-AppGatewayWafWorkshop.ps1 -Stage Fault -Scenario Waf -StatePath $state -Execute
./Invoke-AppGatewayWafWorkshop.ps1 -Stage Validate -Scenario Waf -StatePath $state -Execute

Expected failure: the documentation fixture returns 403 due to its managed-rule matches. Normal content succeeds and backend health stays healthy. A TLS failure cannot substitute for this observation.

Portal checkpoint: open Logs and use the managed-rule query. Find the actual transaction, contributing rule IDs and blocking result. Correlate the request with its access-log result. Rule IDs from another ruleset are not evidence for this run.

Managed-rule evidence

Capture 13 · Portal Live Azure Portal query of genuine calibration events at 17:57:01 UTC: rules 941100, 941110 and 941160 matched; 949110 blocked at anomaly score 15. DRS version 2.2 and event timestamps remain visible.

./Invoke-AppGatewayWafWorkshop.ps1 -Stage Repair -Scenario Waf -StatePath $state -Execute
./Invoke-AppGatewayWafWorkshop.ps1 -Stage Validate -Scenario Waf -StatePath $state -Execute
./Invoke-AppGatewayWafWorkshop.ps1 -Stage Repair -Scenario Waf -StatePath $state -Execute
./Invoke-AppGatewayWafWorkshop.ps1 -Stage Validate -Scenario Waf -StatePath $state -Execute

Required recovery: the legitimate field succeeds after its exclusion is restored. This restores the field/rule exception across all paths using the gateway policy, not just the documentation route. The same fixture in untrustedSnippet and the independent custom marker remain blocked. A custom Allow rule bypasses later inspection and is not the repair.

Exact per-rule exclusion

Capture 14 · Portal Live Azure Portal after repeated WAF repair: RequestArgValues Equals labSnippet excludes only XSS rules 941100, 941110 and 941160 under DRS 2.2. The Portal column was widened to preserve full rule IDs. This policy-level field exclusion covers every associated path.

WAF repair and boundary control

Capture 15 · CLI Genuine Cloud Shell: retained controller checks show the managed-rule 403 fault and two successful repair windows. Fresh CA-trusted requests at 18:26 UTC prove the pinned payload returns 200 in labSnippet and remains blocked with 403 in untrustedSnippet; curl exit is 0 in both cases.

Block 8 — Final validation, cleanup and evidence export

Run healthy validation after all repairs. Original network boundaries, certificates, WAF mode and intended exclusions must remain intact. Final Terraform comparison must show no unexpected changes.

./Invoke-AppGatewayWafWorkshop.ps1 -Stage Validate -StatePath $state -Execute -CollectLogs
./Invoke-AppGatewayWafWorkshop.ps1 -Stage Plan -StatePath $state
Final validation and no drift

Capture 16 · CLI Retained genuine local Windows controller results displayed later in Cloud Shell: final healthy HTTPS checks, Terraform detailed exit code 0, both fault-route 502 log records, custom-rule correlation and DRS 2.2 managed-rule correlation. Original execution timestamps are preserved; Terraform was not rerun in Cloud Shell.

Preview cleanup, inspect the exact targets, and execute using the same private state:

./Invoke-AppGatewayWafWorkshop.ps1 -Stage Cleanup -StatePath $state
./Invoke-AppGatewayWafWorkshop.ps1 -Stage Cleanup -StatePath $state -Execute -ReviewedPlan
./Invoke-AppGatewayWafWorkshop.ps1 -Stage Cleanup -StatePath $state -Execute -ReviewedPlan
./Invoke-AppGatewayWafWorkshop.ps1 -Stage Export -StatePath $state

Cleanup restores active owned faults, checks ownership and unexpected group members, and removes only this recorded deployment. A prefix or tag alone is not deletion authority. Do not add unrelated resources to the group.

Required evidence: the exact group/resources are absent, repeated cleanup creates nothing, and the protected unrelated baseline is unchanged. A successful deletion request does not prove deletion finished. Retain the private ledger until absence checks finish.

Verified cleanup

Capture 17 · CLI Retained actual local Terraform cleanup checks at 18:40 UTC report exact group absence and zero missing baseline resources. Fresh independent Cloud Shell queries at 18:41 UTC confirm GroupExists=False and zero scoped resources. All 27 pre-existing resources were preserved.

Repeated cleanup and baseline comparison

Capture 18 · CLI Genuine Cloud Shell: retained first and repeated local Cleanup checks confirm safe repetition, zero missing pre-existing resources and complete=true with no missing checks. Another fresh Azure CLI query at 18:42 UTC confirms the exact group remains absent.

Troubleshooting and safe stops

Observation

Inspect next

Avoid

All routes fail before a baseline exists

Provisioning, VM Agent, IIS, certificates and network prerequisites

Calling this a successful fault exercise

Only probe exercise returns 502

Periodic health reason, path and accepted status

Accepting 404 as healthy

Only TLS exercise returns 502

Hostname, certificate SAN and chain

Disabling certificate validation

Normal content works; fixture returns 403

Managed-rule rows, contributing IDs and exact field

Disabling WAF or broad Allow rules

A log table is absent

Diagnostic destination and ingestion delay; generate a fresh request

Claiming a log pass from an empty query

An update is still provisioning

Recorded operation and bounded polling

Starting a competing update

Frontend trust fails

Correct CA PEM, synthetic hostname and address mapping

An insecure TLS switch or browser-warning bypass

Windows Schannel reports unknown revocation status

curl.exe 7.70+ and the documented Windows-only best-effort option for this CA without a CRL service

Counting a client readiness failure as the intended backend TLS fault

Deadline/budget guard is reached

Start cleanup and retain exact state

Starting another exercise

Cleanup finds unexpected members

Ownership and baseline differences

Broad deletion to force a pass

Block 9 — Learning pack

Try the challenge before opening hints or the worked solution. The eight questions test reasoning about actual observations; they are not Microsoft exam questions. Complete practical work and cleanup as well as the questions.

Validation and release record

Recorded run: 2026-11-06. Status: PASSED

All three live fault/recovery cycles passed, including first and repeated repair validations. Final trusted HTTPS, DRS 2.2 managed/custom log correlation and Terraform no-drift checks passed. Exact cleanup and repeated cleanup confirmed absence, preserving all 27 pre-existing resources. The sanitized export records complete=true with no missing required checks. This release includes 20 genuine privacy-reviewed captures: 11 Portal and nine CLI.

The release includes separate local-quality and live-Azure records. The local operator uses the tool versions listed above; Azure Cloud Shell versions are recorded separately in screenshot provenance. Captures 01 and 16 display retained local execution reports in real Cloud Shell rather than claiming a fresh Terraform run there. Recovery captures 08, 10 and 15 combine labelled retained local controller reports with fresh Cloud Shell curl checks. Captions distinguish those sources and keep the original execution timestamps visible. Expected outcomes describe what a learner must prove; only the completed validation record may claim observed success. Screenshot provenance identifies the source phase, time, redactions and hash. Raw logs, credentials, private keys, state/plans and unredacted captures remain private.

Download the validation record, sanitized acceptance export, seven curated request/log examples, source map, capture guide, and capture inventory. After packaging, actual archive integrity results are recorded in the separate release receipt. The complete ZIP includes infrastructure, controller/helpers, fixture record, KQL, learning pack, tests and sanitized evidence. The backend-health evidence helper extracts allowlisted real diagnostics and source hashes from the private native-command journals; it does not simulate a health result.

This is independent community training, not an official Microsoft lab or a production availability, security or billing guarantee.

Open the learning pack

Try the challenge first. Open hints progressively, then compare your reasoning with the worked solution.

Independent challenge

Challenge — Identify the failing layer without weakening protection

Start only after healthy validation and the live-calibrated WAF baseline pass. Use the same private run state. Preserve the €20 operating limit and begin cleanup by 4½ hours.

Your assignment

You support a documentation site behind Application Gateway. One teammate treats every 502 as a stopped VM. Another wants to disable WAF whenever a legitimate request returns 403. Produce evidence that separates those assumptions.

  1. Prove all baseline routes use trusted HTTPS and return this run's synthetic marker. Verify periodic backend health for every settings combination.

  2. Inject the Probe fault. Identify the wrong probe path from its actual health reason. Show the exercise request failing while the control route succeeds. Repair the path, verify recovery, and repeat repair safely.

  3. Inject the Tls fault. Prove that the backend can be reachable and its chain trusted while its expected hostname is wrong. Repair the expected name without disabling TLS validation.

  4. Inject the Waf fault. Show the legitimate documentation request returning 403 and correlate it with actual contributing managed rules. Restore only the exact owned field/rule exclusion.

  5. Prove the same attack-like fixture remains blocked in another parameter and the independent custom marker remains blocked. Explain that the labSnippet field/rule exclusion applies across all associated policy paths, not only /docs.

  6. Prove final configuration consistency, clean up exact recorded resources, repeat cleanup, and export reviewed sanitized evidence.

Evidence acceptance

Claim

Minimum evidence

Healthy path

Real TLS-validated 200 response and correct run marker, plus periodic backend health

Probe incident

Missing-path health reason, exercise 502 and contemporaneous control 200

TLS incident

Actual certificate/name diagnostic, exercise 502 and control 200

WAF incident

Legitimate fixture 403 plus correlated managed-rule match/block rows

Narrow tuning

Exact RequestArgValues/Equals/labSnippet rule scope; legitimate request 200 and nonexcluded control 403

Recovery

Fresh successful requests and safe repeated repair for each fault

Completion

No unexpected Terraform drift, exact resources absent after repeated cleanup, unrelated baseline unchanged

A timeout, VM Agent failure or stopped IIS service is not interchangeable with an intended fault. Preserve all network, TLS and WAF boundaries throughout the exercise. Do not use an insecure TLS option, broad Allow rule, accepted 404 probe status, or public VM access to manufacture a pass.

Open HINTS.md one hint at a time. Consult SOLUTION.md after recording your own diagnosis.

Progressive hints

Progressive hints — Follow the evidence

Use one hint at a time. Keep the same private ledger and leave enough time for cleanup.

Hint 1 — Define healthy before introducing a failure

Does the request verify the CA and hostname? Does its body contain this run's marker? Is each periodic backend/settings combination healthy? A configured resource is not a completed data path.

Hint 2 — A backend is not only a VM

Application Gateway health belongs to a backend server and its settings/probe combination. Compare the working control settings with the broken exercise settings before changing the VM.

Hint 3 — Read the actual probe response

The Probe fault requests a file that does not exist. Which path does IIS serve successfully? Does the probe accept only 200? Keeping 404 out of the accepted range preserves its value as a health check.

Hint 4 — Split certificate trust from certificate identity

A certificate signed by a trusted CA can still be wrong for the expected hostname. Compare the backend settings' name with the leaf certificate SAN. Keep the private address, CA trust and validation settings fixed.

Hint 5 — A 403 needs a decision source

Is the denial from the managed WAF rules or the independent custom block marker? Find the actual request transaction and all contributing rule rows. Do not infer managed-rule behavior from the custom marker.

Hint 6 — Scope the exception to the known safe field

Which exact field contains the legitimate documentation snippet? Which managed rules actually matched it? A whole-request Allow rule skips later inspection; a per-rule field exclusion leaves other request fields subject to those rules. This exclusion is on the gateway-associated policy, so it applies to that field across all associated paths. The /docs teaching request does not create a path-specific exception.

Hint 7 — Prove the boundary survived the repair

Run the same attack-like fixture through a different, nonexcluded parameter. Then run the independent custom block marker. Repair is incomplete if everything becomes allowed.

Hint 8 — Recovery and deletion need new observations

Send fresh requests after repair; do not reuse an old response. Repeat repair safely. For cleanup, verify exact-resource absence after Azure finishes deletion, then repeat cleanup and compare the protected baseline.

Worked solution

Worked solution — Probe, TLS and WAF are separate decisions

This solution explains the required observations. Consult VALIDATION.md for actual release results. Expected output must never be presented as a live Azure capture.

1. Establish the complete healthy path

Run Preflight and Plan, review the saved plan, then run the reviewed Deploy and Bootstrap stages. Use the same private state throughout. Wait for periodic backend health and request validation; an ARM deployment success does not establish IIS readiness.

The client connects to the recorded gateway address for appgw.lab.test and trusts the generated public CA certificate explicitly. It verifies the frontend hostname. Application Gateway connects to the private IIS address over HTTPS, trusts the CA, and expects backend.lab.test. The server certificate contains that SAN. CA-chain and hostname checks remain enabled. Windows Schannel uses the documented best-effort handling for the disposable CA's missing CRL distribution service; that client precondition is separate from the backend name-mismatch exercise.

Require successful requests and the exact synthetic marker for all owned routes. Confirm the expected WAF baseline and independent denial controls. This healthy evidence is the starting point for each fault.

2. Probe fault: repair the request path

./Invoke-AppGatewayWafWorkshop.ps1 -Stage Fault -Scenario Probe -StatePath $state -Execute
./Invoke-AppGatewayWafWorkshop.ps1 -Stage Validate -Scenario Probe -StatePath $state -Execute

The fault changes only the owned probe path. The resulting periodic probe reaches IIS but receives the missing-file response. Since only 200 is accepted, the exercise combination becomes unhealthy and Application Gateway returns 502 for its route. The separate control combination remains healthy and returns 200.

Read the actual backend health reason and compare paths. It is not necessary to reinstall IIS, change routing or replace a certificate. Restoring /health.txt repairs the intended mistake. Adding 404 to the accepted statuses would instead label the wrong request healthy.

./Invoke-AppGatewayWafWorkshop.ps1 -Stage Repair -Scenario Probe -StatePath $state -Execute
./Invoke-AppGatewayWafWorkshop.ps1 -Stage Validate -Scenario Probe -StatePath $state -Execute
./Invoke-AppGatewayWafWorkshop.ps1 -Stage Repair -Scenario Probe -StatePath $state -Execute
./Invoke-AppGatewayWafWorkshop.ps1 -Stage Validate -Scenario Probe -StatePath $state -Execute

Both routes must now return the correct marker over trusted TLS. The second repair must be safe and must preserve unrelated values.

3. TLS fault: restore the expected name

./Invoke-AppGatewayWafWorkshop.ps1 -Stage Fault -Scenario Tls -StatePath $state -Execute
./Invoke-AppGatewayWafWorkshop.ps1 -Stage Validate -Scenario Tls -StatePath $state -Execute

The exercise settings now expect wrong.lab.test while IIS presents a certificate for backend.lab.test. The CA chain can remain valid and the VM can remain reachable, but name verification fails. Its custom probe inherits that incorrect expected name and periodic health exposes the TLS mismatch. The exercise route returns 502 while the independent control still returns 200.

Restore the recorded expected hostname. Do not disable SNI/name, chain or expiration validation. A different private IP or VM replacement would add unrelated changes without repairing the original setting.

./Invoke-AppGatewayWafWorkshop.ps1 -Stage Repair -Scenario Tls -StatePath $state -Execute
./Invoke-AppGatewayWafWorkshop.ps1 -Stage Validate -Scenario Tls -StatePath $state -Execute
./Invoke-AppGatewayWafWorkshop.ps1 -Stage Repair -Scenario Tls -StatePath $state -Execute
./Invoke-AppGatewayWafWorkshop.ps1 -Stage Validate -Scenario Tls -StatePath $state -Execute

Require healthy periodic results and fresh successful responses, then safe repeated repair.

4. WAF fault: recover the safe field without bypassing inspection

The final baseline contains the live-calibrated field/rule exclusion. The fault removes only that owned exclusion:

./Invoke-AppGatewayWafWorkshop.ps1 -Stage Fault -Scenario Waf -StatePath $state -Execute
./Invoke-AppGatewayWafWorkshop.ps1 -Stage Validate -Scenario Waf -StatePath $state -Execute

The legitimate documentation fixture now returns 403 while ordinary content succeeds and the backend remains healthy. Use the retained transaction to find actual managed-rule contributors. Group names and IDs belong to the pinned DRS 2.2 catalog; do not copy legacy group names from another tutorial.

The repair restores RequestArgValues, operator Equals, selector labSnippet, scoped to the contributing managed rule IDs. It does not disable the rules globally, exclude the whole request, or exclude the anomaly threshold rule.

The exclusion belongs to the gateway-associated WAF policy and applies across every path inspected by that policy, not only /docs. Its boundary is the exact field name labSnippet plus the three selected XSS rules; it is not a path restriction or an allowlist of specific snippet contents. Other fields and other rules, including aggregation rule 949110, remain inspected. The application is intentionally static and never executes or reflects the snippet: this explicit safe-field assumption is what makes the tuning exercise appropriate.

./Invoke-AppGatewayWafWorkshop.ps1 -Stage Repair -Scenario Waf -StatePath $state -Execute
./Invoke-AppGatewayWafWorkshop.ps1 -Stage Validate -Scenario Waf -StatePath $state -Execute
./Invoke-AppGatewayWafWorkshop.ps1 -Stage Repair -Scenario Waf -StatePath $state -Execute
./Invoke-AppGatewayWafWorkshop.ps1 -Stage Validate -Scenario Waf -StatePath $state -Execute

After repair, the legitimate field returns 200, the same pattern in untrustedSnippet returns 403, and labMarker=awz-block-me still returns 403. The custom block marker confirms a separate policy decision; it does not replace managed-rule evidence.

5. Finish with consistency and absence evidence

Run healthy validation and final Plan. All owned settings must match their intended baseline; final Terraform comparison must show no unexpected changes. Inspect the cleanup preview, execute the reviewed exact deletion and repeat cleanup. Export reviewed sanitized evidence only after required checks are recorded.

./Invoke-AppGatewayWafWorkshop.ps1 -Stage Validate -StatePath $state -Execute -CollectLogs
./Invoke-AppGatewayWafWorkshop.ps1 -Stage Plan -StatePath $state
./Invoke-AppGatewayWafWorkshop.ps1 -Stage Cleanup -StatePath $state
./Invoke-AppGatewayWafWorkshop.ps1 -Stage Cleanup -StatePath $state -Execute -ReviewedPlan
./Invoke-AppGatewayWafWorkshop.ps1 -Stage Cleanup -StatePath $state -Execute -ReviewedPlan
./Invoke-AppGatewayWafWorkshop.ps1 -Stage Export -StatePath $state

Deletion acceptance means the exact recorded resources are absent, not merely that Azure accepted a delete call. The second cleanup must create nothing and unrelated protected resources must remain unchanged. Preserve the private ledger until residual checks finish.

Eight knowledge questions

Knowledge check — Application Gateway WAF on Windows IIS

Choose one best answer before opening its explanation. These original questions test the workshop's reasoning; they are not Microsoft exam questions. Practical completion still requires baseline, three observed fault/recovery cycles and verified cleanup. With eight questions, 0–5 correct means repeat the mapped blocks, 6 means targeted review, and 7–8 means move forward once the practical work is complete.

1. One VM, two different health results

The control route returns 200. The probe-exercise route returns 502, and its periodic health reason shows an unexpected missing-file response. Both target the same VM. What should you inspect first?

A. Whether every backend route needs its own VM.

B. The exercise probe path and its accepted response status.

C. The frontend certificate expiration, because every 502 is a TLS problem.

D. The WAF exclusion, because WAF always generates 502.

Answer: B. Health is evaluated for a backend server/settings combination. A wrong probe path can make one combination unhealthy while another succeeds. A is unnecessary; C ignores the actual health reason; D confuses an unhealthy-backend response with a WAF decision. Review: Block 5.

2. Trusted certificate, wrong name

The backend certificate chain is trusted, but the exercise settings expect wrong.lab.test and the leaf SAN contains backend.lab.test. Which repair preserves the intended boundary?

A. Turn off hostname and chain validation.

B. Add 502 to accepted health statuses.

C. Restore backend.lab.test as the expected hostname and keep TLS validation enabled.

D. Give the VM a public IP so the gateway can bypass its private path.

Answer: C. Trust and identity are separate checks. The expected hostname must match the certificate. A weakens TLS; B does not repair a failed TLS handshake; D changes exposure without correcting the name. Review: Block 6.

3. Which 403 proves the managed-rule fault?

The custom marker labMarker=awz-block-me returns 403. What additional evidence is needed for the legitimate documentation fixture's managed-rule exercise?

A. The marker denial is sufficient because every WAF 403 uses the same rule.

B. A screenshot showing that the VM is running.

C. Any old SQL-injection rule ID from another ruleset version.

D. The fixture's actual blocked request and correlated managed-rule contributors in this run's logs.

Answer: D. The custom marker is a separate deterministic enforcement control. A conflates custom and managed rules; B says nothing about the decision; C is not evidence for the pinned catalog. Review: Blocks 4 and 7.

4. A legitimate snippet needs an exclusion

The known-safe labSnippet value matches several managed rules. Which tuning is narrowest for this exercise?

A. Exclude RequestArgValues Equals labSnippet only for the actual contributing rule IDs.

B. Disable all XSS rules globally.

C. Add a custom Allow rule for every request to the site.

D. Exclude all query-string values from all rules.

Answer: A. It scopes the change to a known field and observed contributors, across every path using the gateway-associated policy. It is not limited to /docs. B changes protection for every field; C stops later enforcement; D removes broad request inspection. The static documentation endpoint never executes or reflects the snippet. Review: Block 7.

5. What proves narrow repair?

After restoring the exclusion, the legitimate labSnippet request returns 200. Which next check best establishes that the same rule protection remains elsewhere?

A. Check only the deployment's provisioning state.

B. Send the same pattern through the nonexcluded untrustedSnippet parameter and require the expected block.

C. Send the identical request again from a cached response.

D. Remove Prevention mode so successful requests are easier to obtain.

Answer: B. The nonexcluded field is a boundary control. A checks configuration lifecycle; C can conceal current behavior; D changes the intended enforcement state. Review: Block 7.

6. The log table has no rows yet

The HTTP test completed, but the resource-specific firewall table is absent or empty. What is the appropriate next step?

A. Copy illustrative output into the evidence report.

B. Mark the log test passed because ingestion is asynchronous.

C. Verify diagnostics and destination, generate a fresh marked request, and poll within the bounded wait.

D. Switch the KQL query to random legacy field names until it returns a row.

Answer: C. Ingestion latency is a reason to wait and inspect, not to invent success. A fabricates evidence; B lacks the required observation; D can query unrelated data or the wrong schema. Review: Block 7 and queries/.

7. Autoscale minimum is zero

The gateway has autoscale minimum zero and currently serves little traffic. Which cost statement is accurate?

A. The gateway is free until a backend receives a request.

B. Stopping the Windows VM removes all charges.

C. The €20 operating limit is an Azure-enforced spending cap.

D. The fixed gateway charge remains, and other provisioned resources can continue charging until cleanup.

Answer: D. Minimum zero does not remove the fixed charge. A and B omit provisioned services; C confuses local operational checks with billing enforcement. Review: Block 2.

8. Azure accepted the deletion

The delete command exits successfully, but the group still reports Deleting. What establishes completion?

A. Retain the exact ledger, wait for confirmed resource absence, repeat cleanup and compare the protected baseline.

B. Delete the local state immediately because the request succeeded.

C. Search by a similar prefix and delete every matching resource.

D. Publish raw state and credentials so another learner can confirm the deletion.

Answer: A. Acceptance is an observation of absence, followed by repeat safety and preservation checks. B discards recovery information; C broadens scope beyond ownership; D exposes private runtime material. Review: Block 8.

All environment evidence must come from genuine Azure observations. Raw private runtime material is excluded from the public workshop package.

 
 
 

Comments


bottom of page