Friday, July 8, 2022

ARM template to create VNet with multiple subnets, NSGs, and security rules using copy loop

 At current client we have a scenario where a number of spoke landing zones (LZs) have been created which contain, typically, a VNet and multiple subnets. The landing zones are filtered via an Azure Firewall in the Hub, however, there is an additional requirement to deny inter-VNet traffic between subnets using NSG rules (traffic not leaving the VNet and therefore won't reach the firewall).

The ARM templates for the LZs use copy loops to create the subnets and NSGs. What I wanted to add was a specific security rule (deny) to each of the NSGs that only contained the other subnets (so multiple IP address ranges) in the VNet but not the subnet that is associated with that subnet. I was struggling a bit with the syntax (around adding multiple ranges) and got some assistance in the end from Microsoft to get it to work.

I've collected the whole thing in a template and uploaded to Github (files: VNet-with-3xSubnet-3xNsgs-3xRules-copyloop.json and VNet-with-3xSubnet-3xNsgs-3xRules-copyloop.parameters.json). See this MS article for more info on copy loops.

It creates a VNet with three subnets using a copy loop. It then creates three NSGs, one for each subnet, and two security rules for each NSG. The first rule is static/identical to all NSGs and the second rule varies according to content of the nsgPrefixes array. The second rule relies on an array within an array that adds the IP ranges of the two other subnets to a deny rule. 

I'll paste the content of the ARM template below, but formatting is not great, so suggest getting the files from Github linked above:

{
    "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
    "contentVersion": "1.0.0.0",
    "parameters": {
        "vnetName": {
            "type": "String"
        },
        "location": {
            "type": "string"
        },
        "addressPrefixes": {
            "type": "string"
        },
        "snetAddressPrefixes": {
            "type": "array"
        },
        "snetNames": {
            "type": "array"
        },
        "nsgNames": {
            "type": "array"
        },
        "nsgPrefixes": {
            "type": "array",
            "metadata": {
                "description": "description"
            }
        }
    },    
    "variables": {},
    "resources": [
        {
            "type": "Microsoft.Network/virtualNetworks",
            "apiVersion": "2020-11-01",
            "name": "[parameters('vnetName')]",
            "location": "[parameters('location')]",
            "properties": {
                "addressSpace": {
                    "addressPrefixes": [
                        "[parameters('addressPrefixes')]"
                    ]
                },
                "copy": [
                    {
                        "name": "subnets",
                        "dependsOn": [
                            "[resourceId('Microsoft.Network/networkSecurityGroups/', parameters('nsgNames')[copyIndex('subnets')])]"
                        ],
                        "count": "[length(parameters('snetNames'))]",
                        "input": {
                        "name": "[concat(parameters('snetNames')[copyIndex('subnets')])]",
                        "properties": {
                            "addressPrefix": "[parameters('snetAddressPrefixes')[copyIndex('subnets')]]",
                            "networkSecurityGroup": {
                                "id": "[resourceId('Microsoft.Network/networkSecurityGroups/', parameters('nsgNames')[copyIndex('subnets')])]"
                                }
                            }
                        }
                    }
                   
                ]
           
            }        
               
        },
        {
            "type": "Microsoft.Network/networkSecurityGroups",
            "apiVersion": "2021-08-01",
            "name": "[concat(parameters('nsgNames')[copyIndex()])]",
            "location": "[parameters('location')]",
            "properties": {
                "securityRules": [
                    {
                        "name": "Port_3389",
                        "properties": {
                            "protocol": "*",
                            "sourcePortRange": "*",
                            "destinationPortRange": "3389",
                            "sourceAddressPrefix": "10.XXX.XXX.XXX",
                            "destinationAddressPrefix": "*",
                            "access": "Allow",
                            "priority": 100,
                            "direction": "Inbound",
                            "sourcePortRanges": [],
                            "destinationPortRanges": [],
                            "sourceAddressPrefixes": [],
                            "destinationAddressPrefixes": []
                        }
                    },
                    {
                        "name": "DenyInternalSubnetInbound",
                        "properties": {
                            "protocol": "*",
                            "sourcePortRange": "*",
                            "destinationPortRange": "*",
                            "destinationAddressPrefix": "*",
                            "access": "Deny",
                            "priority": 4096,
                            "direction": "Inbound",
                            "sourcePortRanges": [],
                            "destinationPortRanges": [],
                            "sourceAddressPrefixes":
                                "[concat(parameters('nsgPrefixes')[copyIndex()])]",
                            "destinationAddressPrefixes": []
                        }
                    }

                ]
            },
            "copy": {
            "name": "NSGcopy",
            "count": "[length(parameters('nsgNames'))]"
            }
        }    
    ]
       
}

-------

Parameters file:

-------

{
    "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#",
    "contentVersion": "1.0.0.0",
    "parameters": {
        "vnetName": {
            "value": "vnet-conn-weu-001"
        },
        "location": {
            "value": "westeurope"
        },
        "addressPrefixes": {
            "value": "10.100.0.0/18"
        },
        "snetAddressPrefixes": {
            "value": [
                "10.100.0.0/24",
                "10.100.1.0/24",
                "10.100.2.0/24"
            ]
        },
        "snetNames": {
            "value": [
                "snet-conn-weu-001",
                "snet-conn-weu-002",
                "snet-conn-weu-003"
            ]
        },
        "nsgNames": {
            "value": [
                "nsg-snet-conn-weu-001",
                "nsg-snet-conn-weu-002",
                "nsg-snet-conn-weu-003"
            ]
        },
        "nsgPrefixes": {
            "value": [
                ["10.100.1.0/24", "10.100.2.0/24"],              
                ["10.100.0.0/24", "10.100.2.0/24"],
                ["10.100.0.0/24", "10.100.1.0/24"]
            ]
        }
    }    
}

No comments:

Post a Comment

Note: Only a member of this blog may post a comment.