If the first post in this series Why Azure Virtual Network Manager is Your New Best Mate was about why you need Azure Virtual Network Manager (AVNM), this one’s about how to build it right — with the proper blocks, scopes, and rules.
Think of AVNM like a grown-up box of Lego Technic: it’s got power, flexibility, and some very pointy edges if you don’t assemble it properly. In this post, we’ll break down the core constructs and the way they interconnect, so your network policies don’t come crashing down like a poorly clipped wing on a cargo chopper.
What’s in the Box?
There are three main building blocks in any AVNM deployment:
- Network Groups: Collections of VNets that act as the target for your configuration (static or dynamic membership)
- Configurations: The actual rules and topology logic you want to apply (Connectivity or Security Admin)
- Deployments: How and where those configurations are applied (per region)
These parts live under your Network Manager resource, which serves as the orchestrator for network state across regions and environments.
AVNM Components Overview
graph TD
AVNM[Azure Virtual Network Manager] --> NG[Network Groups]
AVNM --> CC[Connectivity Configs]
AVNM --> SC[Security Admin Configs]
CC -->|Assigns to| NG
SC -->|Assigns to| NG
NG -->|Includes| VNet1[VNet A]
NG -->|Includes| VNet2[VNet B]
Deployment[Region Deployment] --> CC
Deployment --> SC
1. Network Groups
Network Groups define who gets what. They’re the target for both connectivity and security configurations.
You can build groups using:
- Static members – you manually assign VNets
- Dynamic members – matched automatically via tag-based rules (using Azure Policy behind the scenes)
Dynamic grouping is a real time-saver for scale — spinning up a new VNet with a tag: environment=production
? AVNM will assign it into the correct Network Group automatically.
2. Configurations: Connectivity vs Security Admin
AVNM supports two config types, and they serve different missions.
Connectivity Configs
Use them to automate peering and route control:
- Topologies: Mesh or Hub-and-Spoke
- Scope: Can connect VNets within and across regions
- Gateways: Optionally share hub gateway with peers
Think of it as defining the transport layer of your network — who can see whom, and how.
Security Admin Configs
Think higher-level traffic enforcement:
- Define global allow/deny rules
- Evaluate before NSG rules
- Override inconsistent or missing NSGs without reconfiguring every VNet
They’re ideal for enforcing org-wide traffic standards. (e.g. force-deny egress to public IPs in dev, or allow DNS to Azure resolver in prod).
3. Deployments and Region Scope
Here’s a crucial AVNM detail: configs don’t apply until you explicitly deploy them into regions.
So even if you build your perfect topology or rule set:
- Nothing happens until you deploy that config
- Each config needs to be scoped to a region + network group
This means:
- You’ll have multiple deployments per config for multi-region rollouts
- Each regional deployment independently enforces the configuration
It’s a powerful model, but easy to miss during initial rollout.
AVNM & NSGs
AVNM’s Security Admin Rules work alongside, not instead of, your NSGs.
Here’s how the rule chain flows:
- AVNM’s rules are enforced first
- If AVNM allows traffic, it can still be denied by NSG
- If AVNM denies traffic, NSG rules are irrelevant the packet’s already been blocked
Use AVNM for broad-brush, org-level control. Use NSGs for workload-specific filtering.
And no — AVNM doesn’t delete or override existing NSGs or peerings. It just enforces a central layer you can trust.
Role-Based Access: Who Gets To Touch What?
AVNM impacts infrastructure and security, so set your roles carefully:
- Contributor on AVNM resource: Can deploy configs, change topology
- Network Contributor: Needed for managing peerings and VNet joins
- Policy Contributor: Useful for managing tag-based dynamic groups
Separate concerns where possible. For example: let infra teams manage topology configs, while security teams handle rule definitions in their own admin config sets.
Bicep Resource Structure
Here’s what a real-world AVNM deployment might look like, using dynamic network groups, a mesh topology, and a simple security rule.
In the below example we are deploying a Virtual Network Manager with 3 virtual networks in a mesh configuration set using Azure Policy to find Virtual Networks with the Tag environment=production to automatically added to the network group. Then creating a User Assigned Identity and a deployment script to deploy the Connectivity & Security Configurations
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
|
@description('Location for all resources')
param location string = resourceGroup().location
@description('Name of the Network Manager')
param networkManagerName string = 'myNetworkManager'
// Number of spoke VNets to create
var spokeVnetCount = 3
// Address prefixes for each VNet
var spokeVnetPrefixes = [
'10.0.0.0/16'
'10.1.0.0/16'
'10.2.0.0/16'
]
// Create Spoke VNets using a numbered loop
resource spokeVnets 'Microsoft.Network/virtualNetworks@2024-07-01' = [for i in range(0, spokeVnetCount): {
name: 'MySpokeVNet${padLeft(string(i), 2, '0')}'
location: location
properties: {
addressSpace: {
addressPrefixes: [
spokeVnetPrefixes[i]
]
}
}
tags: {
environment: 'production'
}
}]
// Create the Azure Virtual Network Manager (AVNM)
resource avnm 'Microsoft.Network/networkManagers@2024-07-01' = {
name: networkManagerName
location: location
properties: {
networkManagerScopeAccesses: ['Connectivity', 'SecurityAdmin']
networkManagerScopes: {
subscriptions: [subscription().id]
}
}
}
// Dynamic Network Group
resource prodGroup 'Microsoft.Network/networkManagers/networkGroups@2024-07-01' = {
parent: avnm
name: 'prod-vnets'
properties: {
description: 'Production VNets tagged environment=production'
dynamicMembershipDefinition: {
criteria: [
{
memberType: 'Microsoft.Network/virtualNetworks'
field: 'tag.environment'
operator: 'Equals'
value: 'production'
}
]
}
}
}
// Mesh Topology Config
resource connectivityConfig 'Microsoft.Network/networkManagers/connectivityConfigurations@2024-07-01' = {
parent: avnm
name: 'prod-full-mesh'
properties: {
connectivityTopology: 'Mesh'
appliesToGroups: [
{
networkGroupId: prodGroup.id
isGlobal: false
groupConnectivity: 'DirectlyConnected'
useHubGateway: 'False'
}
]
description: 'Full mesh across prod VNets'
}
}
// Security Admin Configuration
resource SecureAdminConfig 'Microsoft.Network/networkManagers/securityAdminConfigurations@2024-05-01' = {
parent: avnm
name: 'prod-security-admin'
properties: {
appliesToGroups: [
{
networkGroupId: prodGroup.id
}
]
description: 'Security admin configuration for production VNets'
}
}
// Security Admin Rule Collection
resource SecurityRuleCollection 'Microsoft.Network/networkManagers/securityAdminConfigurations/ruleCollections@2024-05-01' = {
parent: SecureAdminConfig
name: 'prod-egress-rules'
properties: {
appliesToGroups: [
{
networkGroupId: prodGroup.id
}
]
}
}
// Security Admin Rule to allow outbound traffic to Azure DNS
resource RuleCollectionAllowDNS 'Microsoft.Network/networkManagers/securityAdminConfigurations/ruleCollections/rules@2024-05-01' = {
parent: SecurityRuleCollection
name: 'AllowDNS'
kind: 'Custom'
properties: {
description: 'Allow outbound DNS traffic'
protocol: 'UDP'
direction: 'Outbound'
sourcePortRanges: ['0-65535']
destinations: [
{
addressPrefixType: 'IPPrefix'
addressPrefix: '168.63.129.16'
}
]
destinationPortRanges: ['53']
access: 'Allow'
priority: 100
}
}
// Create Azure Policy for dynamic network group
module avnmDynamicProductionNetworkGroup 'avnm-policy-networkgroup.bicep' = {
scope: subscription()
name: 'avnm-dynamic-production-network-group-policy'
params: {
networkManagerName: networkManagerName
}
dependsOn: [
prodGroup
]
}
// Create a User Assigned Identity for the Deployment Scripts
resource userAssignedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2024-11-30' = {
name: 'avnmDeploymentScriptIdentity'
location: location
}
// Assign the User Assigned Identity the Network Contributor role on the resource group
resource networkContributorRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
name: guid(resourceGroup().id, userAssignedIdentity.id, 'NetworkContributor')
scope: resourceGroup()
properties: {
roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '4d97b98b-1d4f-4787-a291-c67834d212e7') // Network Contributor built-in role
principalId: userAssignedIdentity.properties.principalId
principalType: 'ServicePrincipal'
}
dependsOn: [
userAssignedIdentity
]
}
// Create a Deployment Script resource to perform the commit/deployment of the Network Manager connectivity configuration.
resource deploymentScriptConnectivity 'Microsoft.Resources/deploymentScripts@2023-08-01' = {
name: 'DeployAVNMConnectivityConfig'
location: location
kind: 'AzurePowerShell'
identity: {
type: 'UserAssigned'
userAssignedIdentities: {
'${userAssignedIdentity.id}': {}
}
}
properties: {
azPowerShellVersion: '8.3'
retentionInterval: 'PT1H'
timeout: 'PT1H'
arguments: '-networkManagerName "${networkManagerName}" -targetLocations ${location} -configIds ${connectivityConfig.id} -subscriptionId ${subscription().subscriptionId} -configType Connectivity -resourceGroupName ${resourceGroup().name}'
scriptContent: '''
param (
# AVNM subscription id
[parameter(mandatory=$true)][string]$subscriptionId,
# AVNM resource name
[parameter(mandatory=$true)][string]$networkManagerName,
# config ids to deploy
[parameter(mandatory=$true)][string[]]$configIds,
# deployment target region
[parameter(mandatory=$true)][string[]]$targetLocations,
# configuration type to deploy. must be either connectivity or securityadmin
[parameter(mandatory=$true)][ValidateSet('Connectivity','SecurityAdmin')][string]$configType,
# AVNM resource group name
[parameter(mandatory=$true)][string]$resourceGroupName
)
$null = Login-AzAccount -Identity -Subscription $subscriptionId
try {
Deploy-AzNetworkManagerCommit -Name $networkManagerName -ResourceGroupName $resourceGroupName -ConfigurationId $configIds -TargetLocation $targetLocations -CommitType $configType -ErrorAction Stop
}
catch {
Write-Error "Deployment failed with error: $_"
throw "Deployment failed with error: $_"
}
'''
}
}
// Create a Deployment Script resource to perform the commit/deployment of the Network Manager Security configuration.
resource deploymentScriptSecurityAdmin 'Microsoft.Resources/deploymentScripts@2023-08-01' = {
name: 'DeployAVNMSecurityAdminConfig'
location: location
kind: 'AzurePowerShell'
identity: {
type: 'UserAssigned'
userAssignedIdentities: {
'${userAssignedIdentity.id}': {}
}
}
properties: {
azPowerShellVersion: '8.3'
retentionInterval: 'PT1H'
timeout: 'PT1H'
arguments: '-networkManagerName "${networkManagerName}" -targetLocations ${location} -configIds ${SecureAdminConfig.id} -subscriptionId ${subscription().subscriptionId} -configType SecurityAdmin -resourceGroupName ${resourceGroup().name}'
scriptContent: '''
param (
# AVNM subscription id
[parameter(mandatory=$true)][string]$subscriptionId,
# AVNM resource name
[parameter(mandatory=$true)][string]$networkManagerName,
# config ids to deploy
[parameter(mandatory=$true)][string[]]$configIds,
# deployment target region
[parameter(mandatory=$true)][string[]]$targetLocations,
# configuration type to deploy. must be either connectivity or securityadmin
[parameter(mandatory=$true)][ValidateSet('Connectivity','SecurityAdmin')][string]$configType,
# AVNM resource group name
[parameter(mandatory=$true)][string]$resourceGroupName
)
$null = Login-AzAccount -Identity -Subscription $subscriptionId
try {
Deploy-AzNetworkManagerCommit -Name $networkManagerName -ResourceGroupName $resourceGroupName -ConfigurationId $configIds -TargetLocation $targetLocations -CommitType $configType -ErrorAction Stop
}
catch {
Write-Error "Deployment failed with error: $_"
throw "Deployment failed with error: $_"
}
'''
}
}
|
Best Practices
- Tag early, tag often – Dynamic groups make expansion painless
- Deploy per region – Don’t assume configs are global unless you tell them to be
- Keep configs modular – Avoid overloading a single deployment or group
- Monitor rule conflict – Document which rules are AVNM vs NSG where it counts
- Review RBAC regularly – Limit config changes to authorised teams only
🍺
Brewed Insight: AVNM’s layered model is deceptively simple. But once you’re deep into cross-region topologies and layered admin rules, things can get real complex, real fast.
Master the structure now, and you’ll avoid the tangled mess of duplicated peerings, mystery firewall blocks, and Spaghetti-as-a-Service.
It takes good clean network governance, brewed slow and served right.
Learn More