Skip to content

Commit 23e5115

Browse files
authored
feat(m365): add defenderidentity_health_issues_no_open security check (#10087)
1 parent d2f4f8c commit 23e5115

File tree

12 files changed

+1129
-1
lines changed

12 files changed

+1129
-1
lines changed

docs/user-guide/providers/microsoft365/authentication.mdx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ When using service principal authentication, add these **Application Permissions
4242
- `AuditLog.Read.All`: Required for Entra service.
4343
- `Directory.Read.All`: Required for all services.
4444
- `Policy.Read.All`: Required for all services.
45+
- `SecurityIdentitiesHealth.Read.All`: Required for `defenderidentity_health_issues_no_open` check.
46+
- `SecurityIdentitiesSensors.Read.All`: Required for `defenderidentity_health_issues_no_open` check.
4547
- `SharePointTenantSettings.Read.All`: Required for SharePoint service.
4648

4749
**External API Permissions:**
@@ -106,6 +108,8 @@ Browser and Azure CLI authentication methods limit scanning capabilities to chec
106108
- `AuditLog.Read.All`: Required for Entra service
107109
- `Directory.Read.All`: Required for all services
108110
- `Policy.Read.All`: Required for all services
111+
- `SecurityIdentitiesHealth.Read.All`: Required for `defenderidentity_health_issues_no_open` check
112+
- `SecurityIdentitiesSensors.Read.All`: Required for `defenderidentity_health_issues_no_open` check
109113
- `SharePointTenantSettings.Read.All`: Required for SharePoint service
110114

111115
![Permission Screenshots](/images/providers/directory-permission.png)

prowler/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ All notable changes to the **Prowler SDK** are documented in this file.
66

77
### 🚀 Added
88

9+
- `defenderidentity_health_issues_no_open` check for M365 provider [(#10087)](https://github.com/prowler-cloud/prowler/pull/10087)
910
- `organization_verified_badge` check for GitHub provider [(#10033)](https://github.com/prowler-cloud/prowler/pull/10033)
1011
- OpenStack provider `clouds_yaml_content` parameter for API integration [(#10003)](https://github.com/prowler-cloud/prowler/pull/10003)
1112
- `defender_safe_attachments_policy_enabled` check for M365 provider [(#9833)](https://github.com/prowler-cloud/prowler/pull/9833)

prowler/compliance/m365/iso27001_2022_m365.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@
117117
"defender_malware_policy_notifications_internal_users_malware_enabled",
118118
"defender_safelinks_policy_enabled",
119119
"defender_zap_for_teams_enabled",
120+
"defender_identity_health_issues_no_open",
120121
"entra_admin_users_phishing_resistant_mfa_enabled",
121122
"entra_identity_protection_sign_in_risk_enabled",
122123
"entra_identity_protection_user_risk_enabled"
@@ -715,7 +716,8 @@
715716
"Checks": [
716717
"defender_malware_policy_common_attachments_filter_enabled",
717718
"defender_malware_policy_comprehensive_attachments_filter_applied",
718-
"defender_malware_policy_notifications_internal_users_malware_enabled"
719+
"defender_malware_policy_notifications_internal_users_malware_enabled",
720+
"defender_identity_health_issues_no_open"
719721
]
720722
},
721723
{

prowler/providers/m365/services/defenderidentity/__init__.py

Whitespace-only changes.
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
from prowler.providers.common.provider import Provider
2+
from prowler.providers.m365.services.defenderidentity.defenderidentity_service import (
3+
DefenderIdentity,
4+
)
5+
6+
defenderidentity_client = DefenderIdentity(Provider.get_global_provider())

prowler/providers/m365/services/defenderidentity/defenderidentity_health_issues_no_open/__init__.py

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
{
2+
"Provider": "m365",
3+
"CheckID": "defenderidentity_health_issues_no_open",
4+
"CheckTitle": "Defender for Identity has no unresolved health issues affecting hybrid infrastructure monitoring",
5+
"CheckType": [],
6+
"ServiceName": "defenderidentity",
7+
"SubServiceName": "",
8+
"ResourceIdTemplate": "",
9+
"Severity": "medium",
10+
"ResourceType": "Defender for Identity Health Issue",
11+
"ResourceGroup": "security",
12+
"Description": "Microsoft Defender for Identity (MDI) monitors your hybrid identity infrastructure and detects advanced threats targeting Active Directory. This check verifies that MDI sensors are deployed and that there are no unresolved health issues that may affect the ability to detect identity-based attacks.",
13+
"Risk": "Without deployed MDI sensors or with unresolved health issues, organizations face critical gaps in threat detection. Misconfigured or missing sensors fail to monitor domain controllers, allowing identity-based attacks like Pass-the-Hash, Golden Ticket, or lateral movement to go undetected. Attackers commonly exploit these blind spots to compromise hybrid environments while evading detection.",
14+
"RelatedUrl": "",
15+
"AdditionalURLs": [
16+
"https://learn.microsoft.com/en-us/defender-for-identity/health-alerts",
17+
"https://learn.microsoft.com/en-us/graph/api/security-identitycontainer-list-healthissues"
18+
],
19+
"Remediation": {
20+
"Code": {
21+
"CLI": "",
22+
"NativeIaC": "",
23+
"Other": "1. Navigate to Microsoft Defender XDR portal at https://security.microsoft.com/\n2. Go to Settings > Identities > Health issues\n3. Review each open health issue and its recommendations\n4. Follow the specific remediation steps provided for each issue\n5. Verify the issue is resolved and status changes to closed",
24+
"Terraform": ""
25+
},
26+
"Recommendation": {
27+
"Text": "Regularly monitor and resolve Defender for Identity health issues to maintain comprehensive visibility into identity-based threats across your hybrid infrastructure.",
28+
"Url": "https://hub.prowler.com/check/defenderidentity_health_issues_no_open"
29+
}
30+
},
31+
"Categories": [
32+
"e5"
33+
],
34+
"DependsOn": [],
35+
"RelatedTo": [],
36+
"Notes": "This check requires SecurityIdentitiesHealth.Read.All permission and a hybrid identity environment with Active Directory on-premises connected to Microsoft Defender for Identity. Health issues can be global (domain-related, such as Directory Services account issues or auditing misconfigurations) or sensor-specific. If no hybrid AD environment is configured, this check will pass with no health issues detected, as MDI only monitors on-premises Active Directory infrastructure."
37+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
"""Check for open health issues in Microsoft Defender for Identity.
2+
3+
This module provides a security check that verifies there are no unresolved
4+
health issues in the Microsoft Defender for Identity deployment.
5+
"""
6+
7+
from typing import List
8+
9+
from prowler.lib.check.models import Check, CheckReportM365, Severity
10+
from prowler.providers.m365.services.defenderidentity.defenderidentity_client import (
11+
defenderidentity_client,
12+
)
13+
14+
15+
class defenderidentity_health_issues_no_open(Check):
16+
"""Ensure Microsoft Defender for Identity has no unresolved health issues.
17+
18+
This check evaluates whether there are open health issues in the MDI deployment
19+
that require attention to maintain proper hybrid identity protection.
20+
21+
- PASS: The health issue has been resolved (status is not open).
22+
- FAIL: The health issue is open and requires attention.
23+
- FAIL: No sensors are deployed (MDI cannot protect the environment).
24+
"""
25+
26+
def execute(self) -> List[CheckReportM365]:
27+
"""Execute the check for open MDI health issues.
28+
29+
This method iterates through all health issues from Microsoft Defender
30+
for Identity and reports on their status. Open issues indicate potential
31+
configuration problems or sensor health concerns that need resolution.
32+
33+
Returns:
34+
List[CheckReportM365]: A list of reports containing the result of the check.
35+
"""
36+
findings = []
37+
38+
# Check sensors first - None means API error, empty list means no sensors
39+
sensors_api_failed = defenderidentity_client.sensors is None
40+
health_issues_api_failed = defenderidentity_client.health_issues is None
41+
has_sensors = (
42+
defenderidentity_client.sensors and len(defenderidentity_client.sensors) > 0
43+
)
44+
45+
# If both APIs failed, it's likely a permission issue
46+
if sensors_api_failed and health_issues_api_failed:
47+
report = CheckReportM365(
48+
metadata=self.metadata(),
49+
resource={},
50+
resource_name="Defender for Identity",
51+
resource_id="defenderIdentity",
52+
)
53+
report.status = "FAIL"
54+
report.status_extended = (
55+
"Defender for Identity APIs are not accessible. "
56+
"Ensure the Service Principal has SecurityIdentitiesSensors.Read.All and "
57+
"SecurityIdentitiesHealth.Read.All permissions granted."
58+
)
59+
findings.append(report)
60+
return findings
61+
62+
# If only health issues API failed but we have sensors
63+
if health_issues_api_failed and has_sensors:
64+
report = CheckReportM365(
65+
metadata=self.metadata(),
66+
resource={},
67+
resource_name="Defender for Identity",
68+
resource_id="defenderIdentity",
69+
)
70+
report.status = "FAIL"
71+
report.status_extended = (
72+
f"Cannot read health issues from Defender for Identity "
73+
f"(found {len(defenderidentity_client.sensors)} sensor(s) deployed). "
74+
"Ensure the Service Principal has SecurityIdentitiesHealth.Read.All permission."
75+
)
76+
findings.append(report)
77+
return findings
78+
79+
# If no sensors are deployed (empty list, not None), MDI cannot monitor
80+
if not has_sensors and not sensors_api_failed:
81+
report = CheckReportM365(
82+
metadata=self.metadata(),
83+
resource={},
84+
resource_name="Defender for Identity",
85+
resource_id="defenderIdentity",
86+
)
87+
report.status = "FAIL"
88+
report.status_extended = (
89+
"No sensors deployed in Defender for Identity. "
90+
"Without sensors, MDI cannot monitor health issues in the environment. "
91+
"Deploy sensors on domain controllers to enable protection."
92+
)
93+
findings.append(report)
94+
return findings
95+
96+
# If health_issues is empty list - no issues exist, this is compliant
97+
if not defenderidentity_client.health_issues:
98+
report = CheckReportM365(
99+
metadata=self.metadata(),
100+
resource={},
101+
resource_name="Defender for Identity",
102+
resource_id="defenderIdentity",
103+
)
104+
report.status = "PASS"
105+
report.status_extended = (
106+
"No open health issues found in Defender for Identity."
107+
)
108+
findings.append(report)
109+
return findings
110+
111+
for health_issue in defenderidentity_client.health_issues:
112+
report = CheckReportM365(
113+
metadata=self.metadata(),
114+
resource=health_issue,
115+
resource_name=health_issue.display_name,
116+
resource_id=health_issue.id,
117+
)
118+
119+
issue_type = health_issue.health_issue_type or "unknown"
120+
severity = health_issue.severity or "unknown"
121+
status = (health_issue.status or "").lower()
122+
123+
if status != "open":
124+
report.status = "PASS"
125+
report.status_extended = f"Defender for Identity {issue_type} health issue {health_issue.display_name} is resolved."
126+
else:
127+
report.status = "FAIL"
128+
report.status_extended = f"Defender for Identity {issue_type} health issue {health_issue.display_name} is open with {severity} severity."
129+
130+
# Adjust severity based on issue severity
131+
if severity == "high":
132+
report.check_metadata.Severity = Severity.high
133+
elif severity == "medium":
134+
report.check_metadata.Severity = Severity.medium
135+
elif severity == "low":
136+
report.check_metadata.Severity = Severity.low
137+
138+
findings.append(report)
139+
140+
return findings

0 commit comments

Comments
 (0)