How To – Manage an NSG Using Bicep and Azure DevOps

In many Azure environments that rely on Virtual Networks, Network Security Groups (NSGs) are still king when it comes to access control. However, this post isn’t going to get into the pros and cons of that approach, that’s possibly an entire series, never mind another post!

This post is simply showing a method for managing your NSGs and rules as IaC. Using Bicep, Github and an Azure DevOps Pipeline.

Now, for anyone new to IaC, there is a learning curve. There is also a tradeoff when it comes to effort. It can often be quicker to deploy resources via portal driven methods. However, there are two fundamental reasons to deploy and manage a resource such as an NSG via IaC.

  • BCP – think version history, backup, DR deployments
  • Security – Managed releases/commits control who can approve and change your NSGs

This post keeps it simple, but the principles can expand to managing a full set of NSGs as required. It also includes deploying an empty NSG, which you wouldn’t in theory need either.

As this uses Bicep, let’s take a look at how it handles NSGs and their rules. The NSG itself is quite a simple resource, in my example I am creating it without any config, similar to when you create one in the portal, no rules included.

resource nsg 'Microsoft.Network/networkSecurityGroups@2021-05-01' = {
  name: nsgName
  location: location
  tags: {
    CreatedOn: date
    Owner: email
  }
  properties: {}   
}

However, you can include security rules here as part of the properties section, details on that here. I have chosen not to, as I would like to manage the rules as separate Bicep files. As a result, I need to understand how Parent and Child resources work in Bicep. With this in mind, I have split my rules into two files, inbound and outbound, as this is the core logic split for ACLs on NSGs. You could do this other ways, an allow file, a deny file etc. but this is the one that works best for my brain 🙂

In terms of the file itself, it becomes a collection of bicep resources, each being a rule itself. This gives you full and immediate granularity. I reference the rule priority in my symbolic name to allow for an order of declaration that makes sense to me, but again, lots of options here and no wrong decision. The below are example from my inbound file. I have declared the direction as a variable, as that will always be the same in this file.

resource nsgRule4000 'Microsoft.Network/networkSecurityGroups/securityRules@2021-05-01' = {
  name: '${nsgName}/IN_VNET_Deny'
  properties: {
    access: 'Deny'
    description: 'Deny default VNET traffic'
    destinationAddressPrefix: 'VirtualNetwork'
    destinationPortRange: '*'
    direction: dir
    priority: 4000
    protocol: '*'
    sourceAddressPrefix: '*'
    sourcePortRange: '*'
  }
}

resource nsgRule3000 'Microsoft.Network/networkSecurityGroups/securityRules@2021-05-01' = {
  name: '${nsgName}/IN_Ping_ALLOW'
  properties: {
    access: 'Allow'
    description: 'Allow PING from VNET'
    destinationAddressPrefix: 'VirtualNetwork'
    destinationPortRange: '*'
    direction: dir
    priority: 3000
    protocol: 'Icmp'
    sourceAddressPrefix: 'VirtualNetwork'
    sourcePortRange: '*'
  }
}

Once your files, structure, and rules are created; congratulations! You now have one of the more cumbersome resources for management addressed as code. Giving you quick RTO should it be needed, human readable documentation of your NSG rules, and version history.

How you then control who can edit rules/files by using releases or pull requests etc is up to you and your workflow. But think about including logic to require approvals or at least reviews.

The files I have used as examples are here, again they keep things very simple so if there are questions, get in touch!