Skip to content

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"]
  }
}
  • name must 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:

  1. Does the request contain only the ports 80 (HTTP) and 443 (HTTPS), or 1433 through the Mssql protocol?
  2. 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

  1. Collect the request fields. Prefer IP groups over literal addresses. They are reusable and keep rules readable.
  2. If needed, create or extend an IP group file in <region>/ip_groups/ (or the global ip_groups/).
  3. 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).
  4. Make the same change in the folder of every platform environment that should get it (canary and prod are separate folders), and apply it to canary first.
  5. Validate locally (pwsh ./dev/dev.ps1 -Action Test with MCP_CONFIG pointing at your mcp.json, see Run the tests locally), then open a pull request.
  6. Run hub. Only the firewall layer should show changes: azurerm_firewall_policy_rule_collection_group and/or azurerm_ip_group added or updated.
  7. 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 rules map.
  • 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_addresses or source_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, never 22. Keep a register of assigned ports, so two storage accounts never get the same port.
  • translated_fqdn and translated_port: the private endpoint FQDN and the port the firewall forwards to. translated_address is null when 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> or curl.
  • Firewall logs: query the Log Analytics workspace for AZFWNetworkRule / AZFWApplicationRule entries 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.