Compare commits

...

9 Commits

Author SHA1 Message Date
Hugo P.Brito
1c028d0795 Revert "fix(ci): ignore generated lockfiles in changelog gate"
This reverts commit 8deb72ffa8.
2026-04-09 09:23:40 +01:00
Hugo P.Brito
8deb72ffa8 fix(ci): ignore generated lockfiles in changelog gate
- Keep api/poetry.lock and mcp_server/uv.lock from forcing changelog updates\n- Preserve the existing changelog requirement for real user-facing changes
2026-04-09 09:13:32 +01:00
Hugo P.Brito
b2f34d2df0 docs(sdk): move unknown_device_blocked changelog entry to 5.23.0 2026-04-08 13:25:28 +01:00
Hugo P.Brito
db6828ec28 Merge remote-tracking branch 'origin/master' into HEAD 2026-04-08 13:25:22 +01:00
Hugo P.Brito
505ff94166 fix(m365): correct metadata for unknown device blocked check
- Set ResourceType to NotDefined (no individual resource assessed)
- Replace broken AdditionalURLs with canonical Microsoft Learn links
- Clear RelatedTo (referenced check does not exist)
2026-04-08 13:25:22 +01:00
Hugo P.Brito
85195096be fix(m365): move KNOWN_PLATFORMS to entra_service and normalize case
Move the constant from the check module to the service module so it
can be reused, and switch to a lowercase frozenset for
case-insensitive comparison.
2026-04-08 13:25:17 +01:00
Hugo P.Brito
a491a50afd Merge remote-tracking branch 'origin/master' into HEAD
# Conflicts:
#	prowler/CHANGELOG.md
#	prowler/compliance/m365/iso27001_2022_m365.json
#	prowler/providers/m365/services/entra/entra_service.py
2026-04-08 11:50:37 +01:00
Hugo P.Brito
6451740b27 fix(sdk): preserve entra platform condition compatibility
- Keep platform condition aliases compatible with existing checks
- Normalize platform values before evaluating unknown-device blocks
- Update tests to cover mixed casing and alias-based input
2026-04-07 15:28:49 +01:00
HugoPBrito
11ff512607 feat(m365): add entra_conditional_access_policy_unknown_device_blocked security check
Add new security check entra_conditional_access_policy_unknown_device_blocked for m365 provider.
Includes check implementation, metadata, and unit tests.
2026-03-03 13:28:35 +01:00
8 changed files with 637 additions and 28 deletions

View File

@@ -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_unknown_device_blocked` check for M365 provider [(#10235)](https://github.com/prowler-cloud/prowler/pull/10235)
- `Vercel` provider support with 30 checks [(#10189)](https://github.com/prowler-cloud/prowler/pull/10189)
### 🔄 Changed

View File

@@ -247,6 +247,7 @@
"entra_break_glass_account_fido2_security_key_registered",
"entra_default_app_management_policy_enabled",
"entra_all_apps_conditional_access_coverage",
"entra_conditional_access_policy_unknown_device_blocked",
"entra_conditional_access_policy_device_registration_mfa_required",
"entra_intune_enrollment_sign_in_frequency_every_time",
"entra_conditional_access_policy_device_code_flow_blocked",
@@ -626,6 +627,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_unknown_device_blocked",
"entra_conditional_access_policy_device_registration_mfa_required",
"entra_intune_enrollment_sign_in_frequency_every_time",
"entra_managed_device_required_for_authentication",
@@ -684,6 +686,7 @@
"entra_admin_portals_access_restriction",
"entra_conditional_access_policy_approved_client_app_required_for_mobile",
"entra_conditional_access_policy_app_enforced_restrictions",
"entra_conditional_access_policy_unknown_device_blocked",
"entra_conditional_access_policy_block_elevated_insider_risk",
"entra_conditional_access_policy_block_o365_elevated_insider_risk",
"entra_policy_guest_users_access_restrictions",
@@ -706,6 +709,7 @@
"entra_admin_users_mfa_enabled",
"entra_admin_users_sign_in_frequency_enabled",
"entra_all_apps_conditional_access_coverage",
"entra_conditional_access_policy_unknown_device_blocked",
"entra_conditional_access_policy_device_registration_mfa_required",
"entra_intune_enrollment_sign_in_frequency_every_time",
"entra_break_glass_account_fido2_security_key_registered",

View File

@@ -101,6 +101,7 @@
"Id": "1.1.6",
"Description": "Ensure a managed device is required for authentication",
"Checks": [
"entra_conditional_access_policy_unknown_device_blocked",
"entra_managed_device_required_for_authentication"
],
"Attributes": [
@@ -119,6 +120,7 @@
"Id": "1.1.7",
"Description": "Ensure a managed device is required for MFA registration",
"Checks": [
"entra_conditional_access_policy_unknown_device_blocked",
"entra_managed_device_required_for_mfa_registration"
],
"Attributes": [

View File

@@ -0,0 +1,37 @@
{
"Provider": "m365",
"CheckID": "entra_conditional_access_policy_unknown_device_blocked",
"CheckTitle": "Conditional Access policy blocks access from unknown or unsupported device platforms",
"CheckType": [],
"ServiceName": "entra",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "medium",
"ResourceType": "NotDefined",
"ResourceGroup": "IAM",
"Description": "Conditional Access policy that includes **all device platforms** and excludes the five known platforms (`android`, `iOS`, `windows`, `macOS`, `linux`) with a **block** grant control prevents sign-ins from unrecognized or unsupported devices.",
"Risk": "Without blocking unknown device platforms, attackers can sign in from **unmanaged or spoofed devices** that bypass compliance and security controls.\n\nThis increases the risk of **unauthorized access** and makes it harder to enforce device-based security policies.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://learn.microsoft.com/en-us/entra/identity/conditional-access/concept-conditional-access-conditions",
"https://learn.microsoft.com/en-us/entra/identity/conditional-access/policy-all-users-device-unknown-unsupported"
],
"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. Click **New policy**.\n4. Under **Users**, select **All users**.\n5. Under **Target resources**, select **All cloud apps**.\n6. Under **Conditions** > **Device platforms**, set **Include** to **Any device** and **Exclude** the five known platforms: Android, iOS, Windows, macOS, Linux.\n7. Under **Grant**, select **Block access**.\n8. Set the policy to **On** and click **Create**.",
"Terraform": ""
},
"Recommendation": {
"Text": "Create a Conditional Access policy that blocks access from unknown device platforms. Include all platforms and exclude the five known ones (`android`, `iOS`, `windows`, `macOS`, `linux`). Pair this with **device compliance** policies for defense in depth, since platform detection relies on user agent strings.",
"Url": "https://hub.prowler.com/check/entra_conditional_access_policy_unknown_device_blocked"
}
},
"Categories": [
"identity-access"
],
"DependsOn": [],
"RelatedTo": [],
"Notes": "Device platform detection relies on user agent strings, which can be spoofed. This policy should be paired with device compliance policies for stronger security."
}

View File

@@ -0,0 +1,99 @@
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 (
KNOWN_PLATFORMS,
ConditionalAccessGrantControl,
ConditionalAccessPolicyState,
)
class entra_conditional_access_policy_unknown_device_blocked(Check):
"""Check if at least one Conditional Access policy blocks unknown device platforms.
This check verifies that at least one enabled Conditional Access policy
blocks access from unknown or unsupported device platforms by including
all platforms and excluding the five known ones (android, iOS, windows,
macOS, linux) with a block grant control.
- PASS: An enabled CA policy blocks access from unknown/unsupported device platforms.
- 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.
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 policy.state == ConditionalAccessPolicyState.DISABLED:
continue
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
}
if "all" not in included_platforms:
continue
if not {
self._normalize_platform(platform) for platform in KNOWN_PLATFORMS
}.issubset(excluded_platforms):
continue
if (
ConditionalAccessGrantControl.BLOCK
not in policy.grant_controls.built_in_controls
):
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}' is configured to block unknown device platforms but is only in report-only mode."
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

View File

@@ -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
from pydantic.v1 import BaseModel, Field
from prowler.lib.logger import logger
from prowler.providers.m365.lib.service.service import M365Service
@@ -273,6 +273,28 @@ class Entra(M365Service):
[],
)
],
platform_conditions=(
PlatformConditions(
included_platforms=[
platform
for platform in getattr(
policy.conditions.platforms,
"include_platforms",
[],
)
],
excluded_platforms=[
platform
for platform in getattr(
policy.conditions.platforms,
"exclude_platforms",
[],
)
],
)
if getattr(policy.conditions, "platforms", None)
else None
),
user_risk_levels=[
RiskLevel(risk_level)
for risk_level in getattr(
@@ -304,30 +326,6 @@ class Entra(M365Service):
and raw_insider_risk
else None
),
platform_conditions=PlatformConditions(
include_platforms=[
platform
for platform in (
getattr(
getattr(policy.conditions, "platforms", None),
"include_platforms",
[],
)
or []
)
],
exclude_platforms=[
platform
for platform in (
getattr(
getattr(policy.conditions, "platforms", None),
"exclude_platforms",
[],
)
or []
)
],
),
authentication_flows=self._parse_authentication_flows(
raw_auth_flows_map.get(policy.id)
),
@@ -995,8 +993,18 @@ class InsiderRiskLevel(Enum):
class PlatformConditions(BaseModel):
"""Model representing platform conditions for Conditional Access policies."""
include_platforms: List[str] = []
exclude_platforms: List[str] = []
include_platforms: List[str] = Field(
default_factory=list, alias="included_platforms"
)
exclude_platforms: List[str] = Field(
default_factory=list, alias="excluded_platforms"
)
class Config:
allow_population_by_field_name = True
KNOWN_PLATFORMS = frozenset({"android", "ios", "windows", "macos", "linux"})
class TransferMethod(Enum):
@@ -1016,10 +1024,10 @@ class Conditions(BaseModel):
application_conditions: Optional[ApplicationsConditions]
user_conditions: Optional[UsersConditions]
client_app_types: Optional[List[ClientAppType]]
platform_conditions: Optional[PlatformConditions] = None
user_risk_levels: List[RiskLevel] = []
sign_in_risk_levels: List[RiskLevel] = []
insider_risk_levels: Optional[InsiderRiskLevel] = None
platform_conditions: Optional[PlatformConditions] = None
authentication_flows: Optional[AuthenticationFlows] = None

View File

@@ -0,0 +1,458 @@
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_unknown_device_blocked.entra_conditional_access_policy_unknown_device_blocked"
KNOWN_PLATFORMS = ["android", "ios", "windows", "macos", "linux"]
def _default_session_controls():
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,
),
)
def _default_conditions(**overrides):
defaults = dict(
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=[],
platform_conditions=PlatformConditions(
included_platforms=["All"],
excluded_platforms=KNOWN_PLATFORMS,
),
user_risk_levels=[],
)
defaults.update(overrides)
return Conditions(**defaults)
def _default_grant_controls(**overrides):
defaults = dict(
built_in_controls=[ConditionalAccessGrantControl.BLOCK],
operator=GrantControlOperator.OR,
authentication_strength=None,
)
defaults.update(overrides)
return GrantControls(**defaults)
class Test_entra_conditional_access_policy_unknown_device_blocked:
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_unknown_device_blocked.entra_conditional_access_policy_unknown_device_blocked import (
entra_conditional_access_policy_unknown_device_blocked,
)
entra_client.conditional_access_policies = {}
check = entra_conditional_access_policy_unknown_device_blocked()
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 Devices"
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_unknown_device_blocked.entra_conditional_access_policy_unknown_device_blocked import (
entra_conditional_access_policy_unknown_device_blocked,
)
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=_default_conditions(),
grant_controls=_default_grant_controls(),
session_controls=_default_session_controls(),
state=ConditionalAccessPolicyState.DISABLED,
)
}
check = entra_conditional_access_policy_unknown_device_blocked()
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 Devices"
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_unknown_device_blocked.entra_conditional_access_policy_unknown_device_blocked import (
entra_conditional_access_policy_unknown_device_blocked,
)
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=_default_conditions(),
grant_controls=_default_grant_controls(),
session_controls=_default_session_controls(),
state=ConditionalAccessPolicyState.ENABLED_FOR_REPORTING,
)
}
check = entra_conditional_access_policy_unknown_device_blocked()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== f"Conditional Access Policy '{display_name}' is configured to block unknown device platforms but is only in report-only mode."
)
assert (
result[0].resource
== entra_client.conditional_access_policies[policy_id].dict()
)
assert result[0].resource_name == display_name
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."""
policy_id = str(uuid4())
display_name = "Block Without 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_unknown_device_blocked.entra_conditional_access_policy_unknown_device_blocked import (
entra_conditional_access_policy_unknown_device_blocked,
)
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=_default_conditions(platform_conditions=None),
grant_controls=_default_grant_controls(),
session_controls=_default_session_controls(),
state=ConditionalAccessPolicyState.ENABLED,
)
}
check = entra_conditional_access_policy_unknown_device_blocked()
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"
def test_policy_does_not_include_all_platforms(self):
"""Test FAIL when the policy does not include all platforms."""
policy_id = str(uuid4())
display_name = "Partial Platform Block"
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_unknown_device_blocked.entra_conditional_access_policy_unknown_device_blocked import (
entra_conditional_access_policy_unknown_device_blocked,
)
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=_default_conditions(
platform_conditions=PlatformConditions(
included_platforms=["android", "ios"],
excluded_platforms=[],
)
),
grant_controls=_default_grant_controls(),
session_controls=_default_session_controls(),
state=ConditionalAccessPolicyState.ENABLED,
)
}
check = entra_conditional_access_policy_unknown_device_blocked()
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 == {}
def test_policy_missing_excluded_platforms(self):
"""Test FAIL when the policy does not exclude all five known platforms."""
policy_id = str(uuid4())
display_name = "Incomplete Exclusions"
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_unknown_device_blocked.entra_conditional_access_policy_unknown_device_blocked import (
entra_conditional_access_policy_unknown_device_blocked,
)
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=_default_conditions(
platform_conditions=PlatformConditions(
included_platforms=["All"],
excluded_platforms=["android", "ios", "windows"],
)
),
grant_controls=_default_grant_controls(),
session_controls=_default_session_controls(),
state=ConditionalAccessPolicyState.ENABLED,
)
}
check = entra_conditional_access_policy_unknown_device_blocked()
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 == {}
def test_policy_no_block_grant_control(self):
"""Test FAIL when the policy does not use block as grant control."""
policy_id = str(uuid4())
display_name = "MFA Instead of Block"
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_unknown_device_blocked.entra_conditional_access_policy_unknown_device_blocked import (
entra_conditional_access_policy_unknown_device_blocked,
)
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=_default_conditions(),
grant_controls=_default_grant_controls(
built_in_controls=[ConditionalAccessGrantControl.MFA]
),
session_controls=_default_session_controls(),
state=ConditionalAccessPolicyState.ENABLED,
)
}
check = entra_conditional_access_policy_unknown_device_blocked()
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 == {}
def test_policy_enabled_and_compliant(self):
"""Test PASS when an enabled policy blocks unknown device platforms."""
policy_id = str(uuid4())
display_name = "Block Unknown Devices"
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_unknown_device_blocked.entra_conditional_access_policy_unknown_device_blocked import (
entra_conditional_access_policy_unknown_device_blocked,
)
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=_default_conditions(),
grant_controls=_default_grant_controls(),
session_controls=_default_session_controls(),
state=ConditionalAccessPolicyState.ENABLED,
)
}
check = entra_conditional_access_policy_unknown_device_blocked()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert (
result[0].status_extended
== f"Conditional Access Policy '{display_name}' blocks access from unknown or unsupported device platforms."
)
assert (
result[0].resource
== entra_client.conditional_access_policies[policy_id].dict()
)
assert result[0].resource_name == display_name
assert result[0].resource_id == policy_id
assert result[0].location == "global"