This post is going to be a bit different than the rest, because I have no complex network designs with many boxes and IP addresses. Instead, I have been confronted with a different challenge: how can you configure Azure Firewall to allow traffic to Office 365 endpoints?
Why would you want to do that? There are different scenarios for this, but the main one I have seen is for users that have their Azure Virtual Desktops on Azure, and Azure Firewall controls the Internet sites that these users can and cannot visit. A similar scenario is when those desktops run on Azure VMware Solution, and the egress traffic to the Internet runs again through Azure Firewall deployed in an Azure VNet.
As usual, if you want the nitty, gritty details, you will find them in a Github repository: https://github.com/erjosito/azure-firewall-rules-scripts.
What Office 365 endpoints?
I am glad you ask! Microsoft documents the Office 365 endpoints in this JSON document, and as you can see if you open that link, they are a collection of IP addresses and/or URLs. For example:
{ "id": 3, "serviceArea": "Exchange", "serviceAreaDisplayName": "Exchange Online", "urls": [ "r1.res.office365.com", "r3.res.office365.com", "r4.res.office365.com" ], "tcpPorts": "80,443", "expressRoute": false, "category": "Default", "required": true },
So the actual question is: Could we create an Azure Firewall Policy that allows traffic to those endpoints? Here we go!
The Script
Of course there is a script. And this time it is Python, and it is cleverly named o365_rules.py (I know, my naming skills are not the greatest). It is actually pretty simple: it will fetch the Office 365 endpoints that we saw earlier in JSON, transform them to a JSON object representing an Azure Firewall Policy, and generate ARM code that you can then import into your resource group. For example:
python3 ./o365_rules.py >o365sample.json
Now you only need to import the generated ARM template (o365sample.json
in the previous command) to Azure. You should know how to do that, I am using Azure CLI in this case:
rg=myrg location=westeurope az group create -n $rg -l $location az deployment group create -n o365$RANDOM -g $rg -o none --template-file ./o365policy.json
What does it do?
Let’s have a look at the ARM policy it generates:
{ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", "contentVersion": "1.0.0.0", "parameters": { }, "variables": { "location": "[resourceGroup().location]" }, "resources": [ { "type": "Microsoft.Network/firewallPolicies", "apiVersion": "2021-02-01", "name": "o365policy", "location": "[variables('location')]", "properties": { "sku": { "tier": "Standard" }, "dnsSettings": { "servers": [], "enableProxy": true }, "threatIntelMode": "Alert" } }, { "type": "Microsoft.Network/firewallPolicies/ruleCollectionGroups", "apiVersion": "2021-02-01", "name": "o365policy/o365", "location": "[variables('location')]", "dependsOn": [ "[resourceId('Microsoft.Network/firewallPolicies', 'o365policy')]" ], "properties": { "priority": 10000, "ruleCollections": [ { "ruleCollectionType": "FirewallPolicyFilterRuleCollection", "name": "o365net", "priority": 10900, "action": { "type": "allow" }, "rules": [ { "ruleType": "NetworkRule", "name": "id2-ExchangeOnline", ...
There is more to it, but the beginning of the file will suffice to understand it: it creates an Azure Firewall Policy (defaults to the name “o365policy” but you can of course change it in the parameters), and a Rule Collection Group inside of that policy (defaults to “o365”). It looks like this in the portal:

We can check the generated network rules:

And of course, the application rules too (the majority of the rules actually):

You might have seen that my Azure Firewall Policy is not called “o365policy”, but “premium01”. Great observation! And a great segway to the next section.
I want it with cream, caramel and two cherries
That was the basic usage, but you probably want to customize a bit. Why? For starters, your Azure Firewall Policy might already exist, and the ARM template we saw earlier would overwrite it. Easy, when you run the script, tell it not to create an Azure Firewall Policy, but only a Rule Collection Group that you can safely import to an existing firewall policy:
python3 ./o365_rules.py --do-not-create-policy >o365rcg.json
I think this is going to be the most usual use case, since most folks out there will have already invested some time in creating a policy.
Ah, you want to create a policy, but a Premium one? And now that we are at that, with a name different than “o365policy”? No problem, I have some parameters for you:
python3 ./o365_rules.py --sku Premium --policy-name MyBrandNewPolicy >myazfwpolicy.json
I am not going to describe all parameters, you can use the --help
command to find them out yourself:
❯ python3 ./o365_rules.py --help usage: o365_rules.py [-h] [--policy-name POLICY_NAME] [--policy-sku POLICY_SKU] [--do-not-create-policy] [--rcg-name RCG_NAME] [--rcg-priority RCG_PRIO] [--format FORMAT] [--ip-version IP_VERSION] [--pretty] [--verbose] Generate an ARM template to create a Rule Collection Group in an Azure policy with rules that allow access to M365 endpoints. optional arguments: -h, --help show this help message and exit --policy-name POLICY_NAME Name for the Azure Firewall Policy. The default is "o365policy" --policy-sku POLICY_SKU SKU for the Azure Firewall Policy. Possible values: Standard, Premium (default: Standard) --do-not-create-policy If specified, do not include ARM code for the policy, only for the rule collection group. Use if the policy already exists. --rcg-name RCG_NAME Name for the Rule Collection Group to create in the Azure Firewall Policy. The default is "o365" --rcg-priority RCG_PRIO Priority for the Rule Collection Group to create in the Azure Firewall Policy. The default is "10000" --format FORMAT Output format. Possible values: json, none --ip-version IP_VERSION IP version of AzFW rules. Possible values: ipv4, ipv6, both. Default: ipv4 --pretty Print JSON in pretty mode (default: False) --verbose Run in verbose mode (default: False)
That’s it!
What we haven’t covered in the article is how to run this script periodically, to cover for changes to the Office 365 endpoints, but there are plenty of articles out there on how to automate stuff on Azure.
Do you see an application for this script in your environment? Thanks for reading!