diff --git a/prowler/providers/m365/services/entra/entra_conditional_access_policy_approved_client_app_required_for_mobile/entra_conditional_access_policy_approved_client_app_required_for_mobile.py b/prowler/providers/m365/services/entra/entra_conditional_access_policy_approved_client_app_required_for_mobile/entra_conditional_access_policy_approved_client_app_required_for_mobile.py index dd865064c2..b6f231af35 100644 --- a/prowler/providers/m365/services/entra/entra_conditional_access_policy_approved_client_app_required_for_mobile/entra_conditional_access_policy_approved_client_app_required_for_mobile.py +++ b/prowler/providers/m365/services/entra/entra_conditional_access_policy_approved_client_app_required_for_mobile/entra_conditional_access_policy_approved_client_app_required_for_mobile.py @@ -1,11 +1,17 @@ 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 ( + MOBILE_PLATFORMS, ConditionalAccessGrantControl, ConditionalAccessPolicyState, GrantControlOperator, ) +MOBILE_APP_GRANT_CONTROLS = { + ConditionalAccessGrantControl.APPROVED_APPLICATION, + ConditionalAccessGrantControl.COMPLIANT_APPLICATION, +} + class entra_conditional_access_policy_approved_client_app_required_for_mobile(Check): """Check if a Conditional Access policy requires approved client apps or app protection for mobile devices. @@ -17,19 +23,6 @@ class entra_conditional_access_policy_approved_client_app_required_for_mobile(Ch - FAIL: No policy restricts mobile app access to approved or protected apps. """ - REQUIRED_MOBILE_PLATFORMS = {"android", "ios"} - MOBILE_APP_GRANT_CONTROLS = { - ConditionalAccessGrantControl.APPROVED_APPLICATION, - ConditionalAccessGrantControl.COMPLIANT_APPLICATION, - } - - @staticmethod - def _normalize_platform(platform: object) -> str: - normalized_platform = getattr(platform, "value", platform) - return ( - normalized_platform.lower() if isinstance(normalized_platform, str) else "" - ) - def execute(self) -> list[CheckReportM365]: """Execute the check logic. @@ -54,43 +47,22 @@ class entra_conditional_access_policy_approved_client_app_required_for_mobile(Ch if not policy.conditions.platform_conditions: continue - included_platforms = { - normalized_platform - for normalized_platform in map( - self._normalize_platform, - policy.conditions.platform_conditions.include_platforms, - ) - if normalized_platform - } - excluded_platforms = { - normalized_platform - for normalized_platform in map( - self._normalize_platform, - policy.conditions.platform_conditions.exclude_platforms, - ) - if normalized_platform - } + included = set(policy.conditions.platform_conditions.include_platforms) + excluded = set(policy.conditions.platform_conditions.exclude_platforms) - targets_mobile_platforms = ( - "all" in included_platforms - or self.REQUIRED_MOBILE_PLATFORMS.issubset(included_platforms) - ) and not ( - "all" in excluded_platforms - or self.REQUIRED_MOBILE_PLATFORMS.intersection(excluded_platforms) - ) - if not targets_mobile_platforms: + targets_mobile = ( + "all" in included or MOBILE_PLATFORMS.issubset(included) + ) and not ("all" in excluded or MOBILE_PLATFORMS.intersection(excluded)) + if not targets_mobile: continue built_in_controls = set(policy.grant_controls.built_in_controls) - has_mobile_app_control = bool( - self.MOBILE_APP_GRANT_CONTROLS.intersection(built_in_controls) - ) - if not has_mobile_app_control: + if not MOBILE_APP_GRANT_CONTROLS.intersection(built_in_controls): continue if ( policy.grant_controls.operator == GrantControlOperator.OR - and not built_in_controls.issubset(self.MOBILE_APP_GRANT_CONTROLS) + and not built_in_controls.issubset(MOBILE_APP_GRANT_CONTROLS) ): continue diff --git a/prowler/providers/m365/services/entra/entra_conditional_access_policy_unknown_device_blocked/entra_conditional_access_policy_unknown_device_blocked.py b/prowler/providers/m365/services/entra/entra_conditional_access_policy_unknown_device_blocked/entra_conditional_access_policy_unknown_device_blocked.py index 4e960f751f..d16e90e659 100644 --- a/prowler/providers/m365/services/entra/entra_conditional_access_policy_unknown_device_blocked/entra_conditional_access_policy_unknown_device_blocked.py +++ b/prowler/providers/m365/services/entra/entra_conditional_access_policy_unknown_device_blocked/entra_conditional_access_policy_unknown_device_blocked.py @@ -19,13 +19,6 @@ class entra_conditional_access_policy_unknown_device_blocked(Check): - FAIL: No CA policy restricts access from unrecognized device platforms. """ - @staticmethod - def _normalize_platform(platform: object) -> str: - normalized_platform = getattr(platform, "value", platform) - return ( - normalized_platform.lower() if isinstance(normalized_platform, str) else "" - ) - def execute(self) -> list[CheckReportM365]: """Execute the check logic. @@ -49,29 +42,13 @@ class entra_conditional_access_policy_unknown_device_blocked(Check): if not policy.conditions.platform_conditions: continue - included_platforms = { - normalized_platform - for normalized_platform in map( - self._normalize_platform, - policy.conditions.platform_conditions.include_platforms, - ) - if normalized_platform - } - excluded_platforms = { - normalized_platform - for normalized_platform in map( - self._normalize_platform, - policy.conditions.platform_conditions.exclude_platforms, - ) - if normalized_platform - } + included = set(policy.conditions.platform_conditions.include_platforms) + excluded = set(policy.conditions.platform_conditions.exclude_platforms) - if "all" not in included_platforms: + if "all" not in included: continue - if not { - self._normalize_platform(platform) for platform in KNOWN_PLATFORMS - }.issubset(excluded_platforms): + if not KNOWN_PLATFORMS.issubset(excluded): continue if ( diff --git a/prowler/providers/m365/services/entra/entra_service.py b/prowler/providers/m365/services/entra/entra_service.py index 0bfac17aef..07d0dd3bda 100644 --- a/prowler/providers/m365/services/entra/entra_service.py +++ b/prowler/providers/m365/services/entra/entra_service.py @@ -9,7 +9,7 @@ from msgraph.generated.models.o_data_errors.o_data_error import ODataError from msgraph.generated.security.microsoft_graph_security_run_hunting_query.run_hunting_query_post_request_body import ( RunHuntingQueryPostRequestBody, ) -from pydantic.v1 import BaseModel, Field +from pydantic.v1 import BaseModel, Field, validator from prowler.lib.logger import logger from prowler.providers.m365.lib.service.service import M365Service @@ -1003,8 +1003,18 @@ class PlatformConditions(BaseModel): class Config: allow_population_by_field_name = True + @validator("include_platforms", "exclude_platforms", pre=True) + def normalize_platforms(cls, v): + normalized = [] + for platform in v: + value = getattr(platform, "value", platform) + if isinstance(value, str) and value: + normalized.append(value.lower()) + return normalized + KNOWN_PLATFORMS = frozenset({"android", "ios", "windows", "macos", "linux"}) +MOBILE_PLATFORMS = frozenset({"android", "ios"}) class TransferMethod(Enum):