mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-10-24 04:14:18 +00:00
feat(azure): add entra_conditional_access_policy_enforce_sign_in_frequency security check
Add new security check entra_conditional_access_policy_enforce_sign_in_frequency for azure provider. Includes check implementation, metadata, and unit tests.
This commit is contained in:
11 files changed
+1185
-7
No files matched your search
@@ -13,6 +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)
|
||||
|
||||
|
||||
### Changed
|
||||
|
||||
@@ -307,6 +307,7 @@
|
||||
}
|
||||
],
|
||||
"Checks": [
|
||||
"entra_conditional_access_policy_enforce_sign_in_frequency",
|
||||
"entra_conditional_access_policy_require_mfa_for_management_api",
|
||||
"entra_non_privileged_user_has_mfa",
|
||||
"entra_privileged_user_has_mfa",
|
||||
@@ -1102,10 +1103,11 @@
|
||||
}
|
||||
],
|
||||
"Checks": [
|
||||
"app_minimum_tls_version_12",
|
||||
"entra_conditional_access_policy_enforce_sign_in_frequency",
|
||||
"entra_conditional_access_policy_require_mfa_for_management_app",
|
||||
"entra_non_privileged_user_has_mfa entra_privileged_user_has_mfa",
|
||||
"entra_user_with_vm_access_has_mfa",
|
||||
"app_minimum_tls_version_12",
|
||||
"sqlserver_tde_encryption_enabled",
|
||||
"storage_ensure_encryption_with_customer_managed_keys",
|
||||
"storage_infrastructure_encryption_is_enabled"
|
||||
|
||||
@@ -212,6 +212,7 @@
|
||||
"Description": "Adversaries may obtain and abuse credentials of existing accounts as a means of gaining Initial Access, Persistence, Privilege Escalation, or Defense Evasion. Compromised credentials may be used to bypass access controls placed on various resources on systems within the network and may even be used for persistent access to remote systems and externally available services, such as VPNs, Outlook Web Access, network devices, and remote desktop.[1] Compromised credentials may also grant an adversary increased privilege to specific systems or access to restricted areas of the network. Adversaries may choose not to use malware or tools in conjunction with the legitimate access those credentials provide to make it harder to detect their presence.",
|
||||
"TechniqueURL": "https://attack.mitre.org/techniques/T1078/",
|
||||
"Checks": [
|
||||
"entra_conditional_access_policy_enforce_sign_in_frequency",
|
||||
"entra_conditional_access_policy_require_mfa_for_management_api",
|
||||
"entra_global_admin_in_less_than_five_users",
|
||||
"entra_non_privileged_user_has_mfa",
|
||||
@@ -804,6 +805,7 @@
|
||||
"Description": "Adversaries may modify authentication mechanisms and processes to access user credentials or enable otherwise unwarranted access to accounts. The authentication process is handled by mechanisms, such as the Local Security Authentication Server (LSASS) process and the Security Accounts Manager (SAM) on Windows, pluggable authentication modules (PAM) on Unix-based systems, and authorization plugins on MacOS systems, responsible for gathering, storing, and validating credentials. By modifying an authentication process, an adversary may be able to authenticate to a service or system without using Valid Accounts.",
|
||||
"TechniqueURL": "https://attack.mitre.org/techniques/T1556/",
|
||||
"Checks": [
|
||||
"entra_conditional_access_policy_enforce_sign_in_frequency",
|
||||
"entra_conditional_access_policy_require_mfa_for_management_api",
|
||||
"entra_global_admin_in_less_than_five_users",
|
||||
"entra_non_privileged_user_has_mfa",
|
||||
@@ -995,6 +997,7 @@
|
||||
"Description": "Adversaries may use alternate authentication material, such as password hashes, Kerberos tickets, and application access tokens, in order to move laterally within an environment and bypass normal system access controls.",
|
||||
"TechniqueURL": "https://attack.mitre.org/techniques/T1550/",
|
||||
"Checks": [
|
||||
"entra_conditional_access_policy_enforce_sign_in_frequency",
|
||||
"entra_global_admin_in_less_than_five_users",
|
||||
"entra_non_privileged_user_has_mfa",
|
||||
"entra_policy_default_users_cannot_create_security_groups",
|
||||
@@ -1187,6 +1190,7 @@
|
||||
"Description": "Adversaries may forge credential materials that can be used to gain access to web applications or Internet services. Web applications and services (hosted in cloud SaaS environments or on-premise servers) often use session cookies, tokens, or other materials to authenticate and authorize user access.",
|
||||
"TechniqueURL": "https://attack.mitre.org/techniques/T1606/",
|
||||
"Checks": [
|
||||
"entra_conditional_access_policy_enforce_sign_in_frequency",
|
||||
"entra_policy_default_users_cannot_create_security_groups",
|
||||
"entra_policy_ensure_default_user_cannot_create_apps",
|
||||
"entra_policy_ensure_default_user_cannot_create_tenants",
|
||||
|
||||
@@ -1603,6 +1603,7 @@
|
||||
"Id": "11.3.2.a",
|
||||
"Description": "establish strong identification, authentication such as multi-factor authentication, and authorisation procedures for privileged accounts and system administration accounts;",
|
||||
"Checks": [
|
||||
"entra_conditional_access_policy_enforce_sign_in_frequency",
|
||||
"entra_conditional_access_policy_require_mfa_for_management_api",
|
||||
"entra_non_privileged_user_has_mfa",
|
||||
"entra_privileged_user_has_mfa",
|
||||
@@ -1690,10 +1691,11 @@
|
||||
"Id": "11.4.2.c",
|
||||
"Description": "protect access to system administration systems through authentication and encryption.",
|
||||
"Checks": [
|
||||
"entra_trusted_named_locations_exists",
|
||||
"entra_user_with_vm_access_has_mfa",
|
||||
"entra_conditional_access_policy_enforce_sign_in_frequency",
|
||||
"entra_conditional_access_policy_require_mfa_for_management_api",
|
||||
"entra_privileged_user_has_mfa"
|
||||
"entra_privileged_user_has_mfa",
|
||||
"entra_trusted_named_locations_exists",
|
||||
"entra_user_with_vm_access_has_mfa"
|
||||
],
|
||||
"Attributes": [
|
||||
{
|
||||
@@ -1762,6 +1764,7 @@
|
||||
"Id": "11.6.2.a",
|
||||
"Description": "ensure the strength of authentication is appropriate to the classification of the asset to be accessed;",
|
||||
"Checks": [
|
||||
"entra_conditional_access_policy_enforce_sign_in_frequency",
|
||||
"entra_conditional_access_policy_require_mfa_for_management_api",
|
||||
"entra_non_privileged_user_has_mfa",
|
||||
"entra_privileged_user_has_mfa",
|
||||
@@ -1794,6 +1797,7 @@
|
||||
"Id": "11.7.2",
|
||||
"Description": "The relevant entities shall ensure that the strength of authentication is appropriate for the classification of the asset to be accessed.",
|
||||
"Checks": [
|
||||
"entra_conditional_access_policy_enforce_sign_in_frequency",
|
||||
"entra_conditional_access_policy_require_mfa_for_management_api",
|
||||
"entra_non_privileged_user_has_mfa",
|
||||
"entra_privileged_user_has_mfa",
|
||||
|
||||
@@ -45,6 +45,7 @@
|
||||
"Id": "1.1.3",
|
||||
"Description": "Ensure Multi-factor Authentication is Required for Windows Azure Service Management API",
|
||||
"Checks": [
|
||||
"entra_conditional_access_policy_enforce_sign_in_frequency",
|
||||
"entra_conditional_access_policy_require_mfa_for_management_api"
|
||||
],
|
||||
"Attributes": [
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
}
|
||||
],
|
||||
"Checks": [
|
||||
"entra_conditional_access_policy_enforce_sign_in_frequency",
|
||||
"entra_conditional_access_policy_require_mfa_for_management_api",
|
||||
"entra_global_admin_in_less_than_five_users",
|
||||
"entra_non_privileged_user_has_mfa",
|
||||
@@ -240,6 +241,7 @@
|
||||
"aks_clusters_public_access_disabled",
|
||||
"app_function_not_publicly_accessible",
|
||||
"containerregistry_not_publicly_accessible",
|
||||
"entra_conditional_access_policy_enforce_sign_in_frequency",
|
||||
"network_public_ip_shodan",
|
||||
"storage_blob_public_access_level_is_disabled"
|
||||
]
|
||||
@@ -280,6 +282,7 @@
|
||||
}
|
||||
],
|
||||
"Checks": [
|
||||
"entra_conditional_access_policy_enforce_sign_in_frequency",
|
||||
"entra_non_privileged_user_has_mfa",
|
||||
"entra_privileged_user_has_mfa",
|
||||
"entra_user_with_vm_access_has_mfa"
|
||||
@@ -298,14 +301,15 @@
|
||||
}
|
||||
],
|
||||
"Checks": [
|
||||
"app_minimum_tls_version_12",
|
||||
"entra_conditional_access_policy_enforce_sign_in_frequency",
|
||||
"mysql_flexible_server_minimum_tls_version_12",
|
||||
"mysql_flexible_server_ssl_connection_enabled",
|
||||
"network_http_internet_access_restricted",
|
||||
"network_rdp_internet_access_restricted",
|
||||
"network_ssh_internet_access_restricted",
|
||||
"network_udp_internet_access_restricted",
|
||||
"mysql_flexible_server_ssl_connection_enabled",
|
||||
"postgresql_flexible_server_enforce_ssl_enabled",
|
||||
"app_minimum_tls_version_12",
|
||||
"mysql_flexible_server_minimum_tls_version_12",
|
||||
"sqlserver_recommended_minimal_tls_version",
|
||||
"storage_ensure_minimum_tls_version_12"
|
||||
]
|
||||
|
||||
Whitespace-only changes.
+39
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"Provider": "azure",
|
||||
"CheckID": "entra_conditional_access_policy_enforce_sign_in_frequency",
|
||||
"CheckTitle": "Conditional Access policy enforces sign-in frequency for non-corporate devices",
|
||||
"CheckType": [],
|
||||
"ServiceName": "entra",
|
||||
"SubServiceName": "",
|
||||
"ResourceIdTemplate": "",
|
||||
"Severity": "medium",
|
||||
"ResourceType": "#microsoft.graph.conditionalAccess",
|
||||
"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.",
|
||||
"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": "",
|
||||
"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": {
|
||||
"Code": {
|
||||
"CLI": "",
|
||||
"NativeIaC": "",
|
||||
"Other": "1. Navigate to **Microsoft Entra admin center** > **Protection** > **Conditional Access**\n2. Click **+ New policy**\n3. Under **Users**, select **All users**\n4. Under **Target resources**, select **All cloud apps**\n5. Under **Conditions** > **Filter for devices**, configure to target non-corporate devices:\n - Mode: **Include filtered devices in policy**\n - Rule: `device.trustType -ne \"ServerAD\" -or device.isCompliant -ne True`\n6. Under **Session** > **Sign-in frequency**, enable and set to desired interval (e.g., 1 hour)\n7. Enable the policy",
|
||||
"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.",
|
||||
"Url": "https://hub.prowler.com/check/entra_conditional_access_policy_enforce_sign_in_frequency"
|
||||
}
|
||||
},
|
||||
"Categories": [
|
||||
"identity-access"
|
||||
],
|
||||
"DependsOn": [],
|
||||
"RelatedTo": [
|
||||
"entra_conditional_access_policy_require_mfa_for_management_api"
|
||||
],
|
||||
"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."
|
||||
}
|
||||
+133
@@ -0,0 +1,133 @@
|
||||
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
|
||||
@@ -365,6 +365,43 @@ class Entra(AzureService):
|
||||
else:
|
||||
block_access_controls.append(str(access_control))
|
||||
|
||||
# Extract session controls (sign-in frequency)
|
||||
sign_in_frequency = None
|
||||
session_controls = getattr(policy, "session_controls", None)
|
||||
if session_controls:
|
||||
sif = getattr(session_controls, "sign_in_frequency", None)
|
||||
if sif:
|
||||
sign_in_frequency = SignInFrequencySessionControl(
|
||||
is_enabled=getattr(sif, "is_enabled", False),
|
||||
frequency_interval=(
|
||||
str(getattr(sif, "frequency_interval", None))
|
||||
if getattr(sif, "frequency_interval", None)
|
||||
else None
|
||||
),
|
||||
type=(
|
||||
str(getattr(sif, "type", None))
|
||||
if getattr(sif, "type", None)
|
||||
else None
|
||||
),
|
||||
value=getattr(sif, "value", None),
|
||||
)
|
||||
|
||||
# Extract device filter
|
||||
device_filter = None
|
||||
if conditions:
|
||||
devices = getattr(conditions, "devices", None)
|
||||
if devices:
|
||||
df = getattr(devices, "device_filter", None)
|
||||
if df:
|
||||
device_filter = DeviceFilter(
|
||||
mode=(
|
||||
str(getattr(df, "mode", None))
|
||||
if getattr(df, "mode", None)
|
||||
else None
|
||||
),
|
||||
rule=getattr(df, "rule", None),
|
||||
)
|
||||
|
||||
conditional_access_policy[tenant].update(
|
||||
{
|
||||
policy.id: ConditionalAccessPolicy(
|
||||
@@ -391,6 +428,8 @@ class Entra(AzureService):
|
||||
"grant": grant_access_controls,
|
||||
"block": block_access_controls,
|
||||
},
|
||||
sign_in_frequency=sign_in_frequency,
|
||||
device_filter=device_filter,
|
||||
)
|
||||
}
|
||||
)
|
||||
@@ -457,10 +496,30 @@ class DirectoryRole(BaseModel):
|
||||
members: List[User]
|
||||
|
||||
|
||||
class SignInFrequencySessionControl(BaseModel):
|
||||
"""Sign-in frequency session control settings."""
|
||||
|
||||
is_enabled: bool = False
|
||||
frequency_interval: Optional[str] = None
|
||||
type: Optional[str] = None
|
||||
value: Optional[int] = None
|
||||
|
||||
|
||||
class DeviceFilter(BaseModel):
|
||||
"""Device filter for conditional access policies."""
|
||||
|
||||
mode: Optional[str] = None
|
||||
rule: Optional[str] = None
|
||||
|
||||
|
||||
class ConditionalAccessPolicy(BaseModel):
|
||||
"""Conditional Access Policy model."""
|
||||
|
||||
id: str
|
||||
name: str
|
||||
state: str
|
||||
users: dict[str, List[str]]
|
||||
target_resources: dict[str, List[str]]
|
||||
access_controls: dict[str, List[str]]
|
||||
sign_in_frequency: Optional[SignInFrequencySessionControl] = None
|
||||
device_filter: Optional[DeviceFilter] = None
|
||||
+931
@@ -0,0 +1,931 @@
|
||||
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."
|
||||
)
|
||||
Reference in new issue
Block a user