Azure Firewall rules for Office 365

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:

Created Rule Collection Group

We can check the generated network rules:

Network Rules

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

Application Rules

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!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: