Skip to main content

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

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
# 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 itemHow to collect
VNet and subnet configurationaz network vnet list, az network vnet subnet list
NSG rules for all subnetsaz network nsg rule list --nsg-name <name>
NSG Flow Logs configurationaz network watcher flow-log list
WAF policy and modeaz network application-gateway waf-policy show
Azure Bastion deploymentaz network bastion list
DDoS Protection planaz network ddos-protection list
App Service HTTPS enforcementaz webapp show --query httpsOnly
Storage account HTTPS enforcementaz storage account list --query "[*].[name,enableHttpsTrafficOnly,minimumTlsVersion]"
Azure Policy compliance (network rules)Defender for Cloud → Regulatory Compliance