feat(azure): add new check monitor_alert_service_health_exists (#8067)

Co-authored-by: Sergio Garcia <hello@mistercloudsec.com>
This commit is contained in:
Rubén De la Torre Vico
2025-06-24 12:04:20 +02:00
committed by GitHub
parent e0465f2aa2
commit df1abb2152
6 changed files with 245 additions and 2 deletions
+1
View File
@@ -37,6 +37,7 @@ All notable changes to the **Prowler SDK** are documented in this file.
- Azure Databricks service integration for Azure provider, including the `databricks_workspace_vnet_injection_enabled` check [(#8008)](https://github.com/prowler-cloud/prowler/pull/8008)
- Azure Databricks check `databricks_workspace_cmk_encryption_enabled` to ensure workspaces use customer-managed keys (CMK) for encryption at rest [(#8017)](https://github.com/prowler-cloud/prowler/pull/8017)
- Add `storage_account_default_to_entra_authorization_enabled` check for Azure provider. [(#7981)](https://github.com/prowler-cloud/prowler/pull/7981)
- New check `monitor_alert_service_health_exists` for Azure provider [(#8067)](https://github.com/prowler-cloud/prowler/pull/8067)
- Replace `Domain.Read.All` with `Directory.Read.All` in Azure and M365 docs [(#8075)](https://github.com/prowler-cloud/prowler/pull/8075)
### Fixed
@@ -0,0 +1,30 @@
{
"Provider": "azure",
"CheckID": "monitor_alert_service_health_exists",
"CheckTitle": "Ensure that an Activity Log Alert exists for Service Health",
"CheckType": [],
"ServiceName": "monitor",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "high",
"ResourceType": "Monitor",
"Description": "Ensure that an Azure activity log alert is configured to trigger when Service Health events occur within your Microsoft Azure cloud account. The alert should activate when new events match the specified conditions in the alert rule configuration.",
"Risk": "Lack of monitoring for Service Health events may result in missing critical service issues, planned maintenance, security advisories, or other changes that could impact Azure services and regions in use.",
"RelatedUrl": "https://learn.microsoft.com/en-us/azure/service-health/overview",
"Remediation": {
"Code": {
"CLI": "az monitor activity-log alert create --subscription <subscription-id> --resource-group <resource-group> --name <alert-rule> --condition category=ServiceHealth and properties.incidentType=Incident --scope /subscriptions/<subscription-id> --action-group <action-group>",
"NativeIaC": "",
"Other": "https://www.trendmicro.com/cloudoneconformity/knowledge-base/azure/ActivityLog/service-health-alert.html",
"Terraform": ""
},
"Recommendation": {
"Text": "Create an activity log alert for Service Health events and configure an action group to notify appropriate personnel.",
"Url": "https://learn.microsoft.com/en-us/azure/service-health/alerts-activity-log-service-notifications-portal"
}
},
"Categories": [],
"DependsOn": [],
"RelatedTo": [],
"Notes": "By default, in your Azure subscription there will not be any activity log alerts configured for Service Health events."
}
@@ -0,0 +1,48 @@
from prowler.lib.check.models import Check, Check_Report_Azure
from prowler.providers.azure.services.monitor.monitor_client import monitor_client
class monitor_alert_service_health_exists(Check):
def execute(self) -> list[Check_Report_Azure]:
findings = []
for (
subscription_name,
activity_log_alerts,
) in monitor_client.alert_rules.items():
for alert_rule in activity_log_alerts:
# Check if alert rule is enabled and has required Service Health conditions
if alert_rule.enabled:
has_service_health_category = False
has_incident_type_incident = False
for element in alert_rule.condition.all_of:
if (
element.field == "category"
and element.equals == "ServiceHealth"
):
has_service_health_category = True
if (
element.field == "properties.incidentType"
and element.equals == "Incident"
):
has_incident_type_incident = True
if has_service_health_category and has_incident_type_incident:
report = Check_Report_Azure(
metadata=self.metadata(), resource=alert_rule
)
report.subscription = subscription_name
report.status = "PASS"
report.status_extended = f"There is an activity log alert for Service Health in subscription {subscription_name}."
break
else:
report = Check_Report_Azure(metadata=self.metadata(), resource={})
report.subscription = subscription_name
report.resource_name = "Monitor"
report.resource_id = "Monitor"
report.status = "FAIL"
report.status_extended = f"There is no activity log alert for Service Health in subscription {subscription_name}."
findings.append(report)
return findings
@@ -1,5 +1,5 @@
from dataclasses import dataclass
from typing import List
from typing import List, Optional
from azure.mgmt.monitor import MonitorManagementClient
@@ -131,4 +131,4 @@ class AlertRule:
name: str
condition: AlertRuleAllOfCondition
enabled: bool
description: str
description: Optional[str]
@@ -0,0 +1,164 @@
from unittest import mock
from tests.providers.azure.azure_fixtures import (
AZURE_SUBSCRIPTION_ID,
set_mocked_azure_provider,
)
class Test_monitor_alert_service_health_exists:
def test_monitor_alert_service_health_exists_no_subscriptions(self):
monitor_client = mock.MagicMock()
monitor_client.alert_rules = {}
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_azure_provider(),
),
mock.patch(
"prowler.providers.azure.services.monitor.monitor_alert_service_health_exists.monitor_alert_service_health_exists.monitor_client",
new=monitor_client,
),
):
from prowler.providers.azure.services.monitor.monitor_alert_service_health_exists.monitor_alert_service_health_exists import (
monitor_alert_service_health_exists,
)
check = monitor_alert_service_health_exists()
result = check.execute()
assert len(result) == 0
def test_no_alert_rules(self):
monitor_client = mock.MagicMock()
monitor_client.alert_rules = {AZURE_SUBSCRIPTION_ID: []}
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_azure_provider(),
),
mock.patch(
"prowler.providers.azure.services.monitor.monitor_alert_service_health_exists.monitor_alert_service_health_exists.monitor_client",
new=monitor_client,
),
):
from prowler.providers.azure.services.monitor.monitor_alert_service_health_exists.monitor_alert_service_health_exists import (
monitor_alert_service_health_exists,
)
check = monitor_alert_service_health_exists()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert result[0].subscription == AZURE_SUBSCRIPTION_ID
assert result[0].resource_name == "Monitor"
assert result[0].resource_id == "Monitor"
assert (
result[0].status_extended
== f"There is no activity log alert for Service Health in subscription {AZURE_SUBSCRIPTION_ID}."
)
def test_alert_rules_configured(self):
monitor_client = mock.MagicMock()
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_azure_provider(),
),
mock.patch(
"prowler.providers.azure.services.monitor.monitor_alert_service_health_exists.monitor_alert_service_health_exists.monitor_client",
new=monitor_client,
),
):
from prowler.providers.azure.services.monitor.monitor_alert_service_health_exists.monitor_alert_service_health_exists import (
monitor_alert_service_health_exists,
)
from prowler.providers.azure.services.monitor.monitor_service import (
AlertRule,
AlertRuleAllOfCondition,
AlertRuleAnyOfOrLeafCondition,
)
monitor_client.alert_rules = {
AZURE_SUBSCRIPTION_ID: [
AlertRule(
id="id1",
name="name1",
condition=AlertRuleAllOfCondition(
all_of=[
AlertRuleAnyOfOrLeafCondition(
field="category", equals="ServiceHealth"
),
AlertRuleAnyOfOrLeafCondition(
field="properties.incidentType", equals="Incident"
),
]
),
enabled=True,
description="desc1",
),
]
}
check = monitor_alert_service_health_exists()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert result[0].subscription == AZURE_SUBSCRIPTION_ID
assert result[0].resource_name == "name1"
assert result[0].resource_id == "id1"
assert (
result[0].status_extended
== f"There is an activity log alert for Service Health in subscription {AZURE_SUBSCRIPTION_ID}."
)
def test_alert_rules_configured_but_disabled(self):
monitor_client = mock.MagicMock()
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_azure_provider(),
),
mock.patch(
"prowler.providers.azure.services.monitor.monitor_alert_service_health_exists.monitor_alert_service_health_exists.monitor_client",
new=monitor_client,
),
):
from prowler.providers.azure.services.monitor.monitor_alert_service_health_exists.monitor_alert_service_health_exists import (
monitor_alert_service_health_exists,
)
from prowler.providers.azure.services.monitor.monitor_service import (
AlertRule,
AlertRuleAllOfCondition,
AlertRuleAnyOfOrLeafCondition,
)
monitor_client.alert_rules = {
AZURE_SUBSCRIPTION_ID: [
AlertRule(
id="id1",
name="name1",
condition=AlertRuleAllOfCondition(
all_of=[
AlertRuleAnyOfOrLeafCondition(
field="category", equals="ServiceHealth"
),
AlertRuleAnyOfOrLeafCondition(
field="properties.incidentType", equals="Incident"
),
]
),
enabled=False,
description="desc1",
),
]
}
check = monitor_alert_service_health_exists()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert result[0].subscription == AZURE_SUBSCRIPTION_ID
assert result[0].resource_name == "Monitor"
assert result[0].resource_id == "Monitor"
assert (
result[0].status_extended
== f"There is no activity log alert for Service Health in subscription {AZURE_SUBSCRIPTION_ID}."
)