What it is
An NSG inbound rule that allows SSH, RDP, database ports, or all ports from a specific list of public IP addresses: the office, someone's home connection, a contractor. This is genuinely better than open-to-the-internet, and teams usually write it as the fix for exactly that. But the port is still reachable from outside Azure, the only control is "packets from these addresses", and the addresses were correct on the day someone typed them in.
Why it happens
It's the path of least resistance from a real emergency. The moment someone needs RDP to a production VM, an allow-list rule takes thirty seconds; Bastion or a VPN takes a change request. The rule works, the incident ends, and the "temporary" rule becomes load-bearing.
Then the list quietly rots. Office ISPs reassign ranges, home IPs churn, contractors roll off but their addresses stay in the rule. Nobody owns the list, because to Azure it's just a valid NSG rule; there is no expiry, no re-certification prompt, and no warning when an entry stops corresponding to anyone you trust.
The deeper problem is what's not in the path. SSH, RDP, and database protocols authenticate with credentials alone: no Entra ID, no MFA, no conditional access, no session recording. An attacker who phishes a password or buys a leaked one only needs to originate from an allow-listed address, and addresses can be reassigned to strangers, NATed alongside hundreds of other users, or reached by compromising any machine at the office.
What it costs / blast radius
The blast radius is the workload behind the port. A database port allow-listed to a public IP is one credential and one address away from a full data-plane connection. An RDP or SSH allowance is the same distance from an interactive session on the VM, and from there lateral movement starts. The allow-list narrows who can knock; it does nothing about what happens when the knock is answered with a stolen password, and it leaves no identity trail to audit afterwards. (Authored assessment of Azure behavior.)
See it
resource nsg 'Microsoft.Network/networkSecurityGroups@2023-09-01' = {
name: 'nsg-app'
location: location
properties: {
securityRules: [
{
name: 'allow-rdp-office'
properties: {
priority: 100
direction: 'Inbound'
access: 'Allow'
protocol: 'Tcp'
sourceAddressPrefixes: [
'203.0.113.7' // the office, two ISP contracts ago
'198.51.100.42' // a contractor who rolled off last year
]
sourcePortRange: '*'
destinationAddressPrefix: '*'
destinationPortRange: '3389'
}
}
]
}
}// No inbound rule for 3389/22 from any public source. RDP/SSH go
// through Bastion inside the VNet, behind Entra ID sign-in and MFA.
resource bastion 'Microsoft.Network/bastionHosts@2023-09-01' = {
name: 'bastion-prod'
location: location
sku: { name: 'Basic' }
properties: {
ipConfigurations: [
{
name: 'ipconfig'
properties: {
subnet: { id: bastionSubnetId } // AzureBastionSubnet
publicIPAddress: { id: bastionPipId }
}
}
]
}
}How StratoLens helps
StratoLens flags NSG rules that expose sensitive ports to specific public IPs as their own finding class, distinct from the open-to-the-internet rules, so the "responsible-looking" allow-lists don't hide among fixed items. It runs continuously across every subscription in your own tenant, which is exactly what a rule created during an incident three teams ago needs.