Azure waste

DDoS Network Protection plan attached to nothing

A DDoS Network Protection plan billing its flat monthly fee with no virtual network or public IP associated with it.
The agent reports this
Reversible
Why it happens

A DDoS Network Protection plan is a flat fee and Microsoft states when it starts: "Network Protection cost begins once the DDoS protection plan is created". Measured live from the retail API, the Network Protection Plan meter is $4.032258 an hour across 51 regions, about $2,943.55 a month, and $5.0400 an hour in the three Azure Government regions, about $3,679.20 a month. A plan attached to nothing bills all of that and protects nothing. It is worth knowing that one plan covers an entire tenant: Microsoft advises that a single plan can be used across multiple subscriptions and that there is no need to create more than one, and a single fee covers up to 100 IP addresses across every subscription in the tenant. So a plan with nothing attached is either abandoned or a duplicate of the one already doing the work. Association is read from the plan itself, from the read-only virtualNetworks and publicIPAddresses lists on the resource, and never by scanning the local subscription for virtual networks that reference it: the agent runs per subscription, a plan legitimately protects networks in other subscriptions of the same tenant, and scanning locally would report a load-bearing plan as unused. This reports and never acts, and it deliberately does not call deletion the remedy. A plan protecting nothing may be waste, or it may be a plan someone forgot to attach a network to, in which case attaching it is the fix and deleting it is the opposite of the fix.

Paste this into Resource Graph Explorer in the Azure portal.

resources
| where type =~ 'microsoft.network/ddosprotectionplans'
// Scope below matches CloudFinOpsPro policy DEFAULTS: exclusions.protectTag, environment.tagKeys,
// environment.allowedTagValues, and allowUntagged false so an untagged resource counts as production.
// If your policy.yaml differs, edit the three lines below or this will disagree with your own agent.
| where tolower(tostring(coalesce(tags['cfop:protect'], ''))) !in ('true', 'yes', '1')
| extend cfopEnv = tolower(trim(' ', tostring(coalesce(tags['Environment'], tags['environment'], tags['env'], ''))))
| where cfopEnv in ('dev', 'development', 'test', 'qa', 'sandbox', 'nonprod', 'non-prod', 'staging')
// Both lists must be PRESENT before they can be judged empty. A missing property is the
// collector not having it, which is not the same fact as Azure saying nothing is attached,
// and treating it as empty would invent the most expensive false positive here.
| where isnotnull(properties.virtualNetworks) and isnotnull(properties.publicIPAddresses)
| extend vnets = array_length(properties.virtualNetworks)
| extend pips = array_length(properties.publicIPAddresses)
| where vnets == 0 and pips == 0
| project name, resourceGroup, location, vnets, pips
| order by name asc

Each row is a DDoS Network Protection plan with no virtual network and no public IP associated with it, which is the flat plan fee buying nothing: $4.032258 an hour, about $2,943.55 a month, in 51 regions, and $5.04 an hour in the three Azure Government regions. Microsoft states the fee starts when the plan is created, not when something is attached to it. The association lists are read off the PLAN, from properties.virtualNetworks and properties.publicIPAddresses, and that direction is deliberate: one plan covers an entire tenant across subscriptions, so a query that instead looked for virtual networks referencing the plan would miss every network outside the subscription you run this in and report a plan that is protecting production as unused. Both lists must exist before they are judged empty, because a property the query cannot see is not the same thing as Azure reporting nothing attached. Run this across every subscription in the tenant rather than one, since the plan itself lives in only one of them. A row here is not automatically something to delete: an unattached plan is as likely to be one somebody forgot to attach a network to, and attaching it is then the fix rather than the opposite.

Applies to microsoft.network/ddosprotectionplans
What the agent does about it

A DDoS Network Protection plan billing its flat monthly fee with no virtual network or public IP associated with it.

It will not fix this one

It reports this and takes no action. A check only earns the right to change something once the action behind it can re-read live state before it fires and knows the way back.