Skip to content

Commit 39ba037

Browse files
committed
feat(m365): add entra_conditional_access_policy_block_elevated_insider_risk security check
Add new security check entra_conditional_access_policy_block_elevated_insider_risk for m365 provider. Includes check implementation, metadata, and unit tests.
1 parent 9c2cb5e commit 39ba037

File tree

6 files changed

+949
-1
lines changed

6 files changed

+949
-1
lines changed

prowler/compliance/m365/iso27001_2022_m365.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@
120120
"defenderxdr_endpoint_privileged_user_exposed_credentials",
121121
"defender_identity_health_issues_no_open",
122122
"entra_admin_users_phishing_resistant_mfa_enabled",
123+
"entra_conditional_access_policy_block_elevated_insider_risk",
123124
"entra_identity_protection_sign_in_risk_enabled",
124125
"entra_identity_protection_user_risk_enabled"
125126
]
@@ -670,6 +671,7 @@
670671
"Checks": [
671672
"entra_admin_portals_access_restriction",
672673
"entra_conditional_access_policy_app_enforced_restrictions",
674+
"entra_conditional_access_policy_block_elevated_insider_risk",
673675
"entra_policy_guest_users_access_restrictions",
674676
"sharepoint_external_sharing_restricted"
675677
]
@@ -755,7 +757,8 @@
755757
"defender_antiphishing_policy_configured",
756758
"defender_safelinks_policy_enabled",
757759
"entra_admin_users_phishing_resistant_mfa_enabled",
758-
"entra_conditional_access_policy_app_enforced_restrictions"
760+
"entra_conditional_access_policy_app_enforced_restrictions",
761+
"entra_conditional_access_policy_block_elevated_insider_risk"
759762
]
760763
},
761764
{

prowler/providers/m365/services/entra/entra_conditional_access_policy_block_elevated_insider_risk/__init__.py

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
{
2+
"Provider": "m365",
3+
"CheckID": "entra_conditional_access_policy_block_elevated_insider_risk",
4+
"CheckTitle": "Conditional Access Policy blocks access for users with elevated insider risk",
5+
"CheckType": [],
6+
"ServiceName": "entra",
7+
"SubServiceName": "",
8+
"ResourceIdTemplate": "",
9+
"Severity": "medium",
10+
"ResourceType": "Conditional Access Policy",
11+
"ResourceGroup": "IAM",
12+
"Description": "This check verifies that at least one **enabled** Conditional Access policy **blocks access** to all cloud applications for users flagged with an **elevated insider risk** level by Microsoft Purview Insider Risk Management and Adaptive Protection.",
13+
"Risk": "Without blocking elevated insider risk users, compromised or malicious insiders retain **full access** to cloud applications. This enables data exfiltration, unauthorized modifications, and lateral movement, directly impacting **confidentiality** and **integrity**.",
14+
"RelatedUrl": "",
15+
"AdditionalURLs": [
16+
"https://learn.microsoft.com/en-us/purview/insider-risk-management-adaptive-protection",
17+
"https://learn.microsoft.com/en-us/entra/identity/conditional-access/how-to-policy-insider-risk"
18+
],
19+
"Remediation": {
20+
"Code": {
21+
"CLI": "",
22+
"NativeIaC": "",
23+
"Other": "1. In the Microsoft Entra admin center, go to Protection > Conditional Access > Policies.\n2. Click New policy.\n3. Under Users, select Include > All users.\n4. Under Target resources, select Include > All cloud apps.\n5. Under Conditions > Insider risk, select Configure > Yes, then check Elevated.\n6. Under Grant, select Block access.\n7. Set Enable policy to On and click Create.",
24+
"Terraform": ""
25+
},
26+
"Recommendation": {
27+
"Text": "Configure **Adaptive Protection** in Microsoft Purview to classify insider risk tiers, then create a Conditional Access policy that **blocks access** to all cloud apps for users with **elevated** risk. Only exclude dedicated break-glass accounts.",
28+
"Url": "https://hub.prowler.com/check/entra_conditional_access_policy_block_elevated_insider_risk"
29+
}
30+
},
31+
"Categories": [
32+
"identity-access",
33+
"e5"
34+
],
35+
"DependsOn": [],
36+
"RelatedTo": [],
37+
"Notes": "This check requires Microsoft 365 E5 with Microsoft Purview Insider Risk Management and Adaptive Protection configured. The insiderRiskLevels condition in Conditional Access evaluates the insider risk level assigned by Purview Adaptive Protection."
38+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
from prowler.lib.check.models import Check, CheckReportM365
2+
from prowler.providers.m365.services.entra.entra_client import entra_client
3+
from prowler.providers.m365.services.entra.entra_service import (
4+
ConditionalAccessGrantControl,
5+
ConditionalAccessPolicyState,
6+
InsiderRiskLevel,
7+
)
8+
9+
10+
class entra_conditional_access_policy_block_elevated_insider_risk(Check):
11+
"""Check if a Conditional Access policy blocks all cloud app access for elevated insider risk users.
12+
13+
This check verifies that at least one enabled Conditional Access policy
14+
blocks access to all cloud applications for users with an elevated insider
15+
risk level, as determined by Microsoft Purview Insider Risk Management
16+
and Adaptive Protection.
17+
18+
- PASS: An enabled CA policy blocks all cloud app access for elevated insider risk users.
19+
- FAIL: No enabled CA policy blocks broad cloud app access based on Purview insider risk signals.
20+
"""
21+
22+
def execute(self) -> list[CheckReportM365]:
23+
"""Execute the check logic.
24+
25+
Returns:
26+
A list of reports containing the result of the check.
27+
"""
28+
findings = []
29+
report = CheckReportM365(
30+
metadata=self.metadata(),
31+
resource={},
32+
resource_name="Conditional Access Policies",
33+
resource_id="conditionalAccessPolicies",
34+
)
35+
report.status = "FAIL"
36+
report.status_extended = "No Conditional Access Policy blocks access for users with elevated insider risk."
37+
38+
for policy in entra_client.conditional_access_policies.values():
39+
if policy.state == ConditionalAccessPolicyState.DISABLED:
40+
continue
41+
42+
if not policy.conditions.application_conditions:
43+
continue
44+
45+
if (
46+
"All"
47+
not in policy.conditions.application_conditions.included_applications
48+
):
49+
continue
50+
51+
if (
52+
InsiderRiskLevel.ELEVATED
53+
not in policy.conditions.insider_risk_levels
54+
):
55+
continue
56+
57+
if (
58+
ConditionalAccessGrantControl.BLOCK
59+
not in policy.grant_controls.built_in_controls
60+
):
61+
continue
62+
63+
report = CheckReportM365(
64+
metadata=self.metadata(),
65+
resource=policy,
66+
resource_name=policy.display_name,
67+
resource_id=policy.id,
68+
)
69+
70+
if policy.state == ConditionalAccessPolicyState.ENABLED_FOR_REPORTING:
71+
report.status = "FAIL"
72+
report.status_extended = f"Conditional Access Policy '{policy.display_name}' blocks elevated insider risk users for all cloud apps but is only in report-only mode."
73+
else:
74+
report.status = "PASS"
75+
report.status_extended = f"Conditional Access Policy '{policy.display_name}' blocks access to all cloud apps for users with elevated insider risk."
76+
break
77+
78+
findings.append(report)
79+
return findings

prowler/providers/m365/services/entra/entra_service.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,13 @@ async def _get_conditional_access_policies(self):
272272
[],
273273
)
274274
],
275+
insider_risk_levels=self._parse_insider_risk_levels(
276+
getattr(
277+
policy.conditions,
278+
"insider_risk_levels",
279+
None,
280+
)
281+
),
275282
),
276283
grant_controls=GrantControls(
277284
built_in_controls=(
@@ -417,6 +424,31 @@ async def _get_default_app_management_policy(self):
417424
)
418425
return default_app_management_policy
419426

427+
@staticmethod
428+
def _parse_insider_risk_levels(raw_value):
429+
"""Parse insider risk levels from a Graph API flag enum value.
430+
431+
The insiderRiskLevels field in the Graph API is a flag enum
432+
(conditionalAccessInsiderRiskLevels). The msgraph SDK may
433+
represent it as an IntFlag, string enum, or raw string
434+
depending on the version. This method normalizes it into a
435+
list of InsiderRiskLevel enum values.
436+
437+
Args:
438+
raw_value: The raw insider risk levels value from the SDK.
439+
440+
Returns:
441+
A list of InsiderRiskLevel enum values present in the raw value.
442+
"""
443+
if raw_value is None:
444+
return []
445+
raw_str = str(raw_value).lower()
446+
return [
447+
InsiderRiskLevel(level)
448+
for level in ["minor", "moderate", "elevated"]
449+
if level in raw_str
450+
]
451+
420452
@staticmethod
421453
def _parse_app_management_restrictions(restrictions):
422454
"""Parse credential restrictions from the Graph API response into AppManagementRestrictions."""
@@ -798,12 +830,24 @@ class ClientAppType(Enum):
798830
OTHER_CLIENTS = "other"
799831

800832

833+
class InsiderRiskLevel(Enum):
834+
"""Insider risk levels for Conditional Access policies.
835+
836+
Reference: https://learn.microsoft.com/en-us/graph/api/resources/conditionalaccessconditionset
837+
"""
838+
839+
MINOR = "minor"
840+
MODERATE = "moderate"
841+
ELEVATED = "elevated"
842+
843+
801844
class Conditions(BaseModel):
802845
application_conditions: Optional[ApplicationsConditions]
803846
user_conditions: Optional[UsersConditions]
804847
client_app_types: Optional[List[ClientAppType]]
805848
user_risk_levels: List[RiskLevel] = []
806849
sign_in_risk_levels: List[RiskLevel] = []
850+
insider_risk_levels: List[InsiderRiskLevel] = []
807851

808852

809853
class PersistentBrowser(BaseModel):

0 commit comments

Comments
 (0)