Network Security
TSC mapping: CC6.6 (Logical access restrictions from outside system boundaries), CC6.7 (Data transmission controls)
Network controls are a heavily-evidenced area for SOC 2. Auditors will review VNet designs, NSG rules, WAF configurations, and TLS policies. The key principle: deny by default, allow by exception.
1. VNet Design — Defence in Depth
Recommended subnet layout
VNet: 10.0.0.0/16
├── snet-agw (10.0.1.0/24) ← Application Gateway / public load balancer
├── snet-app (10.0.2.0/24) ← Application layer (no public IP)
├── snet-data (10.0.3.0/24) ← Azure SQL, Cosmos DB, Redis (Private Endpoints)
├── snet-mgmt (10.0.4.0/24) ← Bastion host only
└── AzureBastionSubnet (10.0.5.0/26) ← Required name for Azure Bastion
Key rules:
- Application VMs must have no public IP addresses — inbound traffic flows through the Application Gateway only.
- Database resources use Private Endpoints — no public network access.
- Management access uses Azure Bastion — no SSH/RDP exposure to the internet.
# Create a VNet with multiple subnets
az network vnet create \
--resource-group rg-prod \
--name vnet-prod \
--address-prefixes 10.0.0.0/16 \
--location eastus
az network vnet subnet create \
--resource-group rg-prod \
--vnet-name vnet-prod \
--name snet-app \
--address-prefixes 10.0.2.0/24
# Disable public network access on an Azure SQL server
az sql server update \
--resource-group rg-prod \
--name sql-prod \
--restrict-outbound-network-access true \
--public-network-access Disabled
Reference: Azure Virtual Network documentation → · Azure Private Endpoint →
2. Network Security Groups — Least Privilege
NSGs are stateful Layer 4 firewalls applied at the subnet or NIC level. SOC 2 auditors specifically look for rules that allow inbound SSH (22) or RDP (3389) from 0.0.0.0/0.
Audit for unrestricted admin access
# Find NSG rules allowing SSH (port 22) from the internet
az network nsg list --query "[*].{NSG:name, RG:resourceGroup}" -o tsv | \
while IFS=$'\t' read -r nsg rg; do
az network nsg rule list --resource-group "$rg" --nsg-name "$nsg" \
--query "[?access=='Allow' && direction=='Inbound' && (destinationPortRange=='22' || destinationPortRange=='3389') && (sourceAddressPrefix=='*' || sourceAddressPrefix=='0.0.0.0/0')].[name,destinationPortRange,sourceAddressPrefix]" \
-o table 2>/dev/null | grep -v "^$" | sed "s/^/${nsg} — /"
done
# Remove an overly permissive SSH rule
az network nsg rule delete \
--resource-group rg-prod \
--nsg-name nsg-app \
--name allow-ssh-from-internet
Recommended NSG rule pattern
# Application subnet NSG: allow inbound from App Gateway only
az network nsg rule create \
--resource-group rg-prod \
--nsg-name nsg-snet-app \
--name allow-inbound-from-agw \
--priority 100 \
--direction Inbound \
--access Allow \
--protocol Tcp \
--source-address-prefixes 10.0.1.0/24 \
--destination-port-ranges 8080 443
# Deny all other inbound
az network nsg rule create \
--resource-group rg-prod \
--nsg-name nsg-snet-app \
--name deny-all-inbound \
--priority 4096 \
--direction Inbound \
--access Deny \
--protocol '*' \
--source-address-prefixes '*' \
--destination-port-ranges '*'
Reference: Network security groups → · NSG best practices →
3. NSG Flow Logs and Network Watcher
NSG Flow Logs capture Layer 4 traffic metadata (source/destination IP, port, protocol, allow/deny) for every NSG rule. Required for CC7.2 evidence and Sentinel threat detection.
# Register the Network Watcher resource provider if not already registered
az provider register --namespace Microsoft.Network
# Enable Network Watcher in each region
az network watcher configure \
--resource-group NetworkWatcherRG \
--location eastus \
--enabled true
# Get the NSG resource ID
NSG_ID=$(az network nsg show \
--resource-group rg-prod \
--name nsg-snet-app \
--query id -o tsv)
# Enable NSG Flow Logs (version 2, to Log Analytics)
az network watcher flow-log create \
--resource-group rg-prod \
--name flowlog-snet-app \
--nsg $NSG_ID \
--storage-account /subscriptions/<sub>/resourceGroups/rg-security/providers/Microsoft.Storage/storageAccounts/stflowlogs \
--workspace $WORKSPACE_ID \
--interval 10 \
--retention 90 \
--traffic-analytics true \
--enabled true
# Verify flow logs are enabled for all NSGs
az network watcher flow-log list \
--resource-group rg-prod \
--location eastus \
--query "[*].[name,enabled,storageId]" \
--output table
Reference: NSG Flow Logs documentation → · Azure Network Watcher →
4. Azure Bastion — Secure Management Access
Azure Bastion provides browser-based SSH and RDP to VMs without exposing management ports to the internet. Required as a replacement for jump boxes with public IPs.
# Create a public IP for Bastion (Standard SKU required)
az network public-ip create \
--resource-group rg-prod \
--name pip-bastion-prod \
--sku Standard \
--allocation-method Static \
--location eastus
# Deploy Azure Bastion
az network bastion create \
--resource-group rg-prod \
--name bastion-prod \
--public-ip-address pip-bastion-prod \
--vnet-name vnet-prod \
--location eastus \
--sku Standard
# Connect to a VM via Bastion (no SSH key or port 22 needed)
az network bastion ssh \
--resource-group rg-prod \
--name bastion-prod \
--target-resource-id /subscriptions/<sub>/resourceGroups/rg-prod/providers/Microsoft.Compute/virtualMachines/vm-prod \
--auth-type password \
--username azureadmin
Reference: Azure Bastion documentation →
5. Azure WAF — Web Application Firewall
Azure WAF protects Application Gateways and Azure Front Door from OWASP Top 10 attacks. Required for CC6.6 (external threat protection).
# Create a WAF policy in Prevention mode
az network application-gateway waf-policy create \
--resource-group rg-prod \
--name waf-policy-prod \
--location eastus
# Set to Prevention mode with OWASP 3.2 rule set
az network application-gateway waf-policy managed-rule rule-set add \
--resource-group rg-prod \
--policy-name waf-policy-prod \
--type OWASP \
--version 3.2
az network application-gateway waf-policy update \
--resource-group rg-prod \
--name waf-policy-prod \
--set policySettings.mode=Prevention \
--set policySettings.state=Enabled \
--set policySettings.requestBodyCheck=true
# Associate WAF policy with an Application Gateway
az network application-gateway update \
--resource-group rg-prod \
--name agw-prod \
--waf-policy /subscriptions/<sub>/resourceGroups/rg-prod/providers/Microsoft.Network/ApplicationGatewayWebApplicationFirewallPolicies/waf-policy-prod
# Verify WAF mode
az network application-gateway waf-config show \
--resource-group rg-prod \
--gateway-name agw-prod \
--query "[firewallMode,ruleSetType,ruleSetVersion,enabled]"
Reference: Azure WAF documentation → · WAF on Application Gateway →
6. Azure DDoS Protection
Enable DDoS Network Protection for production VNets with public-facing endpoints. Required for CC6.6 availability controls.
# Create a DDoS Protection Plan (shared across VNets)
az network ddos-protection create \
--resource-group rg-security \
--name ddos-plan-prod \
--location eastus
# Attach the plan to your production VNet
az network vnet update \
--resource-group rg-prod \
--name vnet-prod \
--ddos-protection true \
--ddos-protection-plan /subscriptions/<sub>/resourceGroups/rg-security/providers/Microsoft.Network/ddosProtectionPlans/ddos-plan-prod
Reference: Azure DDoS Protection →
7. TLS Enforcement — Encryption in Transit
Enforce TLS 1.2+ on all public-facing and internal service communication. Required for CC6.7.
Force HTTPS on App Service
# Enforce HTTPS only on an App Service app
az webapp update \
--resource-group rg-prod \
--name app-prod \
--https-only true
# Set minimum TLS version to 1.2
az webapp config set \
--resource-group rg-prod \
--name app-prod \
--min-tls-version 1.2
Force HTTPS on Storage Accounts
# Require secure transfer (HTTPS) on all storage accounts
az storage account update \
--resource-group rg-prod \
--name stprod \
--https-only true
# Enforce minimum TLS 1.2
az storage account update \
--resource-group rg-prod \
--name stprod \
--min-tls-version TLS1_2
Enforce TLS 1.2 on Application Gateway listener
az network application-gateway ssl-policy set \
--resource-group rg-prod \
--gateway-name agw-prod \
--policy-type Predefined \
--name AppGwSslPolicy20220101
Azure Policy — enforce HTTPS across the board:
# Assign built-in policy: "Web Application should only be accessible over HTTPS"
az policy assignment create \
--name enforce-https-app-service \
--policy /providers/Microsoft.Authorization/policyDefinitions/a4af4a39-4135-47fb-b175-47fbdf85311d \
--scope /subscriptions/<subscription-id>
# Assign built-in policy: "Secure transfer to storage accounts should be enabled"
az policy assignment create \
--name enforce-https-storage \
--policy /providers/Microsoft.Authorization/policyDefinitions/404c3081-a854-4457-ae30-26a93ef643f9 \
--scope /subscriptions/<subscription-id>
Reference: Azure App Service TLS → · Azure Storage secure transfer →
SOC 2 Evidence for Network Security
| Evidence item | How to collect |
|---|---|
| VNet and subnet configuration | az network vnet list, az network vnet subnet list |
| NSG rules for all subnets | az network nsg rule list --nsg-name <name> |
| NSG Flow Logs configuration | az network watcher flow-log list |
| WAF policy and mode | az network application-gateway waf-policy show |
| Azure Bastion deployment | az network bastion list |
| DDoS Protection plan | az network ddos-protection list |
| App Service HTTPS enforcement | az webapp show --query httpsOnly |
| Storage account HTTPS enforcement | az storage account list --query "[*].[name,enableHttpsTrafficOnly,minimumTlsVersion]" |
| Azure Policy compliance (network rules) | Defender for Cloud → Regulatory Compliance |