Skip to content

Commit 7148086

Browse files
feat(m365): add entra_conditional_access_policy_block_o365_elevated_insider_risk security check (#10232)
Co-authored-by: Daniel Barranquero <danielbo2001@gmail.com>
1 parent 4ef0b1b commit 7148086

File tree

7 files changed

+1146
-1
lines changed

7 files changed

+1146
-1
lines changed

prowler/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ All notable changes to the **Prowler SDK** are documented in this file.
1212
- `awslambda_function_no_dead_letter_queue`, `awslambda_function_using_cross_account_layers`, and `awslambda_function_env_vars_not_encrypted_with_cmk` checks for AWS Lambda [(#10381)](https://github.com/prowler-cloud/prowler/pull/10381)
1313
- `entra_conditional_access_policy_mdm_compliant_device_required` check for M365 provider [(#10220)](https://github.com/prowler-cloud/prowler/pull/10220)
1414
- `ec2_securitygroup_allow_ingress_from_internet_to_any_port_from_ip` check for AWS provider using `ipaddress.is_global` for accurate public IP detection [(#10335)](https://github.com/prowler-cloud/prowler/pull/10335)
15+
- `entra_conditional_access_policy_block_o365_elevated_insider_risk` check for M365 provider [(#10232)](https://github.com/prowler-cloud/prowler/pull/10232)
1516
- `--resource-group` and `--list-resource-groups` CLI flags to filter checks by resource group across all providers [(#10479)](https://github.com/prowler-cloud/prowler/pull/10479)
1617

1718
### 🔄 Changed

prowler/compliance/m365/iso27001_2022_m365.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
"Checks": [
2121
"defender_antiphishing_policy_configured",
2222
"defender_antispam_policy_inbound_no_allowed_domains",
23+
"entra_conditional_access_policy_block_o365_elevated_insider_risk",
2324
"entra_identity_protection_sign_in_risk_enabled",
2425
"entra_identity_protection_user_risk_enabled"
2526
]
@@ -120,6 +121,7 @@
120121
"defenderxdr_endpoint_privileged_user_exposed_credentials",
121122
"defender_identity_health_issues_no_open",
122123
"entra_admin_users_phishing_resistant_mfa_enabled",
124+
"entra_conditional_access_policy_block_o365_elevated_insider_risk",
123125
"entra_identity_protection_sign_in_risk_enabled",
124126
"entra_identity_protection_user_risk_enabled"
125127
]
@@ -203,6 +205,7 @@
203205
"admincenter_users_admins_reduced_license_footprint",
204206
"entra_admin_portals_access_restriction",
205207
"entra_admin_users_phishing_resistant_mfa_enabled",
208+
"entra_conditional_access_policy_block_o365_elevated_insider_risk",
206209
"entra_policy_guest_users_access_restrictions",
207210
"entra_seamless_sso_disabled"
208211
]
@@ -676,6 +679,7 @@
676679
"entra_admin_portals_access_restriction",
677680
"entra_conditional_access_policy_approved_client_app_required_for_mobile",
678681
"entra_conditional_access_policy_app_enforced_restrictions",
682+
"entra_conditional_access_policy_block_o365_elevated_insider_risk",
679683
"entra_policy_guest_users_access_restrictions",
680684
"sharepoint_external_sharing_restricted"
681685
]
@@ -764,7 +768,8 @@
764768
"defender_antiphishing_policy_configured",
765769
"defender_safelinks_policy_enabled",
766770
"entra_admin_users_phishing_resistant_mfa_enabled",
767-
"entra_conditional_access_policy_app_enforced_restrictions"
771+
"entra_conditional_access_policy_app_enforced_restrictions",
772+
"entra_conditional_access_policy_block_o365_elevated_insider_risk"
768773
]
769774
},
770775
{

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

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
{
2+
"Provider": "m365",
3+
"CheckID": "entra_conditional_access_policy_block_o365_elevated_insider_risk",
4+
"CheckTitle": "Conditional Access policy blocks Office 365 access for users with elevated insider risk",
5+
"CheckType": [],
6+
"ServiceName": "entra",
7+
"SubServiceName": "",
8+
"ResourceIdTemplate": "",
9+
"Severity": "medium",
10+
"ResourceType": "NotDefined",
11+
"ResourceGroup": "IAM",
12+
"Description": "A Conditional Access policy configured to **block** Office 365 access for users flagged with **elevated insider risk** by Microsoft Purview Adaptive Protection.\n\nThis control uses behavioral signals to restrict access when a user's risk level indicates potential insider threat activity.",
13+
"Risk": "Without a policy blocking Office 365 for elevated insider risk users, compromised or malicious insiders can continue accessing email, SharePoint, OneDrive, and Teams.\n\nThis may lead to **data exfiltration**, unauthorized sharing of sensitive information, or tampering with critical business communications.",
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. Navigate to the Microsoft Entra admin center https://entra.microsoft.com.\n2. Expand **Protection** > **Conditional Access** and select **Policies**.\n3. Click **New policy**.\n4. Under **Users**, select **All users**.\n5. Under **Target resources** > **Cloud apps**, select **Office 365**.\n6. Under **Conditions** > **Insider risk**, select **Elevated**.\n7. Under **Grant**, select **Block access**.\n8. Set the policy to **On** and click **Create**.",
24+
"Terraform": ""
25+
},
26+
"Recommendation": {
27+
"Text": "Configure a Conditional Access policy to **block** Office 365 access for users with **elevated insider risk** levels. This leverages Microsoft Purview Adaptive Protection signals to dynamically restrict access based on user behavior analysis.\n\nEnsure Insider Risk Management and Adaptive Protection are configured in Microsoft Purview as prerequisites.",
28+
"Url": "https://hub.prowler.com/check/entra_conditional_access_policy_block_o365_elevated_insider_risk"
29+
}
30+
},
31+
"Categories": [
32+
"e5"
33+
],
34+
"DependsOn": [],
35+
"RelatedTo": [],
36+
"Notes": "Requires Microsoft 365 E5 or E5 Compliance license with Insider Risk Management configured and Adaptive Protection enabled in Microsoft Purview."
37+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
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+
OFFICE365_APP_ID = "Office365"
10+
11+
12+
class entra_conditional_access_policy_block_o365_elevated_insider_risk(Check):
13+
"""Check if a Conditional Access policy blocks Office 365 access for elevated insider risk users.
14+
15+
This check verifies that at least one enabled Conditional Access policy blocks
16+
access to Office 365 applications for users with elevated insider risk levels,
17+
as determined by Microsoft Purview Adaptive Protection.
18+
19+
- PASS: At least one enabled policy blocks Office 365 access for users with elevated insider risk.
20+
- FAIL: No enabled policy blocks Office 365 access based on insider risk signals.
21+
"""
22+
23+
def execute(self) -> list[CheckReportM365]:
24+
"""Execute the check for insider risk blocking in Conditional Access policies.
25+
26+
Returns:
27+
list[CheckReportM365]: A list containing the result of the check.
28+
"""
29+
findings = []
30+
report = CheckReportM365(
31+
metadata=self.metadata(),
32+
resource={},
33+
resource_name="Conditional Access Policies",
34+
resource_id="conditionalAccessPolicies",
35+
)
36+
report.status = "FAIL"
37+
report.status_extended = "No Conditional Access Policy blocks Office 365 access for users with elevated insider risk."
38+
39+
for policy in entra_client.conditional_access_policies.values():
40+
if policy.state == ConditionalAccessPolicyState.DISABLED:
41+
continue
42+
43+
if "All" not in policy.conditions.user_conditions.included_users:
44+
continue
45+
46+
if (
47+
OFFICE365_APP_ID
48+
not in policy.conditions.application_conditions.included_applications
49+
and "All"
50+
not in policy.conditions.application_conditions.included_applications
51+
):
52+
continue
53+
54+
if (
55+
ConditionalAccessGrantControl.BLOCK
56+
not in policy.grant_controls.built_in_controls
57+
):
58+
continue
59+
60+
# Policy targets all users, O365/All apps, and blocks access.
61+
# Now check if Adaptive Protection is providing insider risk signals.
62+
if policy.conditions.insider_risk_levels is None:
63+
report = CheckReportM365(
64+
metadata=self.metadata(),
65+
resource=policy,
66+
resource_name=policy.display_name,
67+
resource_id=policy.id,
68+
)
69+
report.status = "FAIL"
70+
if policy.state == ConditionalAccessPolicyState.ENABLED_FOR_REPORTING:
71+
report.status_extended = f"Conditional Access Policy {policy.display_name} is configured in report-only mode to block Office 365 and Microsoft Purview Adaptive Protection is not providing insider risk signals."
72+
else:
73+
report.status_extended = f"Conditional Access Policy {policy.display_name} is configured to block Office 365 and Microsoft Purview Adaptive Protection is not providing insider risk signals."
74+
continue
75+
76+
if policy.conditions.insider_risk_levels != InsiderRiskLevel.ELEVATED:
77+
continue
78+
79+
report = CheckReportM365(
80+
metadata=self.metadata(),
81+
resource=policy,
82+
resource_name=policy.display_name,
83+
resource_id=policy.id,
84+
)
85+
if policy.state == ConditionalAccessPolicyState.ENABLED_FOR_REPORTING:
86+
report.status = "FAIL"
87+
report.status_extended = f"Conditional Access Policy {policy.display_name} reports blocking Office 365 for elevated insider risk users but does not enforce it."
88+
else:
89+
report.status = "PASS"
90+
report.status_extended = f"Conditional Access Policy {policy.display_name} blocks Office 365 access for users with elevated insider risk."
91+
break
92+
93+
findings.append(report)
94+
return findings

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

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,21 @@ async def _get_conditional_access_policies(self):
289289
[],
290290
)
291291
],
292+
# The MS Graph SDK deserializes insiderRiskLevels
293+
# as a list via get_collection_of_enum_values, so
294+
# we take the first element when present.
295+
insider_risk_levels=(
296+
InsiderRiskLevel(raw_insider_risk[0].value)
297+
if (
298+
raw_insider_risk := getattr(
299+
policy.conditions,
300+
"insider_risk_levels",
301+
None,
302+
)
303+
)
304+
and raw_insider_risk
305+
else None
306+
),
292307
platform_conditions=PlatformConditions(
293308
include_platforms=[
294309
platform
@@ -966,6 +981,17 @@ class ClientAppType(Enum):
966981
OTHER_CLIENTS = "other"
967982

968983

984+
class InsiderRiskLevel(Enum):
985+
"""Insider risk levels for Conditional Access policies.
986+
987+
Reference: https://learn.microsoft.com/en-us/graph/api/resources/conditionalaccessconditionset#conditionalaccessinsiderrisklevels-values
988+
"""
989+
990+
MINOR = "minor"
991+
MODERATE = "moderate"
992+
ELEVATED = "elevated"
993+
994+
969995
class PlatformConditions(BaseModel):
970996
"""Model representing platform conditions for Conditional Access policies."""
971997

@@ -992,6 +1018,7 @@ class Conditions(BaseModel):
9921018
client_app_types: Optional[List[ClientAppType]]
9931019
user_risk_levels: List[RiskLevel] = []
9941020
sign_in_risk_levels: List[RiskLevel] = []
1021+
insider_risk_levels: Optional[InsiderRiskLevel] = None
9951022
platform_conditions: Optional[PlatformConditions] = None
9961023
authentication_flows: Optional[AuthenticationFlows] = None
9971024

0 commit comments

Comments
 (0)