Draw a hub-and-spoke topology, peer the VNets, and prove segmentation with a route that is meant to be blocked.
Why one flat network is a liability
The instinct of a beginner is to put everything in one virtual network because it "just works" — everything can reach everything. That is precisely the problem: when a single workload is compromised, a flat network lets the blast spread to all of them. The enterprise answer is hub-and-spoke. A central hub holds shared services (a firewall, a gateway, DNS); each workload lives in its own spoke, peered to the hub but not to its siblings. Traffic between workloads is routed and inspected on purpose, never by accident. This lab builds that shape and then demonstrates the isolation — that two spokes genuinely cannot reach each other — without spinning up a single chargeable resource.
You will create a hub and two spokes with non-overlapping address spaces, peer each spoke to the hub, confirm the spokes are not peered to one another, and add an NSG that spells out the allowed and denied paths as explicit rules.
A flat network trusts everything once. Segmentation makes trust a decision.
You need a free Azure account, the Azure CLI (az), then be signed in with az login. First time? The 15-minute Set up your machine page covers the account, the installs (winget / brew / apt), and sign-in. Prefer zero installs? Run everything in Azure Cloud Shell (Bash), preinstalled and already signed in.
This page is the whole lab; a Bicep version of the same topology is in github.com/kloudcaptain/campux-labs under lab-hub-spoke-networking. Run the commands in Azure Cloud Shell (Bash). Virtual networks, peerings, and NSGs have no hourly charge — this lab is genuinely free, VMs are what would have cost money and we deliberately use none.
A hub and two spokes
Address spaces must not overlap or peering will refuse them. Give the hub 10.0.0.0/16 and the spokes their own /16s, each with a workload subnet.
# Windows/Git Bash: stop it mangling /subscriptions/... arguments (harmless on macOS/Linux) export MSYS_NO_PATHCONV=1 RG="campux-lab-net-rg" az group create -n "$RG" -l eastus # hub az network vnet create -g "$RG" -n hub-vnet --address-prefix 10.0.0.0/16 \ --subnet-name shared --subnet-prefix 10.0.1.0/24 # spoke 1 az network vnet create -g "$RG" -n spoke1-vnet --address-prefix 10.1.0.0/16 \ --subnet-name workload --subnet-prefix 10.1.1.0/24 # spoke 2 az network vnet create -g "$RG" -n spoke2-vnet --address-prefix 10.2.0.0/16 \ --subnet-name workload --subnet-prefix 10.2.1.0/24
Peer each spoke to the hub
Peering is directional, so each link needs two halves — spoke→hub and hub→spoke. Create all four.
for S in spoke1 spoke2; do
az network vnet peering create -g "$RG" -n "${S}-to-hub" \
--vnet-name "${S}-vnet" --remote-vnet hub-vnet --allow-vnet-access
az network vnet peering create -g "$RG" -n "hub-to-${S}" \
--vnet-name hub-vnet --remote-vnet "${S}-vnet" --allow-vnet-access
done
Connected — the state you want; anything else (usually Disconnected) means one half of a pair is missing:
az network vnet peering list -g "$RG" --vnet-name hub-vnet \
--query "[].{name:name, state:peeringState}" -o tableProve the spokes are isolated
This is the segmentation payoff, and it needs no traffic test — the routing tells the truth. Azure VNet peering is non-transitive: spoke1 peers with the hub and spoke2 peers with the hub, but that does not connect spoke1 to spoke2. List spoke1's peerings and see that the hub is its only neighbour.
az network vnet peering list -g "$RG" --vnet-name spoke1-vnet \
--query "[].{name:name, remote:remoteVirtualNetwork.id}" -o table
hub-vnet — never spoke2-vnet. There is no route from spoke1 to spoke2, so traffic between the two workloads cannot flow. You have proven the isolation from the network's own configuration, which is exactly how an auditor would check it.Make the intent explicit with an NSG
Non-transitive peering already isolates the spokes, but real landing zones state their rules out loud so intent survives future changes. Attach an NSG to spoke1's workload subnet that allows the hub's range and denies spoke2's range explicitly.
az network nsg create -g "$RG" -n spoke1-nsg az network nsg rule create -g "$RG" --nsg-name spoke1-nsg -n allow-hub \ --priority 100 --direction Inbound --access Allow --protocol '*' \ --source-address-prefixes 10.0.0.0/16 --destination-address-prefixes '*' \ --destination-port-ranges '*' az network nsg rule create -g "$RG" --nsg-name spoke1-nsg -n deny-spoke2 \ --priority 200 --direction Inbound --access Deny --protocol '*' \ --source-address-prefixes 10.2.0.0/16 --destination-address-prefixes '*' \ --destination-port-ranges '*' az network vnet subnet update -g "$RG" --vnet-name spoke1-vnet -n workload \ --network-security-group spoke1-nsg
az network nsg rule list -g "$RG" --nsg-name spoke1-nsg \
--query "sort_by([].{p:priority,name:name,access:access,src:sourceAddressPrefix}, &p)" -o table
Two layers now enforce the same intent: routing that offers no path, and a rule that would deny it even if a path appeared. Defence in depth, stated in code.Tear it down
Everything is in one resource group — delete it and confirm.
az group delete -n campux-lab-net-rg --yes
az group exists -n campux-lab-net-rg # -> false
What you can now honestly claim
You built a hub-and-spoke topology, peered spokes to a hub, demonstrated that non-transitive peering keeps the spokes isolated, and expressed that isolation as explicit NSG rules. That is the networking backbone of every Azure landing zone in the job descriptions — "segmentation and least-privilege connectivity between workloads" is this, exactly. The next step in a real design is putting a firewall or a gateway in the hub so spoke-to-spoke traffic can be forced through inspection when it is wanted; the shape you just built is what that inspection plugs into.
- Azure VNet peering is non-transitive by design: peering A-hub and B-hub never implies A-B. To allow controlled spoke-to-spoke traffic you route it through a network virtual appliance or Azure Firewall in the hub, using user-defined routes — which is why the hub exists at all.
- Virtual networks, subnets, peerings, and NSGs carry no hourly charge; you are billed for data processed by gateways and firewalls and for VM compute, none of which this lab creates. That is what makes a networking lab safe to leave running while you read — though tearing it down is still the habit to keep.