feat(azure): add defender_ensure_defender_cspm_is_on check (#11037)

Co-authored-by: Daniel Barranquero <danielbo2001@gmail.com>
This commit is contained in:
s1ns3nz0
2026-06-18 17:05:02 +09:00
committed by GitHub
parent 2293cab72c
commit ddbf3405a0
7 changed files with 194 additions and 1 deletions
+1
View File
@@ -19,6 +19,7 @@ All notable changes to the **Prowler SDK** are documented in this file.
- `cosmosdb_account_public_network_access_disabled` check for Azure provider, verifying Cosmos DB accounts have public network access disabled so connectivity is restricted to private endpoints or VNet service endpoints [(#11034)](https://github.com/prowler-cloud/prowler/pull/11034)
- `databricks_workspace_public_network_access_disabled` check for Azure provider, verifying Databricks workspaces have public network access disabled so connectivity is restricted to Azure Private Link private endpoints [(#11035)](https://github.com/prowler-cloud/prowler/pull/11035)
- `databricks_workspace_no_public_ip_enabled` check for Azure provider, verifying Databricks workspaces use secure cluster connectivity (no public IP) so compute nodes are not assigned public IP addresses [(#11036)](https://github.com/prowler-cloud/prowler/pull/11036)
- `defender_ensure_defender_cspm_is_on` check for Azure provider, verifying Microsoft Defender Cloud Security Posture Management (CSPM) is enabled on the Standard tier [(#11037)](https://github.com/prowler-cloud/prowler/pull/11037)
- `aks_cluster_auto_upgrade_enabled` check for Azure provider [(#11027)](https://github.com/prowler-cloud/prowler/pull/11027)
- Public `Provider.get_class()` method that resolves a provider class by name for both built-in and external (entry-point) providers [(#11398)](https://github.com/prowler-cloud/prowler/pull/11398)
- Jira timeout preventing the calls from hanging indefinitely when the Jira endpoint is unreachable or slow [(#11602)](https://github.com/prowler-cloud/prowler/pull/11602)
+3 -1
View File
@@ -2094,7 +2094,9 @@
{
"Id": "8.1.1.1",
"Description": "Ensure Microsoft Defender CSPM is set to 'On'",
"Checks": [],
"Checks": [
"defender_ensure_defender_cspm_is_on"
],
"Attributes": [
{
"Section": "8 Security Services",
@@ -1340,6 +1340,7 @@
}
],
"Checks": [
"defender_ensure_defender_cspm_is_on",
"monitor_alert_create_policy_assignment",
"monitor_alert_create_update_nsg",
"monitor_alert_create_update_public_ip_address_rule",
@@ -0,0 +1,37 @@
{
"Provider": "azure",
"CheckID": "defender_ensure_defender_cspm_is_on",
"CheckTitle": "Microsoft Defender CSPM is set to On",
"CheckType": [],
"ServiceName": "defender",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "medium",
"ResourceType": "microsoft.security/pricings",
"ResourceGroup": "security",
"Description": "**Microsoft Defender for Cloud** Cloud Security Posture Management (CSPM) plan is evaluated for **standard tier** activation. Defender CSPM provides advanced posture management capabilities including attack path analysis, cloud security explorer, agentless scanning, and governance rules.",
"Risk": "Without Defender CSPM, the subscription relies on **foundational CSPM** (free tier) which lacks attack path analysis, agentless vulnerability scanning, and security governance. Advanced threats exploiting misconfiguration chains go undetected.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://learn.microsoft.com/en-us/azure/defender-for-cloud/concept-cloud-security-posture-management",
"https://learn.microsoft.com/en-us/azure/defender-for-cloud/enable-enhanced-security"
],
"Remediation": {
"Code": {
"CLI": "az security pricing create -n CloudPosture --tier Standard",
"NativeIaC": "```bicep\ntargetScope = 'subscription'\n\nresource defenderCSPM 'Microsoft.Security/pricings@2024-01-01' = {\n name: 'CloudPosture'\n properties: {\n pricingTier: 'Standard' // Critical: enables Defender CSPM\n }\n}\n```",
"Other": "1. Sign in to Azure portal\n2. Go to Microsoft Defender for Cloud\n3. Select Environment Settings\n4. Click on the subscription\n5. Set Cloud Security Posture Management (CSPM) to On\n6. Click Save",
"Terraform": "```hcl\nresource \"azurerm_security_center_subscription_pricing\" \"<example_resource_name>\" {\n tier = \"Standard\" # Critical: enables Defender CSPM\n resource_type = \"CloudPosture\"\n}\n```"
},
"Recommendation": {
"Text": "Enable **Defender CSPM** standard tier for advanced cloud security posture management. Evaluate the cost against the security benefits \u2014 CSPM provides attack path analysis and agentless scanning.",
"Url": "https://hub.prowler.com/check/defender_ensure_defender_cspm_is_on"
}
},
"Categories": [
"threat-detection"
],
"DependsOn": [],
"RelatedTo": [],
"Notes": ""
}
@@ -0,0 +1,35 @@
from prowler.lib.check.models import Check, Check_Report_Azure
from prowler.providers.azure.services.defender.defender_client import defender_client
class defender_ensure_defender_cspm_is_on(Check):
"""
Ensure Microsoft Defender Cloud Security Posture Management (CSPM) is set to On.
This check evaluates whether the Defender CSPM plan (CloudPosture pricing) is enabled with the Standard tier for each subscription.
- PASS: The CloudPosture pricing tier is "Standard" (Defender CSPM is on).
- FAIL: The CloudPosture pricing tier is not "Standard" (Defender CSPM is off).
"""
def execute(self) -> Check_Report_Azure:
findings = []
for subscription, pricings in defender_client.pricings.items():
subscription_name = defender_client.subscriptions.get(
subscription, subscription
)
if "CloudPosture" in pricings:
report = Check_Report_Azure(
metadata=self.metadata(),
resource=pricings["CloudPosture"],
)
report.subscription = subscription
report.resource_name = "Defender plan CSPM"
report.status = "PASS"
report.status_extended = f"Defender plan CSPM from subscription {subscription_name} ({subscription}) is set to ON (pricing tier standard)."
if pricings["CloudPosture"].pricing_tier != "Standard":
report.status = "FAIL"
report.status_extended = f"Defender plan CSPM from subscription {subscription_name} ({subscription}) is set to OFF (pricing tier not standard)."
findings.append(report)
return findings
@@ -0,0 +1,117 @@
from unittest import mock
from uuid import uuid4
from prowler.providers.azure.services.defender.defender_service import Pricing
from tests.providers.azure.azure_fixtures import (
AZURE_SUBSCRIPTION_DISPLAY,
AZURE_SUBSCRIPTION_ID,
AZURE_SUBSCRIPTION_NAME,
set_mocked_azure_provider,
)
class Test_defender_ensure_defender_cspm_is_on:
def test_defender_no_cspm(self):
defender_client = mock.MagicMock
defender_client.subscriptions = {AZURE_SUBSCRIPTION_ID: AZURE_SUBSCRIPTION_NAME}
defender_client.pricings = {}
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_azure_provider(),
),
mock.patch(
"prowler.providers.azure.services.defender.defender_ensure_defender_cspm_is_on.defender_ensure_defender_cspm_is_on.defender_client",
new=defender_client,
),
):
from prowler.providers.azure.services.defender.defender_ensure_defender_cspm_is_on.defender_ensure_defender_cspm_is_on import (
defender_ensure_defender_cspm_is_on,
)
check = defender_ensure_defender_cspm_is_on()
result = check.execute()
assert len(result) == 0
def test_defender_cspm_pricing_tier_not_standard(self):
resource_id = str(uuid4())
defender_client = mock.MagicMock
defender_client.subscriptions = {AZURE_SUBSCRIPTION_ID: AZURE_SUBSCRIPTION_NAME}
defender_client.pricings = {
AZURE_SUBSCRIPTION_ID: {
"CloudPosture": Pricing(
resource_id=resource_id,
resource_name="Defender plan CSPM",
pricing_tier="Free",
free_trial_remaining_time=0,
)
}
}
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_azure_provider(),
),
mock.patch(
"prowler.providers.azure.services.defender.defender_ensure_defender_cspm_is_on.defender_ensure_defender_cspm_is_on.defender_client",
new=defender_client,
),
):
from prowler.providers.azure.services.defender.defender_ensure_defender_cspm_is_on.defender_ensure_defender_cspm_is_on import (
defender_ensure_defender_cspm_is_on,
)
check = defender_ensure_defender_cspm_is_on()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== f"Defender plan CSPM from subscription {AZURE_SUBSCRIPTION_DISPLAY} is set to OFF (pricing tier not standard)."
)
assert result[0].subscription == AZURE_SUBSCRIPTION_ID
assert result[0].resource_name == "Defender plan CSPM"
assert result[0].resource_id == resource_id
def test_defender_cspm_pricing_tier_standard(self):
resource_id = str(uuid4())
defender_client = mock.MagicMock
defender_client.subscriptions = {AZURE_SUBSCRIPTION_ID: AZURE_SUBSCRIPTION_NAME}
defender_client.pricings = {
AZURE_SUBSCRIPTION_ID: {
"CloudPosture": Pricing(
resource_id=resource_id,
resource_name="Defender plan CSPM",
pricing_tier="Standard",
free_trial_remaining_time=0,
)
}
}
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_azure_provider(),
),
mock.patch(
"prowler.providers.azure.services.defender.defender_ensure_defender_cspm_is_on.defender_ensure_defender_cspm_is_on.defender_client",
new=defender_client,
),
):
from prowler.providers.azure.services.defender.defender_ensure_defender_cspm_is_on.defender_ensure_defender_cspm_is_on import (
defender_ensure_defender_cspm_is_on,
)
check = defender_ensure_defender_cspm_is_on()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert (
result[0].status_extended
== f"Defender plan CSPM from subscription {AZURE_SUBSCRIPTION_DISPLAY} is set to ON (pricing tier standard)."
)
assert result[0].subscription == AZURE_SUBSCRIPTION_ID
assert result[0].resource_name == "Defender plan CSPM"
assert result[0].resource_id == resource_id