Defender for Cloud · Runbook
Defender for Cloud baseline: plans, agents, guardrails
A practical Defender for Cloud baseline for Azure landing zones: plan coverage decisions, agent strategy, and closing the loop with policy-backed guardrails.
- Effort: 1–2 days
- Risk: Medium
- Rollback: Partial
Applies to: Defender for Cloud · Azure Policy · Log Analytics · Recommendations
- Treat Defender for Cloud as a platform service: plans, agents, contacts, and policies are all IaC-driven.
- Day 0: enable the mandatory plans with the right sub-plans and extensions, route telemetry to the agreed workspaces.
- Day 30: promote noisy “audit” recommendations to policy, integrate alerts with SOC tooling, prune exemptions.
- Quarterly: review plan drift, extension coverage, cost dashboards, and KQL reports; redeploy via pipeline.
- Portal is the dashboard, not the control plane. Everything that matters lives in Git.
- Access: Security Admin on each subscription to set plans, plus Resource Policy Contributor at the management group for the guardrails.
- Cost: Defender plans bill per resource per month and enabling them across an estate is not a free action. Price the rollout before you flip anything on.
- Tooling: Azure CLI 2.60+, Terraform 1.7+ with
azurerm5.x. The examples below were checked against the 5.x provider docs; 5.0 removedazurerm_security_center_auto_provisioningand renamed the continuous export action types. - Do this first: confirm who triages the alerts. A plan with no owner produces cost and noise, and nothing else.
Why Defender needs a baseline, not a switch
Defender for Cloud is part signal, part configuration drift detector. Without a baseline:
- workloads inherit random plan coverage
- recommendations never feed back into policy
- agent and extension coverage is left to subscription owners
- breaches go undetected because telemetry never lands in a Log Analytics workspace
A tidy baseline gives you predictable coverage, standard deployment of agents, and an audit trail you can trust when auditors or responders come calling.
What good looks like
- Plans – Defender for Servers, SQL, Storage, Containers, Key Vault, Resource Manager and Defender CSPM enabled at every subscription via IaC.
- Agent coverage – Defender for Servers runs on the Defender for Endpoint agent plus agentless scanning. The retired Log Analytics agent is gone entirely, and AMA is only deployed where something else needs it.
- Telemetry – Continuous export (or the Sentinel Defender for Cloud connector) sends alerts and recommendations to the correct Log Analytics workspace(s) with named owners.
- Guardrails – Top recurring recommendations converted into initiatives and enforced at the LandingZones management group.
- Operations – SOC receives Defender alerts, exemptions have expiry dates, and cost dashboards show who pays.
Baseline decision points
- Plan coverage granularity – Decide which plans are mandatory vs optional. Tie costs to subscription metadata (env/criticality).
- Telemetry landing zone – Document the workspace hierarchy (e.g.
law-security-prod,law-security-nonprod) and retention. - Agent scope – Agentless scanning rides on the plan and the Defender for Endpoint integration is a subscription setting next to it. AMA is no longer part of Defender for Servers; deploy it by policy only for Defender for SQL servers on machines (where the plan settings ask for it) or general monitoring. Enforce all of it centrally, not toggled by workloads.
- Recommendation routing – Assign owners per recommendation category (platform, database, SOC). Portal comments do not count.
- Guardrail feedback loop – Pick the “always ignored” recommendations and codify them as policy initiatives with deployIfNotExists/deny.
- Exemptions – Define request, approval, expiry, and review cadence. Include justification in source control.
- Cost transparency – Produce monthly dashboards so FinOps knows which subscription pays for which plan.
Signal vs noise
- Enable now: Defender plans you can operate (Servers, SQL, Storage, Key Vault, Containers), agentless scanning, Defender for Endpoint integration, security contacts, continuous export.
- Enable at Day 30: Defender for APIs, the Defender for Containers runtime sensor on clusters you have tested, auto-remediation policies that need SOC sign-off, integration with ticketing.
- Probably never: Enabling every plan “for coverage” without owners, leaving legacy MMA agents running forever, or turning on continuous export of every alert without throttling.
Phased rollout
- Day 0 baseline – Define plans, extensions, workspaces, contacts, and policy assignments in Git. Deploy to pilot subscriptions first.
- Day 30 hardening – Review alerts, turn recurring findings into policy, ensure SOC routing works, prune exemptions.
- Quarterly review – Use Resource Graph to confirm coverage, audit extensions, refresh cost dashboards, and test rollback.
Runbook: a Defender for Cloud baseline that sticks
-
Inventory scope and workspaces
List every subscription, note the tenant/management group they sit under, and record the prod/non-prod workspaces they should write to. Capture workspace IDs, as you will reuse them in Terraform and policy.
-
Enable plan coverage via IaC
Use Terraform/Bicep to enable the required Defender plans per subscription. Do not rely on portal toggles or screenshots.
-
Lock down agent coverage
Enable agentless scanning through the plan’s
extensionblocks and turn on the Defender for Endpoint integration. Defender for Servers no longer needs AMA, so only deploy it (with Azure Policy, not Defender auto-provisioning) where Defender for SQL servers on machines or your monitoring needs it. Remove the retired Log Analytics agent as you go. Store all of it in Git and redeploy on every new subscription. -
Route telemetry + contacts
Configure continuous export (
Microsoft.Security/automations) or the Sentinel Defender for Cloud connector so alerts and recommendations reach the correct workspaces, and set security contacts so alerts reach the SOC/platform inboxes. Diagnostic settings are not how Defender data gets to Log Analytics. -
Convert noise into policy
Choose the top recommendations (e.g., public Storage, SQL TDE) and create policy initiatives that either deploy fixes or deny non-compliant resources.
-
Operationalise alerts
Integrate Defender alerts with Sentinel/ticketing/chat, create runbooks for frequent alerts, and document the exemption workflow.
-
Review + cost
Nightly or monthly scripts should confirm coverage; dashboards should show cost per subscription. Raise drift issues via backlog, not chat.
Before you enable plans en masse, apply this Terraform per subscription (or through a module). Plans that carry sub-plans or extensions need declaring individually. If an extension isn’t listed, the provider leaves it disabled, so the simple for_each only covers plans where this baseline takes the defaults.
# Defender for Servers Plan 2, with agentless scanning.
resource "azurerm_security_center_subscription_pricing" "vm" {
tier = "Standard"
resource_type = "VirtualMachines"
subplan = "P2"
extension {
name = "AgentlessVmScanning"
}
}
# Defender for Endpoint integration is a subscription setting, not a pricing extension.
resource "azurerm_security_center_setting" "mde" {
setting_name = "WDATP"
enabled = true
}
resource "azurerm_security_center_subscription_pricing" "storage" {
tier = "Standard"
resource_type = "StorageAccounts"
subplan = "DefenderForStorageV2"
extension {
name = "OnUploadMalwareScanning"
}
extension {
name = "SensitiveDataDiscovery"
}
}
resource "azurerm_security_center_subscription_pricing" "containers" {
tier = "Standard"
resource_type = "Containers"
extension {
name = "ContainerRegistriesVulnerabilityAssessments"
}
extension {
name = "AgentlessDiscoveryForKubernetes"
}
extension {
name = "ContainerSensor"
}
}
# Defender CSPM.
resource "azurerm_security_center_subscription_pricing" "cspm" {
tier = "Standard"
resource_type = "CloudPosture"
extension {
name = "AgentlessVmScanning"
}
extension {
name = "AgentlessDiscoveryForKubernetes"
}
extension {
name = "ContainerRegistriesVulnerabilityAssessments"
}
extension {
name = "SensitiveDataDiscovery"
}
}
# Key Vault and Resource Manager both have sub-plans; pin them so the
# per-resource model is explicit rather than whatever the default is.
resource "azurerm_security_center_subscription_pricing" "keyvaults" {
tier = "Standard"
resource_type = "KeyVaults"
subplan = "PerKeyVault"
}
resource "azurerm_security_center_subscription_pricing" "arm" {
tier = "Standard"
resource_type = "Arm"
subplan = "PerSubscription"
}
# Plans where this baseline takes the defaults.
resource "azurerm_security_center_subscription_pricing" "simple" {
for_each = toset([
"SqlServers",
"AppServices",
])
tier = "Standard"
resource_type = each.value
}Send alerts and recommendations to the security workspace with continuous export. The name ExportToWorkspace makes it show up in the portal’s continuous export page. Set the resource group, location and workspace for your estate.
data "azurerm_subscription" "current" {}
resource "azurerm_security_center_automation" "export" {
name = "ExportToWorkspace"
location = "uksouth"
resource_group_name = "rg-security-uks"
action {
type = "Workspace" # azurerm 5.x value; 4.x used "loganalytics"
resource_id = var.security_workspace_id
}
source {
event_source = "Alerts"
}
source {
event_source = "Assessments"
}
source {
event_source = "AssessmentsSnapshot"
}
scopes = [data.azurerm_subscription.current.id]
}azurerm_security_center_auto_provisioning configured the Log Analytics (MMA) agent, which has been retired, and the resource was removed in azurerm 5.0. If it’s still in your configuration, take it out of state (terraform state rm) and delete the block before you upgrade the provider. Defender for Servers now works through the Defender for Endpoint agent and agentless scanning, set on the plan itself. Where you still need AMA, deploy it through Azure Policy or Azure Arc.
Apply this Bicep initiative template to codify recommendations you do not want to see again. Swap in your policy definition IDs.
targetScope = 'managementGroup'
@description('Region for the assignment identity. Not where policy applies.')
param location string = 'uksouth'
// Resolve real definition IDs first, e.g.:
// az policy definition list --query "[?contains(displayName,'<search term>')].{name:displayName,id:id}" -o table
resource initiative 'Microsoft.Authorization/policySetDefinitions@2023-04-01' = {
name: 'defender-guardrails'
properties: {
displayName: 'Defender Guardrails'
description: 'Turn recurring Defender recommendations into enforced guardrails.'
policyType: 'Custom'
policyDefinitions: [
{
policyDefinitionReferenceId: 'deny-public-storage'
policyDefinitionId: '/providers/Microsoft.Authorization/policyDefinitions/<deny-public-storage-id>'
}
{
policyDefinitionReferenceId: 'deploy-sql-tde'
policyDefinitionId: '/providers/Microsoft.Authorization/policyDefinitions/<deploy-sql-tde-id>'
}
]
}
}
resource assignment 'Microsoft.Authorization/policyAssignments@2023-04-01' = {
name: 'defender-guardrails'
// Required: the initiative contains a deployIfNotExists effect, which cannot
// remediate without an identity that holds rights at the assigned scope.
location: location
identity: {
type: 'SystemAssigned'
}
properties: {
displayName: 'Defender Guardrails'
policyDefinitionId: initiative.id
}
}
resource remediationRights 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
name: guid(managementGroup().id, assignment.id, 'Contributor')
properties: {
// Contributor — narrow this to the least-privileged role your definitions need.
roleDefinitionId: tenantResourceId(
'Microsoft.Authorization/roleDefinitions',
'b24988ac-6180-42a0-ab88-20f7382dd24c'
)
principalId: assignment.identity.principalId
principalType: 'ServicePrincipal'
}
}Use the Azure CLI to confirm plan coverage and sub-plans before you exit the change window. This calls the pricings REST API directly, so the --query matches the documented response shape. Replace the subscription ID with the one under test.
az rest --method get \
--url "https://management.azure.com/subscriptions/<subscription-id>/providers/Microsoft.Security/pricings?api-version=2024-01-01" \
--query "value[].{plan:name, tier:properties.pricingTier, subplan:properties.subPlan}" -o tableValidation checks
Plan coverage is configuration, not telemetry, so query it in Azure Resource Graph rather than Log Analytics. This lists any subscription where a plan you expect isn’t on the Standard tier:
securityresources
| where type =~ 'microsoft.security/pricings'
| project subscriptionId,
plan = name,
tier = tostring(properties.pricingTier),
subPlan = tostring(properties.subPlan)
| where plan in ('VirtualMachines','SqlServers','StorageAccounts','KeyVaults','Containers','Arm','CloudPosture')
| where tier != 'Standard'
| order by subscriptionId ascSeparately, confirm continuous export is actually landing. With weekly snapshots on, every exporting subscription should write recommendations at least once a week. Run this against your security workspace to find subscriptions that have gone quiet:
SecurityRecommendation
| where TimeGenerated > ago(30d)
| extend SubscriptionId = tostring(split(AssessedResourceId, '/')[2])
| summarize lastSeen = max(TimeGenerated) by SubscriptionId
| where lastSeen < ago(8d)
| order by lastSeen asc-
Every subscription shows the expected Defender plans enabled via IaC state.
-
Plan extensions (agentless scanning, sensitive data discovery, container extensions) and the Defender for Endpoint integration match the documented baseline.
-
Security contacts and notification channels receive Defender alerts during testing.
-
Continuous export (or the Sentinel connector) delivers alerts and recommendations to the agreed Log Analytics workspace(s).
-
Policy initiatives exist for the high-impact recommendations you care about.
-
Exemptions are tracked with owners, expiry dates, and an audit trail.
-
Operations teams confirm Defender alerts reach their tooling and have clear response steps.
-
Cost dashboards or reports show Defender charges per subscription.
-
The quarterly drift review is scheduled and the owner is documented.
Common pitfalls
Coverage drifts within days. Lock every plan via Terraform/Bicep and audit nightly.
Sprawling workspaces kill queries and double retention bills. Use a hub-and-spoke or prod/non-prod split.
If Defender nags about the same thing twice, codify it as policy. Noise erodes trust.
Temporary waivers become permanent blind spots. Force expiry dates and justification.
Defender emails without SOC integration are just spam. Wire them into Sentinel/ticketing with owners.
Rollback / back-out plan
- Plan rollback – revert the Terraform/Bicep change, redeploy the previous pricing tier, and document any alert gaps created.
- Plan extensions – remove the
extensionblock in IaC and re-apply, run remediation tasks to uninstall agents, and confirm with theaz restcheck above. - Telemetry routing – reapply the previous continuous export configuration; be aware of ingestion costs when duplicating streams.
- Policy guardrails – disable the assignment instead of deleting definitions, fix parameters offline, then redeploy to a pilot scope.
Rollback is partial: telemetry already ingested stays, and automatic remediations may have mutated resources. Log every rollback in the platform change record.