There is a cool new Azure Networking service that should make locking down access to Platform-as-a-Service (PaaS) services with public and internet-addressable endpoints much easier. It creates a logical isolation boundary for services like Azure Storage, CosmosDB and Azure SQL Database. It might even negate the need for setting up Private Endpoints, which has historically been the approach to lock down such resources and ensure their network isolation.

In this article, I’ll walk you through setting up Azure Network Security Perimeter using Terraform and the AzAPI provider, demonstrating how to define the NSP, create profiles and access rules, and associate PaaS resources efficiently.

Understanding the Service Topology

A Network Security Perimeter consists of several key components:

  • Network Security Perimeter (NSP): The top-level resource that defines the logical network isolation boundary.
  • Profiles: Collections of access rules governing associated resources.
  • Access Rules: Defines inbound and outbound permissions for resources inside the NSP.
  • Resource Associations: Links PaaS resources to the perimeter.
  • Links: Establishes connectivity between perimeters.

Unlike Virtual Networks (VNets), which control access at the subnet level, NSP allows finer-grained control by securing individual PaaS resources directly.

I found the documentation in one of my favorite Azure resources, the Azure Resource Manager REST API Specification and it turns out there are quite a few resources:

  • Microsoft.Network/networkSecurityPerimeters:
  • Microsoft.Network/networkSecurityPerimeters/profiles
  • Microsoft.Network/networkSecurityPerimeters/profiles/accessRules
  • Microsoft.Network/networkSecurityPerimeters/links
  • Microsoft.Network/networkSecurityPerimeters/resourceAssociations

After digging into the documentation, the structure of a Network Security Perimeter looks like this:

Alt

A Network Security Perimeter is not something that you are going to be provisioning for every workload. You’ll likely create one of them per region and then have each workload attach its resources to it.

If you are using a Virtual WAN to connect your workload(s) then there is a natural parallelism in that everwhere you have a Virtual Hub, you should have a Network Security Perimeter.

Then the workloads follow the same pattern they do when attaching their virtual network’s to the relevant Virtual Hub, they just attach individual resources to the corresponding Network Security Perimeter.

Setup with AzAPI Provider

Setting up the Network Security Perimeter & Profiles

First you need the apex resource, the Network Security Perimeter (NSP). This resource type has several sub-resources that will be provisioned with the NSP as their parent. This includes Profiles, Links and Resource Associations.

As I mentioned, this should probably be provisioned with the root module that provisions your core network, whether that’s an Azure V-WAN (as is my preference) or just a Virtual Network.

Documentation:

locals {
  nsp_api_version = "2023-08-01-preview"
}

resource "azapi_resource" "nsp_primary" {
  type      = "Microsoft.Network/networkSecurityPerimeters@${local.nsp_api_version}"
  name      = "nsp-${var.application_name}-${var.environment_name}-${azurerm_resource_group.main.location}"
  parent_id = azurerm_resource_group.main.id
  location  = azurerm_resource_group.main.location

  body = {
  }

  response_export_values = ["*"]
}

Then you need profile(s).

Documentation:

resource "azapi_resource" "nsp_primary_profile" {
  type      = "Microsoft.Network/networkSecurityPerimeters/profiles@${local.nsp_api_version}"
  name      = "nsp-${var.application_name}-${var.environment_name}-${azurerm_resource_group.main.location}"
  parent_id = azapi_resource.nsp_primary.id
  location  = azapi_resource.nsp_primary.location

  body = {
  }

  response_export_values = ["*"]
}

You can attach Access Rules to your profiles.

Documentation:

resource "azapi_resource" "nsp_primary_profil_rule1" {
  type      = "Microsoft.Network/networkSecurityPerimeters/profiles/accessRules@${local.nsp_api_version}"
  name      = "nsp-rule1"
  parent_id = azapi_resource.nsp_primary_profile.id
  location  = azapi_resource.nsp_primary_profile.location

  body = {
    direction       = "Inbound"
    addressPrefixes = ["10.45.0.0/16"]
  }

  response_export_values = ["*"]
}

The direction can be “Inbound” or “Outbound”.

Based on the Azure Portal experience, “Inbound” appears to support only the following:

  • addressPrefixes
  • subscriptions

Alt

Outbound appears to support only the following:

  • fullyQualifiedDomainNames

Alt

Associating Resources

Then you need a Resource Association for each resource. However, this should be done in the root module that provisions your actual workload — not where the NSP is provisioned.

Documentation:

resource "azapi_resource" "cosmosdb_nsp_link" {
  type      = "Microsoft.Network/networkSecurityPerimeters/resourceAssociations@2023-08-01-preview"
  name      = "${azurerm_cosmosdb_account.main.name}-nsp"
  location  = data.azapi_resource.nsp.location
  parent_id = data.azapi_resource.nsp.id
  body = {
    properties = {
      accessMode = "Learning"
      privateLinkResource = {
        id = azurerm_cosmosdb_account.main.id
      }
      profile = {
        id = data.azapi_resource.nsp_profile.id
      }
    }
  }
}

Because I am creating the Resource Association to the Cosmos DB from within a different root module than I am creating the NSP, its profiles and access rules, I need to obtain a reference to the NSP and the NSP Profile I want to attach. That is why I am using Data Sources in the Parent ID and Profile ID properties. With AzAPI this is a bit cumbersome.

# Get the Resource Group ID from a name
data "azurerm_resource_group" "core_network" {
  name = var.nsp.resource_group
}

# Get the NSP
data "azapi_resource" "nsp" {
  name      = var.nsp.name
  parent_id = data.azurerm_resource_group.core_network.id
  type      = "Microsoft.Network/networkSecurityPerimeters@${local.nsp_api_version}"

  response_export_values = ["*"]
}

# Get the NSP Profile
data "azapi_resource" "nsp_profile" {
  name      = var.nsp.profile
  parent_id = data.azapi_resource.nsp.id
  type      = "Microsoft.Network/networkSecurityPerimeters/profiles@${local.nsp_api_version}"

  response_export_values = ["*"]
}

Normally, if azurerm had data sources for these resources it would be quite a bit more concise. This is what the resource will look like in the Azure Portal:

Alt

Potential Terraform Configuration

Ok, so how would I author the azurerm resources to smooth out the edges of this setup? Resources

Network Security Perimeter

resource "azurerm_network_security_perimeter" "main" {
  name                = "nsp-foo"
  location            = "westus"
  resource_group_name = "rg-foo"
}

Network Security Perimeter Profile

resource "azurerm_network_security_perimeter_profile" "profile1" {
  name                = "nsp-foo-profile1"
  location            = "westus"
  resource_group_name = "rg-foo"
  perimeter_id        = azurerm_network_security_perimeter.main.id
}

Network Security Perimeter Access Rule

resource "azurerm_network_security_perimeter_profile_access_rule" "rule1" {
  name                = "nsp-foo-profile1"
  location            = "westus"
  resource_group_name = "rg-foo"
  profile_id          = azurerm_network_security_perimeter_profile.profile1.id
  direction           = "Inbound"
  address_prefixes    = ["10.45.0.0/16"]
}

Data Sources

Network Security Perimeter

data "azurerm_network_security_perimeter" "main" {
  name                = "nsp-foo"
  resource_group_name = "rg-foo"
}

Network Security Perimeter Profile

resource "azurerm_network_security_perimeter_profile" "profile1" {
  perimeter_name      = "nsp-foo"
  name                = "nsp-foo-profile1"
  resource_group_name = "rg-foo"
}

That’s it!

Conclusion

Azure Network Security Perimeter is a powerful new feature that simplifies securing PaaS resources with public endpoints, reducing the need for Private Endpoints.

Using Terraform with the AzAPI provider, we can define the NSP, create profiles, enforce access rules, and associate PaaS resources. While this setup is currently a bit cumbersome due to a lack of azurerm support, once native resources are available in the azurerm provider, Terraform provisioning will become much more streamlined.

What do you think? Do you like how I designed the azurerm schema for the Azure Network Security Perimeter? Let me know! I created an issue for this on GitHub so if you are feeling spritely, go forth and implement the resources and data sources per my spec!

Happy Azure Terraforming!