Firewall Rules (Add, Change, Remove)¶
Standard changes: Add new firewall rules and Change firewall rules in the central Azure Firewall(s).
Effect: rule collection groups and IP groups in the hub firewall policy of each region are created, updated or
deleted. Only the firewall layer changes. Traffic is allowed or denied as soon as the apply finishes.
For naming, priority ranges, rule ownership and IP group practice, see the Firewall rule and IP group guidelines. For the review of a pull request, see Pull request review and the runbooks Handle a firewall rule request, Firewall rule pull request review and Firewall rule deployment. IP groups have their own runbook: IP groups. Workload teams that prepare the pull request themselves start with Firewall rules and IP groups for workload teams.
Request fields¶
| Request field | Where it goes | Notes |
|---|---|---|
| Workload Name | rule collection group / collection / rule names | Use it in names so rules can be traced back, e.g. RCG App1, rule Allow-App1-Sql. |
| Spoke | pull request description | The spoke the rule is meant for. Helps to find the source range and the owner. |
| Rationale | pull request / change record | |
| Description and integration | pull request description | What the rule is used for, and which other workloads or on-premises systems it connects to. |
| Rule name | rule key in the rules map |
Name the rule so that the traffic is clear, e.g. app1-vm-to-servicebus. |
| Rule type (application or network) | which collection map the rule goes in | If the requester does not know, decide with the checklist below. |
| Environment | platform environment (canary / prod) and region folder(s) |
|
| Source IP | source_addresses (IPs/CIDRs) or source_ip_groups (IP group names) |
Only one source type per rule (config test). |
| Source FQDN | not supported | Azure Firewall rules in this configuration cannot match on a source FQDN. Ask for the source IP range or an IP group instead. |
| Destination IP | network rule: destination_addresses (IP/CIDR/service tag) or destination_ip_groups |
One destination type per rule. |
| Destination FQDN | network rule destination_fqdns, or application rule destination_fqdns |
Use an application rule for HTTP/HTTPS/MSSQL. Network rules with FQDNs need DNS proxy on the firewall policy (firewall_policy_dns.proxy_enabled). |
| Protocol | network: TCP, UDP, ICMP, Any. Application: Http:<port>, Https:<port>, Mssql:<port> |
|
| Port | network: destination_ports. Application: part of protocols |
|
| Comments |
Where the files are¶
The locations are set in <env-folder>/<region>/firewall.auto.tfvars:
firewall_rule_collection_global_file_location = "firewall_rule_configuration" # all regions
firewall_rule_collection_file_location = "weu/firewall_rule_configuration" # this region
firewall_rule_collection_file_filter = "/**/*.json"
ip_group_global_json_file_relative_path = "ip_groups/" # optional, all regions
ip_group_json_file_relative_path = "weu/ip_groups/" # this region
ip_group_json_file_pattern = "/**/*.json"
use_new_fwp_rulecollectiongroup_naming = true
use_multiregion_fwp_rulecollectiongroup_naming = true
use_new_ip_group_naming = true
Paths are relative to the <env-folder> (see
How relative paths in tfvars are resolved).
Sub-folders are allowed (/**/*.json), for example weu/firewall_rule_configuration/0.Platform/.
Global and regional files (infra/deploy/firewall/locals.tf, only when
use_multiregion_fwp_rulecollectiongroup_naming = true):
- Every region deploys its regional files plus the global files.
- A regional file with the same file name as a global file replaces the global one in that region.
IP groups follow the same merge rule whenever ip_group_global_json_file_relative_path is set, whatever the value of
the naming flag.
Keep the folder names firewall_rule_configuration and ip_groups. The config tests find rule files and IP groups by
these folder names.
Rule collection group JSON¶
One file is one rule collection group (RCG). All three collection maps must be present. Use {} when a map is empty.
{
"rule_collection_group": {
"name": "App1",
"priority": 400,
"nat_rule_collections": {},
"network_rule_collections": {
"App1-Network": {
"priority": 410,
"action": "Allow",
"rules": {
"Allow-App1-to-Sql": {
"protocols": ["TCP"],
"source_addresses": [],
"source_ip_groups": ["ipgrp-app1"],
"destination_addresses": [],
"destination_ip_groups": ["ipgrp-sql-servers"],
"destination_fqdns": [],
"destination_ports": ["1433"]
}
}
}
},
"application_rule_collections": {
"App1-Application": {
"priority": 420,
"action": "Allow",
"rules": {
"Allow-App1-to-Vendor-Api": {
"protocols": ["Https:443"],
"source_addresses": [],
"source_ip_groups": ["ipgrp-app1"],
"destination_fqdns": ["api.vendor.example"],
"destination_fqdn_tags": []
}
}
}
}
}
}
Every key in the example is required, even when it is an empty array. The Terraform module reads each key directly
(infra/modules/rule_collection_group/main.tf). The schema is src/Xebia.Mcp/Files/json/firewallschema.json.
| Level | Field | Rules |
|---|---|---|
| RCG | name |
Unique across all files deployed to the region (global + regional). It is the Azure name and the Terraform key. |
| RCG | priority |
100–65000 (Azure), and unique per firewall_rule_configuration folder (config test). Also keep it unique between the global and regional sets. Don't use 8999: the platform creates RCG rcg-platform-agent-… with that priority when deployment_agents_private_network_range is set. |
| Collection | map key | The collection name. Pattern ^[a-zA-Z0-9][a-zA-Z0-9._-]*[a-zA-Z0-9_]$. |
| Collection | priority |
In one RCG, the lowest NAT priority < the lowest network priority < the lowest application priority (config test). |
| Collection | action |
Network/application: Allow or Deny. NAT: Dnat. |
| Rule | map key | The rule name, same pattern. |
| Network rule | protocols, source_addresses, source_ip_groups, destination_addresses, destination_ip_groups, destination_fqdns, destination_ports |
One source type and one destination type per rule. |
| Application rule | protocols, source_addresses, source_ip_groups, destination_fqdns, destination_fqdn_tags |
destination_fqdn_tags values: AppServiceEnvironment, AzureBackup, AzureKubernetesService, HDInsight, MicrosoftActiveProtectionService, WindowsDiagnostics, WindowsUpdate, WindowsVirtualDesktop, Internet. Target URLs, web categories and TLS inspection are not supported by the module. |
| NAT rule | protocols, destination_address, destination_ports, source_addresses, source_ip_groups, translated_address, translated_fqdn, translated_port |
Include translated_fqdn (it may be null). |
IP group references (source_ip_groups, destination_ip_groups): use the IP group name. Terraform also
resolves a full Microsoft.Network/ipGroups resource ID, but the pipeline config test only accepts names that are
defined in an ip_groups folder, so a resource ID fails the run. The Terraform check blocks for unknown references
only produce warnings, so the config test is your real safeguard.
IP group JSON¶
The full runbook for IP groups, with naming and the validation, is IP groups.
{
"ip_group": {
"name": "ipgrp-app1",
"cidrs": ["10.20.4.0/24"]
}
}
namemust be unique in the environment. It is the Azure name and the reference used in rules.- IP groups are created in the connectivity resource group
rg-<company>-connectivity-<env>-<region>-001.
Application or network rule¶
When the request does not say which type of rule to use, answer two questions:
- Does the request contain only the ports
80(HTTP) and443(HTTPS), or1433through theMssqlprotocol? - Is the destination an FQDN (or an FQDN tag)?
If both answers are yes, use an application rule. Otherwise use a network rule. The rest of the section
Choosing the rule type and destination explains the
reasons. In an application rule, protocols has the form Http:<port>, Https:<port> or Mssql:<port>.
Global or regional rule¶
A global rule is active in every region and hub. A regional rule only in one region. Decide from the content:
- DNS rules should work everywhere: global.
- A rule between two spokes in a single region: regional.
- When regions back each other up, keep the rules of both regions the same.
If you cannot decide, ask the owner of the platform design. A rule in the wrong folder is either missing in a region or open in a region that does not need it.
Add a rule¶
- Collect the request fields. Prefer IP groups over literal addresses. They are reusable and keep rules readable.
- If needed, create or extend an IP group file in
<region>/ip_groups/(or the globalip_groups/). - Either add a rule to an existing collection in the RCG file of the owning workload, or create a new RCG file with an unused priority. For traffic between two workloads, the owner is the destination workload (who requests which rule).
- Make the same change in the folder of every platform environment that should get it (
canaryandprodare separate folders), and apply it to canary first. - Validate locally (
pwsh ./dev/dev.ps1 -Action TestwithMCP_CONFIGpointing at yourmcp.json, see Run the tests locally), then open a pull request. - Run
hub. Only the firewall layer should show changes:azurerm_firewall_policy_rule_collection_groupand/orazurerm_ip_groupadded or updated. - Apply. The firewall layer applies with
-parallelism=1(ADR-0009), so big changes take several minutes. Keep each pull request to one rule collection group, so a change stays small enough to apply and to roll back (see If a large change times out).
Change or remove an existing rule¶
- Change: edit the rule in place. The RCG is updated in place. Azure replaces its whole rule set in one operation.
- Remove a rule: delete the rule key from the
rulesmap. - Remove a whole RCG: delete the file. The plan shows the RCG being destroyed.
- Rename an RCG (
name): this is a destroy + create, because the name is the Terraform key. For a moment no rules from that group are active.
DNAT rules for inbound traffic¶
A DNAT rule lets a client from the internet reach a private destination through a public IP address of the firewall. The firewall translates the destination address and port. Use it only for specific cases and with a short list of source addresses. A typical case is an external party that must upload files over SFTP to a storage account that has only a private endpoint (see the consumer page Inbound SFTP to a storage account).
The pattern is:
client -> <firewall public IP> : <custom port> -> DNAT -> <storage account FQDN> : 22
Put the DNAT collection in the nat_rule_collections map of an RCG. Its action is Dnat.
{
"rule_collection_group": {
"name": "sftp-inbound",
"priority": 200,
"nat_rule_collections": {
"AllowSftpNatToStorage": {
"priority": 110,
"action": "Dnat",
"rules": {
"AllowSftpNatToApp1Prd": {
"protocols": ["TCP"],
"source_addresses": ["203.0.113.10"],
"source_ip_groups": [],
"destination_address": "<firewall public IP>",
"destination_ports": ["<custom port>"],
"translated_address": null,
"translated_port": "22",
"translated_fqdn": "<storage account>.blob.core.windows.net"
}
}
}
},
"network_rule_collections": {},
"application_rule_collections": {}
}
}
source_addressesorsource_ip_groups: the external party that starts the session. Use fixed, specific addresses.destination_address: the public IP address of the firewall the party connects to. Each region has its own firewall and its own public IP address, so choose the region and use that address.destination_ports: a unique custom port for this destination, never22. Keep a register of assigned ports, so two storage accounts never get the same port.translated_fqdnandtranslated_port: the private endpoint FQDN and the port the firewall forwards to.translated_addressisnullwhen you use the FQDN.
Add the rule in each platform environment folder that should have it, and review it with security. The rule is exposed to
the internet, so also set a review date and remove it when the exchange ends. The firewall logs show DNAT matches in
AZFWNatRule.
Verify¶
- Azure portal → Firewall Policies →
afwp-<company>-connectivity-<env>-<region>-001→ Rule collections: the RCG, collection and rule are present. - Test the flow from the source, for example with
Test-NetConnection <dest> -Port <port>orcurl. - Firewall logs: query the Log Analytics workspace for
AZFWNetworkRule/AZFWApplicationRuleentries that match the source and destination. This needs firewall diagnostics enabled to Log Analytics. The log tables are listed in Read the firewall logs.
Roll back¶
Revert the pull request and run hub again.
Common problems¶
| Symptom | Cause |
|---|---|
Config test Referenced IP Group '<x>' should exist |
Wrong name, a resource ID instead of a name, or the IP group file is not in a folder named ip_groups. |
Config test Priority <n> already used |
Two RCG files in the same folder use the same priority. |
Config test Only one source type allowed per rule |
Both source_addresses and source_ip_groups are filled. |
| Plan wants to destroy all RCGs | Wrong firewall_rule_collection_file_location, or the naming flags changed. Stop and fix the path. |
409 AnotherOperationInProgress |
Parallel updates on the firewall policy. Re-run. The layer already applies with -parallelism=1. |
| Apply fails because a rule collection group already exists | An earlier run was aborted (for example by a time-out) after Azure had created the group but before the state was saved. Import the existing group into the state. Don't destroy it in production. See If a large change times out. |
| An IP group can't be renamed or removed | A rule still refers to it. Remove or move the references first and deploy, then change the group in a second pull request. |
| The rule is deployed but the traffic still fails | See If the traffic still fails. |
If the traffic still fails¶
Check these, in this order:
- Priorities. A collection or RCG with a lower number is processed first. The deployment normally refuses clashing priorities, but check anyway.
- Overlapping rules. The first match decides, and no later rule is evaluated. A network rule that denies port 443 to an FQDN wins over an application rule that allows HTTPS to the same FQDN when it is processed first: in the same group network rules come before application rules, and a group with a lower priority number comes before other groups.
- IP address instead of FQDN. For internet destinations (updates, vendor APIs, third-party applications) use FQDNs. The address of a third-party service can change, and then the rule silently stops matching.
- Other layers. Network security groups, application security groups and settings on the network interface also filter traffic. Check the NSG of the source and destination subnet and the route table.
- Name resolution. FQDN rules in network rules need the DNS proxy of the firewall policy
(
firewall_policy_dns.proxy_enabled). Check name resolution first (Incident management). - Manual rules. A rule that someone created in the portal is overwritten by the next deployment. Don't change the firewall in the portal.