Lab 05 — Virtual Networks, Peering, Routing, and Security
azure administrator series

ZIP SHA-256: 4b1569bffabefc9fc63eb33ae54ab61f00452bea8b0db46523deab88c4ee9ebe
Live-tested and cleaned up: two peered VNets, real private HTTP requests, one active discard-route failure, native PowerShell recovery and repeated recovery/cleanup. Seven genuine Portal captures are included. Context/interruption safety tests use offline doubles; the exclusive-lock test is real and local.
Connected peering. Broken traffic. Learn to prove the difference between a configured connection and a working application path.
This hands-on workshop continues the series based on Microsoft Certified: Azure Administrator Associate (AZ-104). It combines Azure CLI, PowerShell 7 and the Azure Portal. It is independent training, not an official Microsoft lab or a guarantee of exam coverage. Allow 150–210 minutes, including cleanup.
Previous: Lab 04 — ARM and Bicep Deployment Operations. Next: Lab 06 is not published yet. Browse Azure Workshopz.
What you will learn
Plan non-overlapping IPv4 address spaces and workload subnets.
Configure two-way VNet peering, static private IPs and explicit outbound connectivity.
Interpret subnet NSGs, VNet-local application security groups (ASGs), effective security rules and IP flow verify.
Diagnose a more-specific user-defined route using effective routes and Network Watcher next hop.
Restore only the faulty route with Azure PowerShell, verify real HTTP responses, and repeat recovery safely.
Clean up exact recorded resources, preserve a shared Network Watcher and export sanitized evidence.
These exercises relate to AZ-104 virtual networks/subnets, peering, public IPs, user-defined routes, NSGs/ASGs and connectivity troubleshooting. DNS, private endpoints, VPN, load balancing and production network architecture are outside this lab. Check the current AZ-104 study guide for the complete objectives.
Scenario and boundaries
A client VM calls a small HTTP service on a web VM in a different VNet. You establish a healthy baseline, introduce one routing fault, diagnose the actual Azure result, then recover without opening security rules or rebuilding either VM.
Use a disposable training subscription. The scripts create 15 top-level resources: two VMs, disks, NICs, Standard public IPv4 addresses, VNets, NSGs and ASGs, plus one route table. The resource group, subnet/peering children and temporary Run Command extension records are additional objects. Do not put unrelated resources in the lab group.
No public SSH or RDP is allowed. The web public IP accepts synthetic HTTP only from your current operator IPv4 /32. The client public IP has no allowed public inbound traffic. Both public IPs provide explicit outbound connectivity; this is not a fully private network design. The application contains no credentials or real data. Do not repurpose this HTTP service for production.
The scripts do not install tools, register providers, enable paid security plans, create managed identities, grant roles, change policy or retrieve keys. An existing West Europe Network Watcher is required and left unchanged. Run Command uses the guest agent and outbound Azure connectivity; it is not SSH and does not prove public management access.
Address plan and traffic paths
Component | Configuration |
Client VNet / subnet | 10.105.0.0/24 / 10.105.0.0/26 |
Client private IP | 10.105.0.4, static |
Web VNet / subnet | 10.105.1.0/24 / 10.105.1.0/26 |
Web private IP / application | 10.105.1.4, static / TCP 80 |
Peering | Both directions; no gateway transit or forwarded traffic |
NSG placement | One per subnet; no NIC-level NSG |
ASGs | One per VNet, containing only that VNet's VM NIC |
Client route table | Associated with client subnet; empty at baseline |
Outbound | defaultOutboundAccess=false; explicit Standard public IP on each NIC |
A /26 has 64 addresses; Azure reserves the first four and the last, leaving 59 assignable addresses. The .4 addresses are valid first workload addresses. Peering does not merge the VNets and is not transitively routed through a third peer. Both peering objects must be configured and Connected.
Keep the two test paths separate: client → web private IP traverses peering; operator → web public IP uses the operator-only inbound HTTP rule. A successful public request is an independent health check, not proof of private peering connectivity.
ASGs are not cross-VNet labels. The web inbound rule uses the exact client private IP as its source and the local web ASG as its destination. The client outbound rule uses its local ASG and the exact web private IP. An inbound deny at a lower precedence than the intended inbound allow, but above default rules, prevents default AllowVNetInBound from hiding a missing lab inbound allow. Default outbound permissions remain; this is not an egress-filtering exercise. The validator requires the named outbound allow, not just any Allow result. See ASG constraints.
1. Prerequisites, cost and private state
Use Windows with PowerShell 7.5 or later, an already installed Azure CLI/Bicep, Az.Accounts and Az.Network. Sign in interactively beforehand using your normal approved process. Both CLI and PowerShell must select the same tenant and AzureCloud subscription; also verify that you are using your intended operator identity. The tested module versions are Az.Accounts 2.12.1 and Az.Network 5.5.0; the scripts require these minimum installed versions. Do not bypass your organization's approval process to obtain permissions.
Required capabilities include creating/deleting this resource group and its networking/compute resources, deploying/validating/what-if templates, VM action Run Command, and Network Watcher/effective NIC diagnostics. The preflight checks permissions, registered providers, policy/locks, quota, image, address collisions and current retail rates. Applicable policy still needs human review; preflight is not a proof that every future request will be permitted.
The operational ceiling is EUR20, including EUR5 reserved for cleanup. The six-hour estimate must be below EUR15. Stop new exercises at five hours, begin cleanup earlier, and finish before six. These scripts do not create an Azure-enforced spending cap. Retail estimates are not your actual offer rate or final bill; billing data can arrive late. Public IPs, disks and peering/data traffic can cost money even when a VM is not actively serving requests. Do not assume deallocation removes every charge.
Extract the complete ZIP, open PowerShell in its folder, and review the scripts before running. Keep the private state outside the extracted/downloadable package. The initializer creates a new directory with access for the current Windows user and SYSTEM only.
$private = Join-Path $env:LOCALAPPDATA ('AzureWorkshopz-Lab05-' + [guid]::NewGuid().ToString('N'))
./Initialize-Lab05StateDirectory.ps1 -Path $private -Execute
$state = Join-Path $private 'state.json'
./Start-Az104Lab05.ps1 -StatePath $state -Stage Preflight -DetectOperatorIp
./Test-Lab05Safety.ps1 -StatePath $state-DetectOperatorIp calls ipify over HTTPS and records your current public IPv4 privately. You can instead pass -OperatorCidr 'YOUR.PUBLIC.IP/32'. Never substitute 0.0.0.0/0. Review preflight-private.json locally; do not upload it. Stop on insufficient quota, unexpected policies, cost/automation risks, missing dependencies or overlapping addresses. No resources are created by preflight. Re-run it if older than one hour before preparation.
2. Preview and deploy the baseline with Azure CLI
./Start-Az104Lab05.ps1 -StatePath $state -Stage Prepare -Execute -AcknowledgeCostPrepare creates only the owned empty group, generates an in-memory provisioning-only SSH key (private part discarded), saves private parameters, then runs template validation and Incremental what-if. Inspect whatif-private.json: only the exact new lab resources should be created. The two managed OS disks are created implicitly with the VMs. No existing resource should be modified or deleted.
./Start-Az104Lab05.ps1 -StatePath $state -Stage Deploy -Execute -AcknowledgeCost -ReviewedWhatIf
./Test-Az104Lab05.ps1 -StatePath $state -Phase Baseline -ExecuteDeploy checks the reviewed file hashes. The baseline cannot be replayed after deployment. Cloud-init creates a small Python HTTP service named lab05-http; it requires no third-party package installation. Each validation invokes guest Run Command, so it requires explicit -Execute, even though its guest checks only inspect service health and make bounded HTTP requests.
Expected: three fresh client-private HTTP 200 responses matching this run's marker; web-local service active and three matching responses; public HTTP 200 from the approved operator address; both peerings Connected; next hop VirtualNetworkPeering; intended inbound/outbound NSG Allow decisions and an operator-to-web SSH Deny. The manifest records immutable VM/disk identities and a configuration fingerprint.
Portal checkpoint: open the recorded group, count the resources, open the client VNet → Peerings and verify Connected. Inspect both subnet associations and each NIC's ASG membership. Resource names include a unique suffix; published screenshots have private identifiers masked, so yours will differ.


3. Inspect evidence at the right layer
In the client NIC, open Effective routes and find the web VNet prefix with next hop VNet peering. In Effective security rules, inspect both inbound and outbound decisions. The empty route table is deliberately attached at baseline so fault injection changes only one child route.

The validator uses these supported CLI diagnostics with names resolved from the private manifest:
# Read-only diagnostic examples: use your recorded names, not guessed prefixes.
az network nic show-effective-route-table -g <lab-group> -n <client-nic>
az network nic list-effective-nsg -g <lab-group> -n <client-nic>
az network watcher show-next-hop -g <lab-group> --vm <client-vm> --nic <client-nic> --source-ip 10.105.0.4 --dest-ip 10.105.1.4
az network watcher test-ip-flow -g <lab-group> --vm <web-vm> --nic <web-nic> --direction Inbound --protocol TCP --local 10.105.1.4:80 --remote 10.105.0.4:60000These illustrative placeholders are not executable PowerShell; the packaged stage scripts supply the exact values. IP flow verify evaluates NSGs, not the full HTTP path. Next hop/effective routes identify routing decisions, not application health. Real requests close the gap. See routing diagnostics and NSG flow diagnostics.
4. Introduce one controlled routing fault
./Set-Az104Lab05Fault.ps1 -StatePath $state -Execute
./Test-Az104Lab05.ps1 -StatePath $state -Phase Fault -ExecuteThe fault creates only fault-web-blackhole: destination 10.105.1.4/32, next hop None, in the recorded client route table. None means discard. Azure chooses the longest matching prefix: this /32 wins over the broader peering route. This is an actual private application-path failure, not a peering-status or sign-in failure.
Expected: three new private requests time out, bounded to six seconds each. The web-local service, application-file SHA-256, public HTTP path, peerings, VM/disk identities and security configuration must remain healthy and unchanged. The effective route must show the exact /32 as Active/User/None; Network Watcher must identify None and the correct route table.
Portal checkpoint: client route table → Routes shows the deliberate /32. Refresh client NIC → Effective routes. Capture the route source, state, destination and next-hop columns. A generic timeout alone is not enough to pass this stage. If propagation has not completed, inspect and wait briefly before revalidating; never broaden the NSG to manufacture success.


5. Recover with Azure PowerShell and prove it twice
./Restore-Az104Lab05.ps1 -StatePath $state -Execute
./Test-Az104Lab05.ps1 -StatePath $state -Phase Recovery -Execute
./Restore-Az104Lab05.ps1 -StatePath $state -Execute
./Test-Az104Lab05.ps1 -StatePath $state -Phase Recovery -ExecuteRecovery reads the exact route table with Get-AzRouteTable, verifies the sole route's ID/name/prefix/next hop, removes it using Remove-AzRouteConfig, then commits using Set-AzRouteTable. It does not remove the route-table association, change peering, modify NSGs or recreate VMs. An already-absent recorded fault route is a safe no-op.
Expected: private requests return the original marker again; next hop returns to peering; the faulty /32 is absent; public and web-local tests still pass; original identities, configuration and application file are unchanged. Refresh the Portal to avoid mistaking cached results for new evidence. New requests use cache-busting URLs and Connection: close; a previously established connection is not a reliable NSG/routing test.


6. Cleanup, repeat, then export
./Remove-Az104Lab05.ps1 -StatePath $state -Execute
./Remove-Az104Lab05.ps1 -StatePath $state -Execute
./Get-Az104Lab05Evidence.ps1 -StatePath $state -OutputPath (Join-Path $private 'sanitized-evidence.json') -ExecuteCleanup deletes only recorded objects, in dependency order, checking associations before each removal. It deletes the resource group only after the group contains no resources or unexpected deployment/authorization records. Deleting the disposable VMs/disks destroys their synthetic guest contents. No backup or restoration is promised. Group deletion also removes its deployment history. Keep the private evidence first if you need it.
Cleanup verifies exact resource absence, checks for this exact run's tagged residuals, and compares the shared Network Watcher's fingerprint. Repeated cleanup must pass without creating anything. Evidence export is allowed only after baseline, fault, recovery, cleanup and repeat operations pass. Never publish state.json, raw diagnostic responses, public IPs, access tokens, SSH material or unredacted Portal captures.
Safety stops and interrupted operations
The private manifest is an ownership ledger, not a discovery-by-prefix tool. An exclusive file lock rejects concurrent runs. Context, ID, ownership, route, unexpected extension/association and five-hour guards fail closed. Do not change the manifest to make a failing test green.
A pending operation means its result needs reconciliation. First check that the original process has ended; never delete a held lock. Read the exact deployment/VM extension/route or delete target recorded in Pending and compare IDs, timestamps, provisioning state and the saved response. A completed but empty Run Command response is not a passing probe. If the outcome remains uncertain, stop and obtain operator help. Non-delete pending operations are deliberately not auto-cleared. Cleanup can resume only its own exact recorded delete. Interrupted/context tests in this release are offline simulations, not destructive live interruptions.
If the operator IP changes, stop: the scripts will not widen the NSG. If the guest agent fails, distinguish that management failure from application failure. Do not create a new key, open SSH, install extensions or change security policy as a workaround. Start a separately reviewed new run only after the old run is reconciled and cleaned up.
Challenge and learning pack
Try CHALLENGE.md before HINTS.md or SOLUTION.md. The download contains eight questions and answers in KNOWLEDGE-CHECK.md, the live/simulated validation record in VALIDATION.md, sanitized evidence, genuine Portal screenshots and file hashes. The illustrated cover is AI-generated marketing artwork, not Azure evidence.
Further reading
Challenge — Connected, but unreachable
After the instructor establishes and validates the baseline, applies the packaged fault and confirms Fault validation, begin here without reading the solution.
A client VM no longer receives a response from the web VM's private HTTP endpoint. It worked earlier. Both virtual network peerings still say Connected. The service remains healthy locally, and the approved operator can still retrieve the public test marker.
Find evidence for the failed layer. Recover only the recorded lab configuration responsible for the private-path failure. Preserve both VM identities, the application-file hash, peering and NSG rules. Do not open management ports or redeploy the baseline.
Deliver: a short diagnosis, the relevant effective network evidence, three fresh successful private requests after recovery, an independent service-health check, an idempotent repeat recovery, and verified cleanup twice. A screenshot of Running or Connected alone is insufficient.
Eight knowledge-check questions
How many assignable IPv4 addresses does an Azure /26 subnet have, and why?
Does Connected peering prove that an HTTP application is reachable? Name two additional layers to test.
Why does 10.105.1.4/32 with next hop None win over a 10.105.1.0/24 peering route?
Can the lab use one source ASG in the client VNet and a destination ASG in the web VNet in the same NSG rule?
What does IP flow verify Allow establish, and what does it not establish?
Does defaultOutboundAccess=false prevent all outbound Internet traffic when a NIC has a Standard public IP?
Why should validation use fresh connections rather than an existing connection after a rule or route change?
What makes recovery and cleanup safely repeatable here, and what happens if an unrelated resource appears?
Progressive hints, the separate solution, knowledge-check answers and full validation report are included in the download. Try the challenge before opening the solution.
Lab 05 validation report
azure administrator series
Run date: 28 September 2026. Region: West Europe. These results describe one isolated live Azure run, not every tenant, policy or future software version.
Live Azure results
Check | Observed result |
Preflight and deployment | Matching CLI/PowerShell contexts; installed dependencies, providers, permissions, policies/locks, address collision, B2s quota and current retail estimate checked. Bicep build, Azure template validation and reviewed Incremental what-if passed. Baseline deployed successfully. |
Security cost settings | VirtualMachines Defender pricing returned Free; security auto-provisioning returned Off. These read-only checks were individually live-tested, then incorporated into the final preflight. No plan or setting was changed. |
Baseline | Three client-to-web private HTTP responses matched the run marker; web-local service was active; approved-source public HTTP returned 200. Both peerings were Connected and next hop was VirtualNetworkPeering. |
Intended fault | The exact active User route to the web private /32 selected None. Network Watcher identified that next hop and the correct route table. Three fresh private requests timed out. |
Independent health during fault | Web-local and public HTTP remained healthy; application-file SHA-256, VM/disk identities, peerings, NSGs and healthy configuration fingerprint remained unchanged. |
Security decisions | IP flow verify selected the intended named private HTTP inbound/outbound allows and denied operator-to-web SSH. Effective NSGs were inspected. This is not an external port scan or an SSH/RDP login test. |
Recovery | Native Azure PowerShell removed only the recorded fault route. Three fresh private requests passed and next hop returned to peering. |
Repeated recovery | Second recovery was a safe no-op. Repeated Recovery validation passed, preserving identity, application hash and network configuration. |
Cleanup and repeated cleanup | Both passed. No active recorded or exact-run-tagged resources remain; the resource group is absent. The shared Network Watcher fingerprint is unchanged. |
Baseline passed at 10:10 UTC; fault validation at 10:16 UTC; recovery validation at 10:22 UTC and repeated recovery validation at 10:26 UTC. Raw responses, exact identifiers, ownership state and detailed timestamps remain private.
Safety tests: simulated versus live
Seven offline/local checks passed: wrong tenant/subscription, mismatched manifest ID, interrupted-operation guard, five-hour stop, rejection of a broad source CIDR, provisioning public-key format, and an actual exclusive file-lock contention test. Context/interruption checks use offline doubles; no live wrong-tenant mutation or deliberate mid-deployment process termination was performed.
The live ownership/configuration checks ran at each stage. An unexpected tag conversion was correctly blocked; the guard was not relaxed. A synthetic guard test is not proof of all possible production interruption or concurrent-edit scenarios.
Implementation corrections discovered during testing
Windows Azure CLI command-line handling initially returned a completed Run Command result with empty output when a multiline script was passed inline. No passing probe was inferred. The completed exact operation was reconciled, and the final implementation uses a private UTF-8 LF script file with the supported --scripts @file form. All reported HTTP checks used that corrected path.
The installed older Az.Network serializer converted an ISO-like expiry tag string while committing route removal. Ownership validation stopped. After confirming the precise conversion and unchanged ownership/associations, the exact original string was restored through native Get/Set-AzRouteTable and read back. The final recovery code explicitly preserves raw ARM tag strings before committing. Recovery and repeat validation then passed. The corrected tag-preserving Set operation was live-tested with the route already absent; the lab was not faulted a second time to claim another full cycle.
During initial cleanup, the final resource-group inventory still reported resources after their individual deletes had completed, so group deletion was refused. A subsequent read returned an empty resource list; a safe rerun completed cleanup, followed by a second passing cleanup. The inventory guard was not bypassed.
These are engineering corrections and service-propagation handling, not additional learner fault exercises. The workshop contains one deliberate routing fault.
Costs and limitations
The pre-deployment conservative six-hour retail estimate was EUR5.67: two B2s Linux VMs, two Standard public IPv4s, two full monthly E4 disk allowances and a EUR1 traffic contingency. The live estimate was below the EUR15 exercise allowance plus EUR5 cleanup reserve. This is not the actual Visual Studio offer rate or a final bill. Billing ingestion is delayed; no zero-cost claim is made.
Two Standard public IPs were deliberately deployed for explicit outbound access. Only synthetic web HTTP from the current operator /32 was allowed publicly. No public SSH/RDP, real application data, NAT Gateway, Bastion, load balancer, private endpoint, gateway or firewall appliance was deployed. The existing Network Watcher was not created by the lab.
Seven genuine Portal captures were privacy-cropped and masked, never AI-generated or AI-edited. The cover is AI-generated artwork, clearly separated from evidence. The scripts are Windows/PowerShell 7 oriented; macOS/Linux client execution was not tested. No actual-phone testing was performed. Publication layout/download checks are recorded separately in the release record after publication.
The release process rebuilds Bicep, checks all PowerShell syntax and ScriptAnalyzer Error diagnostics, scans public text for private run values, extracts the ZIP and verifies every file against SHA256SUMS.txt. The public ZIP must independently match the release SHA-256 before its download link is published.
Comments