Skip to content

CloudRunbook | Practical Cloud Engineering

Defender for Cloud baseline: plan coverage, agent strategy, and turning recommendations into guardrails

5 min read

A practical Defender for Cloud baseline for Azure landing zones: plan coverage decisions, agent strategy, and closing the loop with policy-backed guardrails.

Applies to
Defender for CloudAzure PolicyLog AnalyticsRecommendations
Effort
1–2 days
Risk
Medium
Rollback
Partial
Last reviewed
26 Jul 2026
TL;DR
  • 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.
Before you start
  • 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 azurerm 3.100+.
  • 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 – Servers, SQL, Storage, Containers, Key Vault, and CSPM enabled at every subscription via IaC.
  • Agent coverage – agentless scanning enabled through the plan’s extensions, AMA deployed by policy, the retired Log Analytics agent gone entirely.
  • Telemetry – Diagnostic settings push all Defender data 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

  1. Plan coverage granularity – Decide which plans are mandatory vs optional. Tie costs to subscription metadata (env/criticality).
  2. Telemetry landing zone – Document the workspace hierarchy (e.g. law-security-prod, law-security-nonprod) and retention.
  3. Agent scope – Agentless scanning rides on the plan; AMA is deployed by policy. Both must be enforced centrally, not toggled by workloads.
  4. Recommendation routing – Assign owners per recommendation category (platform, database, SOC). Portal comments do not count.
  5. Guardrail feedback loop – Pick the “always ignored” recommendations and codify them as policy initiatives with deployIfNotExists/deny.
  6. Exemptions – Define request, approval, expiry, and review cadence. Include justification in source control.
  7. 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, AMA via policy, security contacts, diagnostic routing.
  • Enable at Day 30: Advanced plans (APIM, Kubernetes data plane), 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

  1. 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—you will reuse them in Terraform and policy.

  2. Enable plan coverage via IaC

    Use Terraform/Bicep to enable the required Defender plans per subscription. Do not rely on portal toggles or screenshots.

  3. Lock down agent coverage

    Enable agentless scanning through the plan’s extension blocks, and deploy AMA with Azure Policy rather than Defender auto-provisioning. Remove the retired Log Analytics agent as you go. Store all of it in Git and redeploy on every new subscription.

  4. Route telemetry + contacts

    Apply diagnostic settings and security contacts so telemetry flows into the correct workspaces and alerts reach the SOC/platform inboxes.

  5. 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.

  6. Operationalise alerts

    Integrate Defender alerts with Sentinel/ticketing/chat, create runbooks for frequent alerts, and document the exemption workflow.

  7. 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 agent behaviour need declaring individually — the simple for_each only covers the ones with nothing to configure.

HCL
# Defender for Servers Plan 2, with agentless scanning and MDE integration.
resource "azurerm_security_center_subscription_pricing" "vm" {
  tier          = "Standard"
  resource_type = "VirtualMachines"
  subplan       = "P2"

  extension {
    name = "AgentlessVmScanning"
  }
  extension {
    name = "MdeDesignatedSubscription"
  }
}

resource "azurerm_security_center_subscription_pricing" "storage" {
  tier          = "Standard"
  resource_type = "StorageAccounts"
  subplan       = "DefenderForStorageV2"

  extension {
    name = "OnUploadMalwareScanning"
  }
  extension {
    name = "SensitiveDataDiscovery"
  }
}

# Plans with no sub-plan or extension decisions to make.
resource "azurerm_security_center_subscription_pricing" "simple" {
  for_each = toset([
    "SqlServers",
    "KeyVaults",
    "Containers",
    "AppServices",
    "Arm",
  ])

  tier          = "Standard"
  resource_type = each.value
}
Don't reach for auto-provisioning

azurerm_security_center_auto_provisioning configures the Log Analytics (MMA) agent, which has been retired. It does not turn on the Azure Monitor Agent, and setting it will not give you what you want. Agent behaviour now rides on the plan itself through the extension blocks above; AMA for servers is deployed through Azure Policy or Azure Arc, not through Defender auto-provisioning.

Apply this Bicep initiative template to codify recommendations you do not want to see again. Swap in your policy definition IDs.

BICEP
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: subscriptionResourceId(
      '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. Replace the subscription ID with the one under test.

BASH
az account set --subscription "<subscription-id>"
az security pricing list --query "value[].{plan:name, tier:properties.pricingTier, subplan:properties.subPlan}" -o table

Validation 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:

KUSTO
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')
| where tier != 'Standard'
| order by subscriptionId asc

Separately, confirm the telemetry is actually landing. Run this against your security workspace to find machines that reported once and then went quiet:

KQL
Heartbeat
| summarize lastSeen = max(TimeGenerated) by Computer, _ResourceId
| where lastSeen < ago(1d)
| order by lastSeen asc
  • Every subscription shows the expected Defender plans enabled via IaC state.

  • Plan extensions (agentless scanning, MDE) match the documented baseline, and AMA is deployed by policy.

  • Security contacts and notification channels receive Defender alerts during testing.

  • Diagnostic settings push Defender data into 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

Pitfall: portal toggles left to workload owners

Coverage drifts within days. Lock every plan via Terraform/Bicep and audit nightly.

Pitfall: one workspace per workload

Sprawling workspaces kill queries and double retention bills. Use a hub-and-spoke or prod/non-prod split.

Pitfall: ignoring recurring recommendations

If Defender nags about the same thing twice, codify it as policy. Noise erodes trust.

Pitfall: no exemption expiry

Temporary waivers become permanent blind spots. Force expiry dates and justification.

Pitfall: alert routing left to chance

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 extension block in IaC and re-apply, run remediation tasks to uninstall agents, and confirm with az security pricing list.
  • Telemetry routing – reapply the previous diagnostic settings template; 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.