feat(m365): add entra_conditional_access_policy_enforce_sign_in_frequency security check

Move check from Azure provider to M365 provider and add device filter support
to the M365 entra service. The check validates that at least one Conditional
Access policy enforces sign-in frequency for non-corporate devices.

Changes:
- Add DeviceFilter and DeviceFilterMode models to M365 entra_service
- Add device_filter to Conditions model in M365 entra_service
- Update _get_conditional_access_policies to fetch device filter data
- Create check in M365 provider with proper M365 patterns
- Remove Azure check (wrong provider)
- Update CHANGELOG to reflect M365 provider
This commit is contained in:
Andoni A. committed 2026-01-29 14:00:34 +01:00
1 parent 2ceda9bac1
commit f389ee3d1d
8 files changed
+1172 -1075

No files matched your search

+1 -1
View File
@@ -13,7 +13,7 @@ All notable changes to the **Prowler SDK** are documented in this file.
- CloudTrail Timeline abstraction for querying resource modification history [(#9101)](https://github.com/prowler-cloud/prowler/pull/9101)
- Cloudflare `--account-id` filter argument [(#9894)](https://github.com/prowler-cloud/prowler/pull/9894)
- `rds_instance_extended_support` check for AWS provider [(#9865)](https://github.com/prowler-cloud/prowler/pull/9865)
- `entra_conditional_access_policy_enforce_sign_in_frequency` check for Azure provider [(#9915)](https://github.com/prowler-cloud/prowler/pull/9915)
- `entra_conditional_access_policy_enforce_sign_in_frequency` check for M365 provider [(#9915)](https://github.com/prowler-cloud/prowler/pull/9915)
### Changed
@@ -1,133 +0,0 @@
from prowler.lib.check.models import Check, Check_Report_Azure
from prowler.providers.azure.services.entra.entra_client import entra_client
class entra_conditional_access_policy_enforce_sign_in_frequency(Check):
"""
Ensure at least one Conditional Access policy enforces sign-in frequency for non-corporate devices.
This check evaluates whether the tenant has a Conditional Access policy that:
- Is enabled
- Targets all users and all applications
- Has sign-in frequency enabled with time-based interval
- Targets non-corporate devices via device filter
- PASS: A policy meeting all criteria exists.
- FAIL: No policy enforces sign-in frequency for non-corporate devices.
"""
def execute(self) -> list[Check_Report_Azure]:
"""Execute the check logic.
Returns:
A list of reports containing the result of the check.
"""
findings = []
for (
tenant_name,
conditional_access_policies,
) in entra_client.conditional_access_policy.items():
compliant_policy = None
for policy in conditional_access_policies.values():
# Check if policy is enabled
if policy.state != "enabled":
continue
# Check if sign-in frequency is enabled with time-based interval
if not policy.sign_in_frequency:
continue
if not policy.sign_in_frequency.is_enabled:
continue
if (
policy.sign_in_frequency.frequency_interval
and "timebased"
not in policy.sign_in_frequency.frequency_interval.lower()
):
continue
# Check if targets all users
if "All" not in policy.users.get("include", []):
continue
# Check if targets all applications
if "All" not in policy.target_resources.get("include", []):
continue
# Check if device filter targets non-corporate devices
if not self._targets_non_corporate_devices(policy.device_filter):
continue
# Policy meets all criteria
compliant_policy = policy
break
if compliant_policy:
report = Check_Report_Azure(
metadata=self.metadata(), resource=compliant_policy
)
report.subscription = f"Tenant: {tenant_name}"
report.status = "PASS"
report.status_extended = f"Conditional Access Policy '{compliant_policy.name}' enforces sign-in frequency for non-corporate devices."
else:
report = Check_Report_Azure(
metadata=self.metadata(),
resource=conditional_access_policies,
)
report.subscription = f"Tenant: {tenant_name}"
report.resource_name = "Conditional Access Policy"
report.resource_id = "Conditional Access Policy"
report.status = "FAIL"
report.status_extended = "No Conditional Access Policy enforces sign-in frequency for non-corporate devices."
findings.append(report)
return findings
def _targets_non_corporate_devices(self, device_filter) -> bool:
"""Check if the device filter targets non-corporate devices.
Non-corporate devices are identified by targeting devices that are:
- Not Hybrid Azure AD joined (device.trustType != "ServerAD")
- Not compliant (device.isCompliant != True)
Args:
device_filter: The device filter from the conditional access policy.
Returns:
True if the filter targets non-corporate devices, False otherwise.
"""
if not device_filter or not device_filter.rule:
return False
rule = device_filter.rule.lower()
mode = device_filter.mode.lower() if device_filter.mode else ""
# Include mode: should target non-corporate devices directly
# Rule pattern: device.trustType -ne "ServerAD" or device.isCompliant -ne True
if mode == "filtermode.include" or mode == "include":
# Check for targeting non-compliant OR non-hybrid-joined devices
targets_non_compliant = (
"device.iscompliant" in rule and "-ne" in rule and "true" in rule
)
targets_non_hybrid = (
"device.trusttype" in rule and "-ne" in rule and "serverad" in rule
)
if targets_non_compliant or targets_non_hybrid:
return True
# Exclude mode: should exclude corporate devices (equivalent to targeting non-corporate)
# Rule pattern: device.trustType -eq "ServerAD" and device.isCompliant -eq True
elif mode == "filtermode.exclude" or mode == "exclude":
# Check for excluding compliant AND hybrid-joined devices
excludes_compliant = (
"device.iscompliant" in rule and "-eq" in rule and "true" in rule
)
excludes_hybrid = (
"device.trusttype" in rule and "-eq" in rule and "serverad" in rule
)
if excludes_compliant or excludes_hybrid:
return True
return False
@@ -1,19 +1,18 @@
{
"Provider": "azure",
"Provider": "m365",
"CheckID": "entra_conditional_access_policy_enforce_sign_in_frequency",
"CheckTitle": "Conditional Access policy enforces sign-in frequency for non-corporate devices",
"CheckTitle": "Ensure that Conditional Access policy enforces sign-in frequency for non-corporate devices",
"CheckType": [],
"ServiceName": "entra",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "medium",
"ResourceType": "#microsoft.graph.conditionalAccess",
"ResourceType": "Conditional Access Policy",
"ResourceGroup": "IAM",
"Description": "This check verifies that at least one **Conditional Access policy** enforces sign-in frequency controls for non-corporate devices.\n\nSign-in frequency controls require users to reauthenticate at regular intervals when accessing resources from unmanaged devices, limiting the risk of persistent sessions on untrusted endpoints.",
"Description": "This check verifies that at least one Conditional Access policy enforces sign-in frequency controls for non-corporate devices. Sign-in frequency controls require users to reauthenticate at regular intervals when accessing resources from unmanaged devices, limiting the risk of persistent sessions on untrusted endpoints.",
"Risk": "Without sign-in frequency controls for non-corporate devices:\n\n- **Session hijacking** - Long-lived sessions on unmanaged devices increase exposure to credential theft\n- **Unauthorized access** - Shared or compromised devices may retain access indefinitely\n- **Data leakage** - Sensitive resources remain accessible without periodic verification",
"RelatedUrl": "",
"RelatedUrl": "https://learn.microsoft.com/en-us/entra/identity/conditional-access/concept-session-lifetime",
"AdditionalURLs": [
"https://learn.microsoft.com/en-us/entra/identity/conditional-access/concept-session-lifetime",
"https://learn.microsoft.com/en-us/entra/identity/conditional-access/howto-conditional-access-session-lifetime"
],
"Remediation": {
@@ -24,7 +23,7 @@
"Terraform": ""
},
"Recommendation": {
"Text": "Configure Conditional Access policies to enforce **sign-in frequency** for sessions originating from non-corporate devices. This limits persistent access from unmanaged endpoints and requires users to periodically reauthenticate, reducing the risk of unauthorized access through shared or compromised devices.",
"Text": "Configure Conditional Access policies to enforce sign-in frequency for sessions originating from non-corporate devices. This limits persistent access from unmanaged endpoints and requires users to periodically reauthenticate, reducing the risk of unauthorized access through shared or compromised devices.",
"Url": "https://hub.prowler.com/check/entra_conditional_access_policy_enforce_sign_in_frequency"
}
},
@@ -32,8 +31,6 @@
"identity-access"
],
"DependsOn": [],
"RelatedTo": [
"entra_conditional_access_policy_require_mfa_for_management_api"
],
"RelatedTo": [],
"Notes": "Conditional Access policies require Microsoft Entra ID P1 or P2 licenses. The device filter targets devices that are not Hybrid Azure AD joined or not compliant with Intune policies."
}
@@ -0,0 +1,121 @@
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 (
ConditionalAccessPolicyState,
DeviceFilter,
DeviceFilterMode,
SignInFrequencyInterval,
)
class entra_conditional_access_policy_enforce_sign_in_frequency(Check):
"""Check if at least one Conditional Access policy enforces sign-in frequency for non-corporate devices.
This check evaluates whether the tenant has a Conditional Access policy that:
- Is enabled
- Targets all users and all applications
- Has sign-in frequency enabled with time-based interval
- Targets non-corporate devices via device filter
"""
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 enforces sign-in frequency for non-corporate devices."
for policy in entra_client.conditional_access_policies.values():
if policy.state == ConditionalAccessPolicyState.DISABLED:
continue
if not policy.session_controls.sign_in_frequency.is_enabled:
continue
if (
policy.session_controls.sign_in_frequency.interval
!= SignInFrequencyInterval.TIME_BASED
):
continue
if "All" not in policy.conditions.user_conditions.included_users:
continue
if (
"All"
not in policy.conditions.application_conditions.included_applications
):
continue
if not self._targets_non_corporate_devices(policy.conditions.device_filter):
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 enforce sign-in frequency for non-corporate devices but is in report-only mode."
else:
report.status = "PASS"
report.status_extended = f"Conditional Access Policy '{policy.display_name}' enforces sign-in frequency for non-corporate devices."
break
findings.append(report)
return findings
def _targets_non_corporate_devices(self, device_filter: DeviceFilter) -> bool:
"""Check if the device filter targets non-corporate devices.
Non-corporate devices are identified by targeting devices that are:
- Not Hybrid Azure AD joined (device.trustType != "ServerAD")
- Not compliant (device.isCompliant != True)
Args:
device_filter: The device filter from the conditional access policy.
Returns:
True if the filter targets non-corporate devices, False otherwise.
"""
if not device_filter or not device_filter.rule:
return False
rule = device_filter.rule.lower()
mode = device_filter.mode
# Include mode: should target non-corporate devices directly
if mode == DeviceFilterMode.INCLUDE:
targets_non_compliant = (
"device.iscompliant" in rule and "-ne" in rule and "true" in rule
)
targets_non_hybrid = (
"device.trusttype" in rule and "-ne" in rule and "serverad" in rule
)
if targets_non_compliant or targets_non_hybrid:
return True
# Exclude mode: should exclude corporate devices (equivalent to targeting non-corporate)
elif mode == DeviceFilterMode.EXCLUDE:
excludes_compliant = (
"device.iscompliant" in rule and "-eq" in rule and "true" in rule
)
excludes_hybrid = (
"device.trusttype" in rule and "-eq" in rule and "serverad" in rule
)
if excludes_compliant or excludes_hybrid:
return True
return False
@@ -235,6 +235,28 @@ class Entra(M365Service):
[],
)
],
device_filter=(
DeviceFilter(
mode=(
DeviceFilterMode(
policy.conditions.devices.device_filter.mode.value.lower()
)
if policy.conditions.devices
and policy.conditions.devices.device_filter
and policy.conditions.devices.device_filter.mode
else None
),
rule=(
policy.conditions.devices.device_filter.rule
if policy.conditions.devices
and policy.conditions.devices.device_filter
else None
),
)
if policy.conditions.devices
and policy.conditions.devices.device_filter
else None
),
),
grant_controls=GrantControls(
built_in_controls=(
@@ -503,12 +525,23 @@ class ClientAppType(Enum):
OTHER_CLIENTS = "other"
class DeviceFilterMode(Enum):
INCLUDE = "include"
EXCLUDE = "exclude"
class DeviceFilter(BaseModel):
mode: Optional[DeviceFilterMode]
rule: Optional[str]
class Conditions(BaseModel):
application_conditions: Optional[ApplicationsConditions]
user_conditions: Optional[UsersConditions]
client_app_types: Optional[List[ClientAppType]]
user_risk_levels: List[RiskLevel] = []
sign_in_risk_levels: List[RiskLevel] = []
device_filter: Optional[DeviceFilter] = None
class PersistentBrowser(BaseModel):
@@ -1,931 +0,0 @@
from unittest import mock
from uuid import uuid4
from tests.providers.azure.azure_fixtures import DOMAIN, set_mocked_azure_provider
class Test_entra_conditional_access_policy_enforce_sign_in_frequency:
def test_entra_no_subscriptions(self):
"""Test when no tenants/subscriptions exist - should return empty list."""
entra_client = mock.MagicMock
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_azure_provider(),
),
mock.patch(
"prowler.providers.azure.services.entra.entra_conditional_access_policy_enforce_sign_in_frequency.entra_conditional_access_policy_enforce_sign_in_frequency.entra_client",
new=entra_client,
),
):
from prowler.providers.azure.services.entra.entra_conditional_access_policy_enforce_sign_in_frequency.entra_conditional_access_policy_enforce_sign_in_frequency import (
entra_conditional_access_policy_enforce_sign_in_frequency,
)
entra_client.conditional_access_policy = {}
check = entra_conditional_access_policy_enforce_sign_in_frequency()
result = check.execute()
assert len(result) == 0
def test_entra_tenant_no_policies(self):
"""Test when tenant exists but has no policies - should FAIL."""
entra_client = mock.MagicMock
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_azure_provider(),
),
mock.patch(
"prowler.providers.azure.services.entra.entra_conditional_access_policy_enforce_sign_in_frequency.entra_conditional_access_policy_enforce_sign_in_frequency.entra_client",
new=entra_client,
),
):
from prowler.providers.azure.services.entra.entra_conditional_access_policy_enforce_sign_in_frequency.entra_conditional_access_policy_enforce_sign_in_frequency import (
entra_conditional_access_policy_enforce_sign_in_frequency,
)
entra_client.conditional_access_policy = {DOMAIN: {}}
check = entra_conditional_access_policy_enforce_sign_in_frequency()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert result[0].subscription == f"Tenant: {DOMAIN}"
assert result[0].resource_name == "Conditional Access Policy"
assert result[0].resource_id == "Conditional Access Policy"
assert (
result[0].status_extended
== "No Conditional Access Policy enforces sign-in frequency for non-corporate devices."
)
def test_entra_tenant_policy_disabled(self):
"""Test when policy exists but is disabled - should FAIL."""
entra_client = mock.MagicMock
policy_id = str(uuid4())
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_azure_provider(),
),
mock.patch(
"prowler.providers.azure.services.entra.entra_conditional_access_policy_enforce_sign_in_frequency.entra_conditional_access_policy_enforce_sign_in_frequency.entra_client",
new=entra_client,
),
):
from prowler.providers.azure.services.entra.entra_conditional_access_policy_enforce_sign_in_frequency.entra_conditional_access_policy_enforce_sign_in_frequency import (
entra_conditional_access_policy_enforce_sign_in_frequency,
)
from prowler.providers.azure.services.entra.entra_service import (
ConditionalAccessPolicy,
DeviceFilter,
SignInFrequencySessionControl,
)
policy = ConditionalAccessPolicy(
id=policy_id,
name="Test Policy",
state="disabled",
users={"include": ["All"]},
target_resources={"include": ["All"]},
access_controls={"grant": []},
sign_in_frequency=SignInFrequencySessionControl(
is_enabled=True,
frequency_interval="timeBased",
type="hours",
value=1,
),
device_filter=DeviceFilter(
mode="include",
rule='device.trustType -ne "ServerAD"',
),
)
entra_client.conditional_access_policy = {DOMAIN: {policy_id: policy}}
check = entra_conditional_access_policy_enforce_sign_in_frequency()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert result[0].subscription == f"Tenant: {DOMAIN}"
assert result[0].resource_name == "Conditional Access Policy"
assert result[0].resource_id == "Conditional Access Policy"
assert (
result[0].status_extended
== "No Conditional Access Policy enforces sign-in frequency for non-corporate devices."
)
def test_entra_tenant_policy_no_sign_in_frequency(self):
"""Test when policy has no sign-in frequency configured - should FAIL."""
entra_client = mock.MagicMock
policy_id = str(uuid4())
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_azure_provider(),
),
mock.patch(
"prowler.providers.azure.services.entra.entra_conditional_access_policy_enforce_sign_in_frequency.entra_conditional_access_policy_enforce_sign_in_frequency.entra_client",
new=entra_client,
),
):
from prowler.providers.azure.services.entra.entra_conditional_access_policy_enforce_sign_in_frequency.entra_conditional_access_policy_enforce_sign_in_frequency import (
entra_conditional_access_policy_enforce_sign_in_frequency,
)
from prowler.providers.azure.services.entra.entra_service import (
ConditionalAccessPolicy,
DeviceFilter,
)
policy = ConditionalAccessPolicy(
id=policy_id,
name="Test Policy",
state="enabled",
users={"include": ["All"]},
target_resources={"include": ["All"]},
access_controls={"grant": []},
sign_in_frequency=None,
device_filter=DeviceFilter(
mode="include",
rule='device.trustType -ne "ServerAD"',
),
)
entra_client.conditional_access_policy = {DOMAIN: {policy_id: policy}}
check = entra_conditional_access_policy_enforce_sign_in_frequency()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert result[0].subscription == f"Tenant: {DOMAIN}"
assert result[0].resource_name == "Conditional Access Policy"
assert result[0].resource_id == "Conditional Access Policy"
assert (
result[0].status_extended
== "No Conditional Access Policy enforces sign-in frequency for non-corporate devices."
)
def test_entra_tenant_policy_sign_in_frequency_disabled(self):
"""Test when policy has sign-in frequency but it's disabled - should FAIL."""
entra_client = mock.MagicMock
policy_id = str(uuid4())
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_azure_provider(),
),
mock.patch(
"prowler.providers.azure.services.entra.entra_conditional_access_policy_enforce_sign_in_frequency.entra_conditional_access_policy_enforce_sign_in_frequency.entra_client",
new=entra_client,
),
):
from prowler.providers.azure.services.entra.entra_conditional_access_policy_enforce_sign_in_frequency.entra_conditional_access_policy_enforce_sign_in_frequency import (
entra_conditional_access_policy_enforce_sign_in_frequency,
)
from prowler.providers.azure.services.entra.entra_service import (
ConditionalAccessPolicy,
DeviceFilter,
SignInFrequencySessionControl,
)
policy = ConditionalAccessPolicy(
id=policy_id,
name="Test Policy",
state="enabled",
users={"include": ["All"]},
target_resources={"include": ["All"]},
access_controls={"grant": []},
sign_in_frequency=SignInFrequencySessionControl(
is_enabled=False,
frequency_interval="timeBased",
type="hours",
value=1,
),
device_filter=DeviceFilter(
mode="include",
rule='device.trustType -ne "ServerAD"',
),
)
entra_client.conditional_access_policy = {DOMAIN: {policy_id: policy}}
check = entra_conditional_access_policy_enforce_sign_in_frequency()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert result[0].subscription == f"Tenant: {DOMAIN}"
assert result[0].resource_name == "Conditional Access Policy"
assert result[0].resource_id == "Conditional Access Policy"
assert (
result[0].status_extended
== "No Conditional Access Policy enforces sign-in frequency for non-corporate devices."
)
def test_entra_tenant_policy_sign_in_frequency_not_timebased(self):
"""Test when policy has sign-in frequency but not time-based - should FAIL."""
entra_client = mock.MagicMock
policy_id = str(uuid4())
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_azure_provider(),
),
mock.patch(
"prowler.providers.azure.services.entra.entra_conditional_access_policy_enforce_sign_in_frequency.entra_conditional_access_policy_enforce_sign_in_frequency.entra_client",
new=entra_client,
),
):
from prowler.providers.azure.services.entra.entra_conditional_access_policy_enforce_sign_in_frequency.entra_conditional_access_policy_enforce_sign_in_frequency import (
entra_conditional_access_policy_enforce_sign_in_frequency,
)
from prowler.providers.azure.services.entra.entra_service import (
ConditionalAccessPolicy,
DeviceFilter,
SignInFrequencySessionControl,
)
policy = ConditionalAccessPolicy(
id=policy_id,
name="Test Policy",
state="enabled",
users={"include": ["All"]},
target_resources={"include": ["All"]},
access_controls={"grant": []},
sign_in_frequency=SignInFrequencySessionControl(
is_enabled=True,
frequency_interval="everyTime",
type=None,
value=None,
),
device_filter=DeviceFilter(
mode="include",
rule='device.trustType -ne "ServerAD"',
),
)
entra_client.conditional_access_policy = {DOMAIN: {policy_id: policy}}
check = entra_conditional_access_policy_enforce_sign_in_frequency()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert result[0].subscription == f"Tenant: {DOMAIN}"
assert result[0].resource_name == "Conditional Access Policy"
assert result[0].resource_id == "Conditional Access Policy"
assert (
result[0].status_extended
== "No Conditional Access Policy enforces sign-in frequency for non-corporate devices."
)
def test_entra_tenant_policy_no_all_users(self):
"""Test when policy doesn't target all users - should FAIL."""
entra_client = mock.MagicMock
policy_id = str(uuid4())
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_azure_provider(),
),
mock.patch(
"prowler.providers.azure.services.entra.entra_conditional_access_policy_enforce_sign_in_frequency.entra_conditional_access_policy_enforce_sign_in_frequency.entra_client",
new=entra_client,
),
):
from prowler.providers.azure.services.entra.entra_conditional_access_policy_enforce_sign_in_frequency.entra_conditional_access_policy_enforce_sign_in_frequency import (
entra_conditional_access_policy_enforce_sign_in_frequency,
)
from prowler.providers.azure.services.entra.entra_service import (
ConditionalAccessPolicy,
DeviceFilter,
SignInFrequencySessionControl,
)
policy = ConditionalAccessPolicy(
id=policy_id,
name="Test Policy",
state="enabled",
users={"include": ["specific-user-id"]},
target_resources={"include": ["All"]},
access_controls={"grant": []},
sign_in_frequency=SignInFrequencySessionControl(
is_enabled=True,
frequency_interval="timeBased",
type="hours",
value=1,
),
device_filter=DeviceFilter(
mode="include",
rule='device.trustType -ne "ServerAD"',
),
)
entra_client.conditional_access_policy = {DOMAIN: {policy_id: policy}}
check = entra_conditional_access_policy_enforce_sign_in_frequency()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert result[0].subscription == f"Tenant: {DOMAIN}"
assert result[0].resource_name == "Conditional Access Policy"
assert result[0].resource_id == "Conditional Access Policy"
assert (
result[0].status_extended
== "No Conditional Access Policy enforces sign-in frequency for non-corporate devices."
)
def test_entra_tenant_policy_no_all_apps(self):
"""Test when policy doesn't target all applications - should FAIL."""
entra_client = mock.MagicMock
policy_id = str(uuid4())
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_azure_provider(),
),
mock.patch(
"prowler.providers.azure.services.entra.entra_conditional_access_policy_enforce_sign_in_frequency.entra_conditional_access_policy_enforce_sign_in_frequency.entra_client",
new=entra_client,
),
):
from prowler.providers.azure.services.entra.entra_conditional_access_policy_enforce_sign_in_frequency.entra_conditional_access_policy_enforce_sign_in_frequency import (
entra_conditional_access_policy_enforce_sign_in_frequency,
)
from prowler.providers.azure.services.entra.entra_service import (
ConditionalAccessPolicy,
DeviceFilter,
SignInFrequencySessionControl,
)
policy = ConditionalAccessPolicy(
id=policy_id,
name="Test Policy",
state="enabled",
users={"include": ["All"]},
target_resources={"include": ["specific-app-id"]},
access_controls={"grant": []},
sign_in_frequency=SignInFrequencySessionControl(
is_enabled=True,
frequency_interval="timeBased",
type="hours",
value=1,
),
device_filter=DeviceFilter(
mode="include",
rule='device.trustType -ne "ServerAD"',
),
)
entra_client.conditional_access_policy = {DOMAIN: {policy_id: policy}}
check = entra_conditional_access_policy_enforce_sign_in_frequency()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert result[0].subscription == f"Tenant: {DOMAIN}"
assert result[0].resource_name == "Conditional Access Policy"
assert result[0].resource_id == "Conditional Access Policy"
assert (
result[0].status_extended
== "No Conditional Access Policy enforces sign-in frequency for non-corporate devices."
)
def test_entra_tenant_policy_no_device_filter(self):
"""Test when policy has no device filter - should FAIL."""
entra_client = mock.MagicMock
policy_id = str(uuid4())
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_azure_provider(),
),
mock.patch(
"prowler.providers.azure.services.entra.entra_conditional_access_policy_enforce_sign_in_frequency.entra_conditional_access_policy_enforce_sign_in_frequency.entra_client",
new=entra_client,
),
):
from prowler.providers.azure.services.entra.entra_conditional_access_policy_enforce_sign_in_frequency.entra_conditional_access_policy_enforce_sign_in_frequency import (
entra_conditional_access_policy_enforce_sign_in_frequency,
)
from prowler.providers.azure.services.entra.entra_service import (
ConditionalAccessPolicy,
SignInFrequencySessionControl,
)
policy = ConditionalAccessPolicy(
id=policy_id,
name="Test Policy",
state="enabled",
users={"include": ["All"]},
target_resources={"include": ["All"]},
access_controls={"grant": []},
sign_in_frequency=SignInFrequencySessionControl(
is_enabled=True,
frequency_interval="timeBased",
type="hours",
value=1,
),
device_filter=None,
)
entra_client.conditional_access_policy = {DOMAIN: {policy_id: policy}}
check = entra_conditional_access_policy_enforce_sign_in_frequency()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert result[0].subscription == f"Tenant: {DOMAIN}"
assert result[0].resource_name == "Conditional Access Policy"
assert result[0].resource_id == "Conditional Access Policy"
assert (
result[0].status_extended
== "No Conditional Access Policy enforces sign-in frequency for non-corporate devices."
)
def test_entra_tenant_policy_device_filter_wrong_rule(self):
"""Test when policy has device filter but doesn't target non-corporate devices - should FAIL."""
entra_client = mock.MagicMock
policy_id = str(uuid4())
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_azure_provider(),
),
mock.patch(
"prowler.providers.azure.services.entra.entra_conditional_access_policy_enforce_sign_in_frequency.entra_conditional_access_policy_enforce_sign_in_frequency.entra_client",
new=entra_client,
),
):
from prowler.providers.azure.services.entra.entra_conditional_access_policy_enforce_sign_in_frequency.entra_conditional_access_policy_enforce_sign_in_frequency import (
entra_conditional_access_policy_enforce_sign_in_frequency,
)
from prowler.providers.azure.services.entra.entra_service import (
ConditionalAccessPolicy,
DeviceFilter,
SignInFrequencySessionControl,
)
policy = ConditionalAccessPolicy(
id=policy_id,
name="Test Policy",
state="enabled",
users={"include": ["All"]},
target_resources={"include": ["All"]},
access_controls={"grant": []},
sign_in_frequency=SignInFrequencySessionControl(
is_enabled=True,
frequency_interval="timeBased",
type="hours",
value=1,
),
device_filter=DeviceFilter(
mode="include",
rule="device.displayName -contains 'Windows'",
),
)
entra_client.conditional_access_policy = {DOMAIN: {policy_id: policy}}
check = entra_conditional_access_policy_enforce_sign_in_frequency()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert result[0].subscription == f"Tenant: {DOMAIN}"
assert result[0].resource_name == "Conditional Access Policy"
assert result[0].resource_id == "Conditional Access Policy"
assert (
result[0].status_extended
== "No Conditional Access Policy enforces sign-in frequency for non-corporate devices."
)
def test_entra_tenant_policy_compliant_include_non_compliant_devices(self):
"""Test compliant policy using include mode targeting non-compliant devices - should PASS."""
entra_client = mock.MagicMock
policy_id = str(uuid4())
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_azure_provider(),
),
mock.patch(
"prowler.providers.azure.services.entra.entra_conditional_access_policy_enforce_sign_in_frequency.entra_conditional_access_policy_enforce_sign_in_frequency.entra_client",
new=entra_client,
),
):
from prowler.providers.azure.services.entra.entra_conditional_access_policy_enforce_sign_in_frequency.entra_conditional_access_policy_enforce_sign_in_frequency import (
entra_conditional_access_policy_enforce_sign_in_frequency,
)
from prowler.providers.azure.services.entra.entra_service import (
ConditionalAccessPolicy,
DeviceFilter,
SignInFrequencySessionControl,
)
policy = ConditionalAccessPolicy(
id=policy_id,
name="Sign-in Frequency Policy",
state="enabled",
users={"include": ["All"]},
target_resources={"include": ["All"]},
access_controls={"grant": []},
sign_in_frequency=SignInFrequencySessionControl(
is_enabled=True,
frequency_interval="timeBased",
type="hours",
value=1,
),
device_filter=DeviceFilter(
mode="include",
rule="device.isCompliant -ne True",
),
)
entra_client.conditional_access_policy = {DOMAIN: {policy_id: policy}}
check = entra_conditional_access_policy_enforce_sign_in_frequency()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert result[0].subscription == f"Tenant: {DOMAIN}"
assert result[0].resource_name == "Sign-in Frequency Policy"
assert result[0].resource_id == policy_id
assert (
result[0].status_extended
== "Conditional Access Policy 'Sign-in Frequency Policy' enforces sign-in frequency for non-corporate devices."
)
def test_entra_tenant_policy_compliant_include_non_hybrid_devices(self):
"""Test compliant policy using include mode targeting non-hybrid AD joined devices - should PASS."""
entra_client = mock.MagicMock
policy_id = str(uuid4())
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_azure_provider(),
),
mock.patch(
"prowler.providers.azure.services.entra.entra_conditional_access_policy_enforce_sign_in_frequency.entra_conditional_access_policy_enforce_sign_in_frequency.entra_client",
new=entra_client,
),
):
from prowler.providers.azure.services.entra.entra_conditional_access_policy_enforce_sign_in_frequency.entra_conditional_access_policy_enforce_sign_in_frequency import (
entra_conditional_access_policy_enforce_sign_in_frequency,
)
from prowler.providers.azure.services.entra.entra_service import (
ConditionalAccessPolicy,
DeviceFilter,
SignInFrequencySessionControl,
)
policy = ConditionalAccessPolicy(
id=policy_id,
name="Sign-in Frequency Policy",
state="enabled",
users={"include": ["All"]},
target_resources={"include": ["All"]},
access_controls={"grant": []},
sign_in_frequency=SignInFrequencySessionControl(
is_enabled=True,
frequency_interval="timeBased",
type="hours",
value=1,
),
device_filter=DeviceFilter(
mode="include",
rule='device.trustType -ne "ServerAD"',
),
)
entra_client.conditional_access_policy = {DOMAIN: {policy_id: policy}}
check = entra_conditional_access_policy_enforce_sign_in_frequency()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert result[0].subscription == f"Tenant: {DOMAIN}"
assert result[0].resource_name == "Sign-in Frequency Policy"
assert result[0].resource_id == policy_id
assert (
result[0].status_extended
== "Conditional Access Policy 'Sign-in Frequency Policy' enforces sign-in frequency for non-corporate devices."
)
def test_entra_tenant_policy_compliant_exclude_compliant_devices(self):
"""Test compliant policy using exclude mode excluding compliant devices - should PASS."""
entra_client = mock.MagicMock
policy_id = str(uuid4())
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_azure_provider(),
),
mock.patch(
"prowler.providers.azure.services.entra.entra_conditional_access_policy_enforce_sign_in_frequency.entra_conditional_access_policy_enforce_sign_in_frequency.entra_client",
new=entra_client,
),
):
from prowler.providers.azure.services.entra.entra_conditional_access_policy_enforce_sign_in_frequency.entra_conditional_access_policy_enforce_sign_in_frequency import (
entra_conditional_access_policy_enforce_sign_in_frequency,
)
from prowler.providers.azure.services.entra.entra_service import (
ConditionalAccessPolicy,
DeviceFilter,
SignInFrequencySessionControl,
)
policy = ConditionalAccessPolicy(
id=policy_id,
name="Sign-in Frequency Policy",
state="enabled",
users={"include": ["All"]},
target_resources={"include": ["All"]},
access_controls={"grant": []},
sign_in_frequency=SignInFrequencySessionControl(
is_enabled=True,
frequency_interval="timeBased",
type="hours",
value=1,
),
device_filter=DeviceFilter(
mode="exclude",
rule="device.isCompliant -eq True",
),
)
entra_client.conditional_access_policy = {DOMAIN: {policy_id: policy}}
check = entra_conditional_access_policy_enforce_sign_in_frequency()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert result[0].subscription == f"Tenant: {DOMAIN}"
assert result[0].resource_name == "Sign-in Frequency Policy"
assert result[0].resource_id == policy_id
assert (
result[0].status_extended
== "Conditional Access Policy 'Sign-in Frequency Policy' enforces sign-in frequency for non-corporate devices."
)
def test_entra_tenant_policy_compliant_exclude_hybrid_devices(self):
"""Test compliant policy using exclude mode excluding hybrid AD joined devices - should PASS."""
entra_client = mock.MagicMock
policy_id = str(uuid4())
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_azure_provider(),
),
mock.patch(
"prowler.providers.azure.services.entra.entra_conditional_access_policy_enforce_sign_in_frequency.entra_conditional_access_policy_enforce_sign_in_frequency.entra_client",
new=entra_client,
),
):
from prowler.providers.azure.services.entra.entra_conditional_access_policy_enforce_sign_in_frequency.entra_conditional_access_policy_enforce_sign_in_frequency import (
entra_conditional_access_policy_enforce_sign_in_frequency,
)
from prowler.providers.azure.services.entra.entra_service import (
ConditionalAccessPolicy,
DeviceFilter,
SignInFrequencySessionControl,
)
policy = ConditionalAccessPolicy(
id=policy_id,
name="Sign-in Frequency Policy",
state="enabled",
users={"include": ["All"]},
target_resources={"include": ["All"]},
access_controls={"grant": []},
sign_in_frequency=SignInFrequencySessionControl(
is_enabled=True,
frequency_interval="timeBased",
type="hours",
value=1,
),
device_filter=DeviceFilter(
mode="exclude",
rule='device.trustType -eq "ServerAD"',
),
)
entra_client.conditional_access_policy = {DOMAIN: {policy_id: policy}}
check = entra_conditional_access_policy_enforce_sign_in_frequency()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert result[0].subscription == f"Tenant: {DOMAIN}"
assert result[0].resource_name == "Sign-in Frequency Policy"
assert result[0].resource_id == policy_id
assert (
result[0].status_extended
== "Conditional Access Policy 'Sign-in Frequency Policy' enforces sign-in frequency for non-corporate devices."
)
def test_entra_tenant_policy_compliant_filtermode_include(self):
"""Test compliant policy using filterMode.include mode - should PASS."""
entra_client = mock.MagicMock
policy_id = str(uuid4())
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_azure_provider(),
),
mock.patch(
"prowler.providers.azure.services.entra.entra_conditional_access_policy_enforce_sign_in_frequency.entra_conditional_access_policy_enforce_sign_in_frequency.entra_client",
new=entra_client,
),
):
from prowler.providers.azure.services.entra.entra_conditional_access_policy_enforce_sign_in_frequency.entra_conditional_access_policy_enforce_sign_in_frequency import (
entra_conditional_access_policy_enforce_sign_in_frequency,
)
from prowler.providers.azure.services.entra.entra_service import (
ConditionalAccessPolicy,
DeviceFilter,
SignInFrequencySessionControl,
)
policy = ConditionalAccessPolicy(
id=policy_id,
name="Sign-in Frequency Policy",
state="enabled",
users={"include": ["All"]},
target_resources={"include": ["All"]},
access_controls={"grant": []},
sign_in_frequency=SignInFrequencySessionControl(
is_enabled=True,
frequency_interval="timeBased",
type="hours",
value=1,
),
device_filter=DeviceFilter(
mode="filterMode.include",
rule='device.trustType -ne "ServerAD" -or device.isCompliant -ne True',
),
)
entra_client.conditional_access_policy = {DOMAIN: {policy_id: policy}}
check = entra_conditional_access_policy_enforce_sign_in_frequency()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert result[0].subscription == f"Tenant: {DOMAIN}"
assert result[0].resource_name == "Sign-in Frequency Policy"
assert result[0].resource_id == policy_id
assert (
result[0].status_extended
== "Conditional Access Policy 'Sign-in Frequency Policy' enforces sign-in frequency for non-corporate devices."
)
def test_entra_tenant_policy_compliant_filtermode_exclude(self):
"""Test compliant policy using filterMode.exclude mode - should PASS."""
entra_client = mock.MagicMock
policy_id = str(uuid4())
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_azure_provider(),
),
mock.patch(
"prowler.providers.azure.services.entra.entra_conditional_access_policy_enforce_sign_in_frequency.entra_conditional_access_policy_enforce_sign_in_frequency.entra_client",
new=entra_client,
),
):
from prowler.providers.azure.services.entra.entra_conditional_access_policy_enforce_sign_in_frequency.entra_conditional_access_policy_enforce_sign_in_frequency import (
entra_conditional_access_policy_enforce_sign_in_frequency,
)
from prowler.providers.azure.services.entra.entra_service import (
ConditionalAccessPolicy,
DeviceFilter,
SignInFrequencySessionControl,
)
policy = ConditionalAccessPolicy(
id=policy_id,
name="Sign-in Frequency Policy",
state="enabled",
users={"include": ["All"]},
target_resources={"include": ["All"]},
access_controls={"grant": []},
sign_in_frequency=SignInFrequencySessionControl(
is_enabled=True,
frequency_interval="timeBased",
type="hours",
value=1,
),
device_filter=DeviceFilter(
mode="filterMode.exclude",
rule='device.trustType -eq "ServerAD" -and device.isCompliant -eq True',
),
)
entra_client.conditional_access_policy = {DOMAIN: {policy_id: policy}}
check = entra_conditional_access_policy_enforce_sign_in_frequency()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert result[0].subscription == f"Tenant: {DOMAIN}"
assert result[0].resource_name == "Sign-in Frequency Policy"
assert result[0].resource_id == policy_id
assert (
result[0].status_extended
== "Conditional Access Policy 'Sign-in Frequency Policy' enforces sign-in frequency for non-corporate devices."
)
def test_entra_tenant_multiple_policies_one_compliant(self):
"""Test when one policy is compliant among multiple policies - should PASS."""
entra_client = mock.MagicMock
policy_id_1 = str(uuid4())
policy_id_2 = str(uuid4())
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_azure_provider(),
),
mock.patch(
"prowler.providers.azure.services.entra.entra_conditional_access_policy_enforce_sign_in_frequency.entra_conditional_access_policy_enforce_sign_in_frequency.entra_client",
new=entra_client,
),
):
from prowler.providers.azure.services.entra.entra_conditional_access_policy_enforce_sign_in_frequency.entra_conditional_access_policy_enforce_sign_in_frequency import (
entra_conditional_access_policy_enforce_sign_in_frequency,
)
from prowler.providers.azure.services.entra.entra_service import (
ConditionalAccessPolicy,
DeviceFilter,
SignInFrequencySessionControl,
)
# Non-compliant policy (disabled)
policy_1 = ConditionalAccessPolicy(
id=policy_id_1,
name="Disabled Policy",
state="disabled",
users={"include": ["All"]},
target_resources={"include": ["All"]},
access_controls={"grant": []},
sign_in_frequency=SignInFrequencySessionControl(
is_enabled=True,
frequency_interval="timeBased",
type="hours",
value=1,
),
device_filter=DeviceFilter(
mode="include",
rule="device.isCompliant -ne True",
),
)
# Compliant policy
policy_2 = ConditionalAccessPolicy(
id=policy_id_2,
name="Compliant Policy",
state="enabled",
users={"include": ["All"]},
target_resources={"include": ["All"]},
access_controls={"grant": []},
sign_in_frequency=SignInFrequencySessionControl(
is_enabled=True,
frequency_interval="timeBased",
type="hours",
value=1,
),
device_filter=DeviceFilter(
mode="include",
rule="device.isCompliant -ne True",
),
)
entra_client.conditional_access_policy = {
DOMAIN: {policy_id_1: policy_1, policy_id_2: policy_2}
}
check = entra_conditional_access_policy_enforce_sign_in_frequency()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert result[0].subscription == f"Tenant: {DOMAIN}"
assert result[0].resource_name == "Compliant Policy"
assert result[0].resource_id == policy_id_2
assert (
result[0].status_extended
== "Conditional Access Policy 'Compliant Policy' enforces sign-in frequency for non-corporate devices."
)