Using Network Security Groups (NSG) to secure network access to an environment


Quite a few demos (including mines) ommit security for the sake of simplicity.  One area where you can secure your applications in Azure is in terms of Networking.

Network Security Groups act as a firewall in the cloud.

In this post, I’ll show you how to create a virtual network with 3 subnets:  front-end, middle & back-end.  We’ll then secure network access to those subnets with the following rules:

  1. Front-end can only be accessed on port 80 by anything from the internet
  2. Front-end can only access the virtual network (not the internet)
  3. Middle can only be accessed by the front-end on port 80
  4. Middle can only access the virtual network
  5. Back-end can only be accessed by the middle on port 1433 (SQL default port)
  6. Back-end can’t access anything
  7. Azure Health Monitoring can access everyone (if we don’t allow that, every VMs within the subnet will be marked as unhealthy and be taken down)

This is a typical firewall configuration for a 3 tier application:

image

We’ll create it in the portal to get the feel of each feature.  At the end, I give you the Azure Resource Manager (ARM) template to generate it at the end.

So all of this is using Vnet v2 with ARM.

Resource Group

First, we’ll create a new resource group which will contain every artifact.

In the new portal, i.e. http://portal.azure.com/, you can select Resource groups on the left menu.  This will open a blade with all your resource groups listed.  Click the Add  button at the top.

For a resource group name, type whatever you want.  I suggest NSG.

Put it in a the region closest to your home.  E.g. East-US.

Hit create, it should take under a minute to create.

Having a resource group is mandatory for creating artefacts in Azure.  Having one for a little POC like this is very useful as you’ll be able to delete the resource group and every artefacts associated to it will be deleted at the same time.

Resource groups are also useful to associate RBAC rules, e.g. given access to a co-worker to a resource group.

Virtual Network

Let’s go ahead and create a Virtual Network inside the resource group we just created.

Open the resource group you just created, hit the Add  button then, in the filter text box, type network and hit enter.

Select Virtual Network (Microsoft as Publisher).

At the bottom of the blade, select “Resource Manager” as the deployment model, then hit create.

image

For the name, type “Poc-Net”.

In address space, type “10.0.0.0/24”.  This is using the Classless Inter-Domain Routing (CIDR, pronounced cider).  This means your virtual network starts at internal address 10.0.0.0 and spans 32-24 (i.e. 8) bits of address space, i.e. 256 addresses (28 = 256).

Leave the subnet name as “default”.  This is utterly useless and the first thing we’ll do is delete it.  Same thing for subnet address range.

Leave your subscription there.

Select the resource group we have created.

Ensure the location is the same as resource group and hit create.

Subnets

Create the virtual net (vnet) takes a little more time than a resource group.

Once it’s created, open it.  Go to the subnets settings.

image

As mention, first thing first:  delete the default subnet.  Select it then delete it.

Let’s create our three subnets:

Name Address Range
Front-end 10.0.0.0/28
Middle 10.0.0.16/28
Back-end 10.0.0.32/28

Those three subnets each span a range of 32-28 (4) bits addresses, i.e. 24 (16) addresses.

I’m a bit cheap on addresses…  If you need to put more than 16 VMs in each subnet, feel free to grow the address space.

You will have noted that there was the possibility to add Network Security Groups, but since we haven’t created them yet, let’s leave it to None for each subnet.

Network Security Groups

Finally, our NSGs!

An NSG in Azure is a stand alone artefact that you associate to subnets or VMs.  You can reuse them on multiple subnets or VMs, although we won’t do that here.

Let’s create our three NSGs, one for each subnet.

In the portal main menu, to the left, click the New button.  Type “Network Security Group” in the filter box, hit enter.  Select Network Security Group (published by Microsoft).  Read and understand every word of the legal statement.  Come on, do it!  No, just kidding.  Click Create.

Give it the name frontEndSecurityGroup.  Make sure you put it in the same resource group.  Hit Create.

Select Inbound security rules.

Inbound rules are the rules to apply to the traffic coming in a subnet or VM.  For the front end we want to allow 2 things:  Http-80 and Azure Health Monitoring.

Let’s add an inbound rule:

  • Name:  Allow-HTTP
  • Priority:  100
  • Source:  Tag
  • Source Tag:  Internet
  • Protocol:  TCP
  • Source port range:  *
  • Destination:  Any
  • Destination port range:  80
  • Action:  Allow

This is our first rule and it allows all traffic coming from the internet through port 80 using the TCP protocol.

Let’s create another one:

  • Name:  Allow-Health-Monitoring
  • Priority:  200
  • Source:  Tag
  • Source Tag:  AzureLoadBalancer
  • Protocol:  Any
  • Source port range:  *
  • Destination:  Any
  • Destination port range:  *
  • Action:  Allow

As mentionned, this is to allow Azure Health monitoring to monitor your VMs.

What is the priority?  Rules are apply by priority order until one actually makes sense:  is coming from the defined source & going to the defined destination using the defined protocol.  First rules that makes sense stops the evaluation process.  So if rule 1 makes sense, rule 2 won’t be applied.

Now, for the front end, we allowed everything we wanted to allow.  Let’s disallow everything else as our third rule:

  • Name:  Disallow-everything-else
  • Priority:  300
  • Source:  Any
  • Protocol:  Any
  • Source port range:  *
  • Destination:  Any
  • Destination port range:  *
  • Action:  Deny

This rules will make sure, for instance, that traffic coming from the virtual network can’t get in the front-end subnet (even on port 80).  That is because the first rule only allows traffic coming from the internet.

You should have the following inbound rules:

Priority Name Source Destination Service Action
100 Allow-HTTP Internet Any TCP/80 Allow
200 Allow-Health-Monitoring AzureLoadBalancer Any Any/Any Allow
300 Disallow-everything-else Any Any Any/Any Deny

For the outbound rule of the front-end, we only allow the front-end to speak to the middle.  We could state it explicitely like this but we’ll enforce that in the middle only.  For the front, we’ll let communication go “anywhere” in the vnet only.  So we’ll have the following outbound rules:

Priority Name Source Destination Service Action
100 Allow-to-VNet Any VirtualNetwork Any/Any Allow
200 Deny-All-Traffic Any Any Any/Any Deny

We will then create a new Network Security Group named middleSecurityGroup.  We’ll define the following inbound rules:

Priority Name Source Destination Service Action
100 Allow-Front 10.0.0.0/28 Any TCP/80 Allow
200 Allow-Health-Monitoring AzureLoadBalancer Any Any/Any Allow
300 Deny-All-Traffic Any Any Any/Any Deny

In the first rule we allow traffic coming from a CIDR block corresponding to the front-end subnet, hence only the front end can use this rule.

We then define the following outbound rules:

Priority Name Source Destination Service Action
100 Allow-to-VNet Any VirtualNetwork Any/Any Allow
200 Deny-All-Traffic Any Any Any/Any Deny

Finally, we’ll create a new Network Security Group named backSecurityGroup.  We’ll define the following inbound rules:

Priority Name Source Destination Service Action
100 Allow-Middle 10.0.0.16/28 Any TCP/1433 Allow
200 Allow-Health-Monitoring AzureLoadBalancer Any Any/Any Allow
300 Deny-All-Traffic Any Any Any/Any Deny

and the following outbound rules:

Priority Name Source Destination Service Action
100 Deny-All-Traffic Any Any Any/Any Deny

Attaching NSG to Subnets

Now that we have our virtual network, subnets & NSGs, we need to associate an NSG to each subnet.

Open the virtual network, select its subnets.  Select front-end subnet and then select Network Security Group (should be at None).  Select frontSecurityGroup and save.

Do the same thing with the other two subnets.

As mentionned, NSG can be associated with more than one subnet / VM and that is why they exist on their own.

There we go.  We have all our rules for a standard three tier architecture network.

If you want to test it, simply put VMs in each subnet and test the connectivity.

…  if you want to this, actually, you’ll need to add rules for allowing inbound TCP:3389 connections for letting your RDP coming in.

ARM Template

As promise, here is the ARM template to recreate that.

My best resources for ARM template are:

The last item is a feature of the new portal.  To access it, go on the main left menu & Browse.  Type Resource in the filter and select Resource Explorer.  You’ll be able to browser through your resources and see their ARM template definition.  This saves heaps of time.  Be mindful though:  resource explorer will show a couple of attributes you shouldn’t include in your template such as status, id, guids, etc.  .

I’ve used few tricks in this template:

  • I used variables to define the CIDR of the subnets ; this avoids duplication of the information
  • I used the resource group location to dictate the location of all the other resources ; this avoids defining an ultimately redundant parameter
  • I used dependsOn & resourceId to attach the Network Security Groups to the subnets.
{
  "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
  },
  "variables": {
    "cidrNet": "10.0.0.0/24",
    "frontNet": "10.0.0.0/28",
    "middleNet": "10.0.0.16/28",
    "backNet": "10.0.0.32/28"
  },
  "resources": [
    {
      "apiVersion": "2015-06-15",
      "name": "Poc-Net",
      "type": "Microsoft.Network/virtualNetworks",
      "location": "[resourceGroup().location]",
      "dependsOn": [
        "Microsoft.Network/networkSecurityGroups/frontSecurityGroup",
        "Microsoft.Network/networkSecurityGroups/middleSecurityGroup",
        "Microsoft.Network/networkSecurityGroups/backSecurityGroup"
      ],
      "tags": { },
      "properties": {
        "addressSpace": {
          "addressPrefixes": [
            "[variables('cidrNet')]"
          ]
        },
        "subnets": [
          {
            "name": "front-end",
            "properties": {
              "addressPrefix": "[variables('frontNet')]",
              "networkSecurityGroup": {
                "id": "[resourceId('Microsoft.Network/networkSecurityGroups','frontSecurityGroup')]"
              }
            }
          },
          {
            "name": "middle",
            "properties": {
              "addressPrefix": "[variables('middleNet')]",
              "networkSecurityGroup": {
                "id": "[resourceId('Microsoft.Network/networkSecurityGroups','middleSecurityGroup')]"
              }
            }
          },
          {
            "name": "back-end",
            "properties": {
              "addressPrefix": "[variables('backNet')]",
              "networkSecurityGroup": {
                "id": "[resourceId('Microsoft.Network/networkSecurityGroups','backSecurityGroup')]"
              }
            }
          }
        ]
      }
    },
    {
      "apiVersion": "2015-06-15",
      "name": "frontSecurityGroup",
      "type": "Microsoft.Network/networkSecurityGroups",
      "location": "[resourceGroup().location]",
      "tags": { },
      "properties": {
        "securityRules": [
          {
            "name": "Allow-HTTP",
            "properties": {
              "protocol": "Tcp",
              "sourcePortRange": "*",
              "destinationPortRange": "80",
              "sourceAddressPrefix": "Internet",
              "destinationAddressPrefix": "*",
              "access": "Allow",
              "priority": 100,
              "direction": "Inbound"
            }
          },
          //{
          //  "name": "Allow-RDP",
          //  "properties": {
          //    "protocol": "Tcp",
          //    "sourcePortRange": "*",
          //    "destinationPortRange": "3389",
          //    "sourceAddressPrefix": "*",
          //    "destinationAddressPrefix": "*",
          //    "access": "Allow",
          //    "priority": 150,
          //    "direction": "Inbound"
          //  }
          //},
          {
            "name": "Allow-Health-Monitoring",
            "properties": {
              "protocol": "*",
              "sourcePortRange": "*",
              "destinationPortRange": "*",
              "sourceAddressPrefix": "AzureLoadBalancer",
              "destinationAddressPrefix": "*",
              "access": "Allow",
              "priority": 200,
              "direction": "Inbound"
            }
          },
          {
            "name": "Disallow-everything-else",
            "properties": {
              "protocol": "*",
              "sourcePortRange": "*",
              "destinationPortRange": "*",
              "sourceAddressPrefix": "*",
              "destinationAddressPrefix": "*",
              "access": "Deny",
              "priority": 300,
              "direction": "Inbound"
            }
          },
          {
            "name": "Allow-to-VNet",
            "properties": {
              "protocol": "*",
              "sourcePortRange": "*",
              "destinationPortRange": "*",
              "sourceAddressPrefix": "*",
              "destinationAddressPrefix": "VirtualNetwork",
              "access": "Allow",
              "priority": 100,
              "direction": "Outbound"
            }
          },
          {
            "name": "Deny-All-Traffic",
            "properties": {
              "protocol": "*",
              "sourcePortRange": "*",
              "destinationPortRange": "*",
              "sourceAddressPrefix": "*",
              "destinationAddressPrefix": "*",
              "access": "Deny",
              "priority": 200,
              "direction": "Outbound"
            }
          }
        ],
        "subnets": [ ]
      }
    },
    {
      "apiVersion": "2015-06-15",
      "name": "middleSecurityGroup",
      "type": "Microsoft.Network/networkSecurityGroups",
      "location": "[resourceGroup().location]",
      "tags": { },
      "properties": {
        "provisioningState": "Succeeded",
        "securityRules": [
          {
            "name": "Allow-Front",
            "properties": {
              "provisioningState": "Succeeded",
              "protocol": "Tcp",
              "sourcePortRange": "*",
              "destinationPortRange": "80",
              "sourceAddressPrefix": "[variables('frontNet')]",
              "destinationAddressPrefix": "*",
              "access": "Allow",
              "priority": 100,
              "direction": "Inbound"
            }
          },
          //{
          //  "name": "Allow-RDP",
          //  "properties": {
          //    "protocol": "Tcp",
          //    "sourcePortRange": "*",
          //    "destinationPortRange": "3389",
          //    "sourceAddressPrefix": "*",
          //    "destinationAddressPrefix": "*",
          //    "access": "Allow",
          //    "priority": 150,
          //    "direction": "Inbound"
          //  }
          //},
          {
            "name": "Allow-Health-Monitoring",
            "properties": {
              "provisioningState": "Succeeded",
              "protocol": "*",
              "sourcePortRange": "*",
              "destinationPortRange": "80",
              "sourceAddressPrefix": "AzureLoadBalancer",
              "destinationAddressPrefix": "*",
              "access": "Allow",
              "priority": 200,
              "direction": "Inbound"
            }
          },
          {
            "name": "Deny-Everything-Else",
            "properties": {
              "provisioningState": "Succeeded",
              "protocol": "*",
              "sourcePortRange": "*",
              "destinationPortRange": "80",
              "sourceAddressPrefix": "*",
              "destinationAddressPrefix": "*",
              "access": "Deny",
              "priority": 300,
              "direction": "Inbound"
            }
          },
          {
            "name": "Allow-to-VNet",
            "properties": {
              "provisioningState": "Succeeded",
              "protocol": "*",
              "sourcePortRange": "*",
              "destinationPortRange": "80",
              "sourceAddressPrefix": "*",
              "destinationAddressPrefix": "VirtualNetwork",
              "access": "Allow",
              "priority": 100,
              "direction": "Outbound"
            }
          },
          {
            "name": "Deny-All-Traffic",
            "properties": {
              "provisioningState": "Succeeded",
              "protocol": "*",
              "sourcePortRange": "*",
              "destinationPortRange": "80",
              "sourceAddressPrefix": "*",
              "destinationAddressPrefix": "*",
              "access": "Deny",
              "priority": 200,
              "direction": "Outbound"
            }
          }
        ],
        "subnets": [ ]
      }
    },
    {
      "apiVersion": "2015-06-15",
      "name": "backSecurityGroup",
      "type": "Microsoft.Network/networkSecurityGroups",
      "location": "[resourceGroup().location]",
      "tags": { },
      "properties": {
        "securityRules": [
          {
            "name": "Allow-Middle",
            "properties": {
              "provisioningState": "Succeeded",
              "protocol": "Tcp",
              "sourcePortRange": "*",
              "destinationPortRange": "1433",
              "sourceAddressPrefix": "[variables('middleNet')]",
              "destinationAddressPrefix": "*",
              "access": "Allow",
              "priority": 100,
              "direction": "Inbound"
            }
          },
          //{
          //  "name": "Allow-RDP",
          //  "properties": {
          //    "protocol": "Tcp",
          //    "sourcePortRange": "*",
          //    "destinationPortRange": "3389",
          //    "sourceAddressPrefix": "*",
          //    "destinationAddressPrefix": "*",
          //    "access": "Allow",
          //    "priority": 150,
          //    "direction": "Inbound"
          //  }
          //},
          {
            "name": "Allow-Health-Monitoring",
            "properties": {
              "provisioningState": "Succeeded",
              "protocol": "*",
              "sourcePortRange": "*",
              "destinationPortRange": "80",
              "sourceAddressPrefix": "AzureLoadBalancer",
              "destinationAddressPrefix": "*",
              "access": "Allow",
              "priority": 200,
              "direction": "Inbound"
            }
          },
          {
            "name": "Deny-Everything-Else",
            "properties": {
              "provisioningState": "Succeeded",
              "protocol": "*",
              "sourcePortRange": "*",
              "destinationPortRange": "80",
              "sourceAddressPrefix": "*",
              "destinationAddressPrefix": "*",
              "access": "Deny",
              "priority": 300,
              "direction": "Inbound"
            }
          },
          {
            "name": "Deny-All-Traffic",
            "properties": {
              "provisioningState": "Succeeded",
              "protocol": "*",
              "sourcePortRange": "*",
              "destinationPortRange": "80",
              "sourceAddressPrefix": "*",
              "destinationAddressPrefix": "*",
              "access": "Deny",
              "priority": 100,
              "direction": "Outbound"
            }
          }
        ],
        "subnets": [ ]
      }
    }
  ],
  "outputs": { }
}

Conclusion

You can see that you can “bring your own network” to Azure and define rules that mimic the rules of your on-premise firewall.

This adds a level of security and reliability.  Even if you use authentication for all your services on your middle tier, for instance, it is more secure to simply disallow traffic from the internet to be routed there.  Not only that, but if some attacker tries to breach in, he simply won’t get to your machine and his / her attacks won’t affect the performance of your VMs.  Hence your service will be more reliable.

Lots of variations are possible.  A popular one is to lock down the front end to an IP range to let only people from certain offices access them.

This was a quite standard network.  I chose that configuration since it is quite common but I’m sure you can see how you can start from that and customize it to fit your own requirements.

2 thoughts on “Using Network Security Groups (NSG) to secure network access to an environment

  1. Pingback: Network Access Control on an HDInsight Cluster | Vincent-Philippe Lauzon's blog

  2. Pingback: Troubleshooting NSGs using Diagnostic Logs | Vincent-Philippe Lauzon's blog

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 )

Google+ photo

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

Connecting to %s