Cloud
Azure WAF: prepare an evidence pack before a policy PR
A production runbook for documenting an Azure WAF change with KQL evidence, policy diff, application probes, security validation, merge decision and rollback.
An Azure WAF change often looks small in a pull request: an exclusion, a custom rule, a priority change, a condition on a header or a mode adjustment. In production, that diff can change the entry behavior of a whole application. The question is not only whether the rule is syntactically correct. The team must prove why the change is needed, which traffic it touches, how it will be validated and how to roll it back.
The use case is a platform team managing an Application Gateway WAF policy as infrastructure as code. An application team requests a change after legitimate requests are blocked or after a new exposure is introduced. The runbook aims at one practical decision: merge the PR, ask for more evidence, apply a safer alternative or prepare a bounded rollback.
Define the exact object of change
Before opening a security discussion, name exactly what changes. A WAF PR should not only carry an intent such as “fix the false positive”. It should isolate the rule, path, matched field, mode and expected effect.
Change object
Affected Application Gateway and WAF policy
Hostname and application path
Managed rule, exclusion or custom rule being modified
Matched field: cookie, header, query arg, body, multipart, IP, method
Expected action: allow, block, log, targeted exclusion, priority change
Environment: staging, production, shared gateway
Context
Observed symptom
RuleId or custom rule involved
Log time window
Client, integration or business journey affected
Risk if the change is not applied
Risk if the change is too broad This intake keeps PRs reviewable. If the team cannot name the field and path being touched, the change is probably not ready to merge.
Build the KQL evidence pack
The evidence pack starts with logs. It should show the block, its volume, its scope and useful samples. The goal is not to prove that WAF is annoying. The goal is to prove that the proposed change matches the observed signal.
let Window = 7d;
let Hostname = "api.example.com";
let PathPrefix = "/api/import";
AzureDiagnostics
| where TimeGenerated > ago(Window)
| where Category == "ApplicationGatewayFirewallLog"
| extend hostname = tostring(host_s)
| extend uri = tostring(requestUri_s)
| extend method = tostring(requestMethod_s)
| extend action = tostring(action_s)
| extend ruleId = tostring(ruleId_s)
| extend message = tostring(message_s)
| extend clientIp = tostring(clientIp_s)
| extend transactionId = tostring(transactionId_g)
| where hostname == Hostname
| where uri startswith PathPrefix
| summarize hits=count(),
clients=dcount(clientIp),
firstSeen=min(TimeGenerated),
lastSeen=max(TimeGenerated),
sampleMessages=make_set(message, 5),
sampleTransactions=make_set(transactionId, 5)
by hostname, uri, method, action, ruleId
| order by hits desc Good evidence also includes what is not affected. If the PR claims to target only /api/import, verify that it does not hide blocks on /login, /admin or an external webhook.
let Window = 7d;
let Hostname = "api.example.com";
AzureDiagnostics
| where TimeGenerated > ago(Window)
| where Category == "ApplicationGatewayFirewallLog"
| extend hostname = tostring(host_s)
| extend uri = tostring(requestUri_s)
| extend action = tostring(action_s)
| extend ruleId = tostring(ruleId_s)
| where hostname == Hostname
| where action has_any ("Blocked", "Matched", "Detected")
| summarize hits=count(), rules=make_set(ruleId, 10) by path=split(uri, "?")[0]
| order by hits desc If the evidence pack contains only one isolated example without volume or scope, the review should ask for a wider window or complementary application logs.
Link the IaC diff to operational effect
The policy diff must be readable by someone who did not write the Terraform, Bicep or ARM module. The reviewer needs to understand before, after and expected behavior.
In the PR
Modified file and exact policy
Previous condition and new condition
Previous priority and new priority for a custom rule
Targeted ruleId for an exclusion
Exact match variable and selector
Final action after rule evaluation
Affected environments
Review questions
Is the rule broader than the symptom?
Would a managed-rule exclusion be safer than an allow rule?
Can the priority bypass a useful blocking rule?
Does the change affect several hostnames through a shared policy?
Is there an expiration date or planned review?
Is rollback a simple revert or a manual action? A high-priority custom Allow rule on a broad path does not carry the same risk as a targeted OWASP exclusion on a stable argument. The evidence pack should make that difference obvious.
Add reproducible application validation
WAF evidence is not enough. Replay the legitimate journey and, when possible, a case that should remain blocked. Useful validation produces before and after results, not only “test passed”.
HOST=api.example.com
BASE_URL="https://$HOST"
CORRELATION_ID="waf-pr-$(date +%Y%m%d%H%M%S)"
curl -sk -o /tmp/import-response.txt -w "%{http_code}
" "$BASE_URL/api/import" -H "x-correlation-id: $CORRELATION_ID" -H "content-type: application/json" --data @representative-import-payload.json
curl -sk -o /tmp/negative-response.txt -w "%{http_code}
" "$BASE_URL/api/import" -H "x-correlation-id: $CORRELATION_ID-negative" -H "content-type: application/json" --data @known-bad-payload.json
echo "correlation_id=$CORRELATION_ID" The negative test does not have to be destructive. It can be a synthetic payload expected to be blocked in a test environment, or a non-regression check on sensitive paths. The point is to avoid a PR that proves only the legitimate case passes while saying nothing about whether protection remains active.
Automate the checklist, not the decision
Automation helps make reviews consistent: generate queries, attach the diff, check policy slugs, run probes and verify that rollback exists. It should not turn security review into an automatic merge button.
evidence_pack:
change:
policy: appgw-waf-prod
environment: production
type: managed_rule_exclusion
scope: api.example.com/api/import
required:
- waf_logs_window
- affected_rule_id
- policy_diff
- representative_payload
- negative_or_non_regression_test
- rollback_step
- owner_approval
gates:
merge_allowed_when:
- scope_matches_logs
- no_broader_hostname_impact
- probes_pass
- rollback_is_revertable
merge_blocked_when:
- missing_rule_id
- broad_allow_rule_without_expiration
- policy_shared_without_impact_review
- no_production_observability This checklist can be generated by the pipeline or filled in the PR. The decision stays human when the change modifies a production exposure surface.
Decide merge, hold or rollback
The review should end with an operational decision. “LGTM” is not precise enough for a policy that can block or allow traffic.
Decision: merge
The ruleId or custom rule is identified
The diff scope matches the logs
Legitimate probes pass
Negative or non-regression checks remain acceptable
Rollback is simple and documented
Decision: hold
Logs are insufficient
The critical path was not replayed
Shared-policy impact is not understood
The change should be reduced or given an expiration date
Decision: rollback after merge
Legitimate 403s on a critical path
Application errors rise and correlate with the change
Rule scope is broader than expected
WAF or application visibility is lost
A bypass appears on a control that should remain blocked Rollback should retain the evidence. Reverting the previous commit without keeping transactions, probes and decisions forces the team to replay the same discussion during the next false positive.
Conclusion
A reviewable Azure WAF PR is more than a policy diff. It carries an evidence pack: KQL logs, exact scope, readable diff, application probes, non-regression control, approval and rollback.
The decision then becomes sharper. Merge when the change is targeted and validated. Block when evidence is missing. Roll back when the real behavior exceeds the announced scope. That discipline is what lets teams evolve WAF policies without turning every exception into invisible security debt.