mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-04-09 11:17:08 +00:00
Compare commits
1 Commits
feat/prowl
...
feat/prowl
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e8ae0902a5 |
@@ -20,6 +20,7 @@ All notable changes to the **Prowler SDK** are documented in this file.
|
||||
- `calendar_external_sharing_primary_calendar`, `calendar_external_sharing_secondary_calendar`, and `calendar_external_invitations_warning` checks for Google Workspace provider using the Cloud Identity Policy API [(#10597)](https://github.com/prowler-cloud/prowler/pull/10597)
|
||||
- `entra_conditional_access_policy_device_registration_mfa_required` check and `entra_intune_enrollment_sign_in_frequency_every_time` enhancement for M365 provider [(#10222)](https://github.com/prowler-cloud/prowler/pull/10222)
|
||||
- `entra_conditional_access_policy_block_elevated_insider_risk` check for M365 provider [(#10234)](https://github.com/prowler-cloud/prowler/pull/10234)
|
||||
- `entra_conditional_access_policy_block_unknown_device_platforms` check for m365 provider [(#10615)](https://github.com/prowler-cloud/prowler/pull/10615)
|
||||
- `Vercel` provider support with 30 checks [(#10189)](https://github.com/prowler-cloud/prowler/pull/10189)
|
||||
|
||||
### 🔄 Changed
|
||||
|
||||
@@ -207,6 +207,7 @@
|
||||
"entra_admin_portals_access_restriction",
|
||||
"entra_admin_users_phishing_resistant_mfa_enabled",
|
||||
"entra_conditional_access_policy_block_o365_elevated_insider_risk",
|
||||
"entra_conditional_access_policy_block_unknown_device_platforms",
|
||||
"entra_policy_guest_users_access_restrictions",
|
||||
"entra_seamless_sso_disabled"
|
||||
]
|
||||
@@ -249,6 +250,7 @@
|
||||
"entra_all_apps_conditional_access_coverage",
|
||||
"entra_conditional_access_policy_device_registration_mfa_required",
|
||||
"entra_intune_enrollment_sign_in_frequency_every_time",
|
||||
"entra_conditional_access_policy_block_unknown_device_platforms",
|
||||
"entra_conditional_access_policy_device_code_flow_blocked",
|
||||
"entra_legacy_authentication_blocked",
|
||||
"entra_managed_device_required_for_authentication",
|
||||
@@ -626,6 +628,7 @@
|
||||
"entra_admin_users_phishing_resistant_mfa_enabled",
|
||||
"entra_conditional_access_policy_approved_client_app_required_for_mobile",
|
||||
"entra_conditional_access_policy_app_enforced_restrictions",
|
||||
"entra_conditional_access_policy_block_unknown_device_platforms",
|
||||
"entra_conditional_access_policy_device_registration_mfa_required",
|
||||
"entra_intune_enrollment_sign_in_frequency_every_time",
|
||||
"entra_managed_device_required_for_authentication",
|
||||
@@ -686,6 +689,7 @@
|
||||
"entra_conditional_access_policy_app_enforced_restrictions",
|
||||
"entra_conditional_access_policy_block_elevated_insider_risk",
|
||||
"entra_conditional_access_policy_block_o365_elevated_insider_risk",
|
||||
"entra_conditional_access_policy_block_unknown_device_platforms",
|
||||
"entra_policy_guest_users_access_restrictions",
|
||||
"sharepoint_external_sharing_restricted"
|
||||
]
|
||||
@@ -710,6 +714,7 @@
|
||||
"entra_intune_enrollment_sign_in_frequency_every_time",
|
||||
"entra_break_glass_account_fido2_security_key_registered",
|
||||
"entra_conditional_access_policy_approved_client_app_required_for_mobile",
|
||||
"entra_conditional_access_policy_block_unknown_device_platforms",
|
||||
"entra_conditional_access_policy_device_code_flow_blocked",
|
||||
"entra_identity_protection_sign_in_risk_enabled",
|
||||
"entra_managed_device_required_for_authentication",
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"Provider": "m365",
|
||||
"CheckID": "entra_conditional_access_policy_block_unknown_device_platforms",
|
||||
"CheckTitle": "Conditional Access policy blocks access from unknown or unsupported device platforms",
|
||||
"CheckType": [],
|
||||
"ServiceName": "entra",
|
||||
"SubServiceName": "",
|
||||
"ResourceIdTemplate": "",
|
||||
"Severity": "medium",
|
||||
"ResourceType": "NotDefined",
|
||||
"ResourceGroup": "IAM",
|
||||
"Description": "Microsoft Entra **Conditional Access** can block sign-ins from device platforms that the organization does not recognize or support. A policy that includes **all** platforms and excludes the known ones (Android, iOS, Windows, macOS, Linux) effectively blocks any **unknown or unsupported** platform, reducing the attack surface from unmanaged or unexpected devices.",
|
||||
"Risk": "Without blocking unknown device platforms, attackers may authenticate from **spoofed or uncommon operating systems** that bypass platform-specific security controls. This increases the risk of **unauthorized access** and makes it harder to enforce device compliance, potentially leading to **credential theft** or **data exfiltration** from unmanaged environments.",
|
||||
"RelatedUrl": "",
|
||||
"AdditionalURLs": [
|
||||
"https://learn.microsoft.com/en-us/entra/identity/conditional-access/howto-policy-unknown-unsupported-device",
|
||||
"https://learn.microsoft.com/en-us/entra/identity/conditional-access/concept-conditional-access-conditions#device-platforms",
|
||||
"https://maester.dev/docs/tests/MT.1015"
|
||||
],
|
||||
"Remediation": {
|
||||
"Code": {
|
||||
"CLI": "",
|
||||
"NativeIaC": "",
|
||||
"Other": "1. Navigate to the Microsoft Entra admin center https://entra.microsoft.com.\n2. Expand **Protection** > **Conditional Access** and select **Policies**.\n3. Select **New policy**.\n4. Under **Users**, include the desired scope (e.g., **All users**).\n5. Under **Target resources**, include **All cloud apps** or the applications you want to protect.\n6. Under **Conditions** > **Device platforms**, set **Configure** to **Yes**.\n7. Under **Include**, select **Any device**.\n8. Under **Exclude**, select **Android**, **iOS**, **Windows**, **macOS**, and **Linux**.\n9. Under **Grant**, select **Block access**.\n10. Set **Enable policy** to **On** and click **Create**.",
|
||||
"Terraform": ""
|
||||
},
|
||||
"Recommendation": {
|
||||
"Text": "Create a Conditional Access policy that includes **all device platforms** and excludes the known ones (Android, iOS, Windows, macOS, Linux), then set the grant control to **Block access**. This ensures that only recognized platforms can authenticate, following a **zero-trust** approach to device management.",
|
||||
"Url": "https://hub.prowler.com/check/entra_conditional_access_policy_block_unknown_device_platforms"
|
||||
}
|
||||
},
|
||||
"Categories": [
|
||||
"trust-boundaries",
|
||||
"e3"
|
||||
],
|
||||
"DependsOn": [],
|
||||
"RelatedTo": [],
|
||||
"Notes": "This check corresponds to Maester test **MT.1015** (`Test-MtCaBlockUnknownOrUnsupportedDevicePlatform`). The recommended policy includes all platforms and excludes the five known platforms (Android, iOS, Windows, macOS, Linux) so that only unknown or unsupported platforms are blocked. The check requires the policy to be fully enabled; report-only policies are treated as non-compliant."
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
"""Check for Conditional Access policy blocking unknown or unsupported device platforms."""
|
||||
|
||||
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 (
|
||||
ConditionalAccessGrantControl,
|
||||
ConditionalAccessPolicy,
|
||||
ConditionalAccessPolicyState,
|
||||
)
|
||||
|
||||
|
||||
class entra_conditional_access_policy_block_unknown_device_platforms(Check):
|
||||
"""Ensure a Conditional Access policy blocks access from unknown or unsupported device platforms.
|
||||
|
||||
This check verifies that at least one enabled Conditional Access policy
|
||||
blocks access when the device platform is unknown or unsupported. The
|
||||
recommended configuration includes all device platforms and excludes the
|
||||
known platforms (Android, iOS, Windows, macOS, Linux), so only
|
||||
unrecognised platforms are blocked.
|
||||
|
||||
- PASS: An enabled policy blocks access from unknown or unsupported device platforms.
|
||||
- FAIL: No policy blocks access from unknown or unsupported device platforms.
|
||||
"""
|
||||
|
||||
KNOWN_PLATFORMS = {"android", "ios", "windows", "macos", "linux"}
|
||||
|
||||
@staticmethod
|
||||
def _normalize_platform(platform: object) -> str:
|
||||
"""Normalize a platform value to a lowercase string.
|
||||
|
||||
Args:
|
||||
platform: A platform value that may be a string or an enum.
|
||||
|
||||
Returns:
|
||||
The lowercase string representation of the platform.
|
||||
"""
|
||||
normalized = getattr(platform, "value", platform)
|
||||
return normalized.lower() if isinstance(normalized, str) else ""
|
||||
|
||||
def _is_candidate_policy(self, policy: ConditionalAccessPolicy) -> bool:
|
||||
"""Determine whether a policy is a candidate for blocking unknown device platforms.
|
||||
|
||||
A candidate policy must:
|
||||
- Not be disabled.
|
||||
- Have platform conditions configured.
|
||||
- Include all platforms.
|
||||
- Exclude all known platforms so only unknown ones are affected.
|
||||
- Use the block grant control.
|
||||
|
||||
Args:
|
||||
policy: The Conditional Access policy to evaluate.
|
||||
|
||||
Returns:
|
||||
True if the policy is a candidate, False otherwise.
|
||||
"""
|
||||
if policy.state == ConditionalAccessPolicyState.DISABLED:
|
||||
return False
|
||||
|
||||
if not policy.conditions.platform_conditions:
|
||||
return False
|
||||
|
||||
included_platforms = {
|
||||
p
|
||||
for p in map(
|
||||
self._normalize_platform,
|
||||
policy.conditions.platform_conditions.include_platforms,
|
||||
)
|
||||
if p
|
||||
}
|
||||
|
||||
if "all" not in included_platforms:
|
||||
return False
|
||||
|
||||
excluded_platforms = {
|
||||
p
|
||||
for p in map(
|
||||
self._normalize_platform,
|
||||
policy.conditions.platform_conditions.exclude_platforms,
|
||||
)
|
||||
if p
|
||||
}
|
||||
|
||||
if not self.KNOWN_PLATFORMS.issubset(excluded_platforms):
|
||||
return False
|
||||
|
||||
if (
|
||||
ConditionalAccessGrantControl.BLOCK
|
||||
not in policy.grant_controls.built_in_controls
|
||||
):
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def execute(self) -> list[CheckReportM365]:
|
||||
"""Execute the check logic.
|
||||
|
||||
Returns:
|
||||
A list of reports containing the result of the check.
|
||||
"""
|
||||
findings = []
|
||||
|
||||
report = CheckReportM365(
|
||||
metadata=self.metadata(),
|
||||
resource={},
|
||||
resource_name="Conditional Access Policies",
|
||||
resource_id="conditionalAccessPolicies",
|
||||
)
|
||||
report.status = "FAIL"
|
||||
report.status_extended = "No Conditional Access Policy blocks access from unknown or unsupported device platforms."
|
||||
|
||||
for policy in entra_client.conditional_access_policies.values():
|
||||
if not self._is_candidate_policy(policy):
|
||||
continue
|
||||
|
||||
report = CheckReportM365(
|
||||
metadata=self.metadata(),
|
||||
resource=policy,
|
||||
resource_name=policy.display_name,
|
||||
resource_id=policy.id,
|
||||
)
|
||||
|
||||
if policy.state == ConditionalAccessPolicyState.ENABLED_FOR_REPORTING:
|
||||
report.status = "FAIL"
|
||||
report.status_extended = (
|
||||
f"Conditional Access Policy '{policy.display_name}' reports "
|
||||
"blocking unknown or unsupported device platforms but does not enforce it."
|
||||
)
|
||||
else:
|
||||
report.status = "PASS"
|
||||
report.status_extended = (
|
||||
f"Conditional Access Policy '{policy.display_name}' blocks "
|
||||
"access from unknown or unsupported device platforms."
|
||||
)
|
||||
break
|
||||
|
||||
findings.append(report)
|
||||
return findings
|
||||
@@ -0,0 +1,631 @@
|
||||
from unittest import mock
|
||||
from uuid import uuid4
|
||||
|
||||
from prowler.providers.m365.services.entra.entra_service import (
|
||||
ApplicationsConditions,
|
||||
ConditionalAccessGrantControl,
|
||||
ConditionalAccessPolicyState,
|
||||
Conditions,
|
||||
GrantControlOperator,
|
||||
GrantControls,
|
||||
PersistentBrowser,
|
||||
PlatformConditions,
|
||||
SessionControls,
|
||||
SignInFrequency,
|
||||
SignInFrequencyInterval,
|
||||
UsersConditions,
|
||||
)
|
||||
from tests.providers.m365.m365_fixtures import DOMAIN, set_mocked_m365_provider
|
||||
|
||||
CHECK_MODULE_PATH = "prowler.providers.m365.services.entra.entra_conditional_access_policy_block_unknown_device_platforms.entra_conditional_access_policy_block_unknown_device_platforms"
|
||||
|
||||
KNOWN_PLATFORMS = ["android", "iOS", "windows", "macOS", "linux"]
|
||||
|
||||
|
||||
def _make_session_controls() -> SessionControls:
|
||||
"""Return a minimal SessionControls instance for testing."""
|
||||
return SessionControls(
|
||||
persistent_browser=PersistentBrowser(is_enabled=False, mode="always"),
|
||||
sign_in_frequency=SignInFrequency(
|
||||
is_enabled=False,
|
||||
frequency=None,
|
||||
type=None,
|
||||
interval=SignInFrequencyInterval.EVERY_TIME,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
class Test_entra_conditional_access_policy_block_unknown_device_platforms:
|
||||
"""Tests for the entra_conditional_access_policy_block_unknown_device_platforms check."""
|
||||
|
||||
def test_no_conditional_access_policies(self):
|
||||
"""Test FAIL when there are no Conditional Access policies."""
|
||||
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_block_unknown_device_platforms.entra_conditional_access_policy_block_unknown_device_platforms import (
|
||||
entra_conditional_access_policy_block_unknown_device_platforms,
|
||||
)
|
||||
|
||||
entra_client.conditional_access_policies = {}
|
||||
|
||||
check = entra_conditional_access_policy_block_unknown_device_platforms()
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== "No Conditional Access Policy blocks access from unknown or unsupported device platforms."
|
||||
)
|
||||
assert result[0].resource == {}
|
||||
assert result[0].resource_name == "Conditional Access Policies"
|
||||
assert result[0].resource_id == "conditionalAccessPolicies"
|
||||
assert result[0].location == "global"
|
||||
|
||||
def test_policy_disabled(self):
|
||||
"""Test FAIL when the only matching policy is disabled."""
|
||||
policy_id = str(uuid4())
|
||||
display_name = "Block Unknown Platforms"
|
||||
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_block_unknown_device_platforms.entra_conditional_access_policy_block_unknown_device_platforms import (
|
||||
entra_conditional_access_policy_block_unknown_device_platforms,
|
||||
)
|
||||
from prowler.providers.m365.services.entra.entra_service import (
|
||||
ConditionalAccessPolicy,
|
||||
)
|
||||
|
||||
entra_client.conditional_access_policies = {
|
||||
policy_id: ConditionalAccessPolicy(
|
||||
id=policy_id,
|
||||
display_name=display_name,
|
||||
conditions=Conditions(
|
||||
application_conditions=ApplicationsConditions(
|
||||
included_applications=["All"],
|
||||
excluded_applications=[],
|
||||
included_user_actions=[],
|
||||
),
|
||||
user_conditions=UsersConditions(
|
||||
included_groups=[],
|
||||
excluded_groups=[],
|
||||
included_users=["All"],
|
||||
excluded_users=[],
|
||||
included_roles=[],
|
||||
excluded_roles=[],
|
||||
),
|
||||
client_app_types=[],
|
||||
user_risk_levels=[],
|
||||
platform_conditions=PlatformConditions(
|
||||
include_platforms=["all"],
|
||||
exclude_platforms=KNOWN_PLATFORMS,
|
||||
),
|
||||
),
|
||||
grant_controls=GrantControls(
|
||||
built_in_controls=[ConditionalAccessGrantControl.BLOCK],
|
||||
operator=GrantControlOperator.OR,
|
||||
authentication_strength=None,
|
||||
),
|
||||
session_controls=_make_session_controls(),
|
||||
state=ConditionalAccessPolicyState.DISABLED,
|
||||
)
|
||||
}
|
||||
|
||||
check = entra_conditional_access_policy_block_unknown_device_platforms()
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== "No Conditional Access Policy blocks access from unknown or unsupported device platforms."
|
||||
)
|
||||
assert result[0].resource == {}
|
||||
assert result[0].resource_name == "Conditional Access Policies"
|
||||
assert result[0].resource_id == "conditionalAccessPolicies"
|
||||
assert result[0].location == "global"
|
||||
|
||||
def test_policy_enabled_for_reporting_only(self):
|
||||
"""Test FAIL when the matching policy is only in report-only mode."""
|
||||
policy_id = str(uuid4())
|
||||
display_name = "Block Unknown Platforms"
|
||||
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_block_unknown_device_platforms.entra_conditional_access_policy_block_unknown_device_platforms import (
|
||||
entra_conditional_access_policy_block_unknown_device_platforms,
|
||||
)
|
||||
from prowler.providers.m365.services.entra.entra_service import (
|
||||
ConditionalAccessPolicy,
|
||||
)
|
||||
|
||||
entra_client.conditional_access_policies = {
|
||||
policy_id: ConditionalAccessPolicy(
|
||||
id=policy_id,
|
||||
display_name=display_name,
|
||||
conditions=Conditions(
|
||||
application_conditions=ApplicationsConditions(
|
||||
included_applications=["All"],
|
||||
excluded_applications=[],
|
||||
included_user_actions=[],
|
||||
),
|
||||
user_conditions=UsersConditions(
|
||||
included_groups=[],
|
||||
excluded_groups=[],
|
||||
included_users=["All"],
|
||||
excluded_users=[],
|
||||
included_roles=[],
|
||||
excluded_roles=[],
|
||||
),
|
||||
client_app_types=[],
|
||||
user_risk_levels=[],
|
||||
platform_conditions=PlatformConditions(
|
||||
include_platforms=["all"],
|
||||
exclude_platforms=KNOWN_PLATFORMS,
|
||||
),
|
||||
),
|
||||
grant_controls=GrantControls(
|
||||
built_in_controls=[ConditionalAccessGrantControl.BLOCK],
|
||||
operator=GrantControlOperator.OR,
|
||||
authentication_strength=None,
|
||||
),
|
||||
session_controls=_make_session_controls(),
|
||||
state=ConditionalAccessPolicyState.ENABLED_FOR_REPORTING,
|
||||
)
|
||||
}
|
||||
|
||||
check = entra_conditional_access_policy_block_unknown_device_platforms()
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== "Conditional Access Policy 'Block Unknown Platforms' reports blocking unknown or unsupported device platforms but does not enforce it."
|
||||
)
|
||||
assert result[0].resource_name == "Block Unknown Platforms"
|
||||
assert result[0].resource_id == policy_id
|
||||
assert result[0].location == "global"
|
||||
|
||||
def test_policy_no_platform_conditions(self):
|
||||
"""Test FAIL when the policy has no platform conditions configured."""
|
||||
policy_id = str(uuid4())
|
||||
display_name = "Block Unknown Platforms"
|
||||
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_block_unknown_device_platforms.entra_conditional_access_policy_block_unknown_device_platforms import (
|
||||
entra_conditional_access_policy_block_unknown_device_platforms,
|
||||
)
|
||||
from prowler.providers.m365.services.entra.entra_service import (
|
||||
ConditionalAccessPolicy,
|
||||
)
|
||||
|
||||
entra_client.conditional_access_policies = {
|
||||
policy_id: ConditionalAccessPolicy(
|
||||
id=policy_id,
|
||||
display_name=display_name,
|
||||
conditions=Conditions(
|
||||
application_conditions=ApplicationsConditions(
|
||||
included_applications=["All"],
|
||||
excluded_applications=[],
|
||||
included_user_actions=[],
|
||||
),
|
||||
user_conditions=UsersConditions(
|
||||
included_groups=[],
|
||||
excluded_groups=[],
|
||||
included_users=["All"],
|
||||
excluded_users=[],
|
||||
included_roles=[],
|
||||
excluded_roles=[],
|
||||
),
|
||||
client_app_types=[],
|
||||
user_risk_levels=[],
|
||||
platform_conditions=None,
|
||||
),
|
||||
grant_controls=GrantControls(
|
||||
built_in_controls=[ConditionalAccessGrantControl.BLOCK],
|
||||
operator=GrantControlOperator.OR,
|
||||
authentication_strength=None,
|
||||
),
|
||||
session_controls=_make_session_controls(),
|
||||
state=ConditionalAccessPolicyState.ENABLED,
|
||||
)
|
||||
}
|
||||
|
||||
check = entra_conditional_access_policy_block_unknown_device_platforms()
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== "No Conditional Access Policy blocks access from unknown or unsupported device platforms."
|
||||
)
|
||||
|
||||
def test_policy_does_not_include_all_platforms(self):
|
||||
"""Test FAIL when the policy includes specific platforms instead of all."""
|
||||
policy_id = str(uuid4())
|
||||
display_name = "Block Specific Platforms"
|
||||
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_block_unknown_device_platforms.entra_conditional_access_policy_block_unknown_device_platforms import (
|
||||
entra_conditional_access_policy_block_unknown_device_platforms,
|
||||
)
|
||||
from prowler.providers.m365.services.entra.entra_service import (
|
||||
ConditionalAccessPolicy,
|
||||
)
|
||||
|
||||
entra_client.conditional_access_policies = {
|
||||
policy_id: ConditionalAccessPolicy(
|
||||
id=policy_id,
|
||||
display_name=display_name,
|
||||
conditions=Conditions(
|
||||
application_conditions=ApplicationsConditions(
|
||||
included_applications=["All"],
|
||||
excluded_applications=[],
|
||||
included_user_actions=[],
|
||||
),
|
||||
user_conditions=UsersConditions(
|
||||
included_groups=[],
|
||||
excluded_groups=[],
|
||||
included_users=["All"],
|
||||
excluded_users=[],
|
||||
included_roles=[],
|
||||
excluded_roles=[],
|
||||
),
|
||||
client_app_types=[],
|
||||
user_risk_levels=[],
|
||||
platform_conditions=PlatformConditions(
|
||||
include_platforms=["android", "iOS"],
|
||||
exclude_platforms=[],
|
||||
),
|
||||
),
|
||||
grant_controls=GrantControls(
|
||||
built_in_controls=[ConditionalAccessGrantControl.BLOCK],
|
||||
operator=GrantControlOperator.OR,
|
||||
authentication_strength=None,
|
||||
),
|
||||
session_controls=_make_session_controls(),
|
||||
state=ConditionalAccessPolicyState.ENABLED,
|
||||
)
|
||||
}
|
||||
|
||||
check = entra_conditional_access_policy_block_unknown_device_platforms()
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
|
||||
def test_policy_missing_excluded_known_platforms(self):
|
||||
"""Test FAIL when the policy includes all platforms but does not exclude all known ones."""
|
||||
policy_id = str(uuid4())
|
||||
display_name = "Incomplete Platform Exclusion"
|
||||
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_block_unknown_device_platforms.entra_conditional_access_policy_block_unknown_device_platforms import (
|
||||
entra_conditional_access_policy_block_unknown_device_platforms,
|
||||
)
|
||||
from prowler.providers.m365.services.entra.entra_service import (
|
||||
ConditionalAccessPolicy,
|
||||
)
|
||||
|
||||
# Only exclude 3 of 5 known platforms
|
||||
entra_client.conditional_access_policies = {
|
||||
policy_id: ConditionalAccessPolicy(
|
||||
id=policy_id,
|
||||
display_name=display_name,
|
||||
conditions=Conditions(
|
||||
application_conditions=ApplicationsConditions(
|
||||
included_applications=["All"],
|
||||
excluded_applications=[],
|
||||
included_user_actions=[],
|
||||
),
|
||||
user_conditions=UsersConditions(
|
||||
included_groups=[],
|
||||
excluded_groups=[],
|
||||
included_users=["All"],
|
||||
excluded_users=[],
|
||||
included_roles=[],
|
||||
excluded_roles=[],
|
||||
),
|
||||
client_app_types=[],
|
||||
user_risk_levels=[],
|
||||
platform_conditions=PlatformConditions(
|
||||
include_platforms=["all"],
|
||||
exclude_platforms=["android", "iOS", "windows"],
|
||||
),
|
||||
),
|
||||
grant_controls=GrantControls(
|
||||
built_in_controls=[ConditionalAccessGrantControl.BLOCK],
|
||||
operator=GrantControlOperator.OR,
|
||||
authentication_strength=None,
|
||||
),
|
||||
session_controls=_make_session_controls(),
|
||||
state=ConditionalAccessPolicyState.ENABLED,
|
||||
)
|
||||
}
|
||||
|
||||
check = entra_conditional_access_policy_block_unknown_device_platforms()
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
|
||||
def test_policy_no_block_grant_control(self):
|
||||
"""Test FAIL when the policy has correct platform conditions but does not block."""
|
||||
policy_id = str(uuid4())
|
||||
display_name = "MFA Unknown Platforms"
|
||||
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_block_unknown_device_platforms.entra_conditional_access_policy_block_unknown_device_platforms import (
|
||||
entra_conditional_access_policy_block_unknown_device_platforms,
|
||||
)
|
||||
from prowler.providers.m365.services.entra.entra_service import (
|
||||
ConditionalAccessPolicy,
|
||||
)
|
||||
|
||||
entra_client.conditional_access_policies = {
|
||||
policy_id: ConditionalAccessPolicy(
|
||||
id=policy_id,
|
||||
display_name=display_name,
|
||||
conditions=Conditions(
|
||||
application_conditions=ApplicationsConditions(
|
||||
included_applications=["All"],
|
||||
excluded_applications=[],
|
||||
included_user_actions=[],
|
||||
),
|
||||
user_conditions=UsersConditions(
|
||||
included_groups=[],
|
||||
excluded_groups=[],
|
||||
included_users=["All"],
|
||||
excluded_users=[],
|
||||
included_roles=[],
|
||||
excluded_roles=[],
|
||||
),
|
||||
client_app_types=[],
|
||||
user_risk_levels=[],
|
||||
platform_conditions=PlatformConditions(
|
||||
include_platforms=["all"],
|
||||
exclude_platforms=KNOWN_PLATFORMS,
|
||||
),
|
||||
),
|
||||
grant_controls=GrantControls(
|
||||
built_in_controls=[ConditionalAccessGrantControl.MFA],
|
||||
operator=GrantControlOperator.OR,
|
||||
authentication_strength=None,
|
||||
),
|
||||
session_controls=_make_session_controls(),
|
||||
state=ConditionalAccessPolicyState.ENABLED,
|
||||
)
|
||||
}
|
||||
|
||||
check = entra_conditional_access_policy_block_unknown_device_platforms()
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
|
||||
def test_policy_enabled_and_compliant(self):
|
||||
"""Test PASS when an enabled policy blocks unknown device platforms correctly."""
|
||||
policy_id = str(uuid4())
|
||||
display_name = "Block Unknown Platforms"
|
||||
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_block_unknown_device_platforms.entra_conditional_access_policy_block_unknown_device_platforms import (
|
||||
entra_conditional_access_policy_block_unknown_device_platforms,
|
||||
)
|
||||
from prowler.providers.m365.services.entra.entra_service import (
|
||||
ConditionalAccessPolicy,
|
||||
)
|
||||
|
||||
entra_client.conditional_access_policies = {
|
||||
policy_id: ConditionalAccessPolicy(
|
||||
id=policy_id,
|
||||
display_name=display_name,
|
||||
conditions=Conditions(
|
||||
application_conditions=ApplicationsConditions(
|
||||
included_applications=["All"],
|
||||
excluded_applications=[],
|
||||
included_user_actions=[],
|
||||
),
|
||||
user_conditions=UsersConditions(
|
||||
included_groups=[],
|
||||
excluded_groups=[],
|
||||
included_users=["All"],
|
||||
excluded_users=[],
|
||||
included_roles=[],
|
||||
excluded_roles=[],
|
||||
),
|
||||
client_app_types=[],
|
||||
user_risk_levels=[],
|
||||
platform_conditions=PlatformConditions(
|
||||
include_platforms=["all"],
|
||||
exclude_platforms=KNOWN_PLATFORMS,
|
||||
),
|
||||
),
|
||||
grant_controls=GrantControls(
|
||||
built_in_controls=[ConditionalAccessGrantControl.BLOCK],
|
||||
operator=GrantControlOperator.OR,
|
||||
authentication_strength=None,
|
||||
),
|
||||
session_controls=_make_session_controls(),
|
||||
state=ConditionalAccessPolicyState.ENABLED,
|
||||
)
|
||||
}
|
||||
|
||||
check = entra_conditional_access_policy_block_unknown_device_platforms()
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== "Conditional Access Policy 'Block Unknown Platforms' blocks access from unknown or unsupported device platforms."
|
||||
)
|
||||
assert result[0].resource_name == "Block Unknown Platforms"
|
||||
assert result[0].resource_id == policy_id
|
||||
assert result[0].location == "global"
|
||||
|
||||
def test_mixed_policies_report_only_and_enabled(self):
|
||||
"""Test PASS when both report-only and enabled compliant policies exist."""
|
||||
report_policy_id = str(uuid4())
|
||||
enabled_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_block_unknown_device_platforms.entra_conditional_access_policy_block_unknown_device_platforms import (
|
||||
entra_conditional_access_policy_block_unknown_device_platforms,
|
||||
)
|
||||
from prowler.providers.m365.services.entra.entra_service import (
|
||||
ConditionalAccessPolicy,
|
||||
)
|
||||
|
||||
base_conditions = Conditions(
|
||||
application_conditions=ApplicationsConditions(
|
||||
included_applications=["All"],
|
||||
excluded_applications=[],
|
||||
included_user_actions=[],
|
||||
),
|
||||
user_conditions=UsersConditions(
|
||||
included_groups=[],
|
||||
excluded_groups=[],
|
||||
included_users=["All"],
|
||||
excluded_users=[],
|
||||
included_roles=[],
|
||||
excluded_roles=[],
|
||||
),
|
||||
client_app_types=[],
|
||||
user_risk_levels=[],
|
||||
platform_conditions=PlatformConditions(
|
||||
include_platforms=["all"],
|
||||
exclude_platforms=KNOWN_PLATFORMS,
|
||||
),
|
||||
)
|
||||
grant_controls = GrantControls(
|
||||
built_in_controls=[ConditionalAccessGrantControl.BLOCK],
|
||||
operator=GrantControlOperator.OR,
|
||||
authentication_strength=None,
|
||||
)
|
||||
|
||||
entra_client.conditional_access_policies = {
|
||||
report_policy_id: ConditionalAccessPolicy(
|
||||
id=report_policy_id,
|
||||
display_name="Report Only Policy",
|
||||
conditions=base_conditions,
|
||||
grant_controls=grant_controls,
|
||||
session_controls=_make_session_controls(),
|
||||
state=ConditionalAccessPolicyState.ENABLED_FOR_REPORTING,
|
||||
),
|
||||
enabled_policy_id: ConditionalAccessPolicy(
|
||||
id=enabled_policy_id,
|
||||
display_name="Enforced Block Policy",
|
||||
conditions=base_conditions,
|
||||
grant_controls=grant_controls,
|
||||
session_controls=_make_session_controls(),
|
||||
state=ConditionalAccessPolicyState.ENABLED,
|
||||
),
|
||||
}
|
||||
|
||||
check = entra_conditional_access_policy_block_unknown_device_platforms()
|
||||
result = check.execute()
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
assert result[0].resource_name == "Enforced Block Policy"
|
||||
assert result[0].resource_id == enabled_policy_id
|
||||
Reference in New Issue
Block a user