refactor(m365): scope entra_emergency_access_exclusion to Block-grant policies (#10849)

This commit is contained in:
Hugo Pereira Brito
2026-05-12 10:40:46 +01:00
committed by GitHub
parent 9cedbd3582
commit 80482da1cb
5 changed files with 416 additions and 110 deletions
+4
View File
@@ -17,6 +17,10 @@ All notable changes to the **Prowler SDK** are documented in this file.
- `entra_users_mfa_capable` no longer flags disabled guest users by requesting `accountEnabled` and `userType` from Microsoft Graph via `$select` and using Graph as the source of truth for `account_enabled` (EXO `Get-User` does not return guest users) [(#11002)](https://github.com/prowler-cloud/prowler/pull/11002)
### 🔄 Changed
- `entra_emergency_access_exclusion` check for M365 provider now scopes the exclusion requirement to enabled Conditional Access policies with a `Block` grant control instead of every enabled policy, focusing on the lockout-relevant policy set [(#10849)](https://github.com/prowler-cloud/prowler/pull/10849)
---
## [5.26.0] (Prowler v5.26.0)
@@ -251,6 +251,7 @@
"entra_break_glass_account_fido2_security_key_registered",
"entra_conditional_access_policy_mfa_enforced_for_guest_users",
"entra_default_app_management_policy_enabled",
"entra_emergency_access_exclusion",
"entra_all_apps_conditional_access_coverage",
"entra_conditional_access_policy_device_registration_mfa_required",
"entra_intune_enrollment_sign_in_frequency_every_time",
@@ -671,6 +672,7 @@
"entra_admin_users_phishing_resistant_mfa_enabled",
"entra_admin_users_sign_in_frequency_enabled",
"entra_break_glass_account_fido2_security_key_registered",
"entra_emergency_access_exclusion",
"entra_app_registration_no_unused_privileged_permissions",
"entra_policy_ensure_default_user_cannot_create_tenants",
"entra_policy_guest_invite_only_for_admin_roles",
@@ -727,6 +729,7 @@
"entra_conditional_access_policy_device_code_flow_blocked",
"entra_conditional_access_policy_directory_sync_account_excluded",
"entra_conditional_access_policy_corporate_device_sign_in_frequency_enforced",
"entra_emergency_access_exclusion",
"entra_identity_protection_sign_in_risk_enabled",
"entra_managed_device_required_for_authentication",
"entra_seamless_sso_disabled",
@@ -9,8 +9,8 @@
"Severity": "high",
"ResourceType": "NotDefined",
"ResourceGroup": "IAM",
"Description": "Microsoft Entra **Conditional Access** is verified to have at least one **emergency access** (break glass) account or group excluded from all 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.",
"Description": "Microsoft Entra **Conditional Access** is verified to have at least one **emergency access** (break glass) account or group excluded from every enabled Conditional Access policy with a **Block** grant control. Emergency access accounts provide a fallback mechanism when normal administrative access is blocked due to misconfigured blocking policies.",
"Risk": "Without emergency access accounts excluded from every blocking Conditional Access policy, a misconfigured Block policy can 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",
@@ -20,11 +20,11 @@
"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.",
"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 every Conditional Access policy whose grant control is **Block**, 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.",
"Text": "Create and maintain at least two emergency access accounts that are excluded from every Conditional Access policy with a **Block** grant control so they can never be denied access by a misconfiguration. 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"
}
},
@@ -3,87 +3,38 @@ 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 (
ConditionalAccessGrantControl,
ConditionalAccessPolicyState,
)
class entra_emergency_access_exclusion(Check):
"""Check if at least one emergency access account or group is excluded from all Conditional Access policies.
"""Check that at least one emergency access account or group is excluded
from every enabled Conditional Access policy with a `Block` grant control.
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.
Emergency access (break glass) accounts are, by definition, accounts that
cannot be blocked by Conditional Access. Membership of an account in the
exclusion list of every enabled blocking policy is therefore the necessary
condition for it to act as a true emergency account: if any enabled
blocking policy applies to it, a misconfiguration of that policy can lock
out 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.
- PASS: At least one user or group is excluded from every enabled
Conditional Access policy with a `Block` grant control, or no
enabled blocking Conditional Access policy exists.
- FAIL: One or more enabled blocking Conditional Access policies exist and
no user or group is excluded from all of them.
"""
def execute(self) -> list[CheckReportM365]:
"""Execute the check for emergency access account exclusions.
"""Execute the check for emergency access account exclusions from
blocking Conditional Access policies.
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
)
report = CheckReportM365(
metadata=self.metadata(),
resource={},
@@ -91,27 +42,67 @@ class entra_emergency_access_exclusion(Check):
resource_id="conditionalAccessPolicies",
)
if has_emergency_exclusion:
report.status = "PASS"
exclusion_details = []
if users_excluded_from_all:
user_names = []
for user_id in users_excluded_from_all:
user = entra_client.users.get(user_id)
user_names.append(user.name if user else user_id)
exclusion_details.append(f"user(s): {', '.join(user_names)}")
if groups_excluded_from_all:
group_names = []
groups_by_id = {g.id: g for g in entra_client.groups}
for group_id in groups_excluded_from_all:
group = groups_by_id.get(group_id)
group_names.append(group.name if group else group_id)
exclusion_details.append(f"group(s): {', '.join(group_names)}")
report.status_extended = f"Emergency access {' and '.join(exclusion_details)} excluded from all {total_policy_count} enabled Conditional Access policies."
else:
report.status = "FAIL"
report.status_extended = f"No user or group is excluded as emergency access from all {total_policy_count} enabled Conditional Access policies."
blocking_policies = [
policy
for policy in entra_client.conditional_access_policies.values()
if policy.state != ConditionalAccessPolicyState.DISABLED
and ConditionalAccessGrantControl.BLOCK
in policy.grant_controls.built_in_controls
]
if not blocking_policies:
report.status = "PASS"
report.status_extended = "No enabled Conditional Access policies with a Block grant control found. Emergency access exclusions are not required."
findings.append(report)
return findings
total_blocking_count = len(blocking_policies)
excluded_users_counter = Counter()
excluded_groups_counter = Counter()
for policy in blocking_policies:
user_conditions = policy.conditions.user_conditions
if not user_conditions:
continue
for user_id in user_conditions.excluded_users:
excluded_users_counter[user_id] += 1
for group_id in user_conditions.excluded_groups:
excluded_groups_counter[group_id] += 1
emergency_user_ids = [
user_id
for user_id, count in excluded_users_counter.items()
if count == total_blocking_count
]
emergency_group_ids = [
group_id
for group_id, count in excluded_groups_counter.items()
if count == total_blocking_count
]
if not (emergency_user_ids or emergency_group_ids):
report.status = "FAIL"
report.status_extended = f"No user or group is excluded as emergency access from all {total_blocking_count} enabled Conditional Access policies with a Block grant control."
findings.append(report)
return findings
exclusion_details = []
if emergency_user_ids:
user_names = []
for uid in emergency_user_ids:
user = entra_client.users.get(uid)
user_names.append(user.name if user else uid)
exclusion_details.append(f"user(s): {', '.join(user_names)}")
if emergency_group_ids:
groups_by_id = {g.id: g for g in entra_client.groups}
group_names = []
for gid in emergency_group_ids:
group = groups_by_id.get(gid)
group_names.append(group.name if group else gid)
exclusion_details.append(f"group(s): {', '.join(group_names)}")
report.status = "PASS"
report.status_extended = f"Emergency access {' and '.join(exclusion_details)} excluded from all {total_blocking_count} enabled Conditional Access policies with a Block grant control."
findings.append(report)
return findings
@@ -47,7 +47,7 @@ class Test_entra_emergency_access_exclusion:
assert result[0].status == "PASS"
assert (
result[0].status_extended
== "No enabled Conditional Access policies found. Emergency access exclusions are not required."
== "No enabled Conditional Access policies with a Block grant control found. Emergency access exclusions are not required."
)
assert result[0].resource == {}
assert result[0].resource_name == "Conditional Access Policies"
@@ -98,7 +98,7 @@ class Test_entra_emergency_access_exclusion:
),
),
grant_controls=GrantControls(
built_in_controls=[ConditionalAccessGrantControl.MFA],
built_in_controls=[ConditionalAccessGrantControl.BLOCK],
operator=GrantControlOperator.AND,
),
session_controls=SessionControls(
@@ -122,7 +122,7 @@ class Test_entra_emergency_access_exclusion:
assert result[0].status == "PASS"
assert (
result[0].status_extended
== "No enabled Conditional Access policies found. Emergency access exclusions are not required."
== "No enabled Conditional Access policies with a Block grant control found. Emergency access exclusions are not required."
)
def test_entra_no_emergency_access_exclusion(self):
@@ -172,7 +172,7 @@ class Test_entra_emergency_access_exclusion:
),
),
grant_controls=GrantControls(
built_in_controls=[ConditionalAccessGrantControl.MFA],
built_in_controls=[ConditionalAccessGrantControl.BLOCK],
operator=GrantControlOperator.AND,
),
session_controls=SessionControls(
@@ -207,7 +207,7 @@ class Test_entra_emergency_access_exclusion:
),
),
grant_controls=GrantControls(
built_in_controls=[ConditionalAccessGrantControl.MFA],
built_in_controls=[ConditionalAccessGrantControl.BLOCK],
operator=GrantControlOperator.AND,
),
session_controls=SessionControls(
@@ -230,7 +230,7 @@ class Test_entra_emergency_access_exclusion:
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
"No user or group is excluded as emergency access from all 2 enabled Conditional Access policies"
"No user or group is excluded as emergency access from all 2 enabled Conditional Access policies with a Block grant control"
in result[0].status_extended
)
assert result[0].resource_name == "Conditional Access Policies"
@@ -283,7 +283,7 @@ class Test_entra_emergency_access_exclusion:
),
),
grant_controls=GrantControls(
built_in_controls=[ConditionalAccessGrantControl.MFA],
built_in_controls=[ConditionalAccessGrantControl.BLOCK],
operator=GrantControlOperator.AND,
),
session_controls=SessionControls(
@@ -318,7 +318,7 @@ class Test_entra_emergency_access_exclusion:
),
),
grant_controls=GrantControls(
built_in_controls=[ConditionalAccessGrantControl.MFA],
built_in_controls=[ConditionalAccessGrantControl.BLOCK],
operator=GrantControlOperator.AND,
),
session_controls=SessionControls(
@@ -352,7 +352,7 @@ class Test_entra_emergency_access_exclusion:
assert result[0].status == "PASS"
assert "BreakGlass1" in result[0].status_extended
assert (
"excluded from all 2 enabled Conditional Access policies"
"excluded from all 2 enabled Conditional Access policies with a Block grant control"
in result[0].status_extended
)
assert result[0].resource_name == "Conditional Access Policies"
@@ -405,7 +405,7 @@ class Test_entra_emergency_access_exclusion:
),
),
grant_controls=GrantControls(
built_in_controls=[ConditionalAccessGrantControl.MFA],
built_in_controls=[ConditionalAccessGrantControl.BLOCK],
operator=GrantControlOperator.AND,
),
session_controls=SessionControls(
@@ -440,7 +440,7 @@ class Test_entra_emergency_access_exclusion:
),
),
grant_controls=GrantControls(
built_in_controls=[ConditionalAccessGrantControl.MFA],
built_in_controls=[ConditionalAccessGrantControl.BLOCK],
operator=GrantControlOperator.AND,
),
session_controls=SessionControls(
@@ -474,7 +474,7 @@ class Test_entra_emergency_access_exclusion:
assert result[0].status == "PASS"
assert "BreakGlassGroup" in result[0].status_extended
assert (
"excluded from all 2 enabled Conditional Access policies"
"excluded from all 2 enabled Conditional Access policies with a Block grant control"
in result[0].status_extended
)
assert result[0].resource_name == "Conditional Access Policies"
@@ -528,7 +528,7 @@ class Test_entra_emergency_access_exclusion:
),
),
grant_controls=GrantControls(
built_in_controls=[ConditionalAccessGrantControl.MFA],
built_in_controls=[ConditionalAccessGrantControl.BLOCK],
operator=GrantControlOperator.AND,
),
session_controls=SessionControls(
@@ -563,7 +563,7 @@ class Test_entra_emergency_access_exclusion:
),
),
grant_controls=GrantControls(
built_in_controls=[ConditionalAccessGrantControl.MFA],
built_in_controls=[ConditionalAccessGrantControl.BLOCK],
operator=GrantControlOperator.AND,
),
session_controls=SessionControls(
@@ -605,7 +605,7 @@ class Test_entra_emergency_access_exclusion:
assert "BreakGlass1" in result[0].status_extended
assert "BreakGlassGroup" in result[0].status_extended
assert (
"excluded from all 2 enabled Conditional Access policies"
"excluded from all 2 enabled Conditional Access policies with a Block grant control"
in result[0].status_extended
)
assert result[0].resource_name == "Conditional Access Policies"
@@ -658,7 +658,7 @@ class Test_entra_emergency_access_exclusion:
),
),
grant_controls=GrantControls(
built_in_controls=[ConditionalAccessGrantControl.MFA],
built_in_controls=[ConditionalAccessGrantControl.BLOCK],
operator=GrantControlOperator.AND,
),
session_controls=SessionControls(
@@ -693,7 +693,7 @@ class Test_entra_emergency_access_exclusion:
),
),
grant_controls=GrantControls(
built_in_controls=[ConditionalAccessGrantControl.MFA],
built_in_controls=[ConditionalAccessGrantControl.BLOCK],
operator=GrantControlOperator.AND,
),
session_controls=SessionControls(
@@ -729,7 +729,7 @@ class Test_entra_emergency_access_exclusion:
assert result[0].resource_id == "conditionalAccessPolicies"
assert "BreakGlass1" in result[0].status_extended
assert (
"excluded from all 1 enabled Conditional Access policies"
"excluded from all 1 enabled Conditional Access policies with a Block grant control"
in result[0].status_extended
)
@@ -781,7 +781,7 @@ class Test_entra_emergency_access_exclusion:
),
),
grant_controls=GrantControls(
built_in_controls=[ConditionalAccessGrantControl.MFA],
built_in_controls=[ConditionalAccessGrantControl.BLOCK],
operator=GrantControlOperator.AND,
),
session_controls=SessionControls(
@@ -816,7 +816,7 @@ class Test_entra_emergency_access_exclusion:
),
),
grant_controls=GrantControls(
built_in_controls=[ConditionalAccessGrantControl.MFA],
built_in_controls=[ConditionalAccessGrantControl.BLOCK],
operator=GrantControlOperator.AND,
),
session_controls=SessionControls(
@@ -850,8 +850,316 @@ class Test_entra_emergency_access_exclusion:
assert result[0].status == "PASS"
assert "BreakGlass1" in result[0].status_extended
assert (
"excluded from all 2 enabled Conditional Access policies"
"excluded from all 2 enabled Conditional Access policies with a Block grant control"
in result[0].status_extended
)
assert result[0].resource_name == "Conditional Access Policies"
assert result[0].resource_id == "conditionalAccessPolicies"
def test_entra_only_non_blocking_policies(self):
"""PASS when the tenant has only non-blocking policies (no Block grant)."""
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="MFA for all",
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.ENABLED,
),
}
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 with a Block grant control found. Emergency access exclusions are not required."
)
def test_entra_user_excluded_from_blocking_but_included_in_non_blocking(self):
"""PASS when only Block-grant exclusions are required (non-blocking inclusion is ignored)."""
block_policy_id = str(uuid4())
mfa_policy_id = 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,
)
entra_client.conditional_access_policies = {
block_policy_id: ConditionalAccessPolicy(
id=block_policy_id,
display_name="Block legacy auth",
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.BLOCK],
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,
),
mfa_policy_id: ConditionalAccessPolicy(
id=mfa_policy_id,
display_name="MFA for all",
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.ENABLED,
),
}
entra_client.users = {
emergency_user_id: User(
id=emergency_user_id,
name="BreakGlass1",
on_premises_sync_enabled=False,
authentication_methods=[],
),
}
entra_client.groups = []
check = entra_emergency_access_exclusion()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert "BreakGlass1" in result[0].status_extended
assert (
"excluded from all 1 enabled Conditional Access policies with a Block grant control"
in result[0].status_extended
)
def test_entra_user_excluded_only_from_subset_of_blocking_policies(self):
"""FAIL when the user is excluded from one Block policy but not the other."""
block_policy_id_1 = str(uuid4())
block_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,
)
entra_client.conditional_access_policies = {
block_policy_id_1: ConditionalAccessPolicy(
id=block_policy_id_1,
display_name="Block 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.BLOCK],
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,
),
block_policy_id_2: ConditionalAccessPolicy(
id=block_policy_id_2,
display_name="Block 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=[],
included_roles=[],
excluded_roles=[],
),
),
grant_controls=GrantControls(
built_in_controls=[ConditionalAccessGrantControl.BLOCK],
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,
),
}
entra_client.users = {
emergency_user_id: User(
id=emergency_user_id,
name="BreakGlass1",
on_premises_sync_enabled=False,
authentication_methods=[],
),
}
entra_client.groups = []
check = entra_emergency_access_exclusion()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== "No user or group is excluded as emergency access from all 2 enabled Conditional Access policies with a Block grant control."
)