# Azure Cloud Architecture — Part 7: Networking: Private Access & Security

> **Series:** Azure Cloud Architecture (7 of 16)
> **Part 1:** `01-fundamentals-and-governance.md` — Fundamentals & Governance
> **Part 2:** `02-identity-and-access.md` — Identity & Access
> **Part 3:** `03-compute-vms-and-scale-sets.md` — Compute: Virtual Machines & Scale Sets
> **Part 4:** `04-networking-foundations-vnets-ip-and-dns.md` — Networking Foundations: VNets, IP & DNS
> **Part 5:** `05-networking-hybrid-connectivity.md` — Networking: Hybrid Connectivity
> **Part 6:** `06-networking-application-delivery.md` — Networking: Application Delivery
> **Part 7:** This file — Networking: Private Access & Security
> **Part 8:** `08-storage-blob-files-and-disks.md` — Storage: Blob, Files & Disks
> **Part 9:** `09-databases-and-data-services.md` — Databases & Data Services
> **Part 10:** `10-containers-and-serverless.md` — Containers & Serverless
> **Part 11:** `11-application-architecture-and-messaging.md` — Application Architecture & Messaging
> **Part 12:** `12-security-and-compliance.md` — Security & Compliance
> **Part 13:** `13-monitoring-logging-and-observability.md` — Monitoring, Logging & Observability
> **Part 14:** `14-business-continuity-backup-dr-and-migration.md` — Business Continuity: Backup, DR & Migration
> **Part 15:** `15-cicd-and-iac.md` — CI/CD & Infrastructure as Code
> **Part 16:** `16-multi-region-cost-optimization-and-cheatsheet.md` — Multi-Region, Cost Optimization & Cheat Sheet
> **Questions:** `questions.md`

## Table of Contents

1. [Private Access and Network Security — Closing the Networking Series](#private-access-and-network-security--closing-the-networking-series)
2. [Network Security Groups — Rules, Priority, and Stateful Behavior](#network-security-groups--rules-priority-and-stateful-behavior)
3. [Application Security Groups](#application-security-groups)
4. [Evaluating Effective Security Rules](#evaluating-effective-security-rules)
5. [NSG Flow Logs](#nsg-flow-logs)
6. [Azure Bastion — Browser-Based Remote Access](#azure-bastion--browser-based-remote-access)
7. [Just-in-Time VM Access](#just-in-time-vm-access)
8. [Service Endpoints](#service-endpoints)
9. [Private Link and Private Endpoints — Architecture](#private-link-and-private-endpoints--architecture)
10. [Private Link vs. Service Endpoints — Choosing](#private-link-vs-service-endpoints--choosing)
11. [Private Endpoint DNS Integration](#private-endpoint-dns-integration)
12. [Private Link Service — Exposing Your Own Service Privately](#private-link-service--exposing-your-own-service-privately)
13. [Network Security Perimeter — A Boundary Around Multiple PaaS Resources](#network-security-perimeter--a-boundary-around-multiple-paas-resources)
14. [Azure Firewall — Architecture and SKUs](#azure-firewall--architecture-and-skus)
15. [Azure Firewall Rules — Network, Application, and NAT](#azure-firewall-rules--network-application-and-nat)
16. [Azure Firewall Premium — TLS Inspection and IDPS](#azure-firewall-premium--tls-inspection-and-idps)
17. [Azure Firewall Manager and Policies](#azure-firewall-manager-and-policies)
18. [Web Application Firewall — Custom Rules and Bot Protection](#web-application-firewall--custom-rules-and-bot-protection)
19. [WAF Bot Manager Rule Set and Managed Rule Tuning](#waf-bot-manager-rule-set-and-managed-rule-tuning)
20. [DDoS Protection — Basic vs. Standard](#ddos-protection--basic-vs-standard)
21. [Microsoft Defender for Cloud — Network Security Recommendations](#microsoft-defender-for-cloud--network-security-recommendations)
22. [Cloud Security Explorer and Attack Path Analysis](#cloud-security-explorer-and-attack-path-analysis)
23. [Zero Trust Network Architecture Principles](#zero-trust-network-architecture-principles)
24. [A Full Worked Security Bootstrap for Meridian Freight](#a-full-worked-security-bootstrap-for-meridian-freight)
25. [Part 7 CLI Cheat Sheet](#part-7-cli-cheat-sheet)
26. [Common Mistakes and Interview Traps](#common-mistakes-and-interview-traps)
27. [Worked Practice Problems](#worked-practice-problems)
28. [Summary and What's Next](#summary-and-whats-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.

```mermaid
graph TD
    NSG["Network Security Groups\n(traffic filtering)"] --> Boundary["The security boundary\naround Meridian Freight's\nAzure workloads"]
    PrivateLink["Private Link\n(private PaaS access)"] --> Boundary
    Firewall["Azure Firewall\n(centralized inspection)"] --> Boundary
    WAF["Web Application Firewall\n(Layer 7 threat protection)"] --> Boundary
    DDoS["DDoS Protection"] --> Boundary
```

---

## 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.

```bash
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 rule | Effect |
|---|---|
| `AllowVnetInbound`/`AllowVnetOutBound` | Traffic within the VNet is allowed by default |
| `AllowAzureLoadBalancerInBound` | Azure's own load balancer health probes are allowed by default |
| `DenyAllInBound`/`DenyAllOutBound` | Everything 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.

```bash
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.

```bash
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.

```bash
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.

```bash
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.

```bash
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.

```bash
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 Endpoint | Private Link/Private Endpoint |
|---|---|---|
| IP addressing | Service keeps its public IP | Service gets a PRIVATE IP inside the VNet |
| On-premises access (via VPN/ExpressRoute) | No — Azure VNet traffic only | Yes |
| Setup complexity | Low — a subnet-level toggle | Higher — a dedicated resource with DNS integration |
| Granularity | Whole-service (any storage account in the subscription, unless combined with a resource firewall rule) | Per-resource — one specific storage account |

---

## Private Link and Private Endpoints — Architecture

**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.

```mermaid
graph TD
    VM["VM in vnet-shipment-api"] --> PE["Private Endpoint\n(private IP: 10.1.2.10)"]
    PE -.->|"Private Link —\nnever touches\nthe public internet"| Storage["Storage Account\n(multi-tenant PaaS,\nbut THIS connection\nis fully private)"]
```

```bash
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.**

---

## Private Link vs. Service Endpoints — Choosing

```mermaid
quadrantChart
    title Setup complexity vs. isolation strength
    x-axis Low complexity --> High complexity
    y-axis Weaker isolation --> Stronger isolation
    quadrant-1 Strong isolation, real effort
    quadrant-2 Simple and reasonably strong
    quadrant-3 Rarely the right default
    quadrant-4 Complex without matching benefit
    "No restriction": [0.1, 0.1]
    "Service Endpoint": [0.3, 0.5]
    "Private Endpoint": [0.7, 0.9]
```

**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.

```bash
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.

---

## Private Link Service — Exposing Your Own Service Privately

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.

```bash
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.

```bash
# 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.

```mermaid
graph TD
    NSP["Network Security Perimeter"] --> Storage["Storage Account"]
    NSP --> KeyVault["Key Vault"]
    NSP --> ServiceBus["Service Bus namespace"]
    Storage <-.->|"Allowed WITHIN\nthe perimeter"| KeyVault
    KeyVault <-.->|"Allowed WITHIN\nthe perimeter"| ServiceBus
    External["External/public access"] -.->|"Denied by\ndefault"| NSP
```

```bash
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.

```bash
az network firewall create --name fw-meridian-hub --resource-group rg-networking-prod \
  --vnet-name vnet-meridian-hub --sku AZFW_VNet --tier Standard
```

| Tier | Capability |
|---|---|
| Basic | Cost-optimized, lower throughput, for smaller deployments |
| Standard | Full L3-L7 filtering, threat intelligence-based filtering, FQDN filtering |
| Premium | Everything 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

```bash
# 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

```bash
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
```

```mermaid
sequenceDiagram
    participant Client as Internal client
    participant FW as Azure Firewall Premium
    participant Dest as External HTTPS destination
    Client->>FW: HTTPS request (encrypted)
    FW->>FW: Decrypt using intermediate CA cert from Key Vault
    FW->>FW: IDPS inspects DECRYPTED content
    FW->>FW: Re-encrypt
    FW->>Dest: Forward re-encrypted request
```

**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.

```bash
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.

```bash
az network firewall update --name fw-meridian-hub --resource-group rg-networking-prod \
  --threat-intel-mode Deny
```

| Mode | Behavior |
|---|---|
| Off | No threat intelligence filtering applied |
| Alert | Logs a match against the threat feed, but doesn't block |
| Deny | Actively 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.

```bash
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 category | Managed rule behavior |
|---|---|
| Verified good bots (search engine crawlers) | Allowed by default |
| Verified bad bots | Blocked by default |
| Unknown bots | Configurable — 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.

```bash
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
```

```mermaid
graph TD
    Request["Incoming request"] --> BotCheck["Bot Manager rule set\nclassifies the request"]
    BotCheck -->|"Verified good bot\n(search engine crawler)"| Allow["Allowed"]
    BotCheck -->|"Verified bad bot"| Block["Blocked"]
    BotCheck -->|"Unknown/unverified"| Policy["Configurable:\nlog, challenge, or block\nper policy"]
```

**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

```bash
# 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

```bash
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
```

| Tier | Cost | Coverage | Mitigation reports/support |
|---|---|---|---|
| Basic | Free, always on | Automatic, platform-wide baseline protection | No custom reporting or dedicated support |
| Standard | Paid, per-protected-VNet | Tuned to the specific VNet's traffic patterns | Detailed 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.

```bash
# 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.

```bash
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 model | Zero Trust model |
|---|---|
| "Inside the VNet" is implicitly trusted | Every connection is verified explicitly, VNet membership alone grants nothing |
| A single strong perimeter (the firewall) is the main control | Defense 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 breached | Micro-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.

```bash
# 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

```bash
# 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

| Area | Command | Purpose |
|---|---|---|
| NSG | `az network nsg create` / `rule create` | Create an NSG and its rules |
| ASG | `az network asg create` | Create an Application Security Group |
| Effective rules | `az network nic list-effective-nsg` | Get the authoritative, merged rule set for a NIC |
| Flow logs | `az network watcher flow-log create` | Enable NSG flow logging |
| Bastion | `az network bastion create` | Deploy browser-based remote access |
| Private Endpoint | `az network private-endpoint create` | Create a Private Endpoint for a PaaS resource |
| Private DNS | `az network private-endpoint dns-zone-group create` | Integrate a Private Endpoint with DNS |
| Private Link Service | `az network private-link-service create` | Expose your own service privately |
| Firewall | `az network firewall create` | Deploy Azure Firewall |
| Firewall rules | `az network firewall network-rule create` / `application-rule create` / `nat-rule create` | Create firewall rules |
| Firewall policy | `az network firewall policy create` | Create a shared, reusable firewall policy |
| DDoS | `az network ddos-protection create` | Create a DDoS Standard Protection plan |
| NSP | `az network perimeter create` | Create a Network Security Perimeter |
| NSP | `az network perimeter association create` | Add a PaaS resource to a perimeter |
| WAF custom rule | `az network application-gateway waf-policy custom-rule create` | Create a custom WAF rule |
| Attack paths | `az security assessment list` | List Defender for Cloud security assessments |
| JIT access | `az security jit-policy create` / `initiate` | Define and request time-boxed port access |
| Bot Manager | `az network application-gateway waf-policy managed-rule rule-set add --type Microsoft_BotManagerRuleSet` | Add the Bot Manager rule set |
| WAF logs | `az monitor log-analytics query` | Query WAF logs for rule-trigger frequency |

---

## Common Mistakes and Interview Traps

| Mistake | Why It's Wrong | Fix |
|---|---|---|
| Hardcoding NSG rules to specific VM IPs | Breaks or drifts as instances scale in/out | Use Application Security Groups referencing VMs by role |
| Reading subnet-level and NIC-level NSGs separately to reason about combined effect | Genuinely error-prone — both apply simultaneously | Use `az network nic list-effective-nsg` for the authoritative merged view |
| Opening inbound NAT rules for SSH/RDP as the default remote access pattern | Exposes a management port, even if narrowly scoped | Use Azure Bastion — no public IP or open management port on the target VM at all |
| Creating a Private Endpoint without a linked Private DNS zone | Clients keep resolving the public IP — the Private Endpoint sits unused | Always pair a Private Endpoint with its matching Private DNS zone integration |
| Choosing service endpoints when on-premises (VPN/ExpressRoute) access is needed | Service endpoints only recognize Azure VNet traffic, never on-premises | Use Private Endpoints for any scenario needing on-premises reachability |
| Enabling Azure Firewall Premium's IDPS without also enabling TLS inspection | IDPS can only inspect unencrypted traffic — most real traffic is HTTPS | Enable TLS inspection alongside IDPS to get meaningful HTTPS visibility |
| Assuming "it's internal VNet traffic" is inherently safe | Contradicts Zero Trust's core assumption — no location is implicitly trusted | Apply 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 paths | Individually correct rules can still combine into an exploitable attack path | Use 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 resources | Doesn't scale and drifts as resources are added | Use a Network Security Perimeter to manage public access and cross-service trust from one place |
| Treating Network Security Perimeter as a replacement for Private Link | They solve different problems — per-connection private access vs. group-level public access management | Use 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 it | Leaves a real, unnecessary exposure window open indefinitely | Use 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 version | Misses detection for newly emerged bot patterns Microsoft's updated signature database would otherwise catch | Periodically 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 simultaneously** — `az 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.
