Networking

Azure Firewall: qualify a broad rule before tightening production access

A production runbook for tightening an Azure Firewall rule without breaking useful traffic, with inventory, KQL evidence, dependencies, controlled validation and rollback.

15 Jul 2026 azureazure-firewallnetworkingsecurityegresskqlobservabilityrunbookrollbackproduction

A broad Azure Firewall rule tends to become invisible. It was added to unblock an incident, absorb a migration, let a partner flow through, or buy time during a deployment. A few weeks later, nobody knows whether it still protects a real dependency, hides a DNS problem, or simply allows too many destinations.

The use case is a hub-and-spoke Azure environment where outbound traffic crosses Azure Firewall. An application or network rule allows too much: a wildcard FQDN, a generous IP range, a port shared by several workloads, or a high-priority collection inherited from an emergency. The goal is not to delete it bravely. This runbook helps decide whether the rule can be tightened, expired, split or temporarily kept with usable evidence.

Treat the rule as a production risk

Before changing the policy, describe the rule as a production contract. A Firewall rule is not just configuration. It encodes assumptions about workloads, dependencies, protocols, ports, network identities and the acceptable risk of blocking traffic.

text firewall-rule-scope.txt
Rule to qualify
Policy: azfw-policy-prod
Rule collection group: rcg-egress-prod
Rule collection: app-egress-shared
Rule: allow-partner-or-temporary-wildcard
Type: application or network
Expected sources: snet-app-prod, snet-jobs-prod
Expected destinations: api.partner.example, auth.partner.example
Ports: 443
Rule origin: incident, migration, security exception or deployment
Creation ticket or date
Business owner and platform owner

Questions before tightening
Which real flows still use the rule?
Does traffic come from the expected sources?
Do observed destinations match the documented need?
Is a higher-priority rule hiding the result?
Which test proves tightening will not break production?
Which rollback restores the previous state without widening further?

If the team cannot answer these questions, the right first action is evidence collection, not rule removal.

Read hits before intentions

Rule documentation explains why it exists. Logs show what it actually does. Start by isolating events for the collection, rule and expected sources.

kusto 01-firewall-rule-hits.kql
let StartTime = ago(14d);
let RuleName = "allow-partner-or-temporary-wildcard";
AZFWApplicationRule
| where TimeGenerated > StartTime
| where Rule == RuleName
| summarize
  hits = count(),
  firstSeen = min(TimeGenerated),
  lastSeen = max(TimeGenerated),
  sources = dcount(SourceIp),
  fqdnCount = dcount(Fqdn),
  sampleFqdns = make_set(Fqdn, 20)
by Action, RuleCollection, Rule, Protocol, DestinationPort
| order by hits desc

For a network rule, replace FQDN evidence with observed IPs and ports.

kusto 02-firewall-network-rule-hits.kql
let StartTime = ago(14d);
let RuleName = "allow-shared-egress-443";
AZFWNetworkRule
| where TimeGenerated > StartTime
| where Rule == RuleName
| summarize
  hits = count(),
  firstSeen = min(TimeGenerated),
  lastSeen = max(TimeGenerated),
  sources = dcount(SourceIp),
  destinations = dcount(DestinationIp),
  sampleDestinations = make_set(strcat(DestinationIp, ":", tostring(DestinationPort)), 30)
by Action, RuleCollection, Rule, Protocol
| order by hits desc

A rule with no recent hit can be a candidate for expiration, but not immediate deletion if the flow is monthly, batch-driven or tied to disaster recovery. The useful criterion is consistency between business cadence and observed traffic.

Separate valid consumers from noise

A broad rule often serves several consumers without that being explicit. Group observed sources by subnet, workload or environment, then identify usage outside the intended scope.

kusto 03-firewall-consumers-by-source.kql
let StartTime = ago(14d);
let RuleName = "allow-partner-or-temporary-wildcard";
AZFWApplicationRule
| where TimeGenerated > StartTime
| where Rule == RuleName
| summarize hits=count(), lastSeen=max(TimeGenerated), fqdns=make_set(Fqdn, 15) by SourceIp, DestinationPort, Action
| order by hits desc

Complete that view with network inventory: which subnet owns each IP, which service uses it, and whether the source should depend on this rule. An unexpected source is not automatically hostile. It may reveal a CI runner, a forgotten batch, an old App Service VNet Integration path, or a route that groups too many workloads behind the same policy.

Check application dependencies before cutting

Tightening should start from the real flow. For an FQDN rule, list the exact domains, aliases, redirects, CDN names, authentication endpoints and secondary calls. For an IP rule, check whether the IP is stable and whether it represents an appliance, private service, partner endpoint or volatile Internet destination.

text dependency-checklist.txt
Dependency to validate
Functional name: partner billing API
FQDN called by workload: api.partner.example
Secondary FQDNs: auth.partner.example, token.partner.example
Ports and protocols: HTTPS 443
Authorized sources: snet-app-prod, snet-jobs-prod
Usage window: continuous, daily batch, monthly batch or DR
Blocking sensitivity: user-facing, back-office, monitoring or recovery

Block tightening when
the test was run from an admin workstation instead of runtime
a secondary FQDN appears without an owner
the rule is used by an infrequent batch that was not replayed
rollback depends on undocumented manual work
an IP destination hides an unidentified name or service

Private Endpoint may appear in the analysis if the destination resolves to a private address, but it is not the main lens. The question here is the real Firewall rule scope and whether it can be reversed safely.

Prepare a measurable tightening

Do not replace a broad rule with a narrow one without an observation window. Prepare the diff as a testable hypothesis: which sources remain allowed, which destinations are removed, which temporary rule can receive denials, and which metric triggers rollback.

yaml firewall-tightening-plan.yml
change:
objective: tighten allow-partner-or-temporary-wildcard
from:
  sources: ["10.42.0.0/16"]
  destinations: ["*.partner.example"]
  ports: [443]
to:
  sources: ["10.42.12.0/24", "10.42.18.0/24"]
  destinations: ["api.partner.example", "auth.partner.example"]
  ports: [443]

validation:
before:
  - export current policy
  - capture hits by source and destination over 14 days
  - replay one call from each critical runtime
during:
  - watch denies on removed destinations
  - watch application 401, 403, 5xx errors and timeouts
  - keep rollback ticket open
success:
  - expected hits on the new rule
  - no unexpected deny from critical sources
  - application probe stays healthy
rollback:
  - restore previous rule or collection
  - document the missing destination
  - reopen only the required scope

The plan must be readable by the on-call operator. If only the change author can roll it back, the rule is not ready to be tightened.

Use a canary when risk is high

When Azure Firewall Policy is shared across spokes, a local-looking change can affect neighboring workloads. When possible, start with a canary source: one subnet, one workload, a dedicated collection, or a more specific rule placed before the old broad rule.

text canary-tightening.txt
Acceptable canary
one critical but well-instrumented source
a representative traffic window
an owner available to test
a prepared policy rollback
Firewall and application logs that can be correlated

Insufficient canary
only a manual curl succeeds
the tested source does not represent the real batch
the old broad rule remains higher priority and hides the new rule
denies are not collected in Log Analytics
validation ignores authentication or callback calls

The canary is not there to provide comfort. It is there to find missing dependencies before reducing the global scope.

Decide keep, tighten, expire or roll back

Make the decision explicit. A broad rule can be kept temporarily if the outage risk is higher than the exposure risk, but it must then carry a date, an owner and evidence.

text firewall-rule-decision.txt
Tighten
Real hits match known sources and destinations
Secondary dependencies are documented
Canary produced the same hits on the new rule
Unexpected denies are zero or explained
Rollback was tested or reviewed

Expire
No hit matches the expected business cycle
Owner confirms the flow is retired
A non-regression test covers the historical workload
Removal is announced with a watch window

Keep temporarily
The rule still serves a useful but poorly documented flow
Outage risk is higher than immediate exposure risk
A split plan is open with date and owner
Logs stay monitored until the next change

Rollback
Denies appear on a known critical flow
A secondary destination is missing from the new scope
The new rule is hidden by unexpected priority
Application errors increase after the change
Observed behavior does not match the plan

Avoid the false win where the rule is tightened while a broader exception is added elsewhere. The outcome must compare the before and after scope, not only the name of the modified rule.

Validate after the change

After tightening, read logs in both directions: what now passes through the target rule and what is denied from removed destinations. Add application validation, not only Firewall validation.

kusto 04-post-change-validation.kql
let ChangeTime = datetime(2026-07-15T08:00:00Z);
let NewRule = "allow-partner-api-tight";
let OldRule = "allow-partner-or-temporary-wildcard";
AZFWApplicationRule
| where TimeGenerated > ChangeTime
| where Rule in (NewRule, OldRule) or Action == "Deny"
| summarize hits=count(), sources=dcount(SourceIp), fqdns=make_set(Fqdn, 20) by bin(TimeGenerated, 15m), Action, RuleCollection, Rule
| order by TimeGenerated asc

Validation is complete when the expected flow uses the target rule, unexpected denials are understood, application metrics remain stable and the rollback path can be closed.

Conclusion

Tightening an Azure Firewall rule is not cosmetic cleanup. It is a production change that can remove a network path used by a workload, batch, callback or recovery procedure.

The right runbook starts from real hits, identifies consumers, qualifies dependencies, prepares a measurable tightening, tests a canary when needed, then decides: tighten, expire, keep temporarily or roll back. A stricter rule only has value when it remains operable, validated and reversible.