Article
Useful Azure Front Door WAF KQL Queries for Troubleshooting
When Azure Front Door diagnostic logs are stored in Log Analytics, Kusto Query Language (KQL) can quickly answer questions about WAF activity.
Here are several queries we commonly find useful when troubleshooting Front Door.
These examples use the AzureDiagnostics table. They retrieve user agents from Front Door access logs and correlate them with WAF events using trackingReference_s.
Run each query separately. Copy only the code inside the query block, excluding article headings and explanatory text. For queries beginning with let, run the entire block together, including the ending semicolon on the variable definition.
Set the Log Analytics time range to include the requests you’re investigating. Both access logs and WAF logs must be enabled and sent to the workspace for the queries that combine them. If your workspace contains multiple Front Door profiles, narrow your investigation to the relevant resource.
Show Recent WAF Blocks
AzureDiagnostics
| where Category =~ "FrontDoorWebApplicationFirewallLog"
| where action_s =~ "Block"
| project
TimeGenerated,
ClientIP = clientIP_s,
RequestURL = requestUri_s,
Rule = ruleName_s,
Action = action_s,
TrackingReference = trackingReference_s
| order by TimeGenerated desc
This is usually the best starting point.
The user agent is retrieved from the access log in the queries below rather than directly from the WAF log.
Count Blocks by WAF Rule
AzureDiagnostics
| where Category =~ "FrontDoorWebApplicationFirewallLog"
| where action_s =~ "Block"
| summarize BlockCount = count() by Rule = ruleName_s
| order by BlockCount desc
This makes frequently triggered rules easy to identify.
BlockCount counts blocking WAF log entries. It is not a guaranteed count of unique requests, because multiple log entries can relate to the same request.
Count Blocks by User Agent
let BlockedRequests =
AzureDiagnostics
| where Category =~ "FrontDoorWebApplicationFirewallLog"
| where action_s =~ "Block"
| where isnotempty(trackingReference_s)
| distinct _ResourceId, trackingReference_s;
AzureDiagnostics
| where Category =~ "FrontDoorAccessLog"
| where isnotempty(trackingReference_s)
| summarize arg_max(TimeGenerated, userAgent_s)
by _ResourceId, trackingReference_s
| join kind=inner (BlockedRequests)
on _ResourceId, trackingReference_s
| summarize BlockCount = count() by UserAgent = userAgent_s
| order by BlockCount desc
This is particularly useful when investigating bots, crawlers and automated services.
The query first identifies distinct blocked tracking references for each resource. It then keeps the latest access-log entry for each resource and tracking reference before joining the results. This prevents repeated WAF or access-log entries from multiplying the count.
Here, BlockCount counts matched resource-and-tracking-reference pairs. Blocked requests without a matching access-log entry in the selected time range are excluded. An empty user-agent value means the matched access record did not provide one.
A user-agent string is supplied by the client and can be spoofed. Use it as supporting information rather than proof of identity.
Count Blocks by Client IP
AzureDiagnostics
| where Category =~ "FrontDoorWebApplicationFirewallLog"
| where action_s =~ "Block"
| summarize BlockCount = count() by ClientIP = clientIP_s
| order by BlockCount desc
An IP producing hundreds or thousands of blocking log entries can become obvious immediately.
As with the rule-count query, BlockCount counts blocking WAF log rows rather than guaranteed unique requests.
Keep in mind that IP addresses alone aren’t always reliable indicators of identity. Proxies, shared infrastructure and other network configurations can cause multiple users to appear behind the same address.
Find WAF Activity for a Specific URL
AzureDiagnostics
| where Category =~ "FrontDoorWebApplicationFirewallLog"
| where requestUri_s contains "/your-path/"
| project
TimeGenerated,
Action = action_s,
ClientIP = clientIP_s,
Rule = ruleName_s,
RequestURL = requestUri_s,
TrackingReference = trackingReference_s
| order by TimeGenerated desc
Replace /your-path/ with the portion of the URL you’re investigating.
This is useful for APIs, forms, RSS feeds and application endpoints.
This query includes all recorded WAF actions for matching URLs. To limit it to blocks, add | where action_s =~ "Block" before the project statement.
Search by User Agent
let MatchingRequests =
AzureDiagnostics
| where Category =~ "FrontDoorAccessLog"
| where isnotempty(trackingReference_s)
| summarize arg_max(TimeGenerated, userAgent_s)
by _ResourceId, trackingReference_s
| where userAgent_s contains "bot"
| project
_ResourceId,
trackingReference_s,
UserAgent = userAgent_s;
AzureDiagnostics
| where Category =~ "FrontDoorWebApplicationFirewallLog"
| where isnotempty(trackingReference_s)
| join kind=inner (MatchingRequests)
on _ResourceId, trackingReference_s
| project
TimeGenerated,
Action = action_s,
ClientIP = clientIP_s,
RequestURL = requestUri_s,
Rule = ruleName_s,
UserAgent,
TrackingReference = trackingReference_s
| order by TimeGenerated desc
Replace bot with the user-agent text you’re investigating.
The query searches the user agent in the latest access-log entry for each resource and tracking reference, then retrieves matching WAF events.
It shows WAF activity associated with matching user agents, including actions other than blocking. To show only blocks, add | where action_s =~ "Block" immediately after the WAF category filter.
Requests without a matching WAF event do not appear. Multiple WAF events for the same request can appear as separate rows because each row provides information about a recorded WAF event.
Look Up One Tracking Reference
Copy a TrackingReference value from one of the query results above. Replace PASTE-TRACKING-REFERENCE-HERE with that exact value, keeping the quotation marks:
let Ref = "PASTE-TRACKING-REFERENCE-HERE";
AzureDiagnostics
| where Category in~ (
"FrontDoorAccessLog",
"FrontDoorWebApplicationFirewallLog"
)
| where trackingReference_s == Ref
| order by TimeGenerated asc
The first line stores the tracking reference in a variable named Ref. The query then uses trackingReference_s == Ref to find log entries with that exact value.
For example, if the value you copied were example-reference-123, the first line would be:
let Ref = "example-reference-123";
That value is only an illustration. Use the actual tracking reference from your logs.
Run the entire lookup query together, including the let Ref line and its ending semicolon. Set the Log Analytics time range to include when the request occurred.
The tracking reference is one of the most useful Front Door troubleshooting fields because it can connect access-log and WAF activity belonging to the same request.
If only one log category appears, check that both categories are enabled and sent to the workspace, that the selected time range includes the request, and that the relevant logs have arrived.
Don’t Assume Every Error Is WAF
This is an important part of interpreting these queries.
A failed request in the Front Door access log does not necessarily indicate a firewall problem.
The WAF log records requests that match WAF rules. Access logs provide information about requests processed through Front Door.
Looking at both—and correlating requests using their tracking reference—helps separate WAF problems from origin and application problems.
For example, an HTTP 403 response alone does not prove that the WAF blocked the request. Look for a corresponding WAF event with a blocking action.
Azure Troubleshooting Beyond the Query
KQL tells you what Azure recorded. The next step is interpreting it correctly.
AZTANDC works across the application, Azure Front Door, WAF, APIs and origin infrastructure to help determine not only what failed, but where it failed.