Firewall Rule and IP Group Guidelines¶
The runbook Firewall rules explains how to add, change or remove a rule: the files, the JSON and the pipeline. This page explains how to design rules and IP groups so that the configuration stays readable, stays within the Azure limits and can be handled by more than one team.
Firewall rules are code. Don't create or change rules in the portal: the next deployment of the firewall policy overwrites everything that was not made through the configuration repository.
The structure of the files and the checks of the config tests are fixed by the platform. The naming and numbering schemes on this page are conventions. Two customer platforms use different ones. Choose one scheme for your platform, write it down and apply it consistently. Where this page gives numbers, they are examples.
How Azure Firewall organises rules¶
A firewall policy contains rule collection groups (RCG). An RCG contains rule collections. A rule collection contains rules.
| Level | What it is | Priority |
|---|---|---|
| Rule collection group | The first unit the firewall processes. One JSON file in the configuration repository. | 100 (processed first) to 65000. Must be unique in the policy. |
| Rule collection | Has one type (DNAT, network or application) and one action (Allow or Deny). |
Unique inside its RCG. |
| Rule | Matches source, destination, port and protocol. | No priority. |
The firewall denies everything that no rule allows. Use Allow in nearly every collection. Use Deny only for specific
security needs.
Processing order. Azure Firewall processes the RCGs in priority order, lowest number first. Inside one RCG it processes DNAT collections first, then network collections, then application collections, each by collection priority. That is why the config test requires the lowest NAT priority to be lower than the lowest network priority, and that one lower than the lowest application priority. See Microsoft's rule processing logic.
Rule types:
| Type | Use it for |
|---|---|
| DNAT | Inbound traffic to a firewall public IP, translated to a private address. See DNAT rules. |
| Network | Layer 3 and 4: IP addresses, ports, protocols. Inbound, outbound and traffic between spokes. |
| Application | Layer 7: FQDNs and HTTP, HTTPS or SQL protocols, mainly for outbound traffic. |
Network rules need fewer resources than application rules. Use a network rule when you don't need FQDN matching.
Which type? Use an application rule when the request has only HTTP (80), HTTPS (443) or SQL traffic and the destination is an FQDN. Use a network rule in every other case. The runbook has the checklist: Application or network rule.
Do not let rules overlap. The first match decides, and a deny that matches first hides the allow that follows. A network rule that denies HTTPS to an FQDN wins over an application rule that allows the same FQDN, when it is processed first. Check new rules against existing ones, especially across collection groups.
Azure limits to design for¶
These limits apply to a firewall policy at the time of writing. Check Microsoft's Azure Firewall limits for the current numbers.
| Limit | Value |
|---|---|
| Rule collection groups per policy | 90 |
| Size of one rule collection group | 2 MB |
| Unique IP groups per policy | 600 |
| Addresses or prefixes in one IP group | 5,000 |
Plan with these in mind. A large platform with many workloads can reach 90 RCGs. Don't create an RCG only to organise rules visually. Use rule collections for that.
When to create a new rule collection group¶
Create a separate RCG when:
- the rules need a different priority tier (platform rules, production workloads, non-production workloads);
- a different team owns them, so each team works in its own file;
- they have a different lifecycle, for example temporary rules for a project;
- a logical separation is required, for example a security zone or a compliance boundary;
- the RCG gets close to the 2 MB limit.
Don't create a new RCG when the rules fit in an existing collection, when a collection priority is enough, or when the platform is already close to 90 RCGs.
Organise the configuration around workloads¶
Organise rules per workload, not per technology. A rule collection group called AllowDatabase mixes the traffic of many
workloads. Nobody knows which rule belongs to which workload, rules conflict or repeat, and nobody can tell what to remove
when a workload is decommissioned. One RCG file per workload (or per workload and environment type) fixes this.
A typical layout inside firewall_rule_configuration/ (sub-folders are allowed, see
Firewall rules):
firewall_rule_configuration/
├── 0.platform/ Rules the platform team manages: DNS, updates, monitoring, managed VMs
├── 1.global/ Rules of the customer that apply to every workload
├── <workload-a>/ One folder per workload
│ ├── <workload-a>-prod.json
│ └── <workload-a>-non-prod.json
└── <workload-b>/
Some points about this layout:
- Global and regional files. Put a rule in the global folder when it applies to every region, for example DNS: it must work everywhere. Put it in a region folder only when there is a real regional reason, such as a failover or residency requirement, or when it connects two spokes in one region. A regional file with the same name as a global file replaces it in that region. Keep rules consistent across regions where the regions back each other up.
- Production and non-production apart. Use separate RCGs (or at least separate collections) for production and non-production traffic of a workload, so that production data can't flow to a non-production environment by accident. Don't allow traffic between the two.
- Internal and internet apart. Keep internal rules and internet rules in separate collections. Internet traffic is the higher threat and deserves its own review.
- Don't widen a rule for one source. If only one of several source IP groups needs access to a destination, do not add the destination to the existing rule or collection: that opens it for the other groups too. Add a separate rule.
- One owner per file. The team that owns the workload owns the file. For traffic between two workloads, the rule lives in the file of the destination workload (who requests which rule).
Priorities¶
Azure requires a unique RCG priority per policy. The config test only checks uniqueness inside one
firewall_rule_configuration folder, so also check global files against regional files yourself. Don't use 8999: the
platform uses that priority for the deployment agents RCG when deployment_agents_private_network_range is set.
Give every owner a range, so that teams don't collide and priorities show what a rule is for. Leave gaps (for example steps of 10) so that you can insert rules later.
Example priority scheme
This is an example. Adapt it, and document your own.
| Purpose | RCG priority range |
|---|---|
| Rules managed by the platform team | 1000-1099 |
| Global rules of the customer | 1100-1199 |
| Production workloads | 2000-2999 |
| Non-production workloads | 3000-5999 |
| Reserved | 6000-9999 |
Within the production and non-production ranges, each workload gets its own sub-range (for example 20 numbers). A
workload's production range and non-production range use the same offset (2100-2119 and 3100-3119), which makes
the pairs easy to find.
Collection priorities only need to be unique inside the RCG, and can repeat in other RCGs. Keep the same spacing in each file.
Naming¶
Names are identifiers in Azure and Terraform keys. Choose names that say whose rule it is and what it allows. Use only
lowercase letters, numbers and hyphens (or underscores), keep them short, and avoid names such as rule1, misc,
temp-all or allow-all. The config test accepts the pattern ^[a-zA-Z0-9][a-zA-Z0-9._-]*[a-zA-Z0-9_]$ for collection
and rule names.
| Object | Pattern (example) | Examples |
|---|---|---|
| RCG | <workload>-<project or service>-<environment type>. Platform-owned: <owner>-global-<description>. |
app1-prod, app1-reporting-non-prod, platform-global-dns |
| Rule collection | A fixed name per type, or a name that shows type and environment | NetworkRules, ApplicationRules |
| Rule | <source>-to-<destination>, with extra detail when needed |
app1-vm-to-servicebus, appgw-to-app1-web, onprem-sql-to-app1-sql |
| IP group | ipgrp-<description> (global), ipgrp-<workload>-<description>-<environment> (workload). An alternative that shows where the addresses live: ipgrp-<onprem or azure>-<workload>-<environment>-<use>. |
ipgrp-hub, ipgrp-app1-web-prod, ipgrp-onprem-dns |
The RCG name must be unique across all files of a region, because it is the Azure name and the Terraform key. Renaming an RCG destroys and recreates it (see Change or remove an existing rule).
Design rules well¶
- Use the most specific destination. In order of preference: an FQDN, a single IP address, an IP group, a subnet. Prefer the narrowest scope that works.
- Internet destinations as FQDNs, not as IP addresses. Public services (operating system updates, vendor APIs) and third-party applications can change address. A rule on an address then stops matching without warning. An FQDN or an FQDN tag keeps working.
- Use IP groups for address sets that more than one rule uses or that change often (see below).
- Use service tags for Azure services in network rules (for example
AzureBackup,AzureMonitor). Microsoft maintains the address ranges, and this performs better than FQDNs. - Use FQDN tags for Microsoft services in application rules (for example
WindowsUpdate). The list of supported values is in the Firewall rules runbook. - One source type and one destination type per rule. Use either
source_addressesorsource_ip_groups, and eitherdestination_addresses,destination_ip_groupsordestination_fqdns. Split into two rules if you need both. The config test enforces this. - Consolidate. Combine rules with the same source, destination and action, and list several ports in one rule
(
["80", "443"]) instead of making one rule per port. - Least privilege. Name the exact ports and protocols. Avoid
*as source or destination and avoid theAnyprotocol. Use wildcard FQDNs (*.example.com) with care. - Encrypted protocols by default. Many organisations only allow encrypted protocols and require an extra approval for unencrypted ones.
- Remove what is no longer used. Azure Firewall Policy Analytics helps to find unused rules and to tune the order of rules.
An explicit deny rule¶
Add a Deny collection only for a specific security need, for example to block a sensitive subnet from the internet. Give
the deny collection a lower priority number than the allow rules it must override, put it in a platform-owned RCG, and
make sure that rule is reviewed by security.
IP groups¶
An IP group is a named set of addresses and ranges. Rules refer to it by name. The runbook with the steps and the JSON is IP groups. When a subnet changes, you update one file and every rule that uses the group follows. The name also tells the reader what the addresses are, and it is easy to see which networks a rule opens.
When to use an IP group¶
- Several rules use the same set of addresses.
- The addresses are shared infrastructure that many workloads refer to (hub networks, identity servers, management networks, the Application Gateway subnet).
- The addresses change regularly.
- The set has one logical purpose (the workstations of one workload in production, the Databricks subnets of one workload).
- It is a workload or spoke. A good habit is to give every new spoke an IP group for its address range from the start, so that later rules can refer to it. Before you add a new rule, check whether an existing rule with the same purpose can get the new address through the group.
When not to use an IP group¶
- The addresses are used by one rule only. Put them inline.
- The rule is temporary or a one-off test.
- The ruleset is very small.
- The addresses are unrelated. Don't mix unrelated subnets in one group.
Where the files are and what they look like¶
IP group files live in ip_groups/ (see Firewall rules). One file holds one
IP group. Use the same folder structure as for the rules: global groups in a platform or global folder, workload groups in a
folder per workload, and the file name equal to the group name. A group that doesn't follow the naming convention can wait
in a separate "uncategorized" folder until it is renamed.
{
"ip_group": {
"name": "ipgrp-app1-web-prod",
"cidrs": ["10.20.4.0/26"]
}
}
Write a single host as /32. The schema accepts a plain IPv4 address and an address with a prefix length from /0 to
/32. Use one style within a group, and validate the file (Edit the JSON files with schema support).
Rename or remove an IP group in two steps¶
A rule that refers to a group that no longer exists fails the deployment. Never rename or remove a group in the same change that updates the rules.
Rename:
- Add a new file with the new name and the same addresses. Update every rule to use the new name. Deploy.
- When that is deployed in every environment, delete the old file. Deploy again.
Remove:
- Remove all references from the rules. Deploy.
- Delete the file. Deploy again.
Make firewall changes safely¶
- One RCG change per pull request. A firewall policy update takes several minutes, and Azure doesn't support parallel updates of the same policy. Small pull requests are easier to review, apply and roll back.
- Add, verify, remove. When a rule replaces another, add the new rule first, check that the traffic works, and remove the old rule in a separate pull request. Never remove a rule before its replacement is confirmed. Combining add and remove in one pull request can cause an outage.
- Change every environment folder that needs it. The canary and prod folders are separate. Make the same change in both (canary first), unless the rule only applies to one.
- Run the config tests before you push (Run the tests locally).
- Check the plan. Only the
firewalllayer should show changes, and only for the RCG you touched.
If a large change times out¶
Several rule collection groups in one change can take longer than the apply time-out, because Azure queues the updates. When the run is aborted, some collections may exist in Azure but not in the Terraform state. The next run then fails because the resource already exists.
Don't destroy and recreate production rules to fix a state mismatch
Destroying the collections that already exist interrupts traffic for as long as the repair takes. Use terraform import
for the resources that exist in Azure but not in the state. Better still, prevent it: when a canary run already shows
time-outs, don't continue to production. Split the change into smaller pull requests, and apply new rules before
removing the rules they replace.
Read the firewall logs¶
Firewall diagnostics can send logs to a Log Analytics workspace. The tables you will use most are:
| Table | Content |
|---|---|
AZFWApplicationRule |
Application rule matches |
AZFWNetworkRule |
Network rule matches |
AZFWNatRule |
DNAT rule matches |
AZFWDnsQuery |
DNS queries handled by the DNS proxy |
AZFWFlowTrace, AZFWFatFlow |
Flow traces and the largest flows |
AZFWIdpsSignature |
Intrusion detection signatures (Premium) |
There are also aggregation tables (AZFWApplicationRuleAggregation, AZFWNetworkRuleAggregation,
AZFWNatRuleAggregation). Give the people who troubleshoot rules read access to these tables and to the firewall
resource, and not to the whole workspace. Verify which tables your diagnostic settings fill.