mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-10-14 15:34:19 +00:00
fix(sdk): tighten entra sign-in frequency filter matching
- Validate device filter semantics by mode - Prevent corporate-device false positives - Add regression tests for include and exclude rules
This commit is contained in:
2 files changed
+96
-10
No files matched your search
+20
-8
@@ -1,3 +1,5 @@
|
||||
import re
|
||||
|
||||
from prowler.lib.check.models import Check, CheckReportM365
|
||||
from prowler.providers.m365.services.entra.entra_client import entra_client
|
||||
from prowler.providers.m365.services.entra.entra_service import (
|
||||
@@ -20,6 +22,18 @@ class entra_conditional_access_policy_sign_in_frequency_enforced(Check):
|
||||
non-corporate devices.
|
||||
"""
|
||||
|
||||
NON_CORPORATE_INCLUDE_PATTERNS = (
|
||||
r"device\.iscompliant\s*-ne\s*true",
|
||||
r"device\.iscompliant\s*-eq\s*false",
|
||||
r'device\.trusttype\s*-ne\s*"serverad"',
|
||||
r"device\.trusttype\s*-ne\s*'serverad'",
|
||||
)
|
||||
CORPORATE_EXCLUDE_PATTERNS = (
|
||||
r"device\.iscompliant\s*-eq\s*true",
|
||||
r'device\.trusttype\s*-eq\s*"serverad"',
|
||||
r"device\.trusttype\s*-eq\s*'serverad'",
|
||||
)
|
||||
|
||||
def _targets_all_users(self, included_users: list[str]) -> bool:
|
||||
"""Check if the policy targets all users.
|
||||
|
||||
@@ -61,18 +75,16 @@ class entra_conditional_access_policy_sign_in_frequency_enforced(Check):
|
||||
return False
|
||||
if not device_conditions.device_filter_rule:
|
||||
return False
|
||||
rule = device_conditions.device_filter_rule.lower()
|
||||
if device_conditions.device_filter_mode == DeviceFilterMode.INCLUDE:
|
||||
rule = device_conditions.device_filter_rule.lower()
|
||||
return (
|
||||
"device.iscompliant" in rule or "device.trusttype" in rule
|
||||
)
|
||||
return self._rule_matches_any(rule, self.NON_CORPORATE_INCLUDE_PATTERNS)
|
||||
elif device_conditions.device_filter_mode == DeviceFilterMode.EXCLUDE:
|
||||
rule = device_conditions.device_filter_rule.lower()
|
||||
return (
|
||||
"device.iscompliant" in rule or "device.trusttype" in rule
|
||||
)
|
||||
return self._rule_matches_any(rule, self.CORPORATE_EXCLUDE_PATTERNS)
|
||||
return False
|
||||
|
||||
def _rule_matches_any(self, rule: str, patterns: tuple[str, ...]) -> bool:
|
||||
return any(re.search(pattern, rule) for pattern in patterns)
|
||||
|
||||
def execute(self) -> list[CheckReportM365]:
|
||||
"""Execute the check for sign-in frequency enforcement in Conditional Access policies.
|
||||
|
||||
|
||||
+76
-2
@@ -147,7 +147,7 @@ class Test_entra_conditional_access_policy_sign_in_frequency_enforced:
|
||||
display_name="Disabled Policy",
|
||||
state=ConditionalAccessPolicyState.DISABLED,
|
||||
device_filter_mode=DeviceFilterMode.INCLUDE,
|
||||
device_filter_rule="device.isCompliant -ne True -or device.trustType -ne \"ServerAD\"",
|
||||
device_filter_rule='device.isCompliant -ne True -or device.trustType -ne "ServerAD"',
|
||||
)
|
||||
}
|
||||
|
||||
@@ -187,7 +187,7 @@ class Test_entra_conditional_access_policy_sign_in_frequency_enforced:
|
||||
display_name=display_name,
|
||||
state=ConditionalAccessPolicyState.ENABLED_FOR_REPORTING,
|
||||
device_filter_mode=DeviceFilterMode.INCLUDE,
|
||||
device_filter_rule="device.isCompliant -ne True -or device.trustType -ne \"ServerAD\"",
|
||||
device_filter_rule='device.isCompliant -ne True -or device.trustType -ne "ServerAD"',
|
||||
)
|
||||
}
|
||||
|
||||
@@ -509,6 +509,80 @@ class Test_entra_conditional_access_policy_sign_in_frequency_enforced:
|
||||
assert result[0].status == "FAIL"
|
||||
assert result[0].resource_name == "Conditional Access Policies"
|
||||
|
||||
def test_entra_policy_device_filter_include_corporate_devices(self):
|
||||
"""Test FAIL when include mode targets only corporate devices."""
|
||||
policy_id = str(uuid4())
|
||||
entra_client = mock.MagicMock
|
||||
entra_client.audited_tenant = "audited_tenant"
|
||||
entra_client.audited_domain = DOMAIN
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_m365_provider(),
|
||||
),
|
||||
mock.patch(
|
||||
f"{CHECK_MODULE_PATH}.entra_client",
|
||||
new=entra_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.m365.services.entra.entra_conditional_access_policy_sign_in_frequency_enforced.entra_conditional_access_policy_sign_in_frequency_enforced import (
|
||||
entra_conditional_access_policy_sign_in_frequency_enforced,
|
||||
)
|
||||
|
||||
entra_client.conditional_access_policies = {
|
||||
policy_id: _make_policy(
|
||||
policy_id=policy_id,
|
||||
display_name="Corporate Devices Policy",
|
||||
device_filter_mode=DeviceFilterMode.INCLUDE,
|
||||
device_filter_rule="device.isCompliant -eq True",
|
||||
)
|
||||
}
|
||||
|
||||
check = entra_conditional_access_policy_sign_in_frequency_enforced()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert result[0].resource_name == "Conditional Access Policies"
|
||||
|
||||
def test_entra_policy_device_filter_exclude_non_corporate_devices(self):
|
||||
"""Test FAIL when exclude mode excludes non-corporate devices."""
|
||||
policy_id = str(uuid4())
|
||||
entra_client = mock.MagicMock
|
||||
entra_client.audited_tenant = "audited_tenant"
|
||||
entra_client.audited_domain = DOMAIN
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_m365_provider(),
|
||||
),
|
||||
mock.patch(
|
||||
f"{CHECK_MODULE_PATH}.entra_client",
|
||||
new=entra_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.m365.services.entra.entra_conditional_access_policy_sign_in_frequency_enforced.entra_conditional_access_policy_sign_in_frequency_enforced import (
|
||||
entra_conditional_access_policy_sign_in_frequency_enforced,
|
||||
)
|
||||
|
||||
entra_client.conditional_access_policies = {
|
||||
policy_id: _make_policy(
|
||||
policy_id=policy_id,
|
||||
display_name="Exclude Unmanaged Devices Policy",
|
||||
device_filter_mode=DeviceFilterMode.EXCLUDE,
|
||||
device_filter_rule="device.isCompliant -eq False",
|
||||
)
|
||||
}
|
||||
|
||||
check = entra_conditional_access_policy_sign_in_frequency_enforced()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert result[0].resource_name == "Conditional Access Policies"
|
||||
|
||||
def test_entra_multiple_policies_one_compliant(self):
|
||||
"""Test PASS when at least one policy among multiple is compliant."""
|
||||
policy_id_1 = str(uuid4())
|
||||
|
||||
Reference in new issue
Block a user