forked from erjosito/azcli
-
Notifications
You must be signed in to change notification settings - Fork 0
/
hubandspoke_complex.azcli
452 lines (435 loc) · 29.4 KB
/
hubandspoke_complex.azcli
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
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
# Create a complex hub and spoke environment
# The control variables shape the environment
# Control
no_of_spokes=2
shared_services_spoke=yes
no_of_subnets_per_spoke=4
no_of_vms_per_subnet=2
create_azfw=no
intra_spoke_traffic_to_azfw=no
per_vm_nsg=no
per_subnet_nsg=no
use_asgs=yes
use_ip_groups=yes
# Variables
rg=complexhns
location=westcentralus
hub_vnet_name=hubvnet
ss_vnet_name=sharedservices
hub_vnet_prefix=192.168.0.0/24
azfw_subnet_name=AzureFirewallSubnet
azfw_subnet_prefix=192.168.0.0/26
azfw_name=hnsfw
azfw_policy_name=hnsfwpolicy
spoke_vnet_prefix_beginning='192.168.'
spoke_vnet_prefix_end='.0/24'
spoke_subnet_prefix_ends=('.0/28' '.16/28' '.32/28' '.48/28' '.64/28' '.80/28' '.96/28' '.112/28' '.128/28' '.144/28' '.160/28' '.176/28' '.192/28' '.208/28' '.224/28' '.240/28')
vm_size=Standard_B1s
odd_even_asgs=no
# Get the subnet prefix for a given spoke and subnet
function get_subnet_prefix() {
spoke_id=$1
subnet_id=$2
shell_short=$(echo $SHELL | rev | cut -d/ -f 1 | rev)
if [[ "$shell_short" == "zsh" ]]; then
spoke_subnet_prefix="${spoke_vnet_prefix_beginning}${spoke_id}${spoke_subnet_prefix_ends[$subnet_id]}"
elif [[ "$shell_short" == "bash" ]]; then
spoke_subnet_prefix="${spoke_vnet_prefix_beginning}${spoke_id}${spoke_subnet_prefix_ends[$((subnet_id-1))]}"
else
echo "Unknown shell $shell_short"
exit 1
fi
echo $spoke_subnet_prefix
}
# Create environment
echo "Creating RG $rg and hub VNet with prefix $hub_vnet_prefix..."
az group create -n $rg -l $location -o none --only-show-errors
# If there is no fw, we dont need a hub VNet
if [[ "$create_azfw" == "yes" ]]; then
az network vnet create -g $rg -n $hub_vnet_name --address-prefix $hub_vnet_prefix -l $location -o none --only-show-errors
fi
# Create spokes
for spoke_id in {1..$no_of_spokes}; do
spoke_vnet_name="spoke${spoke_id}"
spoke_vnet_prefix="${spoke_vnet_prefix_beginning}${spoke_id}${spoke_vnet_prefix_end}"
echo "Creating spoke VNet ${spoke_vnet_name} with prefix ${spoke_vnet_prefix}..."
az network vnet create -g $rg -n $spoke_vnet_name --address-prefix $spoke_vnet_prefix -l $location -o none --only-show-errors
# Create subnets
for subnet_id in {1..$no_of_subnets_per_spoke}; do
spoke_subnet_name="subnet${subnet_id}"
spoke_subnet_prefix=$(get_subnet_prefix $spoke_id $subnet_id)
echo "Creating spoke subnet ${spoke_subnet_name} in VNet ${spoke_vnet_name} with prefix ${spoke_subnet_prefix}..."
az network vnet subnet create -n $spoke_subnet_name -g $rg --vnet-name $spoke_vnet_name --address-prefix $spoke_subnet_prefix -o none --only-show-errors
done
done
# Shared services spoke
if [[ "$shared_services_spoke" == "yes" ]]; then
spoke_id=$((no_of_spokes+1))
spoke_vnet_name="$ss_vnet_name"
spoke_vnet_prefix="${spoke_vnet_prefix_beginning}${spoke_id}${spoke_vnet_prefix_end}"
echo "Creating shared services VNet ${spoke_vnet_name} with prefix ${spoke_vnet_prefix}..."
az network vnet create -g $rg -n $spoke_vnet_name --address-prefix $spoke_vnet_prefix -l $location -o none --only-show-errors
nsg_name="SharedServicesNSG"
echo "Creating NSG ${nsg_name}..."
az network nsg create -n $nsg_name -g $rg -o none --only-show-errors
# Create subnets assigned to the NSG
for subnet_id in {1..$no_of_subnets_per_spoke}; do
spoke_subnet_name="subnet${subnet_id}"
spoke_subnet_prefix=$(get_subnet_prefix $spoke_id $subnet_id)
echo "Creating shared services subnet ${spoke_subnet_name} in VNet ${spoke_vnet_name} with prefix ${spoke_subnet_prefix}..."
az network vnet subnet create -n $spoke_subnet_name -g $rg --vnet-name $spoke_vnet_name --address-prefix $spoke_subnet_prefix --nsg $nsg_name -o none --only-show-errors
done
# Add rules to the NSG to explicitly allow traffic from the spokes (and ideally to drop everything else)
for spoke_id in {1..$no_of_spokes}; do
spoke_vnet_name="spoke${spoke_id}"
spoke_vnet_prefix="${spoke_vnet_prefix_beginning}${spoke_id}${spoke_vnet_prefix_end}"
echo "Adding rule to NSG $nsg_name to allow traffic from spoke VNet ${spoke_vnet_name} with prefix ${spoke_vnet_prefix}..."
az network nsg rule create -g $rg --nsg-name $nsg_name -n "AllowTrafficFromSpoke${spoke_id}" --priority "20${spoke_id}" --source-address-prefixes $spoke_vnet_prefix --source-port-ranges '*' --destination-address-prefixes '*' --destination-port-ranges '*' --access Allow --protocol '*' --description "Allow traffic from spoke VNet ${spoke_vnet_name} with prefix ${spoke_vnet_prefix}" -o none --only-show-errors
done
echo "Adding explicit deny rule to NSG $nsg_name..."
az network nsg rule create -g $rg --nsg-name $nsg_name -n "DenyAll" --priority "999" --source-address-prefixes '*' --source-port-ranges '*' --destination-address-prefixes '*' --destination-port-ranges '*' --access Allow --protocol '*' --description "Explicit deny" -o none --only-show-errors
fi
# Azure Firewall (optional)
if [[ "$create_azfw" == "yes" ]]; then
azfw_pip_name="${azfw_name}-pip"
az network public-ip create -g $rg -n $azfw_pip_name --sku standard --allocation-method static -l $location -o none --only-show-errors
azfw_ip=$(az network public-ip show -g $rg -n $azfw_pip_name --query ipAddress -o tsv)
# Create policy
echo "Creating Azure Firewall policy..."
az network firewall policy create -n $azfw_policy_name -g $rg -o none
az network firewall policy rule-collection-group create -n ruleset01 --policy-name $azfw_policy_name -g $rg --priority 1000 -o none --only-show-errors
# Create IP group RFC1918 if required
if [[ "$use_ip_groups" == "yes" ]]; then
echo "Creating IP group for RFC1918 prefixes..."
az network ip-group create -n 'rfc1918' -g $rg --location $location --ip-addresses '10.0.0.0/8' '172.16.0.0/12' '192.168.0.0/16' -o none --only-show-errors
fi
# Allow SSH and HTTP
echo "Creating rule to allow SSH and HTTP..."
if [[ "$use_ip_groups" == "yes" ]]; then
az network firewall policy rule-collection-group collection add-filter-collection --policy-name $azfw_policy_name --rule-collection-group-name ruleset01 -g $rg \
--name mgmt --collection-priority 101 --action Allow --rule-name allowSSHnHTTP --rule-type NetworkRule --description "TCP 22" \
--destination-ip-groups 'rfc1918' --source-ip-groups 'rfc1918' --ip-protocols TCP --destination-ports 22 80 -o none --only-show-errors
else
az network firewall policy rule-collection-group collection add-filter-collection --policy-name $azfw_policy_name --rule-collection-group-name ruleset01 -g $rg \
--name mgmt --collection-priority 101 --action Allow --rule-name allowSSHnHTTP --rule-type NetworkRule --description "TCP 22" \
--destination-addresses 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16 --source-addresses 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16 --ip-protocols TCP --destination-ports 22 80 -o none --only-show-errors
fi
# Allow ICMP
echo "Creating rule to allow ICMP..."
if [[ "$use_ip_groups" == "yes" ]]; then
az network firewall policy rule-collection-group collection add-filter-collection --policy-name $azfw_policy_name --rule-collection-group-name ruleset01 -g $rg \
--name icmp --collection-priority 102 --action Allow --rule-name allowICMP --rule-type NetworkRule --description "ICMP traffic" \
--destination-ip-groups 'rfc1918' --source-ip-groups 'rfc1918' --ip-protocols ICMP --destination-ports "1-65535" -o none --only-show-errors
else
az network firewall policy rule-collection-group collection add-filter-collection --policy-name $azfw_policy_name --rule-collection-group-name ruleset01 -g $rg \
--name icmp --collection-priority 102 --action Allow --rule-name allowICMP --rule-type NetworkRule --description "ICMP traffic" \
--destination-addresses 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16 --source-addresses 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16 --ip-protocols ICMP --destination-ports "1-65535" -o none --only-show-errors
fi
# Allow NTP
echo "Creating rule to allow NTP..."
if [[ "$use_ip_groups" == "yes" ]]; then
az network firewall policy rule-collection-group collection add-filter-collection --policy-name $azfw_policy_name --rule-collection-group-name ruleset01 -g $rg \
--name ntp --collection-priority 103 --action Allow --rule-name allowNTP --rule-type NetworkRule --description "Egress NTP traffic" \
--destination-addresses '*' --source-ip-groups 'rfc1918' --ip-protocols UDP --destination-ports "123" -o none --only-show-errors
else
az network firewall policy rule-collection-group collection add-filter-collection --policy-name $azfw_policy_name --rule-collection-group-name ruleset01 -g $rg \
--name ntp --collection-priority 103 --action Allow --rule-name allowNTP --rule-type NetworkRule --description "Egress NTP traffic" \
--destination-addresses '*' --source-addresses "10.0.0.0/8" "172.16.0.0/12" "192.168.0.0/16" --ip-protocols UDP --destination-ports "123" -o none --only-show-errors
fi
# Create Az FW
echo "Creating Azure Firewall..."
az network firewall create -n $azfw_name -g $rg --policy $azfw_policy_name -l $location -o none --only-show-errors
# Network config
echo "Configuring Azure Firewall in hub VNet..."
az network vnet subnet create -n $azfw_subnet_name -g $rg --vnet-name $hub_vnet_name --address-prefix $azfw_subnet_prefix -o none --only-show-errors
az network firewall ip-config create -f $azfw_name -n azfw-ipconfig -g $rg --public-ip-address $azfw_pip_name --vnet-name $hub_vnet_name -o none --only-show-errors
az network firewall update -n $azfw_name -g $rg -o none --only-show-errors
azfw_private_ip=$(az network firewall show -n $azfw_name -g $rg -o tsv --query 'ipConfigurations[0].privateIpAddress')
echo "Azure Firewall created with private IP $azfw_private_ip"
fi
# Azure Firewall - create VNet-to-VNet rules (WIP)
if [[ "$create_azfw" == "yes" ]]; then
if [[ "$use_ip_groups" == "yes" ]]; then
echo "Creating IP group for spoke VNet $spoke_vnet_name..."
az network ip-group create -n $spoke_vnet_name -g $rg --location $location --ip-addresses $spoke_vnet_prefix -o none --only-show-errors
fi
for spoke_id in {1..$no_of_spokes}; do
spoke_vnet_name="spoke${spoke_id}"
spoke_vnet_prefix="${spoke_vnet_prefix_beginning}${spoke_id}${spoke_vnet_prefix_end}"
rcg_prio="100${spoke_id}"
echo "Creating RCG $spoke_vnet_name..."
az network firewall policy rule-collection-group create -n $spoke_vnet_name --policy-name $azfw_policy_name -g $rg --priority $rcg_prio -o none --only-show-errors
for subnet_id in {1..$no_of_subnets_per_spoke}; do
spoke_subnet_name="subnet${subnet_id}"
spoke_subnet_prefix=$(get_subnet_prefix $spoke_id $subnet_id)
collection_priority="10${spoke_id}${subnet_id}"
if [[ "$use_ip_groups" == "yes" ]]; then
ip_group_name="${spoke_vnet_name}-${spoke_subnet_name}"
echo "Creating IP group ${ip_group_name}..."
az network ip-group create -n $ip_group_name -g $rg --location $location --ip-addresses $spoke_subnet_prefix -o none --only-show-errors
echo "Creating rule to allow SSH and HTTP with priority $collection_priority from subnet $spoke_subnet_name in VNet $spoke_vnet_name using IP groups..."
az network firewall policy rule-collection-group collection add-filter-collection --policy-name "$azfw_policy_name" --rule-collection-group-name "${spoke_vnet_name}" -g $rg \
--name $spoke_subnet_name --collection-priority $collection_priority --action Allow --rule-name allowSSHnHTTP --rule-type NetworkRule --description 'SSH and HTTP' \
--destination-ip-groups 'rfc1918' --source-ip-groups $ip_group_name --ip-protocols TCP --destination-ports 22 80 -o none --only-show-errors
else
echo "Creating rule to allow SSH and HTTP with priority $collection_priority from subnet $spoke_subnet_name in VNet $spoke_vnet_name using hard-coded IP addresses..."
az network firewall policy rule-collection-group collection add-filter-collection --policy-name $azfw_policy_name --rule-collection-group-name "${spoke_vnet_name}" -g $rg \
--name $spoke_subnet_name --collection-priority $collection_priority --action Allow --rule-name allowSSHnHTTP --rule-type NetworkRule --description 'SSH and HTTP' \
--destination-addresses 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16 --source-addresses $spoke_subnet_prefix --ip-protocols TCP --destination-ports 22 80 -o none --only-show-errors
fi
done
done
fi
# VNet Peerings
# If there is a firewall, we do a hub&spoke topology. Otherwise a full mesh
if [[ "$create_azfw" == "yes" ]]; then
for spoke_id in {1..$no_of_spokes}; do
spoke_vnet_name="spoke${spoke_id}"
echo "Peering VNet $spoke_vnet_name with $hub_vnet_name..."
az network vnet peering create -n "hubto${spoke_vnet_name}" -g $rg --vnet-name $hub_vnet_name --remote-vnet $spoke_vnet_name --allow-vnet-access --allow-forwarded-traffic -o none --only-show-errors
az network vnet peering create -n "${spoke_vnet_name}tohub" -g $rg --vnet-name $spoke_vnet_name --remote-vnet $hub_vnet_name --allow-vnet-access --allow-forwarded-traffic -o none --only-show-errors
done
else
for spoke1_id in {1..$no_of_spokes}; do
for spoke2_id in {1..$no_of_spokes}; do
if [[ "$spoke1_id" -lt "$spoke2_id" ]]; then
spoke1_vnet_name="spoke${spoke1_id}"
spoke2_vnet_name="spoke${spoke2_id}"
echo "Peering VNet $spoke1_vnet_name with $spoke2_vnet_name..."
az network vnet peering create -n "${spoke1_vnet_name}to${spoke2_vnet_name}" -g $rg --vnet-name ${spoke1_vnet_name} --remote-vnet ${spoke2_vnet_name} --allow-vnet-access --allow-forwarded-traffic -o none --only-show-errors
az network vnet peering create -n "${spoke2_vnet_name}to${spoke1_vnet_name}" -g $rg --vnet-name ${spoke2_vnet_name} --remote-vnet ${spoke1_vnet_name} --allow-vnet-access --allow-forwarded-traffic -o none --only-show-errors
fi
done
done
fi
# If shared services, peer every spoke to the shared services VNet
if [[ "$shared_services_spoke" == "yes" ]]; then
for spoke_id in {1..$no_of_spokes}; do
spoke_vnet_name="spoke${spoke_id}"
echo "Peering VNet $spoke_vnet_name with $ss_vnet_name..."
az network vnet peering create -n "Sharedto${spoke_vnet_name}" -g $rg --vnet-name $ss_vnet_name --remote-vnet $spoke_vnet_name --allow-vnet-access --allow-forwarded-traffic -o none --only-show-errors
az network vnet peering create -n "${spoke_vnet_name}toShared" -g $rg --vnet-name $spoke_vnet_name --remote-vnet $ss_vnet_name --allow-vnet-access --allow-forwarded-traffic -o none --only-show-errors
done
fi
# VMs
for spoke_id in {1..$no_of_spokes}; do
spoke_vnet_name="spoke${spoke_id}"
if [[ "$use_asgs" == "yes" ]]; then
echo "Creating ASGs for $spoke_vnet_name..."
# Do we need an ASG for all VMs in the VNet?? Maybe for the rule to Shared Services? (using the VNet prefix now)
az network asg create -g $rg -n $spoke_vnet_name --location $location -o none --only-show-errors
if [[ "$odd_even_asgs" == "yes" ]]; then
az network asg create -g $rg -n "${spoke_vnet_name}-EvenSubnet" --location $location -o none --only-show-errors
az network asg create -g $rg -n "${spoke_vnet_name}-OddSubnet" --location $location -o none --only-show-errors
fi
fi
for subnet_id in {1..$no_of_subnets_per_spoke}; do
spoke_subnet_name="subnet${subnet_id}"
# Create VMs, optionally with NSGs
for vm_id in {1..$no_of_vms_per_subnet}; do
spoke_vm_name="spoke${spoke_id}-${subnet_id}-vm-${vm_id}"
if [[ "$per_vm_nsg" == "yes" ]]; then
echo "Creating VM ${spoke_vm_name} in subnet ${spoke_subnet_name} in VNet ${spoke_vnet_name} with a dedicated NSG..."
az vm create -n $spoke_vm_name -g $rg -l $location --image ubuntuLTS --generate-ssh-keys -o none --public-ip-sku Standard --nsg "${spoke_vm_name}-nsg" \
--public-ip-address '' --vnet-name $spoke_vnet_name --size $vm_size --subnet $spoke_subnet_name -o none --only-show-errors --no-wait
else
echo "Creating VM ${spoke_vm_name} in subnet ${spoke_subnet_name} in VNet ${spoke_vnet_name} with no NSG..."
az vm create -n $spoke_vm_name -g $rg -l $location --image ubuntuLTS --generate-ssh-keys -o none --public-ip-sku Standard --nsg '' \
--public-ip-address '' --vnet-name $spoke_vnet_name --size $vm_size --subnet $spoke_subnet_name -o none --only-show-errors --no-wait
fi
done
# Optionally use ASGs
if [[ "$use_asgs" == "yes" ]]; then
echo "Creating ASG "${spoke_vnet_name}-${spoke_subnet_name}"..."
az network asg create -g $rg -n "${spoke_vnet_name}-${spoke_subnet_name}" --location $location -o none --only-show-errors
echo "Updating VM ${spoke_vm_name} in subnet ${spoke_subnet_name} in VNet ${spoke_vnet_name} with ASGs..."
nic_id=$(az vm show -n $spoke_vm_name -g $rg --query 'networkProfile.networkInterfaces[0].id' -o tsv)
nic_name=$(echo $nic_id | cut -d'/' -f9)
ip_config_name=$(az network nic show --ids $nic_id --query 'ipConfigurations[0].name' -o tsv)
if [[ "$odd_even_asgs" == "yes" ]]; then
_value=$(expr $subnet_id % 2)
(( $_value == 0 )) && subnet_even_asg="${spoke_vnet_name}-EvenSubnet" || subnet_even_asg="${spoke_vnet_name}-OddSubnet"
az network nic ip-config update -g $rg --nic-name $nic_name -n $ip_config_name --asgs $spoke_vnet_name "${spoke_vnet_name}-${spoke_subnet_name}" $subnet_even_asg -o none --only-show-errors
else
az network nic ip-config update -g $rg --nic-name $nic_name -n $ip_config_name --asgs $spoke_vnet_name "${spoke_vnet_name}-${spoke_subnet_name}" -o none --only-show-errors
fi
fi
done
done
# Shared services VMs
if [[ "$shared_services_spoke" == "yes" ]]; then
ss_spoke_id=$((no_of_spokes+1))
ss_spoke_vnet_prefix="${spoke_vnet_prefix_beginning}${ss_spoke_id}${spoke_vnet_prefix_end}"
for subnet_id in {1..$no_of_subnets_per_spoke}; do
spoke_subnet_name="subnet${subnet_id}"
# Create VMs, optionally with NSGs
for vm_id in {1..$no_of_vms_per_subnet}; do
spoke_vm_name="SharedServices-${subnet_id}-vm-${vm_id}"
echo "Creating VM ${spoke_vm_name} in subnet ${spoke_subnet_name} in VNet ${ss_vnet_name} with no NSG..."
az vm create -n $spoke_vm_name -g $rg -l $location --image ubuntuLTS --generate-ssh-keys -o none --public-ip-sku Standard --nsg '' \
--public-ip-address '' --vnet-name $ss_vnet_name --size $vm_size --subnet $spoke_subnet_name -o none --only-show-errors --no-wait
done
done
fi
# Shutdown created VMs for cost savings
for spoke_id in {1..$no_of_spokes}; do
for subnet_id in {1..$no_of_subnets_per_spoke}; do
for vm_id in {1..$no_of_vms_per_subnet}; do
spoke_vm_name="spoke${spoke_id}-${subnet_id}-vm-${vm_id}"
echo "Shutting down VM ${spoke_vm_name}..."
az vm deallocate -n $spoke_vm_name -g $rg --no-wait -o none --only-show-errors
done
done
done
if [[ "$shared_services_spoke" == "yes" ]]; then
for subnet_id in {1..$no_of_subnets_per_spoke}; do
for vm_id in {1..$no_of_vms_per_subnet}; do
spoke_vm_name="SharedServices-${subnet_id}-vm-${vm_id}"
echo "Shutting down VM ${spoke_vm_name}..."
az vm deallocate -n $spoke_vm_name -g $rg --no-wait -o none --only-show-errors
done
done
fi
# Create route tables (a single route table is required if intra spoke traffic is not sent to AZFW, otherwise a route table per spoke is required)
if [[ "$create_azfw" == "yes" ]]; then
if [[ "$intra_spoke_traffic_to_azfw" == "no" ]]; then
echo "Creating single route table for all spokes..."
route_table_name="spoke-rt"
az network route-table create -n $route_table_name -g $rg -o none -l $location --only-show-errors
az network route-table route create -n "default" -g $rg --route-table-name $route_table_name --address-prefix "0.0.0.0/0" \
--next-hop-type VirtualAppliance --next-hop-ip-address $azfw_private_ip -o none --only-show-errors
fi
for spoke_id in {1..$no_of_spokes}; do
spoke_vnet_name="spoke${spoke_id}"
spoke_vnet_prefix="${spoke_vnet_prefix_beginning}${spoke_id}${spoke_vnet_prefix_end}"
for subnet_id in {1..$no_of_subnets_per_spoke}; do
spoke_subnet_name="subnet${subnet_id}"
spoke_subnet_prefix=$(get_subnet_prefix $spoke_id $subnet_id)
# If intra-spoke traffic is sent to AzFW, create a route table for each spoke subnet
if [[ "$intra_spoke_traffic_to_azfw" == "yes" ]]; then
route_table_name="${spoke_vnet_name}-${spoke_subnet_name}-rt"
echo "Creating route table $route_table_name for subnet $spoke_subnet_name in VNet $spoke_vnet_name..."
az network route-table create -n $route_table_name -g $rg -o none -l $location --only-show-errors
az network route-table route create -n "default" -g $rg --route-table-name $route_table_name --address-prefix "0.0.0.0/0" \
--next-hop-type VirtualAppliance --next-hop-ip-address $azfw_private_ip -o none --only-show-errors
az network route-table route create -n "localvnet" -g $rg --route-table-name $route_table_name --address-prefix "$spoke_vnet_prefix" \
--next-hop-type VirtualAppliance --next-hop-ip-address $azfw_private_ip -o none --only-show-errors
az network route-table route create -n "localsubnet" -g $rg --route-table-name $route_table_name --address-prefix "$spoke_subnet_prefix" \
--next-hop-type VnetLocal -o none --only-show-errors
fi
# Associate route table with subnet
echo "Associating route table $route_table_name with subnet $spoke_subnet_name..."
az network vnet subnet update -n $spoke_subnet_name -g $rg --vnet-name $spoke_vnet_name --route-table $route_table_name -o none --only-show-errors
done
done
fi
# Create subnet/VNet NSGs
if [[ "$per_vm_nsg" == "no" ]]; then
# Per-subnet NSGs
if [[ "$per_subnet_nsg" == "yes" ]]; then
for spoke_id in {1..$no_of_spokes}; do
spoke_vnet_name="spoke${spoke_id}"
for subnet_id in {1..$no_of_subnets_per_spoke}; do
spoke_subnet_name="subnet${subnet_id}"
nsg_name="${spoke_vnet_name}-${spoke_subnet_name}-nsg"
echo "Creating NSG ${nsg_name}..."
az network nsg create -n $nsg_name -g $rg -o none --only-show-errors
# Rules for intra-VNet traffic, optionally using ASGs
if [[ $use_asgs == "yes" ]]; then
_value=$(expr $subnet_id % 2)
(( $_value == 0 )) && subnet_even_asg="${spoke_vnet_name}-EvenSubnet" || subnet_even_asg="${spoke_vnet_name}-OddSubnet"
echo "Creating NSG rule for intra-VNet traffic using ASGs..."
az network nsg rule create -g $rg --nsg-name $nsg_name -n "OddEvenSubnet" --priority 100 --source-asgs "${spoke_vnet_name}-${spoke_subnet_name}" \
--source-port-ranges '*' --destination-asgs "${subnet_even_asg}" --destination-port-ranges '*' --access Allow \
--protocol '*' --description "Allow traffic to ASG $subnet_even_asg" -o none --only-show-errors
else
echo "Creating NSG rule for intra-VNet traffic..."
spoke_subnet_prefix=$(get_subnet_prefix $spoke_id $subnet_id)
dst_subnet_id=$((subnet_id+1))
if [[ $dst_subnet_id -gt $no_of_subnets_per_spoke ]]; then
dst_subnet_id=1
fi
dst_spoke_subnet_prefix=$(get_subnet_prefix $spoke_id $dst_subnet_id)
az network nsg rule create -g $rg --nsg-name $nsg_name -n "intra-vnet" --priority 100 --source-address-prefixes "$spoke_subnet_prefix" \
--source-port-ranges '*' --destination-address-prefixes "$dst_spoke_subnet_prefix" --destination-port-ranges '*' --access Allow \
--protocol '*' --description "Allow intra-VNet traffic" -o none --only-show-errors
fi
# Associate NSG with subnet
echo "Associating NSG $nsg_name with subnet $spoke_subnet_name..."
az network vnet subnet update -n $spoke_subnet_name -g $rg --vnet-name $spoke_vnet_name --network-security-group $nsg_name -o none --only-show-errors
done
done
# Per-VNet NSGs
else
for spoke_id in {1..$no_of_spokes}; do
spoke_vnet_name="spoke${spoke_id}"
spoke_vnet_prefix="${spoke_vnet_prefix_beginning}${spoke_id}${spoke_vnet_prefix_end}"
nsg_name="${spoke_vnet_name}-nsg"
echo "Creating NSG ${nsg_name}..."
az network nsg create -n $nsg_name -g $rg -o none --only-show-errors
# Create rules for subnet-to-subnet traffic
for subnet_id in {1..$no_of_subnets_per_spoke}; do
if [[ "$subnet_id" -lt "$no_of_subnets_per_spoke" ]]; then
src_subnet_id=$subnet_id
src_spoke_subnet_name="subnet${src_subnet_id}"
dst_subnet_id=$((subnet_id+1))
dst_spoke_subnet_name="subnet${dst_subnet_id}"
if [[ $use_asgs == "yes" ]]; then
src_asg_name="${spoke_vnet_name}-${src_spoke_subnet_name}"
dst_asg_name="${spoke_vnet_name}-${dst_spoke_subnet_name}"
az network nsg rule create -g $rg --nsg-name $nsg_name -n "Subnet${src_subnet_id}to${dst_subnet_id}" --priority 20${src_subnet_id} --source-asgs "${src_asg_name}" \
--source-port-ranges '*' --destination-asgs "${dst_asg_name}" --destination-port-ranges '*' --access Allow \
--protocol '*' --description "Allow traffic from ASG $src_asg_name to ASG $dst_asg_name" -o none --only-show-errors
else
src_spoke_subnet_prefix=$(get_subnet_prefix $spoke_id $src_subnet_id)
dst_spoke_subnet_prefix=$(get_subnet_prefix $spoke_id $dst_subnet_id)
az network nsg rule create -g $rg --nsg-name $nsg_name -n "intra-vnet" --priority 20${src_subnet_id} --source-address-prefixes "$src_spoke_subnet_prefix" \
--source-port-ranges '*' --destination-address-prefixes "$dst_spoke_subnet_prefix" --destination-port-ranges '*' --access Allow \
--protocol '*' --description "Allow intra-VNet traffic from subnet ${src_subnet_id} to subnet ${dst_subnet_id}" -o none --only-show-errors
fi
fi
done
# Create rule for traffic to shared services
if [[ "$shared_services_spoke" == "yes" ]]; then
ss_spoke_id=$((no_of_spokes+1))
ss_spoke_vnet_prefix="${spoke_vnet_prefix_beginning}${ss_spoke_id}${spoke_vnet_prefix_end}"
if [[ "$use_asgs" == "yes" ]]; then
az network nsg rule create -g $rg --nsg-name $nsg_name -n "SharedServices" --priority 400 --source-asgs "$spoke_vnet_name" \
--source-port-ranges '*' --destination-address-prefixes "$ss_spoke_vnet_prefix" --destination-port-ranges '*' --access Allow \
--protocol '*' --description "Allow traffic to shared services VNet" -o none --only-show-errors
else
az network nsg rule create -g $rg --nsg-name $nsg_name -n "SharedServices" --priority 400 --source-address-prefixes "$spoke_vnet_prefix" \
--source-port-ranges '*' --destination-address-prefixes "$ss_spoke_vnet_prefix" --destination-port-ranges '*' --access Allow \
--protocol '*' --description "Allow traffic to shared services VNet" -o none --only-show-errors
fi
fi
# Assign NSG to subnet
for subnet_id in {1..$no_of_subnets_per_spoke}; do
spoke_subnet_name="subnet${subnet_id}"
echo "Associating NSG $nsg_name with subnet $spoke_subnet_name..."
az network vnet subnet update -n $spoke_subnet_name -g $rg --vnet-name $spoke_vnet_name --network-security-group $nsg_name -o none --only-show-errors
done
done
fi
fi
###############
# Diagnostics #
###############
# Print all route tables
function show_routes() {
rt_list=$(az network route-table list -g $rg --query '[].name' -o tsv)
echo $rt_list | while IFS= read -r rt; do
echo "Route table $rt:"
az network route-table route list -g $rg --route-table-name $rt -o table
done
}
# Print all NSGs
function show_nsgs() {
nsg_list=$(az network nsg list -g $rg --query '[].name' -o tsv)
echo $nsg_list | while IFS= read -r nsg; do
echo "NSG $nsg:"
az network nsg rule list -g $rg --nsg-name $nsg -o table
done
}
# Cleanup
# az group delete -y -n $rg --no-wait