Azure Logging & Detection Hardening

Overview

This page covers Microsoft Azure logging and detection hardening — the surfaces that decide whether an attacker's footprints are captured durably, surfaced quickly, and correlated into an actionable incident. Scope is the Azure commercial regions; Azure Government and Azure operated by 21Vianet (China) inherit the same control structure but expose a separate Log Analytics workspace topology, a separate Microsoft Sentinel data residency boundary, and a separate Microsoft Defender for Cloud regulatory compliance catalogue — re-verify region table caveats and the Microsoft Graph endpoint before applying any of the IaC below to a non-commercial cloud. CIS sub-IDs and NIST / ISO mappings throughout this page reference the commercial Microsoft Azure Foundations Benchmark v3.0.0 (Feb 2025) unless explicitly annotated as a post-v3.0.0 feature or a best-practice recommendation that the current benchmark has not yet codified. The crosswalk page at compliance frameworks describes how the seven pinned framework columns relate to each other.

Azure exposes three distinct log streams, and they are routinely conflated in audit reports. Activity Log is the subscription-level control-plane log: every ARM API call against a subscription (role assignments, NSG rule writes, Key Vault deletes, policy assignments, resource manager critical operations) emits an Activity Log event. It is per-subscription, kept by the platform for 90 days only, and must be routed via Diagnostic Settings into a durable sink to outlive that window. Resource (Diagnostic) Logs are the per-resource data-plane logs: Storage Account read/write/delete, Key Vault key operations, Application Gateway requests, AKS audit, SQL audit, Service Bus, Event Hubs, and every other resource-type that emits a diagnostic category. Each resource is configured individually (or via Azure Policy at scale) with a Diagnostic Setting that routes the logs to a Log Analytics workspace, a Storage Account (archive), or an Event Hub (streaming). Microsoft Entra ID Audit and Sign-in Logs are the directory-plane logs: every directory write (user create, group membership change, application consent grant) emits an Audit Log event; every interactive and non-interactive sign-in emits a Sign-in Log event. Entra logs are tenant-scoped, not subscription-scoped, and require a separate Diagnostic Setting on the Entra tenant resource to flow into the same Log Analytics workspace as the resource and Activity logs (covered on the Azure IAM page). Pretending these are one stream — or assuming "Activity Log covers it all" — is the canonical Azure logging misconfiguration; the controls below treat each stream as a separate enforcement target.

Two anti-conflation callouts up front, because the boundary they describe is the one most often blurred in Azure audit reviews. First: Microsoft Defender for Cloud and Microsoft Sentinel are complementary products, not substitutes. Defender for Cloud is the Azure-native CSPM (Cloud Security Posture Management) and CWPP (Cloud Workload Protection Platform) plane — it scores posture against built-in regulatory initiatives (CIS Microsoft Azure Foundations v3.0.0, NIST 800-53, ISO 27001, PCI-DSS), it runs workload-protection plans on Servers / Storage / SQL / Containers / Key Vault / App Service / Resource Manager / DNS, and it emits security alerts and recommendations into the Defender alerts blade. Microsoft Sentinel is the Azure-native SIEM (Security Information and Event Management) and SOAR (Security Orchestration, Automation, and Response) plane — it ingests data via connectors from Defender for Cloud, Microsoft Entra ID, Activity Log, Office 365, Defender XDR, third-party firewalls, and arbitrary syslog / CEF / custom sources; it runs analytics rules expressed in KQL on top of that data; and it executes automated playbooks via Logic Apps in response to incidents. The recommended end-to-end pipeline is Defender for Cloud alerts → Sentinel incidents (via the Defender for Cloud data connector) → Sentinel automation rules → Logic App playbooks (isolate VM, disable identity, snapshot disk, page on-call). They are independently licensed (Defender for Cloud per protected resource-hour; Sentinel per GB ingested into the workspace), independently administered, and answer different questions: Defender answers "what is wrong with my posture and what active threats are running on my workloads right now"; Sentinel answers "across every log source I have, what correlated story can I tell about an incident, and how do I respond to it automatically". Adopting one and skipping the other leaves a gap; adopting both and connecting them is the design intent. The two controls on this page that codify this boundary are azure-log-04 (Defender for Cloud plans) and azure-log-08 (Sentinel onboarding + SOAR). Second: Defender for Cloud's regulatory compliance dashboard (azure-log-03) and Defender for Cloud's Secure Score (azure-log-05) are two different surfaces of the same product. The regulatory dashboard scores a subscription against an assigned policy initiative (e.g., CIS Microsoft Azure Foundations Benchmark v3.0.0) and tracks remediation per CIS sub-ID; Secure Score is the umbrella weighted posture metric across all Defender recommendations regardless of initiative, and is the operational KPI for the security team's monthly review cadence. Both ship; both are populated by the same engine; both have distinct review rituals.

Order and scope matter. Controls 01–02 are foundational invariants: route every subscription's Activity Log into a central Log Analytics workspace via mgmt-group-enforced Diagnostic Settings, and route every regulated Storage Account's data-plane logs into the same workspace. Controls 03–05 are the Defender for Cloud surface in three layers: regulatory compliance initiative assignment + auto-remediation (03), workload-protection plan enablement subscription-wide (04), and Secure Score baseline + monthly cadence + workflow automation (05). Control 06 captures L4 network flow telemetry via NSG Flow Logs v2 with Traffic Analytics. Control 07 wires Activity Log alerts on the canonical critical-event set (role assignment writes, NSG rule changes, Key Vault deletes, policy assignments, Resource Manager critical operations). Control 08 onboards Microsoft Sentinel into the central workspace and closes the SOAR loop with automation rules and Logic App playbooks. Subscription and management-group scope: Azure Policy at the root management group is the single most important lever for keeping the Diagnostic Settings of new subscriptions from drifting out of compliance the moment a workload team self-provisions a sandbox — every control below names the corresponding built-in or custom policy.

azure-log-01-activity-log-centralized ! CRITICAL DETECTIVE

Every subscription in the tenant routes its Activity Log via a Diagnostic Setting into a single central Log Analytics workspace (the "LAW"), and an Azure Policy assigned at the tenant-root management group enforces that the diagnostic setting exists on every subscription — including ones created tomorrow by a workload team self-service-provisioning a sandbox (Microsoft Learn — Azure Monitor Activity Log (accessed 2026-05)). At minimum eight log categories stream: Administrative (ARM control-plane writes), Security (Defender for Cloud alert events into Activity Log), ServiceHealth (Azure platform health), Alert (Activity Log alert fires), Recommendation (Defender recommendations), Policy (policy evaluation results, including non-compliance), Autoscale (autoscale rule executions), and ResourceHealth (per-resource health transitions). The 90-day platform retention on raw Activity Log is the timer that this control beats — anything beyond 90 days requires the durable sink. The cross-cutting principle is reinforced in General Logging — centralization; the underlying audit-integrity expectation in General Logging — log integrity.

Remediation — Azure CLI

# Azure CLI 2.x
# Audit: list every subscription whose Activity Log lacks a diagnostic setting.
for sub in $(az account list --query '[].id' -o tsv); do
  count=$(az monitor diagnostic-settings subscription list \
    --subscription "$sub" --query 'length(value)' -o tsv 2>/dev/null || echo 0)
  echo "sub=$sub diag_settings=$count"
done

# Apply the canonical 8-category diagnostic setting to one subscription.
SUB_ID=$(az account show --query id -o tsv)
LAW_ID=$(az monitor log-analytics workspace show \
  --resource-group rg-security-logging-westeu \
  --workspace-name law-central-prod \
  --query id -o tsv)

az monitor diagnostic-settings subscription create \
  --name send-activity-to-law \
  --subscription "$SUB_ID" \
  --workspace "$LAW_ID" \
  --logs '[
    {"category":"Administrative","enabled":true},
    {"category":"Security","enabled":true},
    {"category":"ServiceHealth","enabled":true},
    {"category":"Alert","enabled":true},
    {"category":"Recommendation","enabled":true},
    {"category":"Policy","enabled":true},
    {"category":"Autoscale","enabled":true},
    {"category":"ResourceHealth","enabled":true}
  ]'

# Enforce tenant-wide via root-mgmt-group policy assignment (built-in initiative
# 'Configure Azure Activity logs to stream to specified Log Analytics workspace').
az policy assignment create \
  --name pa-activity-log-to-law \
  --scope "/providers/Microsoft.Management/managementGroups/tenant-root" \
  --policy "/providers/Microsoft.Authorization/policyDefinitions/<activity-log-diag-setting-policy-id>" \
  --params "{\"logAnalytics\":{\"value\":\"$LAW_ID\"}}" \
  --location westeurope \
  --mi-system-assigned \
  --role Contributor \
  --identity-scope "/providers/Microsoft.Management/managementGroups/tenant-root"

Remediation — Terraform

# Terraform AzureRM provider ~> 3.0
# Source: Microsoft Learn — Activity Log routed via Diagnostic Settings.

resource "azurerm_log_analytics_workspace" "central" {
  name                = "law-central-prod"
  resource_group_name = "rg-security-logging-westeu"
  location            = "westeurope"
  sku                 = "PerGB2018"
  retention_in_days   = 730  # 2-year hot retention; archive tier configured separately
}

resource "azurerm_monitor_diagnostic_setting" "activity_log_to_law" {
  name                       = "send-activity-log-to-central-law"
  target_resource_id         = "/subscriptions/${var.subscription_id}"
  log_analytics_workspace_id = azurerm_log_analytics_workspace.central.id

  enabled_log { category = "Administrative" }
  enabled_log { category = "Security" }
  enabled_log { category = "ServiceHealth" }
  enabled_log { category = "Alert" }
  enabled_log { category = "Recommendation" }
  enabled_log { category = "Policy" }
  enabled_log { category = "Autoscale" }
  enabled_log { category = "ResourceHealth" }
}

# Root-management-group policy assignment: every subscription must stream
# Activity Log to the central LAW.
resource "azurerm_management_group_policy_assignment" "activity_log_to_law" {
  name                 = "activity-log-to-central-law"
  management_group_id  = "/providers/Microsoft.Management/managementGroups/tenant-root"
  policy_definition_id = var.activity_log_diag_policy_definition_id
  description          = "Stream subscription Activity Log to the central Log Analytics workspace"
  location             = "westeurope"

  identity { type = "SystemAssigned" }

  parameters = jsonencode({
    logAnalytics = { value = azurerm_log_analytics_workspace.central.id }
  })
}

Remediation — Bicep

targetScope = 'subscription'

@description('Subscription-level diagnostic setting routing Activity Log to central LAW.')
param workspaceId string

resource diag 'Microsoft.Insights/diagnosticSettings@2024-01-01-preview' = {
  name: 'subscription-activity-log-central'
  scope: subscription()
  properties: {
    workspaceId: workspaceId
    logs: [
      { category: 'Administrative',      enabled: true }
      { category: 'Security',            enabled: true }
      { category: 'ServiceHealth',       enabled: true }
      { category: 'Alert',               enabled: true }
      { category: 'Recommendation',      enabled: true }
      { category: 'Policy',              enabled: true }
      { category: 'Autoscale',           enabled: true }
      { category: 'ResourceHealth',      enabled: true }
    ]
  }
}

Remediation — Pulumi (TypeScript)

import * as pulumi from "@pulumi/pulumi";
import * as insights from "@pulumi/azure-native/insights";

new insights.DiagnosticSetting("subscription-activity-log-central", {
  resourceUri: "/subscriptions/<sub-id>",
  workspaceId: "/subscriptions/.../workspaces/central-law",
  logs: [
    { category: "Administrative", enabled: true },
    { category: "Security", enabled: true },
    { category: "ServiceHealth", enabled: true },
    { category: "Alert", enabled: true },
    { category: "Recommendation", enabled: true },
    { category: "Policy", enabled: true },
    { category: "Autoscale", enabled: true },
    { category: "ResourceHealth", enabled: true },
  ],
});

Compliance mapping

CIS AWS Foundations v3.0.0 CIS Microsoft Azure Foundations v3.0.0 CIS GCP Foundation v4.0.0 CIS OCI Foundation v2.0.0 NIST SP 800-53 rev5 ISO/IEC 27001:2022 ISO/IEC 27017:2015
n/a5.1; 5.xn/an/a AU-2; AU-3; AU-6; AU-9A.8.15; A.5.28CLD.12.4.5

Log signals

  • AzureActivity OperationNameValue = "Microsoft.Insights/diagnosticSettings/delete" targeting a subscription-scope diagnostic setting that exports Administrative, Security, Policy categories — silences the tenant control-plane ledger.
  • AzureActivity Microsoft.Insights/diagnosticSettings/write where the workspace destination is replaced with one outside the centralised security tenant — diverts the export stream away from the SOC workspace.
  • AzureActivity ingestion gap (no rows for > 30 minutes) on a subscription previously emitting steady traffic — absence-of-signal proof that the export pipeline is broken.

Query

AzureActivity
          | where OperationNameValue startswith "Microsoft.Insights/diagnosticSettings/"
          | where ResourceId contains "providers/microsoft.insights/diagnosticsettings" or ResourceId !contains "/resourcegroups/"
          | project TimeGenerated, Caller, OperationNameValue, ResourceId, ActivityStatusValue, Properties
          | order by TimeGenerated desc
          | take 200

Run as a KQL query in Log Analytics. Pair with a heartbeat watchdog: AzureActivity | summarize maxTime=max(TimeGenerated) by SubscriptionId | where maxTime < ago(30m) — any subscription that goes quiet should page the SOC on-call.

Alert threshold

  • Any delete or destination-change of a subscription-scope diagnostic setting that previously exported Administrative + Security categories — page on first occurrence.
  • 30-minute ingestion gap on any subscription whose 30-day baseline is more than 100 rows/hour — page; the SOC's truth source is dark for that subscription.

Initial response

  1. Reapply the centralised diagnostic-settings Bicep module or Azure Policy Deploy diagnostic settings for activity logs to Log Analytics workspace; capture the AzureActivity Caller as the mutator-of-record.
  2. Reconcile the missing window by pulling the Storage Account archive (if also configured as a destination) and replaying via AzureActivity | union storageAccountBackfill; flag any privileged write in the gap window as suspect-pending-review.
  3. Escalate per general/ir.html — confirm the management-group-scoped Azure Policy enforcing diagnostic-settings deployment remains in DeployIfNotExists mode.

References

Equivalent on: AWS · GCP · OCI

azure-log-02-storage-diagnostic ! HIGH DETECTIVE

Every Storage Account holding regulated data has a Diagnostic Setting that streams data-plane operations (StorageRead, StorageWrite, StorageDelete) plus the Transaction and Capacity metrics to the central Log Analytics workspace, for each of the four storage services in use (Blob, File, Queue, Table) (Microsoft Learn — Diagnostic settings in Azure Monitor (accessed 2026-05)). Activity Log alone (azure-log-01) covers control-plane writes against the Storage Account resource (e.g., key rotation, network-ACL change); it does not cover the data-plane events that matter for breach investigation — who read which blob, who wrote which object, who deleted which container. Without this control, the question "which 12,000 objects did the leaked SAS token enumerate" has no answer in the platform; with it, the answer is a KQL query against StorageBlobLogs. Enforce via an Azure Policy initiative at the root management group that targets Microsoft.Storage/storageAccounts and assigns the canonical diagnostic setting on creation and on existing resources via deployIfNotExists.

Remediation — Azure CLI

# Azure CLI 2.x
# Inventory: list every Storage Account in the tenant without a diagnostic setting.
for sub in $(az account list --query '[].id' -o tsv); do
  az storage account list --subscription "$sub" --query '[].id' -o tsv | while read sa_id; do
    count=$(az monitor diagnostic-settings list --resource "$sa_id" --query 'length(value)' -o tsv 2>/dev/null || echo 0)
    [ "$count" -eq 0 ] && echo "MISSING: $sa_id"
  done
done

# Apply the diagnostic setting to one Storage Account, scoping each storage service.
SA_ID=$(az storage account show --resource-group rg-data-prod --name stregprod001 --query id -o tsv)
LAW_ID=$(az monitor log-analytics workspace show \
  --resource-group rg-security-logging-westeu --workspace-name law-central-prod --query id -o tsv)

# Blob service: data-plane read/write/delete + transaction/capacity metrics.
az monitor diagnostic-settings create \
  --name stg-blob-diag \
  --resource "$SA_ID/blobServices/default" \
  --workspace "$LAW_ID" \
  --logs '[
    {"category":"StorageRead","enabled":true},
    {"category":"StorageWrite","enabled":true},
    {"category":"StorageDelete","enabled":true}
  ]' \
  --metrics '[{"category":"Transaction","enabled":true},{"category":"Capacity","enabled":true}]'

# Repeat for fileServices/default, queueServices/default, tableServices/default.

Remediation — Terraform

# Terraform AzureRM provider ~> 3.0
# Source: Microsoft Learn — Storage Account diagnostic settings.

variable "regulated_storage_account_ids" {
  type = map(string)  # keyed by friendly name
}

# Per-service diagnostic setting; iterate the four storage services per account.
locals {
  storage_services = ["blobServices", "fileServices", "queueServices", "tableServices"]
  account_service_pairs = merge([
    for acc_name, acc_id in var.regulated_storage_account_ids : {
      for svc in local.storage_services :
      "${acc_name}-${svc}" => { acc_id = acc_id, svc = svc }
    }
  ]...)
}

resource "azurerm_monitor_diagnostic_setting" "storage" {
  for_each                   = local.account_service_pairs
  name                       = "diag-${each.key}"
  target_resource_id         = "${each.value.acc_id}/${each.value.svc}/default"
  log_analytics_workspace_id = var.central_law_id

  enabled_log { category = "StorageRead" }
  enabled_log { category = "StorageWrite" }
  enabled_log { category = "StorageDelete" }

  metric {
    category = "Transaction"
    enabled  = true
  }
  metric {
    category = "Capacity"
    enabled  = true
  }
}

Remediation — Bicep

targetScope = 'resourceGroup'

@description('Storage account whose blob/queue/table/file ops should be audited.')
param storageName string
@description('Workspace receiving the data-plane logs.')
param workspaceId string

resource storage 'Microsoft.Storage/storageAccounts@2024-01-01' existing = {
  name: storageName
}

resource blobSvc 'Microsoft.Storage/storageAccounts/blobServices@2024-01-01' existing = {
  parent: storage
  name: 'default'
}

resource diagBlob 'Microsoft.Insights/diagnosticSettings@2024-01-01-preview' = {
  scope: blobSvc
  name: 'blob-audit'
  properties: {
    workspaceId: workspaceId
    logs: [
      { category: 'StorageRead',   enabled: true }
      { category: 'StorageWrite',  enabled: true }
      { category: 'StorageDelete', enabled: true }
    ]
  }
}

Compliance mapping

CIS AWS Foundations v3.0.0 CIS Microsoft Azure Foundations v3.0.0 CIS GCP Foundation v4.0.0 CIS OCI Foundation v2.0.0 NIST SP 800-53 rev5 ISO/IEC 27001:2022 ISO/IEC 27017:2015
n/a5.x (verify)n/an/a AU-2; AU-12A.8.15CLD.12.4.5

Log signals

  • AzureActivity Microsoft.Insights/diagnosticSettings/delete or write targeting Storage Account resources where the StorageRead/StorageWrite/StorageDelete categories are dropped — terminates the dataplane audit trail.
  • AzureActivity Microsoft.Storage/storageAccounts/write where the request body reduces minimumTlsVersion on the diagnostics destination account or flips allowSharedKeyAccess = true — weakens the trustworthiness of the archive itself.
  • StorageBlobLogs StatusText = "AuthenticationFailed" spikes on the diagnostics destination — adversary attempting to write decoy events or probe the archive.

Query

AzureActivity
          | where ResourceId contains "Microsoft.Storage/storageAccounts"
          | where OperationNameValue in ("Microsoft.Insights/diagnosticSettings/delete", "Microsoft.Insights/diagnosticSettings/write")
          | extend body = tostring(parse_json(Properties).requestbody)
          | project TimeGenerated, Caller, ResourceId, OperationNameValue, body
          | order by TimeGenerated desc
          | take 200

Run as a KQL query in Log Analytics. The dataplane categories (StorageRead/Write/Delete) are the only direct way to attribute an object-level operation to a principal once at-rest; their disable is incident-grade.

Alert threshold

  • Any drop of StorageRead/Write/Delete diagnostic categories on a Storage Account holding production data — page on first occurrence.
  • Diagnostic-settings change that retargets the destination workspace to one outside the SOC tenant — page; treat as data-residency control failure as well as audit failure.

Initial response

  1. Reapply the Storage Account diagnostic-settings module via the IaC pipeline; capture the AzureActivity Caller and resource-graph snapshot as the change ledger.
  2. Walk StorageBlobLogs (or the archive container if also written to immutable blob) for the exposure window — every read/write against sensitive containers during the gap should be reconciled with documented application access.
  3. Escalate per general/ir.html — reconfirm the Azure Policy Deploy diagnostic settings for Storage to Log Analytics workspace is assigned in DeployIfNotExists mode at the management group.

References

Equivalent on: AWS · GCP · OCI

azure-log-03-defender-recommendations ! HIGH DETECTIVE

Microsoft Defender for Cloud's regulatory compliance dashboard is configured with the CIS Microsoft Azure Foundations Benchmark v3.0.0 policy initiative assigned at the tenant-root management group, plus auto-remediation policies (deployIfNotExists / modify effects) on the high-priority recommendations the organisation has signed off on remediating without human review (typically: enable diagnostic settings, enable encryption-by-default, lock storage to private network access, enforce TLS 1.2+) (Microsoft Learn — Defender for Cloud regulatory compliance dashboard (accessed 2026-05)). The dashboard scores every subscription against every CIS sub-ID in the assigned initiative and gives the security and compliance teams a per-control remediation queue keyed to the audit framework's sub-IDs. Distinction from azure-log-05: this control is the regulatory compliance surface (score per assigned initiative; CIS sub-IDs; deployIfNotExists auto-remediation); azure-log-05 is the umbrella Secure Score surface (cross-initiative weighted score; monthly review cadence; workflow automation on Defender recommendations as events). Both ship; both are populated by the same Defender engine but answer different review-cadence questions. The underlying compliance-engineering principle is in General Compliance Frameworks.

Remediation — Azure CLI

# Azure CLI 2.x
# Find the built-in policy set definition GUID for CIS Microsoft Azure Foundations v3.0.0.
az policy set-definition list --query "[?contains(displayName, 'CIS Microsoft Azure Foundations Benchmark v3')].{name:name, displayName:displayName}" -o table

# Assign at the tenant-root management group.
CIS_SET_ID="/providers/Microsoft.Authorization/policySetDefinitions/<cis-azure-v3-policy-set-guid>"

az policy assignment create \
  --name cis-azure-v3 \
  --scope "/providers/Microsoft.Management/managementGroups/tenant-root" \
  --policy-set-definition "$CIS_SET_ID" \
  --location westeurope \
  --mi-system-assigned \
  --role Contributor \
  --identity-scope "/providers/Microsoft.Management/managementGroups/tenant-root" \
  --display-name "CIS Microsoft Azure Foundations Benchmark v3.0.0"

# Inspect compliance state for one subscription via the regulatory-compliance API.
az rest --method GET --uri "https://management.azure.com/subscriptions/$(az account show --query id -o tsv)/providers/Microsoft.Security/regulatoryComplianceStandards/CIS-Microsoft-Azure-Foundations-Benchmark-v3.0.0/regulatoryComplianceControls?api-version=2019-01-01-preview"

Remediation — Terraform

# Terraform AzureRM provider ~> 3.0
# Source: Microsoft Learn — Defender for Cloud regulatory compliance initiative assignment.

variable "cis_azure_v3_policy_set_definition_id" {
  type        = string
  description = "Built-in policy set GUID for CIS Microsoft Azure Foundations Benchmark v3.0.0"
}

resource "azurerm_management_group_policy_assignment" "cis_azure_v3" {
  name                 = "cis-azure-v3"
  display_name         = "CIS Microsoft Azure Foundations Benchmark v3.0.0"
  management_group_id  = "/providers/Microsoft.Management/managementGroups/tenant-root"
  policy_definition_id = var.cis_azure_v3_policy_set_definition_id
  location             = "westeurope"

  identity { type = "SystemAssigned" }
}

# Auto-remediation on a specific high-priority recommendation: enable secure
# transfer required on Storage Accounts. Uses the modify effect.
resource "azurerm_management_group_policy_assignment" "secure_transfer" {
  name                 = "stg-secure-transfer"
  management_group_id  = "/providers/Microsoft.Management/managementGroups/tenant-root"
  policy_definition_id = "/providers/Microsoft.Authorization/policyDefinitions/404c3081-a854-4457-ae30-26a93ef643f9"
  location             = "westeurope"

  identity { type = "SystemAssigned" }

  parameters = jsonencode({
    effect = { value = "Modify" }
  })
}

Remediation — Bicep

targetScope = 'subscription'

resource autoProv 'Microsoft.Security/autoProvisioningSettings@2024-08-01' = {
  name: 'default'
  properties: {
    autoProvision: 'On'
  }
}

resource defenderContact 'Microsoft.Security/securityContacts@2023-12-01-preview' = {
  name: 'default'
  properties: {
    emails: 'soc@example.org'
    notificationsByRole: { state: 'On', roles: ['Owner', 'ServiceAdmin'] }
    alertNotifications: { state: 'On', minimalSeverity: 'High' }
  }
}

Compliance mapping

CIS AWS Foundations v3.0.0 CIS Microsoft Azure Foundations v3.0.0 CIS GCP Foundation v4.0.0 CIS OCI Foundation v2.0.0 NIST SP 800-53 rev5 ISO/IEC 27001:2022 ISO/IEC 27017:2015
n/a2.xn/an/a CM-8; CM-3A.8.9CLD.12.4.5

Log signals

  • AzureActivity Microsoft.Security/autoProvisioningSettings/write where autoProvision flips from On to Off — disarms the Log Analytics agent deployment that feeds Defender recommendations.
  • AzureActivity Microsoft.Security/assessmentMetadata/write or Microsoft.Security/securityStandards/write where the request body excludes an MCSB recommendation that the org has classified as critical.
  • Sudden drop in SecurityRecommendation table volume per subscription — coverage erosion that the per-assessment delete events alone may not explain.

Query

AzureActivity
          | where OperationNameValue in ("Microsoft.Security/autoProvisioningSettings/write", "Microsoft.Security/assessmentMetadata/write", "Microsoft.Security/securityStandards/write")
          | extend body = tostring(parse_json(Properties).requestbody)
          | where body has "\"autoProvision\":\"Off\"" or body has "exempt" or OperationNameValue endswith "assessmentMetadata/write"
          | project TimeGenerated, Caller, SubscriptionId, OperationNameValue, body
          | order by TimeGenerated desc
          | take 200

Run as a KQL query in Log Analytics. Sentinel can also pivot via the SecurityRecommendation table — sudden recommendation-volume drops are a downstream symptom worth alerting on.

Alert threshold

  • Any flip of autoProvision to Off at the subscription scope — page on first occurrence.
  • Exemption applied to an MCSB recommendation tagged Critical without an attached governance ticket reference — page; exemptions should be ticket-bound by policy.

Initial response

  1. Reapply the IaC baseline that sets autoProvision=On and unassigns any unauthorised exemption; confirm the next Defender for Cloud assessment cycle refreshes within four hours.
  2. Cross-check the new recommendation count vs the 30-day baseline — any subscription where critical recommendations failed to repopulate likely has agent-deployment failures and warrants a Defender for Servers fleet inventory walk.
  3. Escalate per general/ir.html — confirm the management-group Azure Policy Configure Microsoft Defender for Cloud to enable continuous export remains assigned and enforced.

References

Equivalent on: AWS · GCP · OCI

azure-log-04-defender-for-cloud ! CRITICAL DETECTIVE

Microsoft Defender for Cloud's workload-protection plans are enabled subscription-wide for every resource type the tenant operates: Defender for Servers Plan 2 (MDE auto-deploy, vulnerability assessment via Defender Vulnerability Management, just-in-time VM access, file integrity monitoring, adaptive application controls), Defender for Storage v2 (malware scanning + sensitive-data discovery), Defender for SQL (Standard), Defender for SQL Server on machines (Standard), Defender for App Service (Standard), Defender for Key Vault (Standard), Defender for Resource Manager (Standard, "Arm"), Defender for DNS (Standard), Defender for Containers (Standard, MDC for AKS + ACR), and Defender CSPM (Standard, "CloudPosture") (Microsoft Learn — What is Microsoft Defender for Cloud (accessed 2026-05)). Each plan is billed per protected-resource-hour and emits its own alert taxonomy into the Defender alerts blade; alerts flow into Microsoft Sentinel via the Defender for Cloud data connector (azure-log-08) where incidents are correlated and automation rules execute SOAR playbooks. Anti-conflation with azure-log-08 (Sentinel): Defender for Cloud is the CSPM + CWPP plane (posture management, regulatory compliance scoring, workload-protection plans that run against Servers / Storage / SQL / Containers / Key Vault / App Service / RM / DNS); Microsoft Sentinel is the SIEM + SOAR plane (data ingestion across Defender, Entra, Activity, Office 365, third-party syslog; KQL analytics on top; Logic App playbook automation). They complement each other and are independently licensed — Defender for Cloud per protected resource-hour, Sentinel per GB ingested. The intended pipeline is Defender alerts → Sentinel incidents → Sentinel automation rules → playbooks. Adopting Defender without Sentinel leaves correlation and SOAR off the table; adopting Sentinel without Defender leaves a CWPP gap. They are not substitutable.

Remediation — Azure CLI

# Azure CLI 2.x
# Audit: which plans are enabled / standard tier across the current subscription?
az security pricing list --query "value[].{name:name, tier:pricingTier}" -o table

# Enable each plan at Standard tier (subscription-scope).
for plan in VirtualMachines StorageAccounts SqlServers SqlServerVirtualMachines AppServices KeyVaults Arm Dns Containers CloudPosture; do
  az security pricing create --name "$plan" --tier Standard
done

# Defender for Servers Plan 2 (sub-plan of VirtualMachines).
az security pricing create --name VirtualMachines --tier Standard --subplan P2

# Defender for Storage v2 (malware scanning + sensitive-data discovery sub-plan).
az security pricing create --name StorageAccounts --tier Standard --subplan DefenderForStorageV2

# Enforcement: built-in initiative 'Configure Microsoft Defender for Cloud plans'
# assigned at the tenant root management group keeps new subscriptions enrolled.

Remediation — Terraform

# Terraform AzureRM provider ~> 3.0
# Source: Microsoft Learn — Defender for Cloud plan configuration.

locals {
  defender_plans = {
    "VirtualMachines"          = "P2"  # Defender for Servers Plan 2
    "StorageAccounts"          = "DefenderForStorageV2"
    "SqlServers"               = "Standard"
    "SqlServerVirtualMachines" = "Standard"
    "AppServices"              = "Standard"
    "KeyVaults"                = "Standard"
    "Arm"                      = "Standard"
    "Dns"                      = "Standard"
    "Containers"               = "Standard"
    "CloudPosture"             = "Standard"
  }
}

resource "azurerm_security_center_subscription_pricing" "plans" {
  for_each      = local.defender_plans
  resource_type = each.key
  tier          = each.value == "Standard" ? "Standard" : "Standard"
  subplan       = each.value == "Standard" ? null : each.value
}

# Defender alerts → central Log Analytics workspace; required for the Defender
# for Cloud data connector in Sentinel (azure-log-08).
resource "azurerm_security_center_workspace" "this" {
  scope        = "/subscriptions/${var.subscription_id}"
  workspace_id = var.central_law_id
}

Remediation — Bicep

targetScope = 'subscription'

@description('Defender for Cloud plans to enable at Standard tier.')
var plans = [
  'VirtualMachines'
  'AppServices'
  'StorageAccounts'
  'SqlServers'
  'KeyVaults'
  'Containers'
  'CosmosDbs'
  'CloudPosture'
  'Api'
]

resource pricings 'Microsoft.Security/pricings@2024-01-01' = [for plan in plans: {
  name: plan
  properties: { pricingTier: 'Standard' }
}]

Remediation — Pulumi (TypeScript)

import * as pulumi from "@pulumi/pulumi";
import * as security from "@pulumi/azure-native/security";

const plans = [
  "VirtualMachines","AppServices","StorageAccounts","SqlServers","KeyVaults",
  "Containers","CosmosDbs","CloudPosture","Api",
];

plans.forEach((p) => {
  new security.Pricing(p, {
    pricingName: p,
    pricingTier: security.PricingTier.Standard,
  });
});

Compliance mapping

CIS AWS Foundations v3.0.0 CIS Microsoft Azure Foundations v3.0.0 CIS GCP Foundation v4.0.0 CIS OCI Foundation v2.0.0 NIST SP 800-53 rev5 ISO/IEC 27001:2022 ISO/IEC 27017:2015
n/a2.1-2.13n/an/a SI-4; SI-4(2); SI-4(4)A.8.16CLD.12.4.5

Log signals

  • AzureActivity Microsoft.Security/pricings/write where one of the plan tiers (Servers, AppService, SqlServers, StorageAccounts, KeyVaults, Arm, Dns, OpenSourceRelationalDatabases, CosmosDbs, Containers, CloudPosture) flips from Standard to Free.
  • AzureActivity scope reductions on continuous-export rules — Microsoft.Security/automations/delete targeting an automation that forwarded SecurityAlerts to the SOC workspace.
  • SecurityAlert and SecurityIncident tables show ingestion-rate drop for a plan-tier that was downgraded — downstream confirmation that the disable took effect.

Query

AzureActivity
          | where OperationNameValue == "Microsoft.Security/pricings/write"
          | extend body = tostring(parse_json(Properties).requestbody)
          | where body has "\"pricingTier\":\"Free\""
          | extend plan = extract("\\\"name\\\":\\\"([^\\\"]+)\\\"", 1, ResourceId)
          | project TimeGenerated, Caller, SubscriptionId, plan, body
          | order by TimeGenerated desc
          | take 100

Run as a KQL query in Log Analytics; persist as a Sentinel analytics rule with severity High. Any pricing downgrade should be ticket-bound; pair the analytics rule with an automation playbook that opens the change ticket if missing.

Alert threshold

  • Any plan flip from Standard to Free in a production subscription — page on first occurrence.
  • Delete of a continuous-export automation pointing at the SOC workspace — page; downstream alert flow is now broken.

Initial response

  1. Reapply the Defender plan baseline via az security pricing create --name {plan} --tier Standard; capture the AzureActivity Caller and previous pricing tier as the rollback ledger.
  2. Re-create the continuous-export automation via Bicep, and replay any SecurityAlerts emitted during the gap via the Defender for Cloud export-history blade.
  3. Escalate per general/ir.html — confirm the management-group-scoped Azure Policy enforcing Defender plan parity is in DeployIfNotExists mode and not just audit.

References

Equivalent on: AWS · GCP · OCI

azure-log-05-defender-recommendations ! HIGH DETECTIVE

The Microsoft Defender for Cloud Secure Score is treated as the umbrella security-posture KPI: a baseline is captured at adoption, a monthly review cadence is enforced for the security team to walk every high-severity recommendation, and a Defender for Cloud workflow automation rule fires a Logic App on any new high-severity recommendation (or any new high-severity Defender alert) to open a ticket against the owning workload team and to notify the on-call channel (Microsoft Learn — Secure Score in Microsoft Defender for Cloud (accessed 2026-05)). Distinction from azure-log-03: log-03 is the regulatory compliance dashboard (CIS Microsoft Azure Foundations Benchmark v3.0.0 initiative assignment + per-sub-ID remediation queue + deployIfNotExists auto-remediation); log-05 is the cross-initiative umbrella (a single weighted score that aggregates all Defender recommendations regardless of which framework initiative they belong to, plus monthly cadence + workflow automation that fires per-event rather than per-CIS-control). Both ship; both are populated by the same Defender engine; both have distinct review rituals — the compliance dashboard answers "are we passing CIS Azure v3.0.0 right now", Secure Score answers "is our overall Defender-visible posture trending up or down this quarter". Cross-link the score to the General Logging — alerting principle: a recommendation that does not generate a ticket against an owner is a recommendation no one will action.

Remediation — Azure CLI

# Azure CLI 2.x
# Inspect the current Secure Score for the subscription.
az security secure-scores list --query "[].{name:name, score:score.current, max:score.max, percentage:score.percentage}" -o table

# Per-secure-control breakdown — find the lowest-scoring controls first.
az security secure-score-controls list --query "[?score.percentage<0.7].{control:displayName, score:score.percentage, healthy:healthyResourceCount, unhealthy:unhealthyResourceCount}" -o table

# Workflow automation: fire a Logic App on new high-severity Defender recommendation.
LOGIC_APP_ID=$(az logic workflow show -g rg-security-soar-westeu -n la-defender-high-severity --query id -o tsv)

az security workflow-automation create \
  --resource-group rg-security-soar-westeu \
  --name aut-defender-high-severity \
  --location westeurope \
  --scopes "/subscriptions/$(az account show --query id -o tsv)" \
  --sources '[{"eventSource":"Assessments","ruleSets":[{"rules":[{"propertyJPath":"properties.metadata.severity","propertyType":"String","expectedValue":"High","operator":"Equals"}]}]}]' \
  --actions "[{\"actionType\":\"LogicApp\",\"logicAppResourceId\":\"$LOGIC_APP_ID\",\"uri\":\"$(az rest --method POST --uri https://management.azure.com$LOGIC_APP_ID/listCallbackUrl?api-version=2016-06-01 --query value -o tsv)\"}]"

Remediation — Terraform

# Terraform AzureRM provider ~> 3.0
# Source: Microsoft Learn — Defender for Cloud workflow automation.

# Logic App that opens a ticket + pages the on-call channel.
resource "azurerm_logic_app_workflow" "defender_high_severity" {
  name                = "la-defender-high-severity"
  resource_group_name = azurerm_resource_group.soar.name
  location            = azurerm_resource_group.soar.location
}

# Workflow automation on Microsoft.Security/assessments events with severity High.
resource "azurerm_security_center_automation" "defender_high_severity" {
  name                = "aut-defender-high-severity"
  resource_group_name = azurerm_resource_group.soar.name
  location            = azurerm_resource_group.soar.location

  scopes = ["/subscriptions/${var.subscription_id}"]

  source {
    event_source = "Assessments"
    rule_set {
      rule {
        property_path  = "properties.metadata.severity"
        operator       = "Equals"
        expected_value = "High"
        property_type  = "String"
      }
    }
  }

  action {
    type        = "LogicApp"
    resource_id = azurerm_logic_app_workflow.defender_high_severity.id
    trigger_url = "https://prod-XX.westeurope.logic.azure.com/workflows/..."  # listCallbackUrl
  }
}

Remediation — Bicep

targetScope = 'subscription'

@description('Built-in initiative: Azure Security Benchmark.')
var asb = '/providers/Microsoft.Authorization/policySetDefinitions/1f3afdf9-d0c9-4c3d-847f-89da613e70a8'

resource assignAsb 'Microsoft.Authorization/policyAssignments@2024-04-01' = {
  name: 'asb-defender-recommendations'
  properties: {
    policyDefinitionId: asb
    displayName: 'Azure Security Benchmark — Defender recommendations'
    enforcementMode: 'Default'
  }
}

Compliance mapping

CIS AWS Foundations v3.0.0 CIS Microsoft Azure Foundations v3.0.0 CIS GCP Foundation v4.0.0 CIS OCI Foundation v2.0.0 NIST SP 800-53 rev5 ISO/IEC 27001:2022 ISO/IEC 27017:2015
n/a(best-practices)n/an/a CA-7; SI-4A.8.16; A.5.36CLD.12.4.5

Log signals

  • AzureActivity Microsoft.Security/securityStandards/write where a regulatory compliance standard (PCI-DSS v4, ISO 27001:2022, NIST 800-53 rev5) is removed from a subscription assignment — silently strips the recommendation set behind those frameworks.
  • AzureActivity Microsoft.Security/assessmentMetadata/write that creates broad exemptions covering more than 10% of the in-scope resource population.
  • RegulatoryComplianceAssessment table — abrupt drop in row count per standard per subscription corroborates downstream coverage loss.

Query

AzureActivity
          | where OperationNameValue == "Microsoft.Security/securityStandards/write"
          | extend body = tostring(parse_json(Properties).requestbody)
          | where body has "\"state\":\"removed\"" or body has "delete"
          | project TimeGenerated, Caller, SubscriptionId, ResourceId, body
          | order by TimeGenerated desc
          | take 200

Run as a KQL query in Log Analytics. Compliance-standard assignments should be subject to four-eyes change control; persist as a Sentinel analytics rule and require an attached ticket reference in the change.

Alert threshold

  • Any compliance-standard removal at the subscription scope — page on first occurrence; coverage of an audit framework just disappeared.
  • Exemption that scopes more than 10% of resources for a single recommendation — page on first occurrence regardless of recommendation severity.

Initial response

  1. Reassign the missing standard via the Regulatory Compliance blade or az security regulatory-compliance-standards; capture the AzureActivity Properties.requestbody as the prior-state ledger.
  2. Walk the exemption history for any compounding suppressions added during the same operator session — they often co-occur with the standard removal as cover.
  3. Escalate per general/ir.html — confirm the management-group-scoped Azure Policy initiative enforcing compliance assignments remains in deny mode for the framework set the org commits to.

References

Equivalent on: AWS · GCP · OCI

azure-log-06-nsg-flow-logs ! MEDIUM DETECTIVE

Every Network Security Group in every subscription has NSG Flow Logs v2 enabled (v1 was retired by Microsoft in 2024 and is no longer creatable; existing v1 deployments must migrate), with the flow logs landing in a dedicated regional Storage Account and being processed by Traffic Analytics against the central Log Analytics workspace at a 10-minute processing interval (Microsoft Learn — NSG flow logs overview (accessed 2026-05)). Minimum retention is 30 days in the Storage Account; regulated workloads target 90 days or longer. Traffic Analytics turns the raw flow records into the AzureNetworkAnalytics_CL table in the LAW where KQL queries answer "which subnets are talking to which", "which malicious IPs hit the workload last week", and "which VM made a sudden 10× egress jump in the last hour" — none of which the NSG rule definitions themselves can tell you. Cross-link to General Logging — what to log; for the network-segmentation principle this flow data evidences, see azure-net-02 (NSG no-admin-internet).

Remediation — Azure CLI

# Azure CLI 2.x
# Audit: list NSGs without flow logs.
for sub in $(az account list --query '[].id' -o tsv); do
  for region in westeurope northeurope; do
    az network watcher flow-log list --location "$region" --subscription "$sub" \
      --query "[].{nsg:targetResourceId, enabled:enabled, version:format}" -o tsv
  done
done

# Enable Flow Logs v2 + Traffic Analytics on one NSG.
NSG_ID=$(az network nsg show --resource-group rg-net-prod-westeu --name nsg-app-subnet --query id -o tsv)
SA_ID=$(az storage account show --resource-group rg-security-logging-westeu --name stflowlogswesteu --query id -o tsv)
LAW_ID=$(az monitor log-analytics workspace show \
  --resource-group rg-security-logging-westeu --workspace-name law-central-prod --query id -o tsv)
LAW_REGION=$(az monitor log-analytics workspace show \
  --resource-group rg-security-logging-westeu --workspace-name law-central-prod --query location -o tsv)

az network watcher flow-log create \
  --location westeurope \
  --name fl-nsg-app-subnet \
  --nsg "$NSG_ID" \
  --storage-account "$SA_ID" \
  --enabled true \
  --format JSON --log-version 2 \
  --retention 30 \
  --traffic-analytics true \
  --workspace "$LAW_ID" \
  --workspace-region "$LAW_REGION" \
  --interval 10

Remediation — Terraform

# Terraform AzureRM provider ~> 3.0
# Source: Microsoft Learn — NSG Flow Logs v2 + Traffic Analytics.

resource "azurerm_network_watcher_flow_log" "app_subnet" {
  name                 = "fl-nsg-app-subnet"
  network_watcher_name = "NetworkWatcher_westeurope"
  resource_group_name  = "NetworkWatcherRG"
  network_security_group_id = azurerm_network_security_group.app_subnet.id

  storage_account_id = azurerm_storage_account.flow_logs.id
  enabled            = true
  version            = 2  # v1 retired in 2024; v2 only

  retention_policy {
    enabled = true
    days    = 30
  }

  traffic_analytics {
    enabled               = true
    workspace_id          = var.central_law_workspace_guid
    workspace_region      = "westeurope"
    workspace_resource_id = var.central_law_id
    interval_in_minutes   = 10
  }
}

# Dedicated Storage Account for flow logs — separate from regulated-data SA.
resource "azurerm_storage_account" "flow_logs" {
  name                          = "stflowlogswesteu"
  resource_group_name           = "rg-security-logging-westeu"
  location                      = "westeurope"
  account_tier                  = "Standard"
  account_replication_type      = "LRS"
  public_network_access_enabled = false
  min_tls_version               = "TLS1_2"
}

Remediation — Bicep

targetScope = 'resourceGroup'

@description('NSG resource ID being monitored.')
param nsgResourceId string
@description('Storage account ID receiving the raw flow logs (lifecycle-policied to expire).')
param storageAccountId string
@description('Log Analytics workspace ID for Traffic Analytics.')
param workspaceId string

param location string = resourceGroup().location

resource flowLog 'Microsoft.Network/networkWatchers/flowLogs@2024-03-01' = {
  name: 'NetworkWatcher_${location}/flowlog-${uniqueString(nsgResourceId)}'
  location: location
  properties: {
    targetResourceId: nsgResourceId
    storageId: storageAccountId
    enabled: true
    format: { type: 'JSON', version: 2 }
    retentionPolicy: { days: 90, enabled: true }
    flowAnalyticsConfiguration: {
      networkWatcherFlowAnalyticsConfiguration: {
        enabled: true
        workspaceResourceId: workspaceId
        trafficAnalyticsInterval: 10
      }
    }
  }
}

Compliance mapping

CIS AWS Foundations v3.0.0 CIS Microsoft Azure Foundations v3.0.0 CIS GCP Foundation v4.0.0 CIS OCI Foundation v2.0.0 NIST SP 800-53 rev5 ISO/IEC 27001:2022 ISO/IEC 27017:2015
n/a6.5n/an/a AU-2; AU-12; SC-7A.8.15; A.8.16CLD.12.4.5

Log signals

  • AzureActivity Microsoft.Network/networkWatchers/flowLogs/delete targeting a flow-log resource that is referenced by Traffic Analytics — kills both the dataplane archive and the Sentinel-feeding analytics layer.
  • AzureActivity Microsoft.Network/networkWatchers/flowLogs/write where the request body sets enabled = false or where the retention is dropped below the 90-day policy floor.
  • AzureDiagnostics Category = "NetworkSecurityGroupFlowEvent" ingestion gap exceeding 60 minutes on an NSG previously emitting flow data — absence-of-signal indicator.

Query

AzureActivity
          | where OperationNameValue startswith "Microsoft.Network/networkWatchers/flowLogs/"
          | extend body = tostring(parse_json(Properties).requestbody)
          | project TimeGenerated, Caller, OperationNameValue, ResourceId, body
          | order by TimeGenerated desc
          | take 200

Run as a KQL query in Log Analytics. Pair with a watchdog: AzureDiagnostics | where Category == "NetworkSecurityGroupFlowEvent" | summarize last=max(TimeGenerated) by Resource | where last < ago(60m).

Alert threshold

  • Any delete or disable of an NSG flow log attached to a production VNet — page on first occurrence; lateral-movement detection just lost its primary data source.
  • 60-minute ingestion gap on a flow-log resource whose 30-day baseline is steady — page; the flow archive is the truth source for the Sentinel network-anomaly notebooks.

Initial response

  1. Reapply the flow-log resource via az network watcher flow-log create or the IaC baseline; confirm the destination Storage Account and Log Analytics workspace receive the next 10-minute batch.
  2. Pull the archived flow data from Storage for the exposure window and replay any session toward east-west admin ports as suspect-pending-review.
  3. Escalate per general/ir.html — confirm the Azure Policy Deploy a flow log resource with target network security group is assigned in DeployIfNotExists mode at the management group.

References

Equivalent on: AWS · GCP · OCI

azure-log-07-defender-alerts ! HIGH DETECTIVE

A canonical set of Activity Log alerts fires per-subscription on the operations whose execution is, by themselves, the indicator of compromise or the indicator of a high-risk human action: Microsoft.Authorization/roleAssignments/write (any role assignment change), Microsoft.Network/networkSecurityGroups/securityRules/write + .../delete (NSG rule changes), Microsoft.KeyVault/vaults/delete + Microsoft.KeyVault/vaults/keys/delete (Key Vault and key deletes), Microsoft.Authorization/policyAssignments/write (policy assignment writes), Microsoft.Resources/subscriptions/delete, Microsoft.Resources/subscriptions/providers/Microsoft.Security/locations/jitNetworkAccessPolicies/initiate/action (JIT-initiate, attacker mis-use indicator), and tenant-level operations such as directory-tenant change (Microsoft Learn — Activity Log alerts (accessed 2026-05)). Each alert routes to an Action Group whose receivers include the on-call channel, a Sentinel incident-creator (so the alert correlates with concurrent Defender alerts and sign-in anomalies for the same actor), and an ITSM ticket creator. This control is the Azure analog of the Phase 4 four-eyes-on-critical-events principle in General Logging — alerting; for the underlying detection-engineering principle see SIEM / detection engineering.

Remediation — Azure CLI

# Azure CLI 2.x
# Create an Action Group that fans out to email + Sentinel incident + ITSM.
az monitor action-group create \
  --resource-group rg-security-alerts-westeu \
  --name ag-soc-oncall \
  --short-name sococ \
  --action email soc-oncall soc-oncall@example.com \
  --action webhook sentinel-incident https://prod-XX.westeurope.logic.azure.com/workflows/.../triggers/manual/paths/invoke

AG_ID=$(az monitor action-group show \
  --resource-group rg-security-alerts-westeu --name ag-soc-oncall --query id -o tsv)

# Activity Log alert: any role-assignment write at the subscription scope.
az monitor activity-log alert create \
  --resource-group rg-security-alerts-westeu \
  --name alert-role-assignment-write \
  --scope "/subscriptions/$(az account show --query id -o tsv)" \
  --condition category=Administrative \
  --condition operationName=Microsoft.Authorization/roleAssignments/write \
  --action-group "$AG_ID"

# Repeat for: NSG rule write/delete, Key Vault delete, key delete, policy assignment write,
# subscription delete, JIT-initiate, etc.

Remediation — Terraform

# Terraform AzureRM provider ~> 3.0
# Source: Microsoft Learn — Activity Log alerts via Terraform.

locals {
  canonical_critical_operations = {
    "role-assignment-write"       = "Microsoft.Authorization/roleAssignments/write"
    "role-assignment-delete"      = "Microsoft.Authorization/roleAssignments/delete"
    "nsg-rule-write"              = "Microsoft.Network/networkSecurityGroups/securityRules/write"
    "nsg-rule-delete"             = "Microsoft.Network/networkSecurityGroups/securityRules/delete"
    "keyvault-delete"             = "Microsoft.KeyVault/vaults/delete"
    "keyvault-key-delete"         = "Microsoft.KeyVault/vaults/keys/delete"
    "policy-assignment-write"     = "Microsoft.Authorization/policyAssignments/write"
    "policy-assignment-delete"    = "Microsoft.Authorization/policyAssignments/delete"
    "subscription-delete"         = "Microsoft.Resources/subscriptions/delete"
  }
}

resource "azurerm_monitor_action_group" "soc_oncall" {
  name                = "ag-soc-oncall"
  resource_group_name = "rg-security-alerts-westeu"
  short_name          = "sococ"

  email_receiver {
    name          = "soc-oncall"
    email_address = "soc-oncall@example.com"
  }

  webhook_receiver {
    name        = "sentinel-incident"
    service_uri = var.sentinel_incident_logic_app_callback_url
  }
}

resource "azurerm_monitor_activity_log_alert" "critical_ops" {
  for_each            = local.canonical_critical_operations
  name                = "alert-${each.key}"
  resource_group_name = "rg-security-alerts-westeu"
  location            = "global"
  scopes              = ["/subscriptions/${var.subscription_id}"]

  criteria {
    category       = "Administrative"
    operation_name = each.value
  }

  action {
    action_group_id = azurerm_monitor_action_group.soc_oncall.id
  }
}

Remediation — Bicep

targetScope = 'subscription'

resource contact 'Microsoft.Security/securityContacts@2023-12-01-preview' = {
  name: 'default'
  properties: {
    emails: 'soc@example.org;ir-oncall@example.org'
    notificationsByRole: {
      state: 'On'
      roles: ['Owner', 'ServiceAdmin', 'AccountAdmin']
    }
    alertNotifications: {
      state: 'On'
      minimalSeverity: 'Medium'
    }
  }
}

Compliance mapping

CIS AWS Foundations v3.0.0 CIS Microsoft Azure Foundations v3.0.0 CIS GCP Foundation v4.0.0 CIS OCI Foundation v2.0.0 NIST SP 800-53 rev5 ISO/IEC 27001:2022 ISO/IEC 27017:2015
n/a5.2-5.xn/an/a AU-6; SI-4A.8.16CLD.12.4.5

Log signals

  • AzureActivity Microsoft.Insights/activityLogAlerts/delete or write where the rule that fires on Critical-severity SecurityAlerts is disabled or scoped away from production subscriptions.
  • AzureActivity Microsoft.Insights/actionGroups/write where the email or webhook receiver for the SOC on-call rotation is removed — alerts still fire but reach nobody.
  • SecurityAlert + AlertEvaluation telemetry — a Critical alert with no matching AlertProcessingState = "AlertNotified" within 5 minutes is itself an alert-processing failure signal.

Query

AzureActivity
          | where OperationNameValue in ("Microsoft.Insights/activityLogAlerts/delete", "Microsoft.Insights/activityLogAlerts/write", "Microsoft.Insights/actionGroups/write", "Microsoft.Insights/actionGroups/delete")
          | extend body = tostring(parse_json(Properties).requestbody)
          | project TimeGenerated, Caller, OperationNameValue, ResourceId, body
          | order by TimeGenerated desc
          | take 200

Run as a KQL query in Log Analytics. The alert-routing layer is itself the failure mode that is hardest to detect — the absence of paging is silent — so persist as a Sentinel analytics rule with automation that pings the on-call confirming the route remains live.

Alert threshold

  • Any disable of an activity-log alert rule scoped to SecurityAlert Critical events — page immediately.
  • Removal of the SOC receiver from an action group attached to Critical-severity alerts — page; coverage of the most important alert tier is now broken.

Initial response

  1. Restore the alert rule and action-group composition via the IaC baseline; trigger a synthetic SecurityAlert (Test-DefenderForCloudAlert) to confirm the routing reaches the on-call.
  2. Walk the SecurityAlert table for the exposure window — any Critical alert that fired during the routing gap should be treated as not-yet-acknowledged and processed manually.
  3. Escalate per general/ir.html — confirm the Azure Policy Deploy activity-log alert for security-alert categories remains assigned in DeployIfNotExists mode.

References

Equivalent on: AWS · GCP · OCI

azure-log-08-sentinel-data-lake ! MEDIUM RESPONSIVE

Microsoft Sentinel is onboarded to the central Log Analytics workspace with data connectors enabled for Microsoft Entra ID (Audit + Sign-in logs), Azure Activity Log (from azure-log-01), Microsoft Defender for Cloud (from azure-log-04), Office 365, Microsoft Defender XDR (formerly Microsoft 365 Defender), Azure DNS, and any third-party syslog / CEF sources the tenant runs. Built-in analytics rules from the Microsoft Sentinel content hub cover the canonical detection set; custom KQL hunting queries cover the organisation-specific scenarios. SOAR closes the loop: automation rules trigger Logic App playbooks on incident severity High (isolate VM via quarantine NSG, disable identity via Microsoft Graph, snapshot affected managed disks, notify the on-call channel). Retention strategy: 2 years total — 90 days hot in the LAW, 21 months in the Sentinel archive tier (and/or Sentinel auxiliary-logs tier for high-volume low-touch sources such as firewall logs), with rehydration on demand for forensic investigations (Microsoft Learn — What is Microsoft Sentinel (accessed 2026-05)). Note on pricing tiers: Sentinel pricing across the hot / archive / auxiliary tiers, and the relationship between Sentinel commitment tiers and Log Analytics commitment tiers, has changed multiple times since 2023 — re-verify current pricing at writing time before sizing the workspace. Anti-conflation with azure-log-04 (Defender for Cloud): repeating the boundary called out in the Overview: Microsoft Sentinel is the SIEM + SOAR plane — data ingestion, KQL analytics, automation rules, Logic App playbooks; Microsoft Defender for Cloud is the CSPM + CWPP plane — posture management, regulatory compliance scoring, workload-protection plans. The Defender for Cloud data connector pipes Defender alerts into Sentinel where they become correlated incidents alongside Entra sign-in anomalies and third-party EDR alerts. They are not the same product, not licensed together, not substitutable. The canonical Microsoft-recommended pipeline is Defender alerts → Sentinel incidents → Sentinel automation rules → Logic App playbooks → containment + notification. Adopting one and skipping the other leaves a gap; adopting both and connecting them is the design intent.

Remediation — Azure CLI

# Azure CLI 2.x  (with the 'sentinel' extension installed: az extension add --name sentinel)
# Onboard Sentinel to the central LAW.
az sentinel onboarding-state create \
  --resource-group rg-security-logging-westeu \
  --workspace-name law-central-prod \
  --name default \
  --customer-managed-key false

# Enable the Microsoft Entra ID data connector (Audit + Sign-in logs).
az sentinel data-connector create \
  --resource-group rg-security-logging-westeu \
  --workspace-name law-central-prod \
  --data-connector-id aad-id \
  --kind AzureActiveDirectory \
  --tenant-id "$(az account show --query tenantId -o tsv)" \
  --data-types '{"signInLogs":{"state":"Enabled"},"auditLogs":{"state":"Enabled"}}'

# Enable the Defender for Cloud data connector for the current subscription.
az sentinel data-connector create \
  --resource-group rg-security-logging-westeu \
  --workspace-name law-central-prod \
  --data-connector-id mdc-sub \
  --kind AzureSecurityCenter \
  --subscription-id "$(az account show --query id -o tsv)" \
  --data-types '{"alerts":{"state":"Enabled"}}'

# Verify connectors.
az sentinel data-connector list \
  --resource-group rg-security-logging-westeu \
  --workspace-name law-central-prod -o table

Remediation — Terraform

# Terraform AzureRM provider ~> 3.0
# Source: Microsoft Learn — Microsoft Sentinel onboarding + automation rules.

resource "azurerm_sentinel_log_analytics_workspace_onboarding" "this" {
  workspace_id                 = azurerm_log_analytics_workspace.central.id
  customer_managed_key_enabled = false
}

# Entra ID Audit + Sign-in data connector.
resource "azurerm_sentinel_data_connector_azure_active_directory" "entra" {
  name                       = "entra-id"
  log_analytics_workspace_id = azurerm_log_analytics_workspace.central.id
  tenant_id                  = var.tenant_id
}

# Defender for Cloud (subscription-scoped) data connector.
resource "azurerm_sentinel_data_connector_azure_security_center" "mdc" {
  name                       = "mdc-sub"
  log_analytics_workspace_id = azurerm_log_analytics_workspace.central.id
  subscription_id            = var.subscription_id
}

# SOAR: automation rule that fires a Logic App playbook on High-severity incidents.
resource "azurerm_sentinel_automation_rule" "high_severity_isolate" {
  name                       = "isolate-vm-on-high-severity"
  log_analytics_workspace_id = azurerm_log_analytics_workspace.central.id
  display_name               = "Isolate VM on high-severity incident"
  order                      = 1
  enabled                    = true

  condition_json = jsonencode([
    {
      property = "IncidentSeverity"
      operator = "Equals"
      values   = ["High"]
    }
  ])

  action_playbook {
    logic_app_id = azurerm_logic_app_workflow.isolate_vm.id
    order        = 1
  }
}

Remediation — Bicep

targetScope = 'resourceGroup'

@description('Sentinel-onboarded workspace name.')
param workspaceName string

resource workspace 'Microsoft.OperationalInsights/workspaces@2023-09-01' = {
  name: workspaceName
  location: resourceGroup().location
  properties: {
    sku: { name: 'PerGB2018' }
    retentionInDays: 730    // 24-month retention to keep audit trail
    features: { enableLogAccessUsingOnlyResourcePermissions: true }
  }
}

resource onboard 'Microsoft.SecurityInsights/onboardingStates@2024-09-01' = {
  scope: workspace
  name: 'default'
  properties: {}
}

Compliance mapping

CIS AWS Foundations v3.0.0 CIS Microsoft Azure Foundations v3.0.0 CIS GCP Foundation v4.0.0 CIS OCI Foundation v2.0.0 NIST SP 800-53 rev5 ISO/IEC 27001:2022 ISO/IEC 27017:2015
n/an/a (post-v3.0.0)n/an/a AU-11; IR-4(7)A.8.15; A.5.28CLD.12.4.5

Log signals

  • AzureActivity Microsoft.SecurityInsights/dataConnectors/delete targeting a critical connector (AzureActivity, SigninLogs, MicrosoftDefenderXDR, Office 365) — terminates ingestion into the Sentinel workspace.
  • AzureActivity Microsoft.OperationalInsights/workspaces/write where retentionInDays is reduced below the 365-day investigation floor — silently rotates the long-tail signal off the data lake.
  • Heartbeat table summary per connector — zero rows in a rolling 30-minute window for a connector with steady baseline is a downstream confirmation that the connector is broken regardless of the management-plane event.

Query

AzureActivity
          | where OperationNameValue == "Microsoft.SecurityInsights/dataConnectors/delete" or (OperationNameValue == "Microsoft.OperationalInsights/workspaces/write" and tostring(parse_json(Properties).requestbody) has "retentionInDays")
          | extend body = tostring(parse_json(Properties).requestbody)
          | project TimeGenerated, Caller, ResourceId, OperationNameValue, body
          | order by TimeGenerated desc
          | take 200

Run as a KQL query in Log Analytics. Pair with the Heartbeat watchdog query for ingestion gaps, and persist both as Sentinel analytics rules — connectors going dark is often the first overt signal of an adversary preparing to roll forward through the tenant.

Alert threshold

  • Delete of any critical connector — page on first occurrence; the data lake just lost a fundamental signal source.
  • Retention reduction below the documented floor on the Sentinel workspace — page; investigations bound by retention horizon may already be impacted.

Initial response

  1. Recreate the connector via the IaC baseline or az sentinel data-connector create; capture the AzureActivity Caller and timestamp as the change ledger.
  2. If retention was reduced, expand it back via Set-AzOperationalInsightsWorkspace -RetentionInDays {n} within the same hour; any data already rotated cannot be recovered, but the gap is bounded.
  3. Escalate per general/ir.html — confirm the Azure Policy Deploy Sentinel data connectors initiative remains assigned to the Sentinel workspace's resource group.

References

Equivalent on: AWS · GCP · OCI

Sources