Part 7 of 1633 min read · 6 diagramsAI-assisted

Networking: Private Access & Security

Table of Contents#

  1. Private Access and Network Security — Closing the Networking Series
  2. Network Security Groups — Rules, Priority, and Stateful Behavior
  3. Application Security Groups
  4. Evaluating Effective Security Rules
  5. NSG Flow Logs
  6. Azure Bastion — Browser-Based Remote Access
  7. Just-in-Time VM Access
  8. Service Endpoints
  9. Private Link and Private Endpoints — Architecture
  10. Private Link vs. Service Endpoints — Choosing
  11. Private Endpoint DNS Integration
  12. Private Link Service — Exposing Your Own Service Privately
  13. Network Security Perimeter — A Boundary Around Multiple PaaS Resources
  14. Azure Firewall — Architecture and SKUs
  15. Azure Firewall Rules — Network, Application, and NAT
  16. Azure Firewall Premium — TLS Inspection and IDPS
  17. Azure Firewall Manager and Policies
  18. Web Application Firewall — Custom Rules and Bot Protection
  19. WAF Bot Manager Rule Set and Managed Rule Tuning
  20. DDoS Protection — Basic vs. Standard
  21. Microsoft Defender for Cloud — Network Security Recommendations
  22. Cloud Security Explorer and Attack Path Analysis
  23. Zero Trust Network Architecture Principles
  24. A Full Worked Security Bootstrap for Meridian Freight
  25. Part 7 CLI Cheat Sheet
  26. Common Mistakes and Interview Traps
  27. Worked Practice Problems
  28. Summary and What's Next

Private Access and Network Security — Closing the Networking Series#

This chapter closes out the four-part networking arc (Parts 4-7) with the layer that locks everything else down: who can reach what, and how private connectivity to Azure's own PaaS services works without ever touching the public internet. Every VNet (Part 4), hybrid connection (Part 5), and application delivery service (Part 6) this series has built so far gets its actual security boundary defined here.

Diagram

Network Security Groups — Rules, Priority, and Stateful Behavior#

A Network Security Group (NSG) is a stateful Layer 3/4 firewall, applied to a subnet or a network interface, evaluating priority-ordered allow/deny rules.

az network nsg create --name nsg-shipment-api-app --resource-group rg-shipment-api-prod

az network nsg rule create --nsg-name nsg-shipment-api-app --resource-group rg-shipment-api-prod \
  --name allow-https-inbound --priority 100 --direction Inbound --access Allow \
  --protocol Tcp --destination-port-ranges 443 --source-address-prefixes VirtualNetwork

az network vnet subnet update --vnet-name vnet-shipment-api --resource-group rg-shipment-api-prod \
  --name snet-app --network-security-group nsg-shipment-api-app

Two mechanics worth stating precisely: rules are evaluated in PRIORITY order (lowest number first) and processing stops at the first match — a lower-priority-numbered Deny rule can silently make a higher-numbered Allow rule for the same traffic unreachable. NSGs are also stateful: an allowed inbound connection's return traffic is automatically permitted outbound without needing a matching outbound rule, and vice versa — the same behavior AWS Security Groups and GCP firewall rules both share.

Default ruleEffect
AllowVnetInbound/AllowVnetOutBoundTraffic within the VNet is allowed by default
AllowAzureLoadBalancerInBoundAzure's own load balancer health probes are allowed by default
DenyAllInBound/DenyAllOutBoundEverything else is denied by default — the lowest-priority, always-present catch-all

Application Security Groups#

An Application Security Group (ASG) lets an NSG rule reference VMs by ROLE rather than by hardcoded IP address — shipment-api's app-tier VMs join an asg-app-tier group, and NSG rules reference that group instead of a list of IPs that changes every time an instance scales.

az network asg create --name asg-app-tier --resource-group rg-shipment-api-prod

az network nic ip-config update --name ipconfig1 --nic-name nic-shipment-api-01 \
  --resource-group rg-shipment-api-prod --application-security-groups asg-app-tier

az network nsg rule create --nsg-name nsg-shipment-api-data --resource-group rg-shipment-api-prod \
  --name allow-app-to-db --priority 100 --direction Inbound --access Allow \
  --protocol Tcp --destination-port-ranges 5432 \
  --source-asgs asg-app-tier --destination-asgs asg-data-tier

Why this is worth treating as a real best practice rather than a cosmetic convenience: an NSG rule referencing asg-app-tier -> asg-data-tier remains correct automatically as instances scale in and out — a rule hardcoded to specific IPs would need manual updates every single time the app tier's instance set changed, a maintenance burden that compounds as the environment grows.


Evaluating Effective Security Rules#

A resource can have NSGs applied at BOTH the subnet level and the network interface level simultaneously — both apply, and reasoning about the combined effect by reading each NSG separately is genuinely error-prone.

az network nic list-effective-nsg --name nic-shipment-api-01 --resource-group rg-shipment-api-prod

# The equivalent check for a subnet's own effective NSG assignment
az network vnet subnet show --vnet-name vnet-shipment-api --resource-group rg-shipment-api-prod \
  --name snet-app --query "networkSecurityGroup.id"

Why this command matters concretely, echoing Part 4's test-ip-flow recommendation: it returns the single, authoritative, MERGED view of every rule actually in effect for that specific network interface — from both subnet-level and NIC-level NSGs combined — rather than requiring manual cross-referencing of two separate rule sets.


NSG Flow Logs#

NSG Flow Logs record every allowed and denied flow through an NSG — genuinely essential for both security investigation and network troubleshooting.

az network watcher flow-log create --name flowlog-shipment-api-app --resource-group rg-shipment-api-prod \
  --nsg nsg-shipment-api-app --storage-account stmeridianlogs \
  --workspace "<log-analytics-workspace-id>" --format JSON --log-version 2 --interval 10

# Confirm flow logging is actually enabled and check its retention setting
az network watcher flow-log show --name flowlog-shipment-api-app --resource-group rg-shipment-api-prod \
  --query "{enabled: enabled, retentionDays: retentionPolicy.days}"

Version 2 flow logs are worth specifying explicitly over the older version 1 format: version 2 adds throughput information (bytes/packets in each direction) that version 1 simply doesn't capture, which matters for genuinely understanding traffic volume, not just allow/deny outcomes — Part 13 covers analyzing this data via Log Analytics/Traffic Analytics in full depth.


Azure Bastion — Browser-Based Remote Access#

Azure Bastion provides RDP/SSH access to VMs through the Azure Portal over TLS, with the actual session initiated from inside the VNet to the VM's PRIVATE IP — no public IP on the target VM, no inbound NSG rule opening port 22/3389 to the internet, and no VPN client required.

az network bastion create --name bastion-meridian-hub --resource-group rg-networking-prod \
  --vnet-name vnet-meridian-hub --public-ip-address pip-bastion-meridian \
  --sku Standard

# Standard SKU specifically unlocks native client support (using a
# local SSH/RDP client through a tunnel) rather than browser-only access
az network bastion tunnel --name bastion-meridian-hub --resource-group rg-networking-prod \
  --target-resource-id "<target-vm-resource-id>" --resource-port 22 --port 2222

A genuinely important, easy-to-miss requirement worth stating precisely: Bastion needs its own dedicated subnet named exactly AzureBastionSubnet, at least a /26, in the SAME VNet as the target VMs (or a peered VNet) — the same non-negotiable exact-naming pattern Part 5's GatewaySubnet required. Combined with Part 3's earlier recommendation to prefer Bastion over inbound NAT rules for remote administration, this closes off the single most common attack surface — an open management port — entirely.


Just-in-Time VM Access#

A Defender for Cloud capability worth covering here, since it directly complements this chapter's Bastion recommendation for the specific case where a port genuinely does need to be open temporarily (a third-party diagnostic tool requiring direct RDP, for instance, rather than a use case Bastion itself covers): Just-in-Time (JIT) VM Access keeps a management port closed in the NSG by default, opening it only for a specific requester, a specific source IP, and a specific, limited time window.

az security jit-policy create --resource-group rg-driver-portal-prod --name jit-policy-driver-portal \
  --virtual-machines "[{\"id\":\"<vm-resource-id>\",\"ports\":[{\"number\":3389,\"protocol\":\"*\",\"allowedSourceAddressPrefix\":\"*\",\"maxRequestAccessDuration\":\"PT3H\"}]}]"

# A request to actually open access, time-boxed and logged
az security jit-policy initiate --resource-group rg-driver-portal-prod --name jit-policy-driver-portal \
  --justification "Vendor diagnostic session, ticket MF-4821" \
  --virtual-machines "[{\"id\":\"<vm-resource-id>\",\"ports\":[{\"number\":3389,\"allowedSourceAddressPrefix\":\"203.0.113.50\",\"endTimeUtc\":\"2026-09-01T18:00:00Z\"}]}]"

Why JIT is worth stating as a distinct control from Bastion, not a redundant overlap: Bastion is the correct default for ROUTINE administrative access, replacing open ports entirely — JIT is for the narrower case where a port genuinely must be open temporarily (third-party tooling requiring direct protocol access Bastion's tunnel doesn't support), keeping that exposure window as short and auditable as possible rather than leaving the port open indefinitely "just in case." Every JIT request requires an explicit justification, is logged, and automatically closes the port again once the time window expires — a structural fix for the common anti-pattern of a temporarily-opened port that never gets closed again once the original need has passed.


Service Endpoints#

A service endpoint extends a VNet's identity to a specific PaaS service, letting Azure evaluate traffic as coming FROM that VNet — but the traffic still reaches the service's PUBLIC IP; it never gets a private address.

az network vnet subnet update --vnet-name vnet-shipment-api --resource-group rg-shipment-api-prod \
  --name snet-app --service-endpoints Microsoft.Storage

# Pair the subnet-level endpoint with a resource-level firewall rule
# restricting the storage account to ONLY this specific subnet
az storage account network-rule add --account-name stmeridianfreight \
  --resource-group rg-shipment-api-prod --vnet-name vnet-shipment-api --subnet snet-app
Service EndpointPrivate Link/Private Endpoint
IP addressingService keeps its public IPService gets a PRIVATE IP inside the VNet
On-premises access (via VPN/ExpressRoute)No — Azure VNet traffic onlyYes
Setup complexityLow — a subnet-level toggleHigher — a dedicated resource with DNS integration
GranularityWhole-service (any storage account in the subscription, unless combined with a resource firewall rule)Per-resource — one specific storage account

Private Link injects a specific PaaS resource directly into a VNet with its own private IP — the single most important private-connectivity mechanism in Azure, and the mechanism Part 6's Front Door Premium origin security and this series' Key Vault access (Part 12) both build on.

Diagram
az network private-endpoint create --name pe-storage-shipment-api --resource-group rg-shipment-api-prod \
  --vnet-name vnet-shipment-api --subnet snet-private-endpoints \
  --private-connection-resource-id "<storage-account-resource-id>" \
  --group-id blob --connection-name conn-storage-shipment-api

# The resource owner must APPROVE the connection request (auto-approved
# only if the requester already has sufficient RBAC on the target resource)
az network private-endpoint-connection approve --resource-name stmeridianfreight \
  --resource-group rg-shipment-api-prod --type Microsoft.Storage/storageAccounts \
  --name conn-storage-shipment-api

Why a Private Endpoint's traffic "never touching the public internet" is worth stating precisely rather than just "more private": the connection uses Microsoft's own backbone network end to end, meaning a fully compromised or misconfigured public internet path is not even a THEORETICAL risk for that specific connection — genuinely different from a service endpoint, which still routes to a public IP, just from a recognized VNet source.


Diagram

Current Microsoft guidance, worth stating as the default recommendation rather than a nuanced "it depends": Private Endpoints are the recommended approach for new designs — service endpoints remain supported and simpler to configure, but Private Endpoints' per-resource granularity, on-premises reachability, and complete public-IP avoidance make them the stronger default for anything genuinely security-sensitive, which in practice is most production workloads.


Private Endpoint DNS Integration#

A Private Endpoint's private IP needs the resource's normal DNS name (stmeridianfreight.blob.core.windows.net) to actually resolve to THAT private IP, not the public one — otherwise clients keep reaching the public endpoint despite the Private Endpoint existing.

az network private-dns zone create --name "privatelink.blob.core.windows.net" \
  --resource-group rg-networking-prod

az network private-dns link vnet create --zone-name "privatelink.blob.core.windows.net" \
  --resource-group rg-networking-prod --name link-shipment-api \
  --virtual-network vnet-shipment-api --registration-enabled false

az network private-endpoint dns-zone-group create --endpoint-name pe-storage-shipment-api \
  --resource-group rg-shipment-api-prod --name default-zone-group \
  --private-dns-zone "privatelink.blob.core.windows.net" --zone-name blob

From the Trenches: A team created a Private Endpoint for a Storage Account, confirmed it showed as "Approved" and had a private IP assigned, and moved on — but the linked Private DNS zone was never created. Every client in the VNet kept resolving stmeridianfreight.blob.core.windows.net to its PUBLIC IP via ordinary internet DNS, meaning traffic continued flowing over the public internet exactly as before, with the Private Endpoint sitting entirely unused. The Private Endpoint resource existing correctly is necessary but not sufficient — DNS resolution actually pointing at it is the step that makes it take effect, and it's exactly the kind of "looks done in the portal" gap this chapter's earlier test-ip-flow-style verification habit exists to catch.


The other direction: Private Link Service lets Meridian Freight expose ITS OWN service (behind a Standard Load Balancer) to be consumed privately by another VNet — including one in a completely different subscription or tenant — via a Private Endpoint on the consumer's side.

az network private-link-service create --name pls-shipment-api-partner-access \
  --resource-group rg-shipment-api-prod --vnet-name vnet-shipment-api --subnet snet-pls \
  --lb-name lb-shipment-api-internal --lb-frontend-ip-configs fe-internal

Why this matters concretely for a B2B integration pattern like Meridian Freight granting a specific carrier partner private access to an internal API, without exposing it publicly at all: the partner's own VNet creates a Private Endpoint connecting to Meridian Freight's Private Link Service, with Meridian Freight explicitly approving the connection request — a deliberate, auditable, per-partner private connection, rather than either a public endpoint or a full VNet peering relationship that would expose far more than just the one intended service.

# Review and manage pending connection requests from partners
az network private-link-service connection list --service-name pls-shipment-api-partner-access \
  --resource-group rg-shipment-api-prod --query "[].{name:name, status:privateLinkServiceConnectionState.status}"

Network Security Perimeter — A Boundary Around Multiple PaaS Resources#

A genuinely newer, current capability worth knowing about explicitly: Network Security Perimeter (NSP) defines a logical boundary around a GROUP of PaaS resources (Storage, Key Vault, Service Bus, Event Hubs, AI Search among the currently supported services), restricting public access and managing cross-service communication at the perimeter level rather than per-resource.

Diagram
az network perimeter create --name nsp-shipment-api-data --resource-group rg-shipment-api-prod

az network perimeter association create --perimeter-name nsp-shipment-api-data \
  --resource-group rg-shipment-api-prod --name assoc-storage \
  --private-link-resource "<storage-account-resource-id>" --access-mode Enforced

Why this is worth treating as complementary to Private Link, not a replacement for it, worth stating precisely: Private Endpoints solve per-connection private access from a specific VNet to a specific resource; NSP solves a different problem — managing PUBLIC network access and cross-service trust across a whole GROUP of PaaS resources from one place, rather than configuring each resource's own firewall/public-access settings individually. A genuinely useful, current constraint worth knowing: as of mid-2026, new customers are capped at 200 rule elements per NSP profile — worth factoring into planning for an organization with a large number of resources needing perimeter membership.


Azure Firewall — Architecture and SKUs#

Azure Firewall is a fully managed, stateful network firewall — the natural centralization point for the hub-and-spoke UDR pattern Part 4 introduced.

az network firewall create --name fw-meridian-hub --resource-group rg-networking-prod \
  --vnet-name vnet-meridian-hub --sku AZFW_VNet --tier Standard
TierCapability
BasicCost-optimized, lower throughput, for smaller deployments
StandardFull L3-L7 filtering, threat intelligence-based filtering, FQDN filtering
PremiumEverything in Standard, plus TLS inspection and IDPS (next section)

Azure Firewall requires its own dedicated subnet, named exactly AzureFirewallSubnet — the third instance in this series of a hard, exact-name subnet requirement, alongside GatewaySubnet and AzureBastionSubnet.


Azure Firewall Rules — Network, Application, and NAT#

# Network rule — filters by IP/port, like a traditional firewall rule
az network firewall network-rule create --firewall-name fw-meridian-hub \
  --resource-group rg-networking-prod --collection-name allow-dns --name rule-dns \
  --protocols UDP --source-addresses 10.0.0.0/8 --destination-addresses 168.63.129.16 \
  --destination-ports 53 --priority 100 --action Allow

# Application rule — filters by FQDN, understanding the actual destination hostname
az network firewall application-rule create --firewall-name fw-meridian-hub \
  --resource-group rg-networking-prod --collection-name allow-updates --name rule-ubuntu-updates \
  --source-addresses 10.0.0.0/8 --target-fqdns "*.ubuntu.com" --protocols Http=80 Https=443 \
  --priority 200 --action Allow

# NAT rule — DNAT, translating an inbound public IP:port to an internal destination
az network firewall nat-rule create --firewall-name fw-meridian-hub \
  --resource-group rg-networking-prod --collection-name inbound-dnat --name rule-inbound-web \
  --source-addresses "*" --destination-addresses "<firewall-public-ip>" --destination-ports 443 \
  --translated-address 10.1.1.100 --translated-port 443 --protocols TCP --priority 100 --action Dnat

Azure Firewall Premium — TLS Inspection and IDPS#

az network firewall update --name fw-meridian-hub --resource-group rg-networking-prod --tier Premium

az network firewall policy update --name fw-policy-meridian --resource-group rg-networking-prod \
  --set intrusionDetection.mode=Alert
Diagram

Why TLS inspection is a prerequisite for IDPS to meaningfully inspect HTTPS traffic, worth stating precisely: without TLS inspection, IDPS can only see and analyze UNENCRYPTED traffic — the large majority of real traffic today is HTTPS, so IDPS alone, without TLS inspection, would miss most of what it's designed to catch. TLS inspection requires a valid intermediate CA certificate stored in Key Vault (Part 12) to generate per-connection certificates, and adds real, measurable latency from the decrypt/inspect/re-encrypt sequence — a genuine performance-vs-visibility tradeoff worth stating explicitly rather than treating as a free upgrade.


Azure Firewall Manager and Policies#

Firewall Manager centrally manages firewall policy across multiple firewall instances (one per hub, in a multi-region hub-and-spoke or Virtual WAN design) as reusable, inheritable policy objects rather than per-firewall configuration.

az network firewall policy create --name fw-policy-meridian --resource-group rg-networking-prod \
  --sku Premium

az network firewall policy rule-collection-group create --policy-name fw-policy-meridian \
  --resource-group rg-networking-prod --name rcg-application --priority 200

Why a shared policy object matters concretely for Meridian Freight's eventual multi-region design (Part 16): a rule change made once in the shared policy applies consistently to every firewall instance referencing it — a genuine "single source of truth" for firewall rules across regions, rather than manually keeping N separate firewall configurations in sync as the topology grows.

Firewall Threat Intelligence Filtering#

A Standard-tier-and-above capability worth naming explicitly: threat intelligence-based filtering automatically blocks traffic to/from IP addresses and domains Microsoft's own threat intelligence feed identifies as malicious, with no manual rule authoring required at all.

az network firewall update --name fw-meridian-hub --resource-group rg-networking-prod \
  --threat-intel-mode Deny
ModeBehavior
OffNo threat intelligence filtering applied
AlertLogs a match against the threat feed, but doesn't block
DenyActively blocks traffic matching the threat feed

Why starting in Alert mode before switching to Deny follows the same tuning discipline this chapter already recommended for WAF policies: confirming the threat feed doesn't produce false positives against Meridian Freight's own legitimate traffic patterns before actively blocking on its verdict — a genuinely rare but real occurrence when a legitimate service happens to share infrastructure with something flagged elsewhere.


Web Application Firewall — Custom Rules and Bot Protection#

Part 6 introduced WAF attaching to Application Gateway and Front Door; this section covers the policy-tuning depth that chapter deferred here.

az network application-gateway waf-policy custom-rule create --policy-name waf-policy-shipment-api \
  --resource-group rg-shipment-api-prod --name block-known-bad-country --priority 1 \
  --rule-type MatchRule --action Block \
  --match-conditions match-variables=RemoteAddr operator=GeoMatch match-values=CN
Bot categoryManaged rule behavior
Verified good bots (search engine crawlers)Allowed by default
Verified bad botsBlocked by default
Unknown botsConfigurable — log, block, or allow based on policy

Custom rules evaluate BEFORE managed rules — a custom Allow rule for a specific, known-legitimate high-volume caller (an internal monitoring service, for instance) can bypass a managed rule that would otherwise flag its traffic pattern as suspicious, without weakening the managed rule set for everyone else.


WAF Bot Manager Rule Set and Managed Rule Tuning#

Beyond the OWASP Core Rule Set, both Application Gateway and Front Door Premium support a dedicated Bot Manager rule set, maintained and updated by Microsoft to keep pace with evolving bot signatures — genuinely relevant for shipment-api's carrier-partner-facing endpoint given this chapter's earlier bot-abuse threat model discussion.

az network application-gateway waf-policy managed-rule rule-set add \
  --policy-name waf-policy-shipment-api --resource-group rg-shipment-api-prod \
  --type Microsoft_BotManagerRuleSet --version 1.0
Diagram

Why keeping the Bot Manager rule set updated matters concretely, worth stating explicitly rather than treating a WAF policy as configure-once: Microsoft updates the underlying bot signature database continuously as new bot patterns emerge — a WAF policy pinned to an old rule set version misses detection for genuinely new bot behavior that didn't exist when the policy was last touched. Reviewing and updating the managed rule set version periodically (not just at initial setup) is a real, ongoing operational task, not a one-time configuration step.

Log Analysis for WAF Tuning#

# Query WAF logs in Log Analytics (Part 13) for the most frequently
# triggered rules — the practical starting point for tuning exclusions
az monitor log-analytics query --workspace "<workspace-id>" --analytics-query "
AzureDiagnostics
| where Category == 'ApplicationGatewayFirewallLog'
| summarize count() by ruleId_s, action_s
| order by count_ desc
"

Reviewing which specific rules trigger MOST frequently (not just whether the WAF is "working") is the practical starting point for tuning — a rule triggering thousands of times against legitimate traffic patterns is a strong signal for the scoped-exclusion approach this chapter already recommended, while a rule that never triggers might indicate a gap in traffic actually reaching the expected protection path at all.


DDoS Protection — Basic vs. Standard#

az network ddos-protection create --name ddos-plan-meridian --resource-group rg-networking-prod

az network vnet update --name vnet-meridian-hub --resource-group rg-networking-prod \
  --ddos-protection true --ddos-protection-plan ddos-plan-meridian
TierCostCoverageMitigation reports/support
BasicFree, always onAutomatic, platform-wide baseline protectionNo custom reporting or dedicated support
StandardPaid, per-protected-VNetTuned to the specific VNet's traffic patternsDetailed mitigation reports, cost protection for scaled-out resources during an attack, DDoS Rapid Response support

Why Standard's "cost protection" is worth naming specifically, a real and underappreciated benefit: if a DDoS attack causes a workload to autoscale dramatically (Part 3) to absorb the load, Standard Protection credits the resulting cost spike — a real, concrete financial protection beyond just the mitigation itself, worth factoring into the cost-benefit case for upgrading from Basic.

# Review a past mitigation report after a detected attack —
# genuinely useful for a post-incident review (Incident Management series)
az network ddos-protection show --name ddos-plan-meridian --resource-group rg-networking-prod \
  --query "virtualNetworks"

Microsoft Defender for Cloud — Network Security Recommendations#

A brief preview ahead of Part 12's full treatment: Defender for Cloud continuously evaluates network configuration against security best practices, surfacing specific, actionable recommendations.

az security assessment list --query "[?contains(displayName, 'network')].{name:displayName, status:status.code}"

Common network-specific findings include internet-exposed management ports, subnets without an NSG attached at all, and Private Endpoints not fully DNS-integrated — directly catching the exact "From the Trenches" DNS integration gap covered earlier in this chapter, systematically rather than by chance discovery.


Cloud Security Explorer and Attack Path Analysis#

Also previewed briefly ahead of Part 12: Cloud Security Explorer lets a security team query the ENTIRE cloud estate's resource graph combined with security context — "show me every internet-exposed VM with a path to a database containing sensitive data" — and attack path analysis automatically surfaces the most exploitable chains of misconfigurations an attacker could realistically walk through.

Why this matters conceptually for this chapter's own content, worth stating explicitly: an individual NSG rule, Private Endpoint, or Firewall policy can each look correct in isolation while still combining into an exploitable path — attack path analysis is what catches the COMBINATION a rule-by-rule review might miss entirely.


Zero Trust Network Architecture Principles#

A closing conceptual frame worth stating explicitly, tying this entire chapter together: Zero Trust assumes no network location — not even "inside the VNet" — is inherently trustworthy, and every access request should be explicitly verified regardless of source.

Traditional perimeter modelZero Trust model
"Inside the VNet" is implicitly trustedEvery connection is verified explicitly, VNet membership alone grants nothing
A single strong perimeter (the firewall) is the main controlDefense in depth — NSGs, Private Link, identity (Part 2), and the firewall all independently enforce
Lateral movement inside the network is often easy once perimeter is breachedMicro-segmentation (ASGs, per-tier NSGs) limits lateral movement even after a single component is compromised

This chapter's own recommendations already embody Zero Trust in practice: end-to-end TLS (Part 6) over trusting the internal network segment, Private Link over "it's just VNet-internal traffic," and per-tier NSG/ASG segmentation over one broad "allow VNet-internal" rule.

# A micro-segmented NSG rule set, per tier, rather than one broad
# "allow all VNet-internal traffic" rule — the practical embodiment
# of Zero Trust's "verify explicitly" principle at the network layer
az network nsg rule create --nsg-name nsg-shipment-api-data --resource-group rg-shipment-api-prod \
  --name deny-all-other-inbound --priority 4096 --direction Inbound --access Deny \
  --protocol "*" --source-address-prefixes VirtualNetwork --destination-port-ranges "*"

Adding an explicit, low-priority Deny rule for "everything else from the VNet" — rather than relying purely on the NSG's own implicit DenyAllInBound default — makes the segmentation intent visible and auditable directly in the rule list, rather than depending on a reviewer already knowing the platform's implicit default behavior.


A Full Worked Security Bootstrap for Meridian Freight#

# 1. NSGs with ASG-based rules for each tier
az network nsg create --name nsg-shipment-api-app --resource-group rg-shipment-api-prod
az network asg create --name asg-app-tier --resource-group rg-shipment-api-prod

# 2. Azure Bastion for all remote administration — no inbound NAT rules
az network bastion create --name bastion-meridian-hub --resource-group rg-networking-prod \
  --vnet-name vnet-meridian-hub --public-ip-address pip-bastion-meridian --sku Standard

# 3. Private Endpoints (with matching Private DNS zones) for every PaaS
#    dependency — Storage, Key Vault, databases
az network private-endpoint create --name pe-storage-shipment-api --resource-group rg-shipment-api-prod \
  --vnet-name vnet-shipment-api --subnet snet-private-endpoints \
  --private-connection-resource-id "<storage-account-resource-id>" --group-id blob

# 4. Azure Firewall Premium in the hub, with routing intent (Part 5) or
#    manual UDRs (Part 4) forcing all spoke traffic through it
az network firewall create --name fw-meridian-hub --resource-group rg-networking-prod \
  --vnet-name vnet-meridian-hub --sku AZFW_VNet --tier Premium

# 5. DDoS Standard Protection on the hub VNet
az network ddos-protection create --name ddos-plan-meridian --resource-group rg-networking-prod
az network vnet update --name vnet-meridian-hub --resource-group rg-networking-prod \
  --ddos-protection true --ddos-protection-plan ddos-plan-meridian

# 6. Enable NSG flow logs on every NSG for both security and troubleshooting visibility
az network watcher flow-log create --name flowlog-shipment-api-app --resource-group rg-shipment-api-prod \
  --nsg nsg-shipment-api-app --storage-account stmeridianlogs --format JSON --log-version 2

Part 7 CLI Cheat Sheet#

AreaCommandPurpose
NSGaz network nsg create / rule createCreate an NSG and its rules
ASGaz network asg createCreate an Application Security Group
Effective rulesaz network nic list-effective-nsgGet the authoritative, merged rule set for a NIC
Flow logsaz network watcher flow-log createEnable NSG flow logging
Bastionaz network bastion createDeploy browser-based remote access
Private Endpointaz network private-endpoint createCreate a Private Endpoint for a PaaS resource
Private DNSaz network private-endpoint dns-zone-group createIntegrate a Private Endpoint with DNS
Private Link Serviceaz network private-link-service createExpose your own service privately
Firewallaz network firewall createDeploy Azure Firewall
Firewall rulesaz network firewall network-rule create / application-rule create / nat-rule createCreate firewall rules
Firewall policyaz network firewall policy createCreate a shared, reusable firewall policy
DDoSaz network ddos-protection createCreate a DDoS Standard Protection plan
NSPaz network perimeter createCreate a Network Security Perimeter
NSPaz network perimeter association createAdd a PaaS resource to a perimeter
WAF custom ruleaz network application-gateway waf-policy custom-rule createCreate a custom WAF rule
Attack pathsaz security assessment listList Defender for Cloud security assessments
JIT accessaz security jit-policy create / initiateDefine and request time-boxed port access
Bot Manageraz network application-gateway waf-policy managed-rule rule-set add --type Microsoft_BotManagerRuleSetAdd the Bot Manager rule set
WAF logsaz monitor log-analytics queryQuery WAF logs for rule-trigger frequency

Common Mistakes and Interview Traps#

MistakeWhy It's WrongFix
Hardcoding NSG rules to specific VM IPsBreaks or drifts as instances scale in/outUse Application Security Groups referencing VMs by role
Reading subnet-level and NIC-level NSGs separately to reason about combined effectGenuinely error-prone — both apply simultaneouslyUse az network nic list-effective-nsg for the authoritative merged view
Opening inbound NAT rules for SSH/RDP as the default remote access patternExposes a management port, even if narrowly scopedUse Azure Bastion — no public IP or open management port on the target VM at all
Creating a Private Endpoint without a linked Private DNS zoneClients keep resolving the public IP — the Private Endpoint sits unusedAlways pair a Private Endpoint with its matching Private DNS zone integration
Choosing service endpoints when on-premises (VPN/ExpressRoute) access is neededService endpoints only recognize Azure VNet traffic, never on-premisesUse Private Endpoints for any scenario needing on-premises reachability
Enabling Azure Firewall Premium's IDPS without also enabling TLS inspectionIDPS can only inspect unencrypted traffic — most real traffic is HTTPSEnable TLS inspection alongside IDPS to get meaningful HTTPS visibility
Assuming "it's internal VNet traffic" is inherently safeContradicts Zero Trust's core assumption — no location is implicitly trustedApply the same segmentation and verification principles to internal traffic as external
Reviewing NSG/firewall/Private Link configuration rule-by-rule without checking for combined exploitable pathsIndividually correct rules can still combine into an exploitable attack pathUse Defender for Cloud's attack path analysis to catch combination risks a rule-by-rule review misses
Configuring per-resource firewall settings individually across many related PaaS resourcesDoesn't scale and drifts as resources are addedUse a Network Security Perimeter to manage public access and cross-service trust from one place
Treating Network Security Perimeter as a replacement for Private LinkThey solve different problems — per-connection private access vs. group-level public access managementUse both together: Private Link for specific private connections, NSP for perimeter-wide public access control
Opening a management port temporarily for a one-off need and forgetting to close itLeaves a real, unnecessary exposure window open indefinitelyUse Just-in-Time VM Access for temporary needs Bastion doesn't cover — it closes automatically
Configuring the Bot Manager rule set once and never revisiting its versionMisses detection for newly emerged bot patterns Microsoft's updated signature database would otherwise catchPeriodically review and update the managed rule set version as an ongoing operational task

Worked Practice Problems#

Problem 1: A security team creates a Private Endpoint for shipment-api's Storage Account, confirms it shows "Approved" with a private IP assigned in the Azure Portal, and closes the ticket as complete. A week later, a network review finds Storage Account traffic is still traversing the public internet. What was skipped, and how should it have been verified?

Answer: The linked Private DNS zone integration was skipped — a Private Endpoint resource existing and showing "Approved" is necessary but not sufficient for it to actually take effect; the resource's normal DNS name still needs to resolve to the Private Endpoint's private IP instead of the public one, which requires an explicit Private DNS zone linked to the VNet and integrated with the Private Endpoint. Verification should not have stopped at the portal's "Approved" status — running nslookup (or an equivalent DNS query) for the Storage Account's hostname from a VM inside the VNet, and confirming it resolves to the private IP rather than the public one, is the actual proof the Private Endpoint is taking effect.

Problem 2: Meridian Freight enables Azure Firewall Premium specifically for its IDPS capability, expecting comprehensive threat detection across all outbound traffic, but a security audit later finds the vast majority of actual malicious traffic patterns in test scenarios go undetected. What's the likely configuration gap?

Answer: TLS inspection was very likely not enabled alongside IDPS. Without TLS inspection, IDPS can only analyze unencrypted traffic, and since the large majority of real-world traffic today is HTTPS, IDPS alone provides only partial visibility — it's effectively blind to threats embedded in encrypted traffic, which is most traffic. The fix is enabling TLS inspection (requiring a valid intermediate CA certificate stored in Key Vault) alongside IDPS, accepting the real, measurable latency cost of the decrypt/inspect/re-encrypt sequence as the necessary tradeoff for IDPS to meaningfully cover HTTPS traffic rather than just the small remaining fraction of plaintext traffic.

Problem 3: A platform team debates whether shipment-api's connection to its Storage Account should use a service endpoint or a Private Endpoint, and a team member argues "service endpoints are simpler to set up and traffic still stays on the Azure backbone, so they're good enough." Evaluate this reasoning against Meridian Freight's stated requirement that carrier-partner integrations reachable via ExpressRoute (Part 5) must also be able to reach this same Storage Account privately.

Answer: The reasoning is correct that service endpoints keep traffic on the Azure backbone rather than the public internet, but it misses a hard, disqualifying constraint for this specific requirement: service endpoints only recognize traffic originating from an Azure VNet — they explicitly do NOT support on-premises access via VPN or ExpressRoute. Since Meridian Freight's requirement specifically needs ExpressRoute-connected carrier partners to reach the Storage Account privately, a service endpoint cannot satisfy this at all, regardless of its other merits. A Private Endpoint is the only option that supports both VNet-based AND on-premises/ExpressRoute-based private access to the same resource, making it the correct choice here — not because service endpoints are inferior in general, but because this specific requirement falls squarely into the gap service endpoints cannot cover.

Problem 4: Meridian Freight wants to grant a specific carrier partner private, non-public access to one internal API, without exposing the API publicly and without granting the partner's network broad VNet peering access to Meridian Freight's entire hub-and-spoke topology. What Azure mechanism fits this requirement precisely, and why would VNet peering be the wrong choice here?

Answer: Private Link Service is the precise fit — Meridian Freight exposes the specific internal API (behind its Standard Load Balancer) as a Private Link Service, and the partner creates a Private Endpoint in their own VNet connecting to it, with Meridian Freight explicitly approving the specific connection request. This grants access to exactly ONE service, nothing more. VNet peering would be the wrong choice because it establishes full network-level connectivity between the two VNets' entire address spaces — the partner's network would gain a routable path to every other resource in the peered VNet (and, depending on topology, potentially reachable hub resources too), vastly exceeding the "access to one specific API" requirement and creating unnecessary, hard-to-audit exposure.

Problem 5: A security review of Meridian Freight's environment finds each individual NSG rule, Private Endpoint configuration, and Firewall policy passes its own isolated compliance check, yet the review still flags an overall "high risk" finding. The platform team is confused, arguing "every individual control is configured correctly — what's the actual problem?" What's the likely explanation, and what tool would surface it directly?

Answer: The likely explanation is a genuine attack path — a combination of individually-correct configurations that together create an exploitable chain a rule-by-rule review cannot see, since each control is being evaluated in isolation rather than as part of the full resource graph. A concrete example matching this chapter's content: an NSG correctly allows only necessary traffic, a Private Endpoint is correctly configured, but a Firewall rule elsewhere in the path might correctly allow broad outbound access that, combined with an over-permissive RBAC assignment (Part 2) on the same resource, creates a genuine path from an internet-exposed entry point to sensitive data. Cloud Security Explorer's attack path analysis is the tool that surfaces this directly — it evaluates the cloud estate's full resource graph together with security context, rather than checking each control against its own isolated compliance baseline, which is exactly the gap a per-control review structurally cannot close.

Problem 6: Meridian Freight's data platform team manages five related PaaS resources (a Storage Account, Key Vault, and three Service Bus namespaces) that all need to communicate with each other but should have no public internet access at all. The team considers configuring each resource's own public network access and firewall rules individually. What's a more scalable alternative, and why?

Answer: A Network Security Perimeter is the more scalable alternative — rather than configuring public access and cross-service trust rules on each of the five resources individually (and keeping all five in sync as requirements change), the team defines one NSP, associates all five resources with it, and manages public access denial and inter-resource trust at the perimeter level in one place. This directly avoids the maintenance burden and drift risk of five separate, individually-managed configurations, the same "single source of truth" argument this chapter already made for Firewall Manager's shared policies across multiple firewall instances.

Problem 7: An architect proposes using Network Security Perimeter as a full replacement for Private Link across Meridian Freight's environment, arguing "NSP handles access control, so we don't need Private Endpoints anymore." Evaluate this proposal.

Answer: This proposal conflates two mechanisms solving different problems. Network Security Perimeter manages public network access and cross-service trust across a GROUP of PaaS resources — it's about denying public access and controlling which perimeter members can talk to each other. Private Link/Private Endpoints solve a different, complementary problem: giving a SPECIFIC VNet a private, dedicated network path (with a private IP) to a SPECIFIC resource, including from on-premises via VPN/ExpressRoute — something NSP does not provide on its own. Removing Private Endpoints in favor of NSP alone would lose per-VNet private connectivity and on-premises reachability entirely; the correct architecture uses both together — NSP to lock down public access at the perimeter level, Private Link for the specific private connections that still need to reach resources both inside and outside that perimeter.

Problem 8: A vendor performing a one-time diagnostic session on a driver-portal VM requests direct RDP access. An engineer opens an inbound NSG rule allowing RDP from the vendor's IP, intending to remove it after the session. Three months later, a security audit finds the rule still open, though the vendor engagement ended weeks earlier. What structural fix would have prevented this specific recurrence?

Answer: Just-in-Time VM Access is the structural fix — rather than manually opening an NSG rule and relying on someone remembering to close it afterward, a JIT request opens the port only for a specific, time-boxed window (with a required justification, logged for audit) and closes it automatically once that window expires, with no manual cleanup step for anyone to forget. This is exactly the kind of "temporary" exception that manual processes reliably fail to clean up over time — the same class of problem this chapter's earlier PIM discussion (Part 2) solved for privileged role assignments, applied here to network-level port exposure instead.

Problem 9: Six months after deploying WAF Bot Manager on shipment-api's Front Door endpoint, a security review finds a new category of scraping bot — one not recognized by the rule set version deployed at initial setup — is successfully evading detection entirely. The rule set has not been touched since initial configuration. What's the root cause, and what should the team's ongoing process include?

Answer: The root cause is treating the Bot Manager rule set as a configure-once setting rather than a continuously maintained control — Microsoft updates the underlying bot signature database on an ongoing basis as new bot patterns emerge, and a policy pinned to an old rule set version simply has no visibility into bot behavior that didn't exist when that version was current. The team's ongoing process should include a periodic (e.g. quarterly) review and update of the managed rule set version, treated the same way OS patching (Part 3) is treated — a recurring operational task, not a one-time setup step that's considered permanently done once configured.


Summary and What's Next#

  • NSGs are stateful, priority-ordered, and evaluated at both subnet and NIC level simultaneouslyaz network nic list-effective-nsg gives the authoritative merged view, avoiding error-prone manual cross-referencing.
  • Application Security Groups let rules reference VMs by role, not IP — the correct default over hardcoded IP-based rules for anything that scales.
  • Azure Bastion eliminates open management ports entirely — the correct default over inbound NAT rules for SSH/RDP access.
  • Private Endpoints are Microsoft's current recommended default over service endpoints for new designs — per-resource granularity, on-premises reachability, and complete public-IP avoidance, at the cost of needing explicit Private DNS zone integration to actually take effect.
  • Azure Firewall Premium's IDPS requires TLS inspection to meaningfully cover HTTPS traffic — without it, IDPS only sees the small remaining fraction of unencrypted traffic.
  • Zero Trust assumes no network location is inherently trusted — this chapter's own recommendations (Private Link over "just VNet traffic," end-to-end TLS, ASG-based micro-segmentation) all embody that principle directly.
  • Individually correct controls can still combine into an exploitable attack path — Cloud Security Explorer's attack path analysis catches combinations a rule-by-rule review structurally cannot.
  • Network Security Perimeter manages public access and cross-service trust across a group of PaaS resources — complementary to Private Link, not a replacement for its per-VNet, on-premises-reachable private connections.
  • Just-in-Time VM Access closes the "temporary port opening that never gets closed" gap structurally — the same pattern PIM (Part 2) applies to privileged roles, applied here to network exposure.
  • WAF Bot Manager and managed rule sets need periodic version updates, not just initial configuration — Microsoft's bot signature database evolves continuously, and a stale rule set misses newly emerged patterns.

Continue to Part 8 (08-storage-blob-files-and-disks.md) — this series' four-part networking arc is complete; Part 8 moves to Azure's storage layer, which every Private Endpoint pattern in this chapter has been quietly assuming exists.