Azure hub and spoke topology, Peering and ARM Templates
Tags: azure, arm, iac, devops
Published: 2020-01-03 16:20:55

Hub/Spoke network topology is one of the network best practices in Azure. The Hub is a central Vnet connected to an on-premises network via an Express Route circuit, a VPN Gateway. Spokes are VNET peered to the Hub used to run workloads.

The Hub network can run a Firewall or a security Network virtual appliance and it enables connectivity across multiple subscriptions and regions. It controls the traffic between spokes and between spoke and on-premises network.

Using this kind of topology is more secure and flexible. Adding a VNET is simple. You just need to create a Vnet with a non-overlapping prefix and create the peering to the Hub Vnet.  
Creating a Hub and Spoke topology from the Azure portal is easy, but it will not scale well and it's error-prone. How to do it with ARM Templates?

In a template, peering is defined inside a virtual network resource. It needs a name, four boolean values to define the properties of the peering: 

* allowVirtualNetworkAccess, to define if flows from the remote VNET can enter the current VNET
* allowForwardedTraffic, do define if the flow from outside remote VNET (another peered VNET) can enter the VNET.
* allowGatewayTransit, to allow traffic routing to the gateway in the Vnet
* useRemoteGateways, the VNET can use a remote VNET

Last things, the remote Vnet ID and the remote prefix are also needed.  

Finally, to be valid, the peering information configuration must be made in the two Vnet.
Take this example with 2 VNET in the same Azure region.

```json
    "variables": {
        "VnetAObject": {
            "Name": "VnetA",
            "prefix": "192.168.0.0/24",
            "defaultSubnetName": "subnet1",
            "defaultSubnetPrefix": "192.168.0.0/26"
        },
        "VnetBObject": {
            "Name": "VnetB",
            "prefix": "192.168.1.0/24",
            "defaultSubnetName": "subnet1",
            "defaultSubnetPrefix": "192.168.1.0/26"
        }
    },
```

In this example, VNET information are in the "variables" section, it works also as parameters.

But to be possible, the 2 Vnet must already up, if not we will have a sort of chicken and egg problem. If you need to create VNETs and peering in the same template with the default configuration, it won’t work. The first VNET will need the second to create the peering but to be created the second VNET needs the first one. So, it will fail and no peering will be created. 

```json
            {
                "type": "Microsoft.Network/virtualNetworks",
                "name": "[variables('VnetAObject').Name]",
                "apiVersion": "2017-06-01",
                "location":"[resourceGroup().location]",
                "comments": "This will build a Virtual Network.",
                "properties": {
                    "addressSpace": {
                        "addressPrefixes": [
                            "[variables('VnetAObject').prefix]"
                        ]
                    },
                    "subnets": [
                        {
                            "name": "[variables('VnetAObject').defaultSubnetName]",
                            "properties": {
                            "addressPrefix": "[variables('VnetAObject').defaultSubnetPrefix]"
                            }
                        }
                    ],
                    // this will not really work
                    "virtualNetworkPeerings": [
                    {
                    "properties": {
                            "allowVirtualNetworkAccess": true,
                            "allowForwardedTraffic": false,
                            "allowGatewayTransit": false,
                            "useRemoteGateways": false,
                            "remoteVirtualNetwork": {
                            "id": "[resourceId('Microsoft.Network/virtualNetworks', variables('VnetBObject').Name)]"
                        },
                            "remoteAddressSpace": {
                                "addressPrefixes": [
                                    "[variables('VnetBObject').prefix]"
                                ]
                            }
                        },
                        "name": "PeeringToVnetB"
                        }
                    ]
                }
            }
```
How to avoid that? Peering, like subnet, is child resources. These kinds of resources only exist in the context of a parent resource. 

In an ARM Template, a child resource can be built inside the parent resource like in the first example. It can also, be built outside the parent resource. But first, we need to identify the parent resource, the VNET here. As there is no "ParentResource" property ARM needs a way to identify the parent resource. The identification works with the type of the resource which includes the parent type and the child type

```json
"type": "Microsoft.Network/virtualNetworks/virtualNetworkPeerings",
```

And the name of the resource. The name of the resource must include the parent resource name, a slash and the name of the child resource. 

```json
"name": "VnetB/peeringtoA",
```

If we want to deploy VNET and Peering from the same JSON file, one piece is still missing. We need to make sure VNET is deployed before peering. We need to remove peering properties from the VNET definition and create separate peering resources. To make sure VNET exists during the peering deployment, we need to use dependsOn and reference the 2 VNET names.  

```json
                "dependsOn": [
                    "VnetA",
                    "VnetB"
                 ],
```

Here the full example of the 2 peering. 

```json
{
                "name": "[concat(variables('VnetAObject').Name,'/peeringtoB')]",
                "dependsOn": [
                    "[variables('VnetAObject').Name]",
                    "[variables('VnetBObject').Name]"
                 ],
                "type": "Microsoft.Network/virtualNetworks/virtualNetworkPeerings",
                "apiVersion": "2019-09-01",
                "properties": {
                            "allowVirtualNetworkAccess": true,
                            "allowForwardedTraffic": false,
                            "allowGatewayTransit": false,
                            "useRemoteGateways": false,
                            "remoteVirtualNetwork": {
                            "id": "[resourceId('Microsoft.Network/virtualNetworks', variables('VnetBObject').Name)]"
                        },
                        "remoteAddressSpace": {
                            "addressPrefixes": [
                                "[variables('VnetBObject').prefix]"
                            ]
                        }
                    }
            },
            {
                "name": "[concat(variables('VnetBObject').Name,'/peeringtoA')]",
                "dependsOn": [
                    "[variables('VnetAObject').Name]",
                    "[variables('VnetBObject').Name]"
                 ],
                "type": "Microsoft.Network/virtualNetworks/virtualNetworkPeerings",
                "apiVersion": "2019-09-01",
                "properties": {
                            "allowVirtualNetworkAccess": true,
                            "allowForwardedTraffic": false,
                            "allowGatewayTransit": false,
                            "useRemoteGateways": false,
                            "remoteVirtualNetwork": {
                            "id": "[resourceId('Microsoft.Network/virtualNetworks', variables('VnetAObject').Name)]"
                        },
                        "remoteAddressSpace": {
                            "addressPrefixes": [
                                "[variables('VnetAObject').prefix]"
                            ]
                        }
                    }
            }
```

The same principle works with the couple Vnet/Subnet, Vm/VmExtension, VPN/VPN Connections, RouteTables/Routes, and many others. Take a look at the [ARM Template reference documentation](https://docs.microsoft.com/en-us/azure/templates/). You will find all Parent/Child resources.