feat(m365): add entra_conditional_access_policy_emergency_access_exclusion security check (#9903)

Co-authored-by: HugoPBrito <hugopbrit@gmail.com>
This commit is contained in:
Andoni Alonso
2026-02-24 11:35:31 +01:00
committed by GitHub
parent e688e60fde
commit 75d01efc0d
7 changed files with 955 additions and 2 deletions

View File

@@ -100,6 +100,7 @@ All notable changes to the **Prowler SDK** are documented in this file.
### 🚀 Added
- `entra_emergency_access_exclusion` check for M365 provider [(#9903)](https://github.com/prowler-cloud/prowler/pull/9903)
- `defender_zap_for_teams_enabled` check for M365 provider [(#9838)](https://github.com/prowler-cloud/prowler/pull/9838)
- `compute_instance_suspended_without_persistent_disks` check for GCP provider [(#9747)](https://github.com/prowler-cloud/prowler/pull/9747)
- `codebuild_project_webhook_filters_use_anchored_patterns` check for AWS provider to detect CodeBreach vulnerability [(#9840)](https://github.com/prowler-cloud/prowler/pull/9840)

View File

@@ -31,7 +31,9 @@
{
"Id": "1.1.2",
"Description": "Emergency access or \"break glass\" accounts are limited for emergency scenarios where normal administrative accounts are unavailable. They are not assigned to a specific user and will have a combination of physical and technical controls to prevent them from being accessed outside a true emergency. These emergencies could be due to several things, including:- Technical failures of a cellular provider or Microsoft related service such as MFA.- The last remaining Global Administrator account is inaccessible.Ensure two `Emergency Access` accounts have been defined.**Note:** Microsoft provides several recommendations for these accounts and how to configure them. For more information on this, please refer to the references section. The CIS Benchmark outlines the more critical things to consider.",
"Checks": [],
"Checks": [
"entra_emergency_access_exclusion"
],
"Attributes": [
{
"Section": "1 Microsoft 365 admin center",

View File

@@ -31,7 +31,9 @@
{
"Id": "1.1.2",
"Description": "Emergency access or 'break glass' accounts are limited for emergency scenarios where normal administrative accounts are unavailable. They are not assigned to a specific user and will have a combination of physical and technical controls to prevent them from being accessed outside a true emergency. Ensure two Emergency Access accounts have been defined.",
"Checks": [],
"Checks": [
"entra_emergency_access_exclusion"
],
"Attributes": [
{
"Section": "1 Microsoft 365 admin center",

View File

@@ -0,0 +1,41 @@
{
"Provider": "m365",
"CheckID": "entra_emergency_access_exclusion",
"CheckTitle": "Emergency access exclusions prevent lockout from Conditional Access policies",
"CheckType": [],
"ServiceName": "entra",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "high",
"ResourceType": "Conditional Access Policy",
"ResourceGroup": "IAM",
"Description": "This check verifies that at least one **emergency access** (break glass) account or group is excluded from all **Conditional Access policies**. Emergency access accounts provide a fallback mechanism when normal administrative access is blocked due to misconfigured policies.",
"Risk": "Without emergency access accounts excluded from Conditional Access policies, a misconfiguration could lock out all administrators from the tenant. This creates a **critical availability risk** where legitimate administrators cannot access or remediate issues in the environment.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://learn.microsoft.com/en-us/entra/identity/role-based-access-control/security-emergency-access",
"https://learn.microsoft.com/en-us/entra/identity/conditional-access/howto-conditional-access-policy-block-access"
],
"Remediation": {
"Code": {
"CLI": "",
"NativeIaC": "",
"Other": "1. Create dedicated emergency access accounts or a security group in Microsoft Entra admin center.\n2. Navigate to Protection > Conditional Access > Policies.\n3. For each Conditional Access policy, add the emergency access account or group to the exclusion list under Users > Exclude.\n4. Ensure the emergency accounts are protected with strong credentials and limited usage.",
"Terraform": ""
},
"Recommendation": {
"Text": "Create and maintain at least two emergency access accounts that are excluded from all Conditional Access policies. Store credentials securely offline, monitor usage, and test access regularly. Follow **least privilege** principles for these accounts while ensuring they can recover tenant access when needed.",
"Url": "https://hub.prowler.com/check/entra_emergency_access_exclusion"
}
},
"Categories": [
"identity-access",
"e3"
],
"DependsOn": [],
"RelatedTo": [
"entra_legacy_authentication_blocked",
"entra_managed_device_required_for_authentication"
],
"Notes": ""
}

View File

@@ -0,0 +1,111 @@
from collections import Counter
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,
)
class entra_emergency_access_exclusion(Check):
"""Check if at least one emergency access account or group is excluded from all Conditional Access policies.
This check ensures that the tenant has at least one emergency/break glass account
or account exclusion group that is excluded from all Conditional Access policies.
This prevents accidental lockout scenarios where misconfigured CA policies could
block all administrative access to the tenant.
- PASS: At least one user or group is excluded from all enabled Conditional Access policies,
or there are no enabled policies.
- FAIL: No user or group is excluded from all enabled Conditional Access policies.
"""
def execute(self) -> list[CheckReportM365]:
"""Execute the check for emergency access account exclusions.
Returns:
list[CheckReportM365]: A list containing the result of the check.
"""
findings = []
# Get all enabled CA policies (excluding disabled ones)
enabled_policies = [
policy
for policy in entra_client.conditional_access_policies.values()
if policy.state != ConditionalAccessPolicyState.DISABLED
]
# If there are no enabled policies, there's nothing to exclude from
if not enabled_policies:
report = CheckReportM365(
metadata=self.metadata(),
resource={},
resource_name="Conditional Access Policies",
resource_id="conditionalAccessPolicies",
)
report.status = "PASS"
report.status_extended = "No enabled Conditional Access policies found. Emergency access exclusions are not required."
findings.append(report)
return findings
total_policy_count = len(enabled_policies)
# Count how many policies exclude each user
excluded_users_counter = Counter()
for policy in enabled_policies:
user_conditions = policy.conditions.user_conditions
if user_conditions:
for user_id in user_conditions.excluded_users:
excluded_users_counter[user_id] += 1
# Count how many policies exclude each group
excluded_groups_counter = Counter()
for policy in enabled_policies:
user_conditions = policy.conditions.user_conditions
if user_conditions:
for group_id in user_conditions.excluded_groups:
excluded_groups_counter[group_id] += 1
# Find users excluded from ALL policies
users_excluded_from_all = [
user_id
for user_id, count in excluded_users_counter.items()
if count == total_policy_count
]
# Find groups excluded from ALL policies
groups_excluded_from_all = [
group_id
for group_id, count in excluded_groups_counter.items()
if count == total_policy_count
]
has_emergency_exclusion = bool(
users_excluded_from_all or groups_excluded_from_all
)
for policy in enabled_policies:
report = CheckReportM365(
metadata=self.metadata(),
resource=policy,
resource_name=policy.display_name,
resource_id=policy.id,
)
if has_emergency_exclusion:
report.status = "PASS"
exclusion_details = []
if users_excluded_from_all:
exclusion_details.append(f"{len(users_excluded_from_all)} user(s)")
if groups_excluded_from_all:
exclusion_details.append(
f"{len(groups_excluded_from_all)} group(s)"
)
report.status_extended = f"Conditional Access Policy '{policy.display_name}' has {' and '.join(exclusion_details)} excluded as emergency access across all {total_policy_count} enabled policies."
else:
report.status = "FAIL"
report.status_extended = f"Conditional Access Policy '{policy.display_name}' does not have any user or group excluded as emergency access from all enabled Conditional Access policies."
findings.append(report)
return findings

View File

@@ -0,0 +1,796 @@
from unittest import mock
from uuid import uuid4
from prowler.providers.m365.services.entra.entra_service import (
ApplicationsConditions,
ConditionalAccessGrantControl,
ConditionalAccessPolicyState,
Conditions,
GrantControlOperator,
GrantControls,
PersistentBrowser,
SessionControls,
SignInFrequency,
SignInFrequencyInterval,
UsersConditions,
)
from tests.providers.m365.m365_fixtures import DOMAIN, set_mocked_m365_provider
class Test_entra_emergency_access_exclusion:
def test_entra_no_conditional_access_policies(self):
"""Test when there are no Conditional Access policies."""
entra_client = mock.MagicMock
entra_client.audited_tenant = "audited_tenant"
entra_client.audited_domain = DOMAIN
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_m365_provider(),
),
mock.patch(
"prowler.providers.m365.services.entra.entra_emergency_access_exclusion.entra_emergency_access_exclusion.entra_client",
new=entra_client,
),
):
from prowler.providers.m365.services.entra.entra_emergency_access_exclusion.entra_emergency_access_exclusion import (
entra_emergency_access_exclusion,
)
entra_client.conditional_access_policies = {}
check = entra_emergency_access_exclusion()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert (
result[0].status_extended
== "No enabled Conditional Access policies found. Emergency access exclusions are not required."
)
assert result[0].resource == {}
assert result[0].resource_name == "Conditional Access Policies"
assert result[0].resource_id == "conditionalAccessPolicies"
assert result[0].location == "global"
def test_entra_all_policies_disabled(self):
"""Test when all Conditional Access policies are disabled."""
policy_id = str(uuid4())
entra_client = mock.MagicMock
entra_client.audited_tenant = "audited_tenant"
entra_client.audited_domain = DOMAIN
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_m365_provider(),
),
mock.patch(
"prowler.providers.m365.services.entra.entra_emergency_access_exclusion.entra_emergency_access_exclusion.entra_client",
new=entra_client,
),
):
from prowler.providers.m365.services.entra.entra_emergency_access_exclusion.entra_emergency_access_exclusion import (
entra_emergency_access_exclusion,
)
from prowler.providers.m365.services.entra.entra_service import (
ConditionalAccessPolicy,
)
entra_client.conditional_access_policies = {
policy_id: ConditionalAccessPolicy(
id=policy_id,
display_name="Disabled Policy",
conditions=Conditions(
application_conditions=ApplicationsConditions(
included_applications=["All"],
excluded_applications=[],
included_user_actions=[],
),
user_conditions=UsersConditions(
included_groups=[],
excluded_groups=[],
included_users=["All"],
excluded_users=[],
included_roles=[],
excluded_roles=[],
),
),
grant_controls=GrantControls(
built_in_controls=[ConditionalAccessGrantControl.MFA],
operator=GrantControlOperator.AND,
),
session_controls=SessionControls(
persistent_browser=PersistentBrowser(
is_enabled=False, mode="always"
),
sign_in_frequency=SignInFrequency(
is_enabled=False,
frequency=None,
type=None,
interval=SignInFrequencyInterval.EVERY_TIME,
),
),
state=ConditionalAccessPolicyState.DISABLED,
)
}
check = entra_emergency_access_exclusion()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert (
result[0].status_extended
== "No enabled Conditional Access policies found. Emergency access exclusions are not required."
)
def test_entra_no_emergency_access_exclusion(self):
"""Test when no user or group is excluded from all policies."""
policy_id_1 = str(uuid4())
policy_id_2 = str(uuid4())
entra_client = mock.MagicMock
entra_client.audited_tenant = "audited_tenant"
entra_client.audited_domain = DOMAIN
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_m365_provider(),
),
mock.patch(
"prowler.providers.m365.services.entra.entra_emergency_access_exclusion.entra_emergency_access_exclusion.entra_client",
new=entra_client,
),
):
from prowler.providers.m365.services.entra.entra_emergency_access_exclusion.entra_emergency_access_exclusion import (
entra_emergency_access_exclusion,
)
from prowler.providers.m365.services.entra.entra_service import (
ConditionalAccessPolicy,
)
# Policy 1 excludes user-1, Policy 2 excludes user-2
# No user is excluded from ALL policies
entra_client.conditional_access_policies = {
policy_id_1: ConditionalAccessPolicy(
id=policy_id_1,
display_name="Policy 1",
conditions=Conditions(
application_conditions=ApplicationsConditions(
included_applications=["All"],
excluded_applications=[],
included_user_actions=[],
),
user_conditions=UsersConditions(
included_groups=[],
excluded_groups=[],
included_users=["All"],
excluded_users=["user-1"],
included_roles=[],
excluded_roles=[],
),
),
grant_controls=GrantControls(
built_in_controls=[ConditionalAccessGrantControl.MFA],
operator=GrantControlOperator.AND,
),
session_controls=SessionControls(
persistent_browser=PersistentBrowser(
is_enabled=False, mode="always"
),
sign_in_frequency=SignInFrequency(
is_enabled=False,
frequency=None,
type=None,
interval=SignInFrequencyInterval.EVERY_TIME,
),
),
state=ConditionalAccessPolicyState.ENABLED,
),
policy_id_2: ConditionalAccessPolicy(
id=policy_id_2,
display_name="Policy 2",
conditions=Conditions(
application_conditions=ApplicationsConditions(
included_applications=["All"],
excluded_applications=[],
included_user_actions=[],
),
user_conditions=UsersConditions(
included_groups=[],
excluded_groups=[],
included_users=["All"],
excluded_users=["user-2"],
included_roles=[],
excluded_roles=[],
),
),
grant_controls=GrantControls(
built_in_controls=[ConditionalAccessGrantControl.MFA],
operator=GrantControlOperator.AND,
),
session_controls=SessionControls(
persistent_browser=PersistentBrowser(
is_enabled=False, mode="always"
),
sign_in_frequency=SignInFrequency(
is_enabled=False,
frequency=None,
type=None,
interval=SignInFrequencyInterval.EVERY_TIME,
),
),
state=ConditionalAccessPolicyState.ENABLED,
),
}
check = entra_emergency_access_exclusion()
result = check.execute()
assert len(result) == 2
for finding in result:
assert finding.status == "FAIL"
assert (
"does not have any user or group excluded as emergency access"
in finding.status_extended
)
assert result[0].resource_name == "Policy 1"
assert result[0].resource_id == policy_id_1
assert result[1].resource_name == "Policy 2"
assert result[1].resource_id == policy_id_2
def test_entra_user_excluded_from_all_policies(self):
"""Test when a user is excluded from all enabled policies."""
policy_id_1 = str(uuid4())
policy_id_2 = str(uuid4())
emergency_user_id = "emergency-access-user"
entra_client = mock.MagicMock
entra_client.audited_tenant = "audited_tenant"
entra_client.audited_domain = DOMAIN
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_m365_provider(),
),
mock.patch(
"prowler.providers.m365.services.entra.entra_emergency_access_exclusion.entra_emergency_access_exclusion.entra_client",
new=entra_client,
),
):
from prowler.providers.m365.services.entra.entra_emergency_access_exclusion.entra_emergency_access_exclusion import (
entra_emergency_access_exclusion,
)
from prowler.providers.m365.services.entra.entra_service import (
ConditionalAccessPolicy,
)
# Both policies exclude the emergency user
entra_client.conditional_access_policies = {
policy_id_1: ConditionalAccessPolicy(
id=policy_id_1,
display_name="Policy 1",
conditions=Conditions(
application_conditions=ApplicationsConditions(
included_applications=["All"],
excluded_applications=[],
included_user_actions=[],
),
user_conditions=UsersConditions(
included_groups=[],
excluded_groups=[],
included_users=["All"],
excluded_users=[emergency_user_id],
included_roles=[],
excluded_roles=[],
),
),
grant_controls=GrantControls(
built_in_controls=[ConditionalAccessGrantControl.MFA],
operator=GrantControlOperator.AND,
),
session_controls=SessionControls(
persistent_browser=PersistentBrowser(
is_enabled=False, mode="always"
),
sign_in_frequency=SignInFrequency(
is_enabled=False,
frequency=None,
type=None,
interval=SignInFrequencyInterval.EVERY_TIME,
),
),
state=ConditionalAccessPolicyState.ENABLED,
),
policy_id_2: ConditionalAccessPolicy(
id=policy_id_2,
display_name="Policy 2",
conditions=Conditions(
application_conditions=ApplicationsConditions(
included_applications=["All"],
excluded_applications=[],
included_user_actions=[],
),
user_conditions=UsersConditions(
included_groups=[],
excluded_groups=[],
included_users=["All"],
excluded_users=[emergency_user_id],
included_roles=[],
excluded_roles=[],
),
),
grant_controls=GrantControls(
built_in_controls=[ConditionalAccessGrantControl.MFA],
operator=GrantControlOperator.AND,
),
session_controls=SessionControls(
persistent_browser=PersistentBrowser(
is_enabled=False, mode="always"
),
sign_in_frequency=SignInFrequency(
is_enabled=False,
frequency=None,
type=None,
interval=SignInFrequencyInterval.EVERY_TIME,
),
),
state=ConditionalAccessPolicyState.ENABLED,
),
}
check = entra_emergency_access_exclusion()
result = check.execute()
assert len(result) == 2
for finding in result:
assert finding.status == "PASS"
assert (
"1 user(s) excluded as emergency access across all 2 enabled policies"
in finding.status_extended
)
assert result[0].resource_name == "Policy 1"
assert result[0].resource_id == policy_id_1
assert result[1].resource_name == "Policy 2"
assert result[1].resource_id == policy_id_2
def test_entra_group_excluded_from_all_policies(self):
"""Test when a group is excluded from all enabled policies."""
policy_id_1 = str(uuid4())
policy_id_2 = str(uuid4())
emergency_group_id = "emergency-access-group"
entra_client = mock.MagicMock
entra_client.audited_tenant = "audited_tenant"
entra_client.audited_domain = DOMAIN
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_m365_provider(),
),
mock.patch(
"prowler.providers.m365.services.entra.entra_emergency_access_exclusion.entra_emergency_access_exclusion.entra_client",
new=entra_client,
),
):
from prowler.providers.m365.services.entra.entra_emergency_access_exclusion.entra_emergency_access_exclusion import (
entra_emergency_access_exclusion,
)
from prowler.providers.m365.services.entra.entra_service import (
ConditionalAccessPolicy,
)
# Both policies exclude the emergency group
entra_client.conditional_access_policies = {
policy_id_1: ConditionalAccessPolicy(
id=policy_id_1,
display_name="Policy 1",
conditions=Conditions(
application_conditions=ApplicationsConditions(
included_applications=["All"],
excluded_applications=[],
included_user_actions=[],
),
user_conditions=UsersConditions(
included_groups=[],
excluded_groups=[emergency_group_id],
included_users=["All"],
excluded_users=[],
included_roles=[],
excluded_roles=[],
),
),
grant_controls=GrantControls(
built_in_controls=[ConditionalAccessGrantControl.MFA],
operator=GrantControlOperator.AND,
),
session_controls=SessionControls(
persistent_browser=PersistentBrowser(
is_enabled=False, mode="always"
),
sign_in_frequency=SignInFrequency(
is_enabled=False,
frequency=None,
type=None,
interval=SignInFrequencyInterval.EVERY_TIME,
),
),
state=ConditionalAccessPolicyState.ENABLED,
),
policy_id_2: ConditionalAccessPolicy(
id=policy_id_2,
display_name="Policy 2",
conditions=Conditions(
application_conditions=ApplicationsConditions(
included_applications=["All"],
excluded_applications=[],
included_user_actions=[],
),
user_conditions=UsersConditions(
included_groups=[],
excluded_groups=[emergency_group_id],
included_users=["All"],
excluded_users=[],
included_roles=[],
excluded_roles=[],
),
),
grant_controls=GrantControls(
built_in_controls=[ConditionalAccessGrantControl.MFA],
operator=GrantControlOperator.AND,
),
session_controls=SessionControls(
persistent_browser=PersistentBrowser(
is_enabled=False, mode="always"
),
sign_in_frequency=SignInFrequency(
is_enabled=False,
frequency=None,
type=None,
interval=SignInFrequencyInterval.EVERY_TIME,
),
),
state=ConditionalAccessPolicyState.ENABLED,
),
}
check = entra_emergency_access_exclusion()
result = check.execute()
assert len(result) == 2
for finding in result:
assert finding.status == "PASS"
assert (
"1 group(s) excluded as emergency access across all 2 enabled policies"
in finding.status_extended
)
def test_entra_user_and_group_excluded_from_all_policies(self):
"""Test when both a user and group are excluded from all enabled policies."""
policy_id_1 = str(uuid4())
policy_id_2 = str(uuid4())
emergency_user_id = "emergency-access-user"
emergency_group_id = "emergency-access-group"
entra_client = mock.MagicMock
entra_client.audited_tenant = "audited_tenant"
entra_client.audited_domain = DOMAIN
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_m365_provider(),
),
mock.patch(
"prowler.providers.m365.services.entra.entra_emergency_access_exclusion.entra_emergency_access_exclusion.entra_client",
new=entra_client,
),
):
from prowler.providers.m365.services.entra.entra_emergency_access_exclusion.entra_emergency_access_exclusion import (
entra_emergency_access_exclusion,
)
from prowler.providers.m365.services.entra.entra_service import (
ConditionalAccessPolicy,
)
# Both policies exclude the emergency user and group
entra_client.conditional_access_policies = {
policy_id_1: ConditionalAccessPolicy(
id=policy_id_1,
display_name="Policy 1",
conditions=Conditions(
application_conditions=ApplicationsConditions(
included_applications=["All"],
excluded_applications=[],
included_user_actions=[],
),
user_conditions=UsersConditions(
included_groups=[],
excluded_groups=[emergency_group_id],
included_users=["All"],
excluded_users=[emergency_user_id],
included_roles=[],
excluded_roles=[],
),
),
grant_controls=GrantControls(
built_in_controls=[ConditionalAccessGrantControl.MFA],
operator=GrantControlOperator.AND,
),
session_controls=SessionControls(
persistent_browser=PersistentBrowser(
is_enabled=False, mode="always"
),
sign_in_frequency=SignInFrequency(
is_enabled=False,
frequency=None,
type=None,
interval=SignInFrequencyInterval.EVERY_TIME,
),
),
state=ConditionalAccessPolicyState.ENABLED,
),
policy_id_2: ConditionalAccessPolicy(
id=policy_id_2,
display_name="Policy 2",
conditions=Conditions(
application_conditions=ApplicationsConditions(
included_applications=["All"],
excluded_applications=[],
included_user_actions=[],
),
user_conditions=UsersConditions(
included_groups=[],
excluded_groups=[emergency_group_id],
included_users=["All"],
excluded_users=[emergency_user_id],
included_roles=[],
excluded_roles=[],
),
),
grant_controls=GrantControls(
built_in_controls=[ConditionalAccessGrantControl.MFA],
operator=GrantControlOperator.AND,
),
session_controls=SessionControls(
persistent_browser=PersistentBrowser(
is_enabled=False, mode="always"
),
sign_in_frequency=SignInFrequency(
is_enabled=False,
frequency=None,
type=None,
interval=SignInFrequencyInterval.EVERY_TIME,
),
),
state=ConditionalAccessPolicyState.ENABLED,
),
}
check = entra_emergency_access_exclusion()
result = check.execute()
assert len(result) == 2
for finding in result:
assert finding.status == "PASS"
assert (
"1 user(s) and 1 group(s) excluded as emergency access across all 2 enabled policies"
in finding.status_extended
)
def test_entra_disabled_policies_ignored(self):
"""Test that disabled policies are ignored when checking exclusions."""
policy_id_1 = str(uuid4())
policy_id_2 = str(uuid4())
emergency_user_id = "emergency-access-user"
entra_client = mock.MagicMock
entra_client.audited_tenant = "audited_tenant"
entra_client.audited_domain = DOMAIN
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_m365_provider(),
),
mock.patch(
"prowler.providers.m365.services.entra.entra_emergency_access_exclusion.entra_emergency_access_exclusion.entra_client",
new=entra_client,
),
):
from prowler.providers.m365.services.entra.entra_emergency_access_exclusion.entra_emergency_access_exclusion import (
entra_emergency_access_exclusion,
)
from prowler.providers.m365.services.entra.entra_service import (
ConditionalAccessPolicy,
)
# Policy 1 is enabled and excludes user, Policy 2 is disabled (should be ignored)
entra_client.conditional_access_policies = {
policy_id_1: ConditionalAccessPolicy(
id=policy_id_1,
display_name="Enabled Policy",
conditions=Conditions(
application_conditions=ApplicationsConditions(
included_applications=["All"],
excluded_applications=[],
included_user_actions=[],
),
user_conditions=UsersConditions(
included_groups=[],
excluded_groups=[],
included_users=["All"],
excluded_users=[emergency_user_id],
included_roles=[],
excluded_roles=[],
),
),
grant_controls=GrantControls(
built_in_controls=[ConditionalAccessGrantControl.MFA],
operator=GrantControlOperator.AND,
),
session_controls=SessionControls(
persistent_browser=PersistentBrowser(
is_enabled=False, mode="always"
),
sign_in_frequency=SignInFrequency(
is_enabled=False,
frequency=None,
type=None,
interval=SignInFrequencyInterval.EVERY_TIME,
),
),
state=ConditionalAccessPolicyState.ENABLED,
),
policy_id_2: ConditionalAccessPolicy(
id=policy_id_2,
display_name="Disabled Policy",
conditions=Conditions(
application_conditions=ApplicationsConditions(
included_applications=["All"],
excluded_applications=[],
included_user_actions=[],
),
user_conditions=UsersConditions(
included_groups=[],
excluded_groups=[],
included_users=["All"],
excluded_users=[], # No exclusions
included_roles=[],
excluded_roles=[],
),
),
grant_controls=GrantControls(
built_in_controls=[ConditionalAccessGrantControl.MFA],
operator=GrantControlOperator.AND,
),
session_controls=SessionControls(
persistent_browser=PersistentBrowser(
is_enabled=False, mode="always"
),
sign_in_frequency=SignInFrequency(
is_enabled=False,
frequency=None,
type=None,
interval=SignInFrequencyInterval.EVERY_TIME,
),
),
state=ConditionalAccessPolicyState.DISABLED,
),
}
check = entra_emergency_access_exclusion()
result = check.execute()
# Only 1 enabled policy, so only 1 finding
assert len(result) == 1
assert result[0].status == "PASS"
assert result[0].resource_name == "Enabled Policy"
assert result[0].resource_id == policy_id_1
assert (
"1 user(s) excluded as emergency access across all 1 enabled policies"
in result[0].status_extended
)
def test_entra_enabled_for_reporting_policies_included(self):
"""Test that policies in reporting mode are considered enabled."""
policy_id_1 = str(uuid4())
policy_id_2 = str(uuid4())
emergency_user_id = "emergency-access-user"
entra_client = mock.MagicMock
entra_client.audited_tenant = "audited_tenant"
entra_client.audited_domain = DOMAIN
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_m365_provider(),
),
mock.patch(
"prowler.providers.m365.services.entra.entra_emergency_access_exclusion.entra_emergency_access_exclusion.entra_client",
new=entra_client,
),
):
from prowler.providers.m365.services.entra.entra_emergency_access_exclusion.entra_emergency_access_exclusion import (
entra_emergency_access_exclusion,
)
from prowler.providers.m365.services.entra.entra_service import (
ConditionalAccessPolicy,
)
# Policy 1 is enabled, Policy 2 is in reporting mode
# User is excluded from both, so it should PASS
entra_client.conditional_access_policies = {
policy_id_1: ConditionalAccessPolicy(
id=policy_id_1,
display_name="Enabled Policy",
conditions=Conditions(
application_conditions=ApplicationsConditions(
included_applications=["All"],
excluded_applications=[],
included_user_actions=[],
),
user_conditions=UsersConditions(
included_groups=[],
excluded_groups=[],
included_users=["All"],
excluded_users=[emergency_user_id],
included_roles=[],
excluded_roles=[],
),
),
grant_controls=GrantControls(
built_in_controls=[ConditionalAccessGrantControl.MFA],
operator=GrantControlOperator.AND,
),
session_controls=SessionControls(
persistent_browser=PersistentBrowser(
is_enabled=False, mode="always"
),
sign_in_frequency=SignInFrequency(
is_enabled=False,
frequency=None,
type=None,
interval=SignInFrequencyInterval.EVERY_TIME,
),
),
state=ConditionalAccessPolicyState.ENABLED,
),
policy_id_2: ConditionalAccessPolicy(
id=policy_id_2,
display_name="Reporting Policy",
conditions=Conditions(
application_conditions=ApplicationsConditions(
included_applications=["All"],
excluded_applications=[],
included_user_actions=[],
),
user_conditions=UsersConditions(
included_groups=[],
excluded_groups=[],
included_users=["All"],
excluded_users=[emergency_user_id],
included_roles=[],
excluded_roles=[],
),
),
grant_controls=GrantControls(
built_in_controls=[ConditionalAccessGrantControl.MFA],
operator=GrantControlOperator.AND,
),
session_controls=SessionControls(
persistent_browser=PersistentBrowser(
is_enabled=False, mode="always"
),
sign_in_frequency=SignInFrequency(
is_enabled=False,
frequency=None,
type=None,
interval=SignInFrequencyInterval.EVERY_TIME,
),
),
state=ConditionalAccessPolicyState.ENABLED_FOR_REPORTING,
),
}
check = entra_emergency_access_exclusion()
result = check.execute()
assert len(result) == 2
for finding in result:
assert finding.status == "PASS"
assert (
"1 user(s) excluded as emergency access across all 2 enabled policies"
in finding.status_extended
)