loading...

ARM Templates, using NSG and Application Security Groups in your Azure Deployments

omiossec profile image Olivier Miossec ・4 min read

When you want to use Infrastructure as Code in your projects, you must remember that you need to put all the infrastructure in the code. But often something is missing, especially with IaaS deployment, network security.

In an IaaS environment, you typically have VMs inside multiple subnets. Creating this kind of architecture with ARM Template is straightforward but commonly, one part is neglected, network security with Network Security Groups. There is much reason for that; it is complexed, you may not have the connections matrix, the number of VM, the IP, … So, most of the time you think; Network Security Group will be done later. But Later often the synonym of never.

It is a best practice to deploy Networks Security Groups in the same way you deploy VMs and other services. It must be at the same time. Keeping all things together.

Take this example.

Imagine a situation where you need to implement a template to create two VMs, one front, and one DB server. The two VMs are in separate subnets and you know that you will need to add some restrictions so the front will be only able to connect to the DB on restricted TCP ports.

Even if we do not know yet which port the solution will use, we can start to create the Network Security Group on these subnets.

To avoid using IP addresses in the rules and to be able to group several VM in the same NSG rule, you will need to use Application Security Groups.
Application security groups are a simple way to link VM, in fact, VM's NIC, network configuration to an object you can use in NSG.
You can apply Network Security Groups either to a VM’s virtual NIC or a subnet. I generally prefer to link NSG to a subnet rather than a VM.

For our example we need:

  • One VNET
  • Two subnets; front and backend
  • Two VMs, front01 and db01

The basic infrastructure in ARM look like this:

"resources": [
        {
            "name": "vnet01",
            "type": "Microsoft.Network/virtualNetworks",
            "apiVersion": "2019-11-01",
            "location": "[variables('location')]",
            "properties": {
                "addressSpace": {
                    "addressPrefixes": [
                        "172.24.0.0/24"
                    ]
                },
                "subnets": [
                    {
                        "name": "front",
                        "properties": {
                            "addressPrefix": "172.24.0.0/26"
                        }
                    },
                    {
                        "name": "backend",
                        "properties": {
                            "addressPrefix": "172.24.0.64/26"
                        }
                    }
                ]
            }
        },
        {
            "name": "vmfront01-nic",
            "type": "Microsoft.Network/networkInterfaces",
            "apiVersion": "2019-11-01",
            "location": "[variables('location')]",
            "dependsOn": [

                "[resourceId('Microsoft.Network/virtualNetworks', 'vnet01')]"
            ],
            "properties": {
                "ipConfigurations": [
                    {
                        "name": "ipConfig1",
                        "properties": {
                            "privateIPAllocationMethod": "Dynamic",
                            "subnet": {
                                "id": "[resourceId('Microsoft.Network/virtualNetworks/subnets', 'vnet01', 'front')]"
                            }
                        }
                    }
                ]
            }
        },
        {
            "name": "vmfront01",
            "type": "Microsoft.Compute/virtualMachines",
            "apiVersion": "2019-07-01",
            "location": "[resourceGroup().location]",
            "dependsOn": [
                "[resourceId('Microsoft.Network/networkInterfaces', 'vmfront01-nic')]"
            ],

            "properties": {
                "hardwareProfile": {
                    "vmSize": "Standard_F2s_v2"
                },
                "osProfile": {
                    "computerName": "vmfront01",
                    "adminUsername": "[parameters('adminUsername')]",
                    "adminPassword": "[parameters('adminPassword')]"
                },
                "storageProfile": {
                    "imageReference": {
                        "publisher": "MicrosoftWindowsServer",
                        "offer": "WindowsServer",
                        "sku": "2019-Datacenter-Core",
                        "version": "latest"
                    },
                    "osDisk": {
                        "name": "vmfront01-os.vhd",
                        "caching": "ReadWrite",
                        "createOption": "FromImage"
                    }
                },
                "networkProfile": {
                    "networkInterfaces": [
                        {
                            "id": "[resourceId('Microsoft.Network/networkInterfaces', 'vmfront01-nic')]"
                        }
                    ]
                }
            }
        },
    {
            "name": "vmdb01-nic",
            "type": "Microsoft.Network/networkInterfaces",
            "apiVersion": "2019-11-01",
            "location": "[variables('location')]",
            "dependsOn": [

                "[resourceId('Microsoft.Network/virtualNetworks', 'vnet01')]"
            ],
            "properties": {
                "ipConfigurations": [
                    {
                        "name": "ipConfig1",
                        "properties": {
                            "privateIPAllocationMethod": "Dynamic",
                            "subnet": {
                                "id": "[resourceId('Microsoft.Network/virtualNetworks/subnets', 'vnet01', 'backend')]"
                            }
                        }
                    }
                ]
            }
        },
        {
            "name": "vmdb01",
            "type": "Microsoft.Compute/virtualMachines",
            "apiVersion": "2019-07-01",
            "location": "[resourceGroup().location]",
            "dependsOn": [
                "[resourceId('Microsoft.Network/networkInterfaces', 'vmdb01-nic')]"
            ],

            "properties": {
                "hardwareProfile": {
                    "vmSize": "Standard_F2s_v2"
                },
                "osProfile": {
                    "computerName": "vmdb01",
                    "adminUsername": "[parameters('adminUsername')]",
                    "adminPassword": "[parameters('adminPassword')]"
                },
                "storageProfile": {
                    "imageReference": {
                        "publisher": "MicrosoftWindowsServer",
                        "offer": "WindowsServer",
                        "sku": "2019-Datacenter-Core",
                        "version": "latest"
                    },
                    "osDisk": {
                        "name": "vmdb01-os.vhd",
                        "caching": "ReadWrite",
                        "createOption": "FromImage"
                    }
                },
                "networkProfile": {
                    "networkInterfaces": [
                        {
                            "id": "[resourceId('Microsoft.Network/networkInterfaces', 'vmdb01-nic')]"
                        }
                    ]
                }
            }
        }
    ]

Now you will need to add Application Security Groups, one for front VM and one for DB VM. Application Security Groups in ARM are simple. Just a name and a location (you can also add tags as any other objects).

 {
            "type": "Microsoft.Network/applicationSecurityGroups",
            "apiVersion": "2019-11-01",
            "name": "frontVMs-asg",
            "location": "[variables('location')]",
            "properties": {}
        },
        {
            "type": "Microsoft.Network/applicationSecurityGroups",
            "apiVersion": "2019-11-01",
            "name": "dbVMs-asg",
            "location": "[variables('location')]",
            "properties": {}
        },

Now that you have your Application Security Groups you can associate them to the IP configuration of the VMs.
You need to do that a the virtual NIC level, in the Ip configuration properties by adding a applicationSecurityGroups properties.

"ipConfigurations": [
                    {
                        "name": "ipConfig1",
                        "properties": {
                            "privateIPAllocationMethod": "Dynamic",
                            "subnet": {
                                "id": "[resourceId('Microsoft.Network/virtualNetworks/subnets', 'vnet01', 'front')]"
                            },
                            "applicationSecurityGroups" : [
                                {
                                    "ID": "[resourceId('Microsoft.Network/applicationSecurityGroups', 'frontVMs-asg')]"
                                }
                            ]
                        }
                    }
                ]

The applicationSecurityGroups is an array. This mean you can add more than one ASG in a configuration.
You will need to add a depends on clause to be sure the Application Security Group is created before the virtual NIC.

"dependsOn": [
                "[resourceId('Microsoft.Network/virtualNetworks', 'vnet01')]",
                "[resourceId('Microsoft.Network/applicationSecurityGroups', 'frontVMs-asg')]"
            ],

You can start to build the Networks Security Groups. You want to allow communication to the Front on ports 80 and 443 and you want to limit communication to the DB server only from the Front VMs.

To create a NSG rule using Application Security Groups you should replace sourceAddressPrefixes/destinationAddressPrefixes with sourceApplicationSecurityGroups/destinationApplicationSecurityGroups with the ID of the Application Security Group.

For example, for a rule to allow communication from front VMs to DB VMs on a specific TCP port you can use:

 {
                        "name": "sqlRule",
                        "properties": {
                            "description": "Allow Sql Servers flows",
                            "protocol": "Tcp",
                            "sourcePortRange": "*",
                            "destinationPortRange": "1433",
                            "sourceApplicationSecurityGroups": [
                                {
                                    "ID":  "[resourceId('Microsoft.Network/applicationSecurityGroups', 'frontVMs-asg')]"
                                }
                            ],
                            "destinationApplicationSecurityGroups": [
                                {
                                "ID": "[resourceId('Microsoft.Network/applicationSecurityGroups', 'dbVMs-asg')]"
                                }
                            ],
                            "access": "Allow",
                            "priority": 100,
                            "direction": "Inbound"
                        }
                    }

Of course, you should not forget to add a dependency for the Application Security Group when creating Network Security Groups.

            "name": "backend-nsg",
            "type": "Microsoft.Network/networkSecurityGroups",
            "apiVersion": "2019-11-01",
            "dependsOn": [
                "[resourceId('Microsoft.Network/applicationSecurityGroups', 'dbVMs-asg')]",
                "[resourceId('Microsoft.Network/applicationSecurityGroups', 'frontVMs-asg')]"

            ]

Application Security Groups are simple. It is an object you can use to group several IP configurations from virtual NICs. It makes Network Security Groups simpler to use as you do not need to know the IP of a VM to create a rule.
You can scale your deployments without then needs to update Networks security groups. If you need to add a VM in an existing rule, you just need to add it to the Application Security Group.

It the same way, if you need to add a rule, no need to collect IP information, you just need to add the reference ID of an Application Security Group to create your rules.

The complete JSON file is on Gist

Discussion

pic
Editor guide
Collapse
tshaiman profile image
Tomer Shaiman

Very nice , Is there a reason the 'backend-nsg' isn't applied to the backend subnet and instead the the front-nsg is applied to it ?

Collapse
omiossec profile image
Olivier Miossec Author

yes, a simple error, I will correct ASAP

Collapse
omiossec profile image
Olivier Miossec Author

corrected in the Gist