Enable Defender CSPM, create custom security initiatives, configure governance rules, map controls to regulatory frameworks, build a compliance dashboard, and automate remediation to manage cloud security posture at scale.
Cloud Security Posture Management (CSPM) is a discipline within Defender for Cloud that continuously assesses your Azure resources against security benchmarks and regulatory frameworks. Defender CSPM extends foundational CSPM with attack path analysis, cloud security graph, governance rules, and advanced compliance mapping capabilities. In this lab you will enable the Defender CSPM plan, create custom security initiatives using Azure Policy, configure governance rules for recommendation ownership, and build compliance dashboards. You will also configure attack path analysis, build KQL queries for posture assessment, set up continuous export, and automate remediation with DeployIfNotExists policies. By the end of this lab, you will have a comprehensive CSPM strategy that covers posture assessment, governance, compliance mapping, and automated remediation.
Scenario: Contoso Insurance operates 500 Azure resources spanning virtual machines, storage accounts, SQL databases, and App Services across three Azure regions. The company must meet multiple regulatory frameworks: PCI DSS for payment card processing, SOC 2 for service organization controls, and HIPAA for protected health information.
Each framework assigns different controls to the same underlying resources, creating a complex compliance matrix that the security team must manage and report against. The CISO requires automated governance so that each security recommendation has an assigned owner, a remediation deadline, and an escalation path. Auditors request quarterly compliance reports showing control coverage, remediation trends, and risk posture across all three frameworks.
Success criteria: Defender CSPM enabled, custom initiatives aligned to all three frameworks, governance rules assigning ownership, automated remediation for common misconfigurations, and auditor-ready reports.
Organizations with 500+ cloud resources cannot manually track security posture; automation and governance are essential. Regulatory compliance requires demonstrable evidence that controls are in place and misconfigurations are remediated within defined timeframes. Without CSPM, security teams lack visibility into which resources are non-compliant and which misconfigurations pose the greatest risk. Attack path analysis reveals how an attacker could chain together multiple low-severity misconfigurations to reach critical assets. Automated remediation with DeployIfNotExists policies ensures that new resources are born compliant, reducing the backlog of security findings.
Defender for Cloud provides two complementary security disciplines: Cloud Security Posture Management (CSPM) and Cloud Workload Protection (CWP). Understanding the distinction is critical before configuring posture management.
Enable the Defender CSPM plan on your subscription to unlock advanced posture management capabilities including attack path analysis and governance rules.
# Switch to the subscription where you want to enable CSPM.
az account set --subscription "YOUR_SUBSCRIPTION_ID"
# Enable the Defender CSPM (Cloud Security Posture Management) plan.
# "CloudPosture" is the API name for CSPM; Standard tier activates
# paid features: attack path analysis, governance rules, and agentless scanning.
az security pricing create \
--name CloudPosture \
--tier Standard
# Confirm the plan is active. Expect Tier = "Standard".
az security pricing show \
--name CloudPosture \
--query "{Plan:name, Tier:pricingTier}" \
-o json# Authenticate to Azure (opens browser login).
Connect-AzAccount
Set-AzContext -SubscriptionId "YOUR_SUBSCRIPTION_ID"
# Enable Defender CSPM. This turns on attack path analysis,
# governance rules, cloud security explorer, and agentless scanning.
Set-AzSecurityPricing -Name "CloudPosture" -PricingTier "Standard"
# Verify. PricingTier should show "Standard" (not "Free").
Get-AzSecurityPricing -Name "CloudPosture" | Format-List Name, PricingTierSecurity initiatives in Defender for Cloud are collections of Azure Policy definitions grouped by security domain. The default initiative is the Microsoft Cloud Security Benchmark (MCSB).
# Show every Azure Policy assignment on this subscription.
# Look for "SecurityCenterBuiltIn" - this is the Microsoft Cloud Security Benchmark
# that Defender for Cloud assigns automatically.
az policy assignment list \
--scope "/subscriptions/YOUR_SUBSCRIPTION_ID" \
--query "[].{Name:displayName, Id:name}" \
-o table
# Inspect the default security benchmark assignment.
# EnforcementMode = "Default" means non-compliant resources are flagged
# but not blocked (audit only).
az policy assignment show \
--name "SecurityCenterBuiltIn" \
--scope "/subscriptions/YOUR_SUBSCRIPTION_ID" \
--query "{Name:displayName, EnforcementMode:enforcementMode}" \
-o jsonWhen built-in standards do not cover your organization's specific requirements, create a custom initiative that groups the exact policy definitions you need.
{
"properties": {
"displayName": "Contoso Insurance. Cloud Security Baseline",
"description": "Custom security initiative for Contoso Insurance covering PCI DSS, SOC 2, and HIPAA requirements across all Azure resources.",
"metadata": {
"category": "Security Center",
"version": "1.0.0"
},
"policyDefinitions": [
{
"policyDefinitionId": "/providers/Microsoft.Authorization/policyDefinitions/404c3081-a854-4457-ae30-26a93ef643f9",
"policyDefinitionReferenceId": "secureTransferToStorageAccounts",
"parameters": {}
},
{
"policyDefinitionId": "/providers/Microsoft.Authorization/policyDefinitions/22bee202-a82f-4305-9a2a-6d7f44d4dedb",
"policyDefinitionReferenceId": "allowedLocations",
"parameters": {
"listOfAllowedLocations": {
"value": ["eastus", "westus2", "westeurope"]
}
}
},
{
"policyDefinitionId": "/providers/Microsoft.Authorization/policyDefinitions/0961003e-5a0a-4549-abde-af6a37f2724d",
"policyDefinitionReferenceId": "auditVMsWithoutDiskEncryption",
"parameters": {}
},
{
"policyDefinitionId": "/providers/Microsoft.Authorization/policyDefinitions/a6fb4358-5bf4-4ad7-ba82-2cd2f41ce5e9",
"policyDefinitionReferenceId": "sqlAuditingEnabled",
"parameters": {}
}
]
}
}# Upload the JSON file as a custom initiative (policy set) definition.
# --definitions points to the JSON file containing the policy definitions array.
az policy set-definition create \
--name "contoso-security-baseline" \
--display-name "Contoso Insurance. Cloud Security Baseline" \
--description "Custom initiative for PCI DSS, SOC 2, and HIPAA" \
--definitions @initiative-definitions.json \
--subscription "YOUR_SUBSCRIPTION_ID"
# Assign the initiative to the subscription so policies begin evaluating.
# --enforcement-mode Default = audit mode (flags non-compliance without blocking).
az policy assignment create \
--name "contoso-baseline-assignment" \
--display-name "Contoso Security Baseline Assignment" \
--policy-set-definition "contoso-security-baseline" \
--scope "/subscriptions/YOUR_SUBSCRIPTION_ID" \
--enforcement-mode Default# Confirm the Contoso initiative assignment was created successfully.
az policy assignment list \
--scope "/subscriptions/YOUR_SUBSCRIPTION_ID" \
--query "[?contains(displayName,'Contoso')].{Name:displayName, Enforcement:enforcementMode}" \
-o table
# Trigger an immediate compliance evaluation (normally runs every ~24 hours).
# --no-wait returns immediately; the scan runs asynchronously in the background.
az policy state trigger-scan \
--subscription "YOUR_SUBSCRIPTION_ID" \
--no-waitGovernance rules in Defender for Cloud let you assign owners to security recommendations, set remediation deadlines, and send email notifications. This ensures accountability and tracks remediation progress.
# Assign any Azure resource a "SecurityOwner" tag so governance rules
# can automatically route new recommendations to the right person.
az tag update \
--resource-id "/subscriptions/YOUR_SUBSCRIPTION_ID/resourceGroups/rg-production/providers/Microsoft.Compute/virtualMachines/vm-web-01" \
--operation Merge \
--tags SecurityOwner="alice@contoso.com"
# Bulk-tag every VM in a resource group with a team-level owner.
# The for-loop iterates all VM IDs and merges the tag non-destructively.
for vm in $(az vm list -g rg-production --query "[].id" -o tsv); do
az tag update --resource-id "$vm" --operation Merge \
--tags SecurityOwner="infra-team@contoso.com"
doneRegulatory compliance in Defender for Cloud maps your security recommendations to framework-specific controls. This step shows how to interpret and work with the compliance dashboard for PCI DSS, SOC 2, and HIPAA.
# List all regulatory compliance standards currently configured.
# State = "Enabled" means Defender for Cloud is evaluating against that standard.
az security regulatory-compliance-standards list \
--query "[].{Standard:name, State:state}" \
-o table
# Show each PCI DSS v4 control with pass/fail counts.
# Controls with high FailedAssessments need immediate remediation attention.
az security regulatory-compliance-controls list \
--standard-name "PCI-DSS-4" \
--query "[].{Control:name, State:state, Passed:passedAssessments, Failed:failedAssessments}" \
-o table
# Drill into a specific control (e.g., Control 1: network security).
# Shows which individual assessments (recommendations) are passing or failing.
az security regulatory-compliance-assessments list \
--standard-name "PCI-DSS-4" \
--control-name "1" \
--query "[].{Assessment:name, State:state, ResourceCount:resourceCount}" \
-o tableKQL queries against the SecurityRecommendation and SecurityAlert tables in Log Analytics provide deep visibility into your security posture. These queries power dashboards, alerts, and executive reports.
// Find the 20 recommendations affecting the most resources.
// AffectedResources = distinct count of non-compliant resources per recommendation.
// Use this to prioritise: fixing one recommendation may resolve hundreds of findings.
SecurityRecommendation
| where TimeGenerated > ago(1d)
| where RecommendationState == "Unhealthy"
| summarize AffectedResources = dcount(ResourceId) by
RecommendationDisplayName, RecommendationSeverity
| order by AffectedResources desc
| take 20// Track security posture improvement over 30 days.
// Healthy = resources passing all checks; Unhealthy = resources with open findings.
// CompliancePercent trending upward means your remediation efforts are working.
// Use this as a line chart in Azure Workbooks for executive reporting.
SecurityRecommendation
| where TimeGenerated > ago(30d)
| summarize
Healthy = dcountif(ResourceId, RecommendationState == "Healthy"),
Unhealthy = dcountif(ResourceId, RecommendationState == "Unhealthy")
by bin(TimeGenerated, 1d)
| extend CompliancePercent = round(100.0 * Healthy / (Healthy + Unhealthy), 1)
| order by TimeGenerated asc
| project TimeGenerated, Healthy, Unhealthy, CompliancePercent// Identify the resource groups with the most security findings.
// split(ResourceId, "/")[4] extracts the resource-group name from the ARM path.
// Resource groups at the top of this list should be reviewed first -
// they represent the biggest concentration of security debt.
SecurityRecommendation
| where TimeGenerated > ago(1d)
| where RecommendationState == "Unhealthy"
| extend ResourceGroup = tostring(split(ResourceId, "/")[4])
| summarize FindingCount = count() by ResourceGroup, RecommendationSeverity
| order by FindingCount desc
| take 15// Measure encryption and network security compliance for each recommendation.
// ComplianceRate = percentage of resources meeting the control.
// Rates below 90% signal areas where data-in-transit or network exposure
// controls need urgent attention (relevant to PCI DSS 4.1 and HIPAA 164.312(e)).
SecurityRecommendation
| where TimeGenerated > ago(1d)
| where RecommendationDisplayName has_any (
"encryption", "TLS", "SSL", "HTTPS",
"network security group", "public access", "firewall"
)
| summarize
HealthyCount = countif(RecommendationState == "Healthy"),
UnhealthyCount = countif(RecommendationState == "Unhealthy")
by RecommendationDisplayName
| extend ComplianceRate = round(100.0 * HealthyCount / (HealthyCount + UnhealthyCount), 1)
| order by UnhealthyCount descAttack path analysis uses the cloud security graph to identify sequences of misconfigurations that an attacker could exploit to reach high-value targets. This capability is exclusive to the Defender CSPM plan.
// Cloud Security Explorer query: Find VMs exposed to the internet
// with high-privilege managed identities
//
// Navigate to: Defender for Cloud > Cloud Security Explorer
// Build the query:
// Source: Virtual Machines
// Condition 1: Has public IP address
// Condition 2: Has managed identity with role assignment
// Condition 3: Role is Contributor or Owner
//
// This identifies attack paths where a compromised VM could
// escalate privileges through its managed identityAzure Monitor Workbooks provide interactive dashboards for visualizing your cloud security posture. You will build a workbook that displays posture trends, top recommendations, and compliance status.
// Posture Summary tile: single-row overview of total, healthy, and unhealthy
// resource counts with an overall PostureScore percentage.
// Display as a Tile visualization in Workbooks.
SecurityRecommendation
| where TimeGenerated > ago(1d)
| summarize
TotalResources = dcount(ResourceId),
Healthy = dcountif(ResourceId, RecommendationState == "Healthy"),
Unhealthy = dcountif(ResourceId, RecommendationState == "Unhealthy")
| extend PostureScore = round(100.0 * Healthy / TotalResources, 1)// Severity Distribution tile: count of unhealthy resources per severity level.
// Display as a Pie chart so stakeholders instantly see the High vs. Medium balance.
SecurityRecommendation
| where TimeGenerated > ago(1d)
| where RecommendationState == "Unhealthy"
| summarize Count = dcount(ResourceId) by RecommendationSeverity
| order by Count desc// 30-Day Trend tile: daily count of unhealthy resources.
// Display as a Line chart. A downward trend confirms remediation progress;
// a spike may indicate new resources deployed without security controls.
SecurityRecommendation
| where TimeGenerated > ago(30d)
| summarize Unhealthy = dcountif(ResourceId, RecommendationState == "Unhealthy")
by bin(TimeGenerated, 1d)
| order by TimeGenerated ascContinuous export streams Defender for Cloud recommendations, alerts, and compliance data to a Log Analytics workspace. This is required for the KQL queries and workbooks you built in previous steps.
# Create a continuous export automation that streams recommendations,
# secure scores, and compliance data to your Log Analytics workspace.
# This populates the SecurityRecommendation and SecureScores tables
# used by the KQL queries and Workbook panels in this lab.
az security automation create \
--name "export-to-log-analytics" \
--resource-group "rg-security" \
--scopes "[{\"description\":\"Subscription\",\"scopePath\":\"/subscriptions/YOUR_SUBSCRIPTION_ID\"}]" \
--sources "[{\"eventSource\":\"Assessments\"},{\"eventSource\":\"SecureScores\"},{\"eventSource\":\"RegulatoryComplianceAssessment\"}]" \
--actions "[{\"actionType\":\"Workspace\",\"workspaceResourceId\":\"/subscriptions/YOUR_SUBSCRIPTION_ID/resourceGroups/rg-security/providers/Microsoft.OperationalInsights/workspaces/law-security\"}]"# Build the continuous export configuration object in PowerShell.
# Three event sources ensure you capture:
# Assessments - individual security recommendations per resource.
# SecureScores - aggregate posture scores per subscription.
# RegulatoryComplianceAssessment - per-control compliance status.
$scope = @{
Description = "Subscription scope"
ScopePath = "/subscriptions/YOUR_SUBSCRIPTION_ID"
}
$source = @(
@{ EventSource = "Assessments" },
@{ EventSource = "SecureScores" },
@{ EventSource = "RegulatoryComplianceAssessment" }
)
# Replace with your actual Log Analytics workspace resource ID.
$workspaceId = "/subscriptions/YOUR_SUBSCRIPTION_ID/resourceGroups/rg-security/providers/Microsoft.OperationalInsights/workspaces/law-security"
# Assemble the REST API request body and deploy via Invoke-AzRestMethod.
$body = @{
properties = @{
scopes = @($scope)
sources = $source
actions = @(@{
actionType = "Workspace"
workspaceResourceId = $workspaceId
})
}
} | ConvertTo-Json -Depth 10
# Send the PUT request to create the export automation resource.
Invoke-AzRestMethod -Method PUT `
-Path "/subscriptions/YOUR_SUBSCRIPTION_ID/resourceGroups/rg-security/providers/Microsoft.Security/automations/export-to-la?api-version=2019-01-01-preview" `
-Payload $body// Verify continuous export is working: check for data ingested in the last hour.
// If Count > 0, the export pipeline is healthy.
// Allow 15-30 minutes after initial setup before running this check.
SecurityRecommendation
| where TimeGenerated > ago(1h)
| summarize Count = count() by RecommendationState
| order by Count descDeployIfNotExists (DINE) policies automatically remediate non-compliant resources by deploying the required configuration. This ensures new resources are born compliant and reduces the security team's manual workload.
{
"mode": "All",
"policyRule": {
"if": {
"allOf": [
{
"field": "type",
"equals": "Microsoft.Storage/storageAccounts"
},
{
"field": "Microsoft.Storage/storageAccounts/supportsHttpsTrafficOnly",
"notEquals": true
}
]
},
"then": {
"effect": "DeployIfNotExists",
"details": {
"type": "Microsoft.Storage/storageAccounts",
"existenceCondition": {
"field": "Microsoft.Storage/storageAccounts/supportsHttpsTrafficOnly",
"equals": true
},
"roleDefinitionIds": [
"/providers/Microsoft.Authorization/roleDefinitions/17d1049b-9a84-46fb-8f53-869881c3d3ab"
],
"deployment": {
"properties": {
"mode": "incremental",
"template": {
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"resources": [
{
"type": "Microsoft.Storage/storageAccounts",
"apiVersion": "2023-01-01",
"name": "[field('name')]",
"location": "[field('location')]",
"properties": {
"supportsHttpsTrafficOnly": true
}
}
]
}
}
}
}
}
}
}# Create a custom policy definition from the JSON rule file above.
# This policy uses the DeployIfNotExists effect to automatically enable
# HTTPS on any storage account that has it disabled.
az policy definition create \
--name "enforce-https-storage" \
--display-name "Enforce HTTPS on Storage Accounts" \
--mode All \
--rules @policy-rule.json \
--subscription "YOUR_SUBSCRIPTION_ID"
# Assign the policy. --mi-system-assigned creates a managed identity
# so the policy engine can deploy the remediation ARM template.
az policy assignment create \
--name "enforce-https-storage-assignment" \
--display-name "Enforce HTTPS on Storage Accounts" \
--policy "enforce-https-storage" \
--scope "/subscriptions/YOUR_SUBSCRIPTION_ID" \
--mi-system-assigned \
--location "eastus"
# Fix existing non-compliant storage accounts with a remediation task.
# This triggers the DeployIfNotExists template on every non-compliant resource.
az policy remediation create \
--name "remediate-https-storage" \
--policy-assignment "enforce-https-storage-assignment" \
--resource-group "rg-production"
# Monitor remediation progress. Status = "Succeeded" when all resources are fixed.
az policy remediation show \
--name "remediate-https-storage" \
--resource-group "rg-production" \
--query "{Status:provisioningState, Deployed:deploymentStatus.totalDeployments}" \
-o json# Group policy evaluation results by compliance state.
# Expected output: "Compliant" and "NonCompliant" counts.
# 100% Compliant means the DINE policy has remediated all storage accounts.
Get-AzPolicyState -SubscriptionId "YOUR_SUBSCRIPTION_ID" `
-Filter "PolicyDefinitionName eq 'enforce-https-storage'" |
Group-Object ComplianceState |
Select-Object Name, Count
# List the specific resources still failing the HTTPS policy.
# Use this to investigate why certain storage accounts were not auto-remediated
# (e.g., resource locks, insufficient managed identity permissions).
Get-AzPolicyState -SubscriptionId "YOUR_SUBSCRIPTION_ID" `
-Filter "PolicyDefinitionName eq 'enforce-https-storage' and ComplianceState eq 'NonCompliant'" |
Select-Object ResourceId, ComplianceState, TimestampAuditors require evidence of security controls, compliance status, and remediation activities. This step shows how to generate comprehensive reports from Defender for Cloud and Log Analytics.
// Compliance summary: pass/fail counts and compliance percentage per framework.
// CompliancePercent below 80% indicates significant gaps needing remediation.
// Export this output as evidence for auditors during compliance assessments.
SecurityRegulatoryCompliance
| where TimeGenerated > ago(1d)
| summarize
PassedControls = countif(State == "Passed"),
FailedControls = countif(State == "Failed"),
SkippedControls = countif(State == "Skipped")
by ComplianceStandard = RecommendationName
| extend CompliancePercent = round(
100.0 * PassedControls / (PassedControls + FailedControls), 1)
| project ComplianceStandard, PassedControls, FailedControls,
SkippedControls, CompliancePercent
| order by CompliancePercent asc// Track weekly remediation velocity over the past 90 days.
// RemediatedCount = distinct resources that moved to "Healthy" state each week.
// An increasing count demonstrates steady security improvement to auditors.
SecurityRecommendation
| where TimeGenerated > ago(90d)
| where RecommendationState == "Healthy"
| summarize RemediatedCount = dcount(ResourceId)
by bin(TimeGenerated, 7d), RecommendationSeverity
| order by TimeGenerated asc
| project WeekOf = TimeGenerated, Severity = RecommendationSeverity,
RemediatedCount# Export all compliance standards with their enabled/disabled state to CSV.
az security regulatory-compliance-standards list \
--query "[].{Standard:name, State:state}" \
-o csv > compliance-standards.csv
# Export only the failing PCI DSS controls with pass/fail counts.
# Share this file with auditors as evidence of control gaps.
az security regulatory-compliance-controls list \
--standard-name "PCI-DSS-4" \
--query "[?state=='Failed'].{Control:name, Failed:failedAssessments, Passed:passedAssessments}" \
-o csv > pci-dss-failed-controls.csv
# Export all unhealthy (non-compliant) recommendations with severity and resource.
# This provides the full remediation backlog for the security team.
az security assessment list \
--query "[?properties.status.code=='Unhealthy'].{Name:properties.displayName, Severity:properties.metadata.severity, Resource:properties.resourceDetails.id}" \
-o csv > unhealthy-recommendations.csvaz policy assignment delete --name "contoso-baseline-assignment"az policy set-definition delete --name "contoso-security-baseline"| Resource | Description |
|---|---|
| Cloud Security Posture Management overview | Understand CSPM capabilities and posture assessment fundamentals |
| Enable enhanced security features in Defender for Cloud | Activate paid Defender plans for advanced security capabilities |
| Identify and remediate attack paths | Discover exploitable resource chains across your cloud environment |
| Configure governance rules for recommendations | Assign owners and remediation deadlines to security recommendations |
| Create custom security initiatives and policies | Build tailored policy sets for organization-specific requirements |
| Regulatory compliance dashboard | Track compliance against PCI DSS, SOC 2, and HIPAA standards |
| Continuous export of Defender for Cloud data | Stream recommendations and alerts to Log Analytics or Event Hub |
| Azure Policy DeployIfNotExists effect | Auto-remediate misconfigurations using policy-driven deployments |
| Secure Score and security controls | Measure and improve your cloud security posture over time |
| Microsoft Defender for Cloud pricing | Review CSPM plan billing and cost optimization options |