By default everything in a VNet can reach everything else in it. So the payments database answers anyone who can reach its subnet - including the test VM someone left running. You need a firewall rule list per subnet, and on Azure that is an NSG.
What you need to know already: 23.1 (VNets, subnets, NICs), 9.1 (TCP connections), 9.8 (ports), 8.3 (CIDR ranges), 22.4 (--query, sort_by).
What an NSG is
An NSG (network security group) is a list of allow/deny rules, each matching on source address, source port, destination address, destination port and protocol (TCP, UDP, ...). You attach it to a subnet or to a NIC, and it filters every packet going in or out.
Two words from that: it works at L4 - layer 4, the TCP/UDP layer with ports (Ch 9): it sees addresses and ports, not URLs. And it is stateful: it remembers connections, so the reply traffic of an allowed connection is allowed automatically - you never write the reply rule.
Evaluation
Inbound means traffic coming into the subnet or NIC, outbound traffic leaving it. For each direction separately, rules are evaluated by priority (a number on each rule), lowest number first, and the first match wins. Your rules use 100-4096. Every NSG also has six default rules you cannot delete:
az network nsg rule list -g <rg> --nsg-name <nsg> --include-default -o table: list an NSG's rules; --include-default also shows the built-in ones.
# nsg-app = the NSG you create in the next mission
az network nsg rule list -g rg-oncall-lab --nsg-name nsg-app --include-default -o table
Name Priority SourceAddressPrefixes Access Protocol Direction DestinationPortRanges DestinationAddressPrefixes
----------------------------- -------- --------------------- ------ -------- --------- --------------------- --------------------------
AllowVnetInBound 65000 VirtualNetwork Allow * Inbound * VirtualNetwork
AllowAzureLoadBalancerInBound 65001 AzureLoadBalancer Allow * Inbound * *
DenyAllInBound 65500 * Deny * Inbound * *
AllowVnetOutBound 65000 VirtualNetwork Allow * Outbound * VirtualNetwork
AllowInternetOutBound 65001 * Allow * Outbound * Internet
DenyAllOutBound 65500 * Deny * Outbound * *
(columns trimmed). Read what they mean:
- Everything inside the VNet can talk to everything inside the VNet (
AllowVnetInBound).VirtualNetworkincludes peered VNets and on-prem ranges learnt over VPN. An empty NSG is not "deny all" - it is "deny the internet". - The Azure load balancer's health probes (from
168.63.129.16) are allowed. A health probe is the load balancer (23.16, and Ch 9.23) regularly checking each backend; deny the probes and every backend looks dead. - All outbound to the internet is allowed. Controlling egress (traffic leaving your network) needs explicit deny rules or a firewall.
So to isolate a subnet from the rest of the VNet you need your own deny rule above 65000, e.g. priority 4000 "deny VirtualNetwork inbound", with specific allows below it (lower numbers).
Service tags
A service tag is a name that stands for a set of IP ranges Microsoft keeps up to date. Instead of IP ranges, rules can name them: Internet, VirtualNetwork, AzureLoadBalancer, AzureCloud, AzureKeyVault.WestEurope, Storage, AzureMonitor, GatewayManager... They update automatically as Azure's ranges change. App Gateway v2 (23.16) subnets must allow GatewayManager (Azure's own management traffic) on 65200-65535, or the gateway fails to provision (fails to be created and started).
Creating rules - and az's surprise default
az network nsg create -g <rg> -n <name>: create an empty NSG (it gets the six default rules). az network nsg rule create: add one rule; --nsg-name which NSG; -n the rule's name; --priority its number; --direction Inbound or Outbound; --access Allow or Deny; --protocol Tcp, Udp or *; --source-address-prefixes where the traffic comes from (CIDR or service tag); --destination-port-ranges which ports it goes to.
az network nsg create -g rg-oncall-lab -n nsg-app
az network nsg rule create -g rg-oncall-lab --nsg-name nsg-app -n allow-https-from-appgw \
--priority 100 --direction Inbound --access Allow --protocol Tcp \
--source-address-prefixes 10.20.1.0/24 --destination-port-ranges 443
Leave out --destination-port-ranges and the CLI defaults it to 80:
# ... = -g rg-oncall-lab --nsg-name nsg-app
az network nsg rule create ... -n allow-https --priority 100 --protocol Tcp --source-address-prefixes 10.20.1.0/24
az network nsg rule show ... -n allow-https --query destinationPortRange
"80"
The rule is called "allow-https" and allows HTTP. Always pass the port. The other defaults: --access Allow, --direction Inbound, --protocol *, source and destination *.
Priorities must be unique per direction:
ERROR: (SecurityRuleConflict) Security rule dup conflicts with rule allow-https. Rules cannot
have the same Priority and Direction. To learn more, see aka.ms/nsgrules.
Leave gaps (100, 110, 120 ... or 100, 200) so a rule can be inserted later without renumbering.
Subnet and NIC
az network vnet subnet update ... --nsg nsg-app attaches an NSG to a subnet. An NSG can sit on the subnet, on the NIC, or both. With both, inbound traffic must be allowed by the subnet NSG and then the NIC NSG; outbound by the NIC NSG and then the subnet NSG. Most teams use subnet NSGs only - one place to look.
az network vnet subnet update -g rg-oncall-lab --vnet-name vnet-sysop -n snet-app --nsg nsg-app
AKS and NSGs
AKS (Azure's managed Kubernetes, 23.20) creates its node VMs in a separate resource group whose name starts with MC_ (the node resource group). It manages an NSG on the node pool's NICs there and adds rules for LoadBalancer Services (Ch 16) itself. Do not edit that NSG by hand - AKS keeps putting it back the way it wants it (it is reconciled, like a controller does in 15.9). Put your own policy on the subnet NSG, and remember the AKS subnet must still allow: node-to-node, the load balancer probes, and egress to the control plane and required endpoints.
Debugging
- NSG flow logs (now VNet flow logs) record every allowed and denied connection (flow) to a storage account (22.29).
Later (Ch 24): Traffic Analytics loads flow logs into a Log Analytics workspace so you can query them.
- IP flow verify (
az network watcher test-ip-flow, part of Network Watcher, Azure's network diagnostics service) answers "would this packet be allowed, and by which rule?" for a VM NIC. - Or reason it out: list the rules with
--include-default, sort by priority, walk down until the first match. The drill makes you do exactly that.
Walking a packet through by hand
The same list sorted by priority, inbound only (sort_by(..., &priority) is JMESPath, 22.4):
# nsg-app after the NSG mission
az network nsg rule list -g rg-oncall-lab --nsg-name nsg-app --include-default \
--query "sort_by([?direction=='Inbound'], &priority)[].{p:priority, name:name, access:access, src:sourceAddressPrefix, port:destinationPortRange}" -o table
P Name Access Src Port
----- ----------------------------- -------- ----------------- ------
100 allow-https-from-appgw Allow 10.20.1.0/24 443
110 allow-lb-probes Allow AzureLoadBalancer *
4000 deny-vnet-in Deny VirtualNetwork *
65000 AllowVnetInBound Allow VirtualNetwork *
65001 AllowAzureLoadBalancerInBound Allow AzureLoadBalancer *
65500 DenyAllInBound Deny * *
- 10.20.1.7 -> 443: rule 100 matches first. Allow.
- 10.20.1.7 -> 22: not 100 (port), not 110 (source), 4000 matches. Deny.
- 10.20.0.37 (AKS node) -> 443: not 100 (source), not 110, 4000. Deny.
- 168.63.129.16 (probe) -> 8080: 110. Allow.
- 203.0.113.9 (internet) -> 443: nothing until 65500. Deny.
The drill makes you do this on random rule sets until it is automatic.
Application security groups
Instead of IP prefixes, NICs can be members of ASGs (application security groups - named groups of NICs, such as asg-web, asg-db) and rules can say "from asg-web to asg-db on 5432". The rule survives VMs being recreated with new IPs. ASGs work on NICs (VMs, and VM scale sets - VMSS, a group of identical VMs Azure grows and shrinks as one, which is what an AKS node pool is, 23.20) - not for pods, which is one reason pod-level policy lives in Kubernetes NetworkPolicy (16.29) instead.
Where NSGs stop
NSGs filter by address and port. They cannot say "only to *.github.com", see the TLS SNI (the host name a client sends at the start of TLS, 9.15), or inspect HTTP. That is a firewall's (FQDN rules, 23.7) or a WAF's (web application firewall, 23.16) job.
What you can now do
- Read an NSG's rules, defaults included, and walk a packet down them to the first match.
- Isolate a subnet: specific allows at low numbers, then your own deny above the 65000 defaults.
- Create rules with
az network nsg rule createwithout the port-80 surprise.