Skip to content

Commit 9cf63a2

Browse files
feat(m365): add custom entra_conditional_access_policy_compliant_device_hybrid_joined_device_mfa_required check (#10197)
Co-authored-by: Daniel Barranquero <danielbo2001@gmail.com>
1 parent e2fe482 commit 9cf63a2

File tree

5 files changed

+549
-0
lines changed

5 files changed

+549
-0
lines changed

prowler/CHANGELOG.md

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

99
- `entra_conditional_access_policy_approved_client_app_required_for_mobile` check for m365 provider [(#10216)](https://github.com/prowler-cloud/prowler/pull/10216)
10+
- `entra_conditional_access_policy_compliant_device_hybrid_joined_device_mfa_required` check for M365 provider [(#10197)](https://github.com/prowler-cloud/prowler/pull/10197)
1011

1112
### 🔄 Changed
1213

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

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
{
2+
"Provider": "m365",
3+
"CheckID": "entra_conditional_access_policy_compliant_device_hybrid_joined_device_mfa_required",
4+
"CheckTitle": "Conditional Access requires compliant device OR hybrid joined device OR MFA for admins or all users",
5+
"CheckType": [],
6+
"ServiceName": "entra",
7+
"SubServiceName": "",
8+
"ResourceIdTemplate": "",
9+
"Severity": "high",
10+
"ResourceType": "NotDefined",
11+
"ResourceGroup": "IAM",
12+
"Description": "A **Conditional Access policy** enforces one of the following grant controls for admin roles or all users across all cloud apps: - 'Require device to be marked as compliant' - 'Require Microsoft Entra hybrid joined device' - 'Require multifactor authentication' This ensures that access is provided only under strong authentication or trusted device conditions.",
13+
"Risk": "If this policy is not implemented, attackers with compromised credentials may gain access from unmanaged devices or without strong authentication, increasing the likelihood of **unauthorized access and data breaches**.",
14+
"RelatedUrl": "",
15+
"AdditionalURLs": [
16+
"https://learn.microsoft.com/en-us/entra/identity/conditional-access/concept-conditional-access-grant",
17+
"https://learn.microsoft.com/en-us/entra/identity/conditional-access/howto-conditional-access-policy-compliant-device"
18+
],
19+
"Remediation": {
20+
"Code": {
21+
"CLI": "",
22+
"NativeIaC": "",
23+
"Other": "1. Navigate to Microsoft Entra admin center https://entra.microsoft.com.\n2. Go to Protection > Conditional Access > Policies and create or edit a policy.\n3. Under Users, include All users or administrative roles.\n4. Under Target resources, include All cloud apps.\n5. Under Grant, select Grant access and enable these controls: Require multifactor authentication, Require device to be marked as compliant, and Require Microsoft Entra hybrid joined device.\n6. Set Grant operator to Require one of the selected controls.\n7. Start in Report-only mode for validation and then switch to On.",
24+
"Terraform": ""
25+
},
26+
"Recommendation": {
27+
"Text": "Enforce a Conditional Access baseline where admins or all users must satisfy at least one strong control: compliant device, hybrid joined device, or MFA.",
28+
"Url": "https://hub.prowler.com/check/entra_conditional_access_policy_compliant_device_hybrid_joined_device_mfa_required"
29+
}
30+
},
31+
"Categories": [
32+
"identity-access",
33+
"e3"
34+
],
35+
"DependsOn": [],
36+
"RelatedTo": [
37+
"entra_managed_device_required_for_authentication"
38+
],
39+
"Notes": ""
40+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
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+
AdminRoles,
5+
ConditionalAccessGrantControl,
6+
ConditionalAccessPolicyState,
7+
GrantControlOperator,
8+
)
9+
10+
REQUIRED_GRANT_CONTROLS = {
11+
ConditionalAccessGrantControl.MFA,
12+
ConditionalAccessGrantControl.COMPLIANT_DEVICE,
13+
ConditionalAccessGrantControl.DOMAIN_JOINED_DEVICE,
14+
}
15+
ADMIN_ROLE_IDS = {role.value for role in AdminRoles}
16+
17+
18+
class entra_conditional_access_policy_compliant_device_hybrid_joined_device_mfa_required(
19+
Check
20+
):
21+
"""Check that CA enforces compliant or hybrid joined device or MFA for admins/all users."""
22+
23+
def _targets_admins_or_all_users(self, policy) -> bool:
24+
if "All" in policy.conditions.user_conditions.included_users:
25+
return True
26+
27+
included_roles = set(policy.conditions.user_conditions.included_roles)
28+
return bool(ADMIN_ROLE_IDS.intersection(included_roles))
29+
30+
def execute(self) -> list[CheckReportM365]:
31+
findings = []
32+
33+
report = CheckReportM365(
34+
metadata=self.metadata(),
35+
resource={},
36+
resource_name="Conditional Access Policies",
37+
resource_id="conditionalAccessPolicies",
38+
)
39+
report.status = "FAIL"
40+
report.status_extended = "No Conditional Access Policy requires compliant device, hybrid joined device, or MFA for admin roles or all users across all cloud apps."
41+
42+
for policy in entra_client.conditional_access_policies.values():
43+
if policy.state == ConditionalAccessPolicyState.DISABLED:
44+
continue
45+
46+
if not self._targets_admins_or_all_users(policy):
47+
continue
48+
49+
if (
50+
"All"
51+
not in policy.conditions.application_conditions.included_applications
52+
):
53+
continue
54+
55+
policy_grant_controls = set(policy.grant_controls.built_in_controls)
56+
if not REQUIRED_GRANT_CONTROLS.issubset(policy_grant_controls):
57+
continue
58+
59+
if policy.grant_controls.operator != GrantControlOperator.OR:
60+
continue
61+
62+
report = CheckReportM365(
63+
metadata=self.metadata(),
64+
resource=policy,
65+
resource_name=policy.display_name,
66+
resource_id=policy.id,
67+
)
68+
69+
if policy.state == ConditionalAccessPolicyState.ENABLED_FOR_REPORTING:
70+
report.status = "FAIL"
71+
report.status_extended = f"Conditional Access Policy {policy.display_name} reports compliant device, hybrid joined device, or MFA for admin roles or all users but does not enforce it."
72+
else:
73+
report.status = "PASS"
74+
report.status_extended = f"Conditional Access Policy {policy.display_name} enforces compliant device, hybrid joined device, or MFA for admin roles or all users across all cloud apps."
75+
break
76+
77+
findings.append(report)
78+
return findings

0 commit comments

Comments
 (0)