Changing address space in peered Azure Virtual Networks and Hub/Spoke infra
Tags: azure, powershell, iac
Published: 2021-08-23 21:40:09

In most Azure enterprise architecture, you will have a hub/spoke network topology. In short, a virtual network act as a central connection point with on-premises links. Spoke VNETs are peered to the hub to access this point. You can see the reference architecture [here](https://docs.microsoft.com/en-us/azure/architecture/reference-architectures/hybrid-networking/hub-spoke?WT.mc_id=AZ-MVP-5003620). 
But there is a limitation, when it comes to updating the address space in the spoke VNET you need to remove the peering with the hub VNET. In other words, you need to remove the connectivity to the central and it could impair the way your applications work. 

Recently, Microsoft announced a new way to handle this problem, changing address space without the need to remove the [peering](https://azure.microsoft.com/en-us/blog/how-to-resize-azure-virtual-networks-that-are-peered-now-in-preview/?WT.mc_id=AZ-MVP-5003620). 
Let's look at how it works. If you have two VNETs, "vnet-hub", the Hub network, and "vnet-spoke", the spoke network. Both VNETs have only one prefix in their address space. How to update the "vnet-spoke" address space, add a new prefix.

The first thing to do, is to register the provider feature

```powershell
if ((Get-AzProviderFeature -ProviderNamespace Microsoft.Network -ListAvailable | Where-Object featureName -eq "AllowUpdateAddressSpaceInPeeredVnets").RegistrationState -eq "NotRegistered") {
    Register-AzProviderFeature -ProviderNamespace Microsoft.Network -FeatureName AllowUpdateAddressSpaceInPeeredVnets
}
``` 

After that, it is possible to add a new network prefix without needing to delete the peering.

```powershell$newIpRange = "172.24.1.0/24"

$spokeVVnet = Get-AzVirtualNetwork -name "vnet-spoke" -ResourceGroupName "01-Azure-peering"

$spokeVVnet.AddressSpace.AddressPrefixes.Add($newIpRange)

Set-AzVirtualNetwork -VirtualNetwork $spokeVVnet
```
Without the provider feature registration, this command would fail.

But if the command works, it doesn’t mean that the configuration is over. If you look at the peering state by using PowerShell.

```powershell
Get-AzVirtualNetworkPeering -VirtualNetworkName "vnet-spoke" -ResourceGroupName "01-Azure-peering" -Name "link-to-hub"
```
You will see, PeeringSyncLevel: RemoteNotInSync

In the Azure portal, you will see a message like this in the peering settings of the peered VNET.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fmeeyojhf2djns3mf9h1.png)

It’s possible to syn both peering from the portal, but you can also do it with the latest version of Azure PowerShell as a new Cmdlet to manage the sync. The [Sync-AzVirtualNetworkPeering](https://docs.microsoft.com/en-us/powershell/module/az.network/sync-azvirtualnetworkpeering?WT.mc_id=AZ-MVP-5003620&view=azps-6.3.0).

This cmdlet only takes the VNET name, the resource group name, and the peering name

Sync-AzVirtualNetworkPeering -Name "link-to-hub" -VirtualNetworkName "vnet-spoke"  -ResourceGroupName  "01-Azure-peering" 
 Sync-AzVirtualNetworkPeering -Name "link-to-spoke" -VirtualNetworkName "vnet-hub"  -ResourceGroupName  "01-Azure-peering"


During the configuration, adding prefix and the synchronization of the two peerings, the network will still work, and no packet has been lost.


But your internal policy may require all your deployment to be based on infrastructure as code, ARM Template, or Bicep, how to perform the synchronization action in PowerShell. 
There is a repones, using the deploymentScripts resource (Microsoft.Resources/deploymentScripts) in the ARM template (or Bicep).

The first thing to do is to create a User managed identity object and give it a sufficient role to perform the sync operation.

```powershell
$userManagedIdentity = New-AzUserAssignedIdentity -ResourceGroupName "01-Azure-peering" -Name "armdepscript01"

New-AzRoleAssignment -ObjectId $userManagedIdentity.PrincipalId -ResourceGroupName "01-Azure-peering"  -RoleDefinitionName Contributor
```
Note: This is for a demo. In a production situation, you should give a minimal role, like “Network Contributor”. The User-Managed Identity should not be created in the same resource group also.

The script used in the deploymentScripts resource should test if one of the two peerings. The Get-AzVirtualNetworkPeering cmdlet returns several properties, among them the PeeringSyncLevel can be used to determine if the peering needs to be synchronised. If the value is LocalNotInSync or RemoteNotInSync, synchronisation is needed.

```pwershell
 $hubPeeringStatus = Get-AzVirtualNetworkPeering -Name 'link-to-spoke01' -VirtualNetworkName 'vnet-hub01'  -ResourceGroupName  '01-Azure-peering'

$spokePeeringStatus = Get-AzVirtualNetworkPeering -Name 'link-to-hub01' -VirtualNetworkName 'vnet-spoke01'  -ResourceGroupName  '01-Azure-peering'

if (($hubPeeringStatus.PeeringSyncLevel -eq 'LocalNotInSync') -OR ($hubPeeringStatus.PeeringSyncLevel -eq 'RemoteNotInSync')) {
    $resultSpoke = Sync-AzVirtualNetworkPeering -Name 'link-to-spoke01' -VirtualNetworkName 'vnet-hub01'  -ResourceGroupName  '01-Azure-peering' 
}

if (($spokePeeringStatus.PeeringSyncLevel -eq 'LocalNotInSync') -OR ($spokePeeringStatus.PeeringSyncLevel -eq 'RemoteNotInSync')) {
    $resultHub = SSync-AzVirtualNetworkPeering -Name 'link-to-hub01' -VirtualNetworkName 'vnet-spoke01'  -ResourceGroupName  '01-Azure-peering'
}
```
How to write the ARM template. First things you will need to use the latest version of the Microsoft.network API, it should be 2021-02-21. Using an older API version will result in an error when trying to add a prefix to a peered VNET.
The second thing is to carefully select the Azure PowerShell version. Only the 6.3 and the 6.2 versions support the new peering capability, giving the synchronization level of the peering and synchronize the peering. You need to use the 6.3 or the 6.2 version of the PowerShell module. But the deploymentScript resource, based on Azure Container Instance only supports the 6.2, with older Azure PowerShell version will not be able to test the synchronization status and to perform the synchronization.

The complete template is here

```json
{
    "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
    "contentVersion": "1.0.0.0",
    "parameters": {
        "vnetHubName": {
            "defaultValue": "vnet-hub01",
            "type": "String"
        },
        "vnetSpokeName": {
            "defaultValue": "vnet-spoke01",
            "type": "String"
        },
        "peeringToHubName": {
            "defaultValue": "link-to-hub01",
            "type": "String"
        },
        "peeringToSpokeName": {
            "defaultValue": "link-to-spoke01",
            "type": "String"
        },
        "forceExecut": {
            "defaultValue": "[newGuid()]",
            "type": "String"
        }
    },
    "variables": {},
    "resources": [
        {
            "type": "Microsoft.Network/virtualNetworks",
            "apiVersion": "2021-02-01",
            "name": "[parameters('vnetHubName')]",
            "location": "northeurope",
            "dependsOn": [],
            "properties": {
                "addressSpace": {
                    "addressPrefixes": [
                        "10.0.0.0/24"
                    ]
                },
                "enableDdosProtection": false
            }
        },
        {
            "type": "Microsoft.Network/virtualNetworks",
            "apiVersion": "2021-02-01",
            "name": "[parameters('vnetSpokeName')]",
            "location": "northeurope",
            "dependsOn": [],
            "properties": {
                "addressSpace": {
                    "addressPrefixes": [
                        "10.0.1.0/24",
                        "172.24.1.0/24",
                        "172.24.2.0/24",
                        "172.24.3.0/24",
                        "172.24.4.0/24"
                    ]
                },
                "enableDdosProtection": false
            }
        },
        {
            "type": "Microsoft.Network/virtualNetworks/subnets",
            "apiVersion": "2021-02-01",
            "name": "[concat(parameters('vnetHubName'), '/default')]",
            "dependsOn": [
                "[resourceId('Microsoft.Network/virtualNetworks', parameters('vnetHubName'))]"
            ],
            "properties": {
                "addressPrefix": "10.0.0.0/24",
                "delegations": [],
                "privateEndpointNetworkPolicies": "Enabled",
                "privateLinkServiceNetworkPolicies": "Enabled"
            }
        },
        {
            "type": "Microsoft.Network/virtualNetworks/subnets",
            "apiVersion": "2021-02-01",
            "name": "[concat(parameters('vnetSpokeName'), '/default')]",
            "dependsOn": [
                "[resourceId('Microsoft.Network/virtualNetworks', parameters('vnetSpokeName'))]"
            ],
            "properties": {
                "addressPrefix": "10.0.1.0/24",
                "delegations": [],
                "privateEndpointNetworkPolicies": "Enabled",
                "privateLinkServiceNetworkPolicies": "Enabled"
            }
        },
        {
            "type": "Microsoft.Network/virtualNetworks/virtualNetworkPeerings",
            "apiVersion": "2021-02-01",
            "name": "[concat(parameters('vnetSpokeName'), '/' ,parameters('peeringToHubName'))]",
            "dependsOn": [
                "[resourceId('Microsoft.Network/virtualNetworks', parameters('vnetSpokeName'))]",
                "[resourceId('Microsoft.Network/virtualNetworks', parameters('vnetHubName'))]",
                "[resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('vnetHubName'), 'default')]",
                "[resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('vnetSpokeName'), 'default')]"
            ],
            "properties": {
                "peeringState": "Connected",
                "remoteVirtualNetwork": {
                    "id": "[resourceId('Microsoft.Network/virtualNetworks', parameters('vnetHubName'))]"
                },
                "allowVirtualNetworkAccess": true,
                "allowForwardedTraffic": false,
                "allowGatewayTransit": false,
                "useRemoteGateways": false,
                "remoteAddressSpace": {
                    "addressPrefixes": [
                        "10.0.0.0/24"
                    ]
                }
            }
        },
        {
            "type": "Microsoft.Network/virtualNetworks/virtualNetworkPeerings",
            "apiVersion": "2021-02-01",
            "name": "[concat(parameters('vnetHubName'), '/' ,parameters('peeringToSpokeName'))]",
            "dependsOn": [
                "[resourceId('Microsoft.Network/virtualNetworks', parameters('vnetSpokeName'))]",
                "[resourceId('Microsoft.Network/virtualNetworks', parameters('vnetHubName'))]",
                "[resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('vnetHubName'), 'default')]",
                "[resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('vnetSpokeName'), 'default')]"
            ],
            "properties": {
                "peeringState": "Connected",
                "remoteVirtualNetwork": {
                    "id": "[resourceId('Microsoft.Network/virtualNetworks', parameters('vnetSpokeName'))]"
                },
                "allowVirtualNetworkAccess": true,
                "allowForwardedTraffic": false,
                "allowGatewayTransit": false,
                "useRemoteGateways": false,
                "remoteAddressSpace": {
                    "addressPrefixes": [
                        "10.0.1.0/24"
                    ]
                }
            }
        },
        {
            "type": "Microsoft.Resources/deploymentScripts",
            "apiVersion": "2020-10-01",
            "name": "resolvePeeringSync",
            "dependsOn": [
                "[resourceId('Microsoft.Network/virtualNetworks', parameters('vnetSpokeName'))]",
                "[resourceId('Microsoft.Network/virtualNetworks', parameters('vnetHubName'))]",
                "[resourceId('Microsoft.Network/virtualNetworks/virtualNetworkPeerings',parameters('vnetSpokeName'),parameters('peeringToHubName'))]",
                "[resourceId('Microsoft.Network/virtualNetworks/virtualNetworkPeerings',parameters('vnetHubName'), parameters('peeringToSpokeName'))]"
            ],
            "location": "[resourceGroup().location]",
            "kind": "AzurePowerShell", 
            "identity": {
                "type": "userAssigned",
                "userAssignedIdentities": {
                "/subscriptions/087f441f-6200-4226-bac3-22a1dbe98fae/resourceGroups/01-Azure-peering/providers/Microsoft.ManagedIdentity/userAssignedIdentities/armdepscript01": {}
                }  
            },
            "properties": {
                "azPowerShellVersion": "6.2",
                "forceUpdateTag": "[parameters('forceExecut')]",
                "scriptContent": "

$hubPeeringStatus = Get-AzVirtualNetworkPeering -Name 'link-to-spoke01' -VirtualNetworkName 'vnet-hub01'  -ResourceGroupName  '01-Azure-peering'
$spokePeeringStatus = Get-AzVirtualNetworkPeering -Name 'link-to-hub01' -VirtualNetworkName 'vnet-spoke01'  -ResourceGroupName  '01-Azure-peering'

if (($hubPeeringStatus.PeeringSyncLevel -eq 'LocalNotInSync') -OR ($hubPeeringStatus.PeeringSyncLevel -eq 'RemoteNotInSync')) {
    $resultSpoke = Sync-AzVirtualNetworkPeering -Name 'link-to-spoke01' -VirtualNetworkName 'vnet-hub01'  -ResourceGroupName  '01-Azure-peering' 
}
if (($spokePeeringStatus.PeeringSyncLevel -eq 'LocalNotInSync') -OR ($spokePeeringStatus.PeeringSyncLevel -eq 'RemoteNotInSync')) {
    $resultHub = SSync-AzVirtualNetworkPeering -Name 'link-to-hub01' -VirtualNetworkName 'vnet-spoke01'  -ResourceGroupName  '01-Azure-peering'
}
                ",
            "supportingScriptUris":[],
            "timeout": "PT30M",
            "cleanupPreference": "OnExpiration",
            "retentionInterval": "P1D"

            }
        }
    ],
"outputs": {
    }
} 
```
The possibility to update address prefixes in a spoke VNET is mostly one of the most requested features for those who run the Hub and Spoke model in Azure. It should go GA soon.