feat(m365): add entra_conditional_access_policy_block_o365_elevated_insider_risk security check (#10232)

Co-authored-by: Daniel Barranquero <danielbo2001@gmail.com>
This commit is contained in:
Hugo Pereira Brito
2026-03-30 10:49:29 +01:00
committed by GitHub
parent 4ef0b1bf2c
commit 7148086410
7 changed files with 1146 additions and 1 deletions

View File

@@ -12,6 +12,7 @@ All notable changes to the **Prowler SDK** are documented in this file.
- `awslambda_function_no_dead_letter_queue`, `awslambda_function_using_cross_account_layers`, and `awslambda_function_env_vars_not_encrypted_with_cmk` checks for AWS Lambda [(#10381)](https://github.com/prowler-cloud/prowler/pull/10381)
- `entra_conditional_access_policy_mdm_compliant_device_required` check for M365 provider [(#10220)](https://github.com/prowler-cloud/prowler/pull/10220)
- `ec2_securitygroup_allow_ingress_from_internet_to_any_port_from_ip` check for AWS provider using `ipaddress.is_global` for accurate public IP detection [(#10335)](https://github.com/prowler-cloud/prowler/pull/10335)
- `entra_conditional_access_policy_block_o365_elevated_insider_risk` check for M365 provider [(#10232)](https://github.com/prowler-cloud/prowler/pull/10232)
- `--resource-group` and `--list-resource-groups` CLI flags to filter checks by resource group across all providers [(#10479)](https://github.com/prowler-cloud/prowler/pull/10479)
### 🔄 Changed

View File

@@ -20,6 +20,7 @@
"Checks": [
"defender_antiphishing_policy_configured",
"defender_antispam_policy_inbound_no_allowed_domains",
"entra_conditional_access_policy_block_o365_elevated_insider_risk",
"entra_identity_protection_sign_in_risk_enabled",
"entra_identity_protection_user_risk_enabled"
]
@@ -120,6 +121,7 @@
"defenderxdr_endpoint_privileged_user_exposed_credentials",
"defender_identity_health_issues_no_open",
"entra_admin_users_phishing_resistant_mfa_enabled",
"entra_conditional_access_policy_block_o365_elevated_insider_risk",
"entra_identity_protection_sign_in_risk_enabled",
"entra_identity_protection_user_risk_enabled"
]
@@ -203,6 +205,7 @@
"admincenter_users_admins_reduced_license_footprint",
"entra_admin_portals_access_restriction",
"entra_admin_users_phishing_resistant_mfa_enabled",
"entra_conditional_access_policy_block_o365_elevated_insider_risk",
"entra_policy_guest_users_access_restrictions",
"entra_seamless_sso_disabled"
]
@@ -676,6 +679,7 @@
"entra_admin_portals_access_restriction",
"entra_conditional_access_policy_approved_client_app_required_for_mobile",
"entra_conditional_access_policy_app_enforced_restrictions",
"entra_conditional_access_policy_block_o365_elevated_insider_risk",
"entra_policy_guest_users_access_restrictions",
"sharepoint_external_sharing_restricted"
]
@@ -764,7 +768,8 @@
"defender_antiphishing_policy_configured",
"defender_safelinks_policy_enabled",
"entra_admin_users_phishing_resistant_mfa_enabled",
"entra_conditional_access_policy_app_enforced_restrictions"
"entra_conditional_access_policy_app_enforced_restrictions",
"entra_conditional_access_policy_block_o365_elevated_insider_risk"
]
},
{

View File

@@ -0,0 +1,37 @@
{
"Provider": "m365",
"CheckID": "entra_conditional_access_policy_block_o365_elevated_insider_risk",
"CheckTitle": "Conditional Access policy blocks Office 365 access for users with elevated insider risk",
"CheckType": [],
"ServiceName": "entra",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "medium",
"ResourceType": "NotDefined",
"ResourceGroup": "IAM",
"Description": "A Conditional Access policy configured to **block** Office 365 access for users flagged with **elevated insider risk** by Microsoft Purview Adaptive Protection.\n\nThis control uses behavioral signals to restrict access when a user's risk level indicates potential insider threat activity.",
"Risk": "Without a policy blocking Office 365 for elevated insider risk users, compromised or malicious insiders can continue accessing email, SharePoint, OneDrive, and Teams.\n\nThis may lead to **data exfiltration**, unauthorized sharing of sensitive information, or tampering with critical business communications.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://learn.microsoft.com/en-us/purview/insider-risk-management-adaptive-protection",
"https://learn.microsoft.com/en-us/entra/identity/conditional-access/how-to-policy-insider-risk"
],
"Remediation": {
"Code": {
"CLI": "",
"NativeIaC": "",
"Other": "1. Navigate to the Microsoft Entra admin center https://entra.microsoft.com.\n2. Expand **Protection** > **Conditional Access** and select **Policies**.\n3. Click **New policy**.\n4. Under **Users**, select **All users**.\n5. Under **Target resources** > **Cloud apps**, select **Office 365**.\n6. Under **Conditions** > **Insider risk**, select **Elevated**.\n7. Under **Grant**, select **Block access**.\n8. Set the policy to **On** and click **Create**.",
"Terraform": ""
},
"Recommendation": {
"Text": "Configure a Conditional Access policy to **block** Office 365 access for users with **elevated insider risk** levels. This leverages Microsoft Purview Adaptive Protection signals to dynamically restrict access based on user behavior analysis.\n\nEnsure Insider Risk Management and Adaptive Protection are configured in Microsoft Purview as prerequisites.",
"Url": "https://hub.prowler.com/check/entra_conditional_access_policy_block_o365_elevated_insider_risk"
}
},
"Categories": [
"e5"
],
"DependsOn": [],
"RelatedTo": [],
"Notes": "Requires Microsoft 365 E5 or E5 Compliance license with Insider Risk Management configured and Adaptive Protection enabled in Microsoft Purview."
}

View File

@@ -0,0 +1,94 @@
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,
InsiderRiskLevel,
)
OFFICE365_APP_ID = "Office365"
class entra_conditional_access_policy_block_o365_elevated_insider_risk(Check):
"""Check if a Conditional Access policy blocks Office 365 access for elevated insider risk users.
This check verifies that at least one enabled Conditional Access policy blocks
access to Office 365 applications for users with elevated insider risk levels,
as determined by Microsoft Purview Adaptive Protection.
- PASS: At least one enabled policy blocks Office 365 access for users with elevated insider risk.
- FAIL: No enabled policy blocks Office 365 access based on insider risk signals.
"""
def execute(self) -> list[CheckReportM365]:
"""Execute the check for insider risk blocking in Conditional Access policies.
Returns:
list[CheckReportM365]: A list containing the result of the check.
"""
findings = []
report = CheckReportM365(
metadata=self.metadata(),
resource={},
resource_name="Conditional Access Policies",
resource_id="conditionalAccessPolicies",
)
report.status = "FAIL"
report.status_extended = "No Conditional Access Policy blocks Office 365 access for users with elevated insider risk."
for policy in entra_client.conditional_access_policies.values():
if policy.state == ConditionalAccessPolicyState.DISABLED:
continue
if "All" not in policy.conditions.user_conditions.included_users:
continue
if (
OFFICE365_APP_ID
not in policy.conditions.application_conditions.included_applications
and "All"
not in policy.conditions.application_conditions.included_applications
):
continue
if (
ConditionalAccessGrantControl.BLOCK
not in policy.grant_controls.built_in_controls
):
continue
# Policy targets all users, O365/All apps, and blocks access.
# Now check if Adaptive Protection is providing insider risk signals.
if policy.conditions.insider_risk_levels is None:
report = CheckReportM365(
metadata=self.metadata(),
resource=policy,
resource_name=policy.display_name,
resource_id=policy.id,
)
report.status = "FAIL"
if policy.state == ConditionalAccessPolicyState.ENABLED_FOR_REPORTING:
report.status_extended = f"Conditional Access Policy {policy.display_name} is configured in report-only mode to block Office 365 and Microsoft Purview Adaptive Protection is not providing insider risk signals."
else:
report.status_extended = f"Conditional Access Policy {policy.display_name} is configured to block Office 365 and Microsoft Purview Adaptive Protection is not providing insider risk signals."
continue
if policy.conditions.insider_risk_levels != InsiderRiskLevel.ELEVATED:
continue
report = CheckReportM365(
metadata=self.metadata(),
resource=policy,
resource_name=policy.display_name,
resource_id=policy.id,
)
if policy.state == ConditionalAccessPolicyState.ENABLED_FOR_REPORTING:
report.status = "FAIL"
report.status_extended = f"Conditional Access Policy {policy.display_name} reports blocking Office 365 for elevated insider risk users but does not enforce it."
else:
report.status = "PASS"
report.status_extended = f"Conditional Access Policy {policy.display_name} blocks Office 365 access for users with elevated insider risk."
break
findings.append(report)
return findings

View File

@@ -289,6 +289,21 @@ class Entra(M365Service):
[],
)
],
# The MS Graph SDK deserializes insiderRiskLevels
# as a list via get_collection_of_enum_values, so
# we take the first element when present.
insider_risk_levels=(
InsiderRiskLevel(raw_insider_risk[0].value)
if (
raw_insider_risk := getattr(
policy.conditions,
"insider_risk_levels",
None,
)
)
and raw_insider_risk
else None
),
platform_conditions=PlatformConditions(
include_platforms=[
platform
@@ -966,6 +981,17 @@ class ClientAppType(Enum):
OTHER_CLIENTS = "other"
class InsiderRiskLevel(Enum):
"""Insider risk levels for Conditional Access policies.
Reference: https://learn.microsoft.com/en-us/graph/api/resources/conditionalaccessconditionset#conditionalaccessinsiderrisklevels-values
"""
MINOR = "minor"
MODERATE = "moderate"
ELEVATED = "elevated"
class PlatformConditions(BaseModel):
"""Model representing platform conditions for Conditional Access policies."""
@@ -992,6 +1018,7 @@ class Conditions(BaseModel):
client_app_types: Optional[List[ClientAppType]]
user_risk_levels: List[RiskLevel] = []
sign_in_risk_levels: List[RiskLevel] = []
insider_risk_levels: Optional[InsiderRiskLevel] = None
platform_conditions: Optional[PlatformConditions] = None
authentication_flows: Optional[AuthenticationFlows] = None

View File

@@ -0,0 +1,981 @@
from unittest import mock
from uuid import uuid4
from prowler.providers.m365.services.entra.entra_service import (
ApplicationsConditions,
ConditionalAccessGrantControl,
ConditionalAccessPolicyState,
Conditions,
GrantControlOperator,
GrantControls,
InsiderRiskLevel,
PersistentBrowser,
SessionControls,
SignInFrequency,
SignInFrequencyInterval,
UsersConditions,
)
from tests.providers.m365.m365_fixtures import DOMAIN, set_mocked_m365_provider
class Test_entra_conditional_access_policy_block_o365_elevated_insider_risk:
def test_no_conditional_access_policies(self):
"""Test FAIL when no conditional access policies exist."""
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_conditional_access_policy_block_o365_elevated_insider_risk.entra_conditional_access_policy_block_o365_elevated_insider_risk.entra_client",
new=entra_client,
),
):
from prowler.providers.m365.services.entra.entra_conditional_access_policy_block_o365_elevated_insider_risk.entra_conditional_access_policy_block_o365_elevated_insider_risk import (
entra_conditional_access_policy_block_o365_elevated_insider_risk,
)
entra_client.conditional_access_policies = {}
check = entra_conditional_access_policy_block_o365_elevated_insider_risk()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== "No Conditional Access Policy blocks Office 365 access for users with elevated insider risk."
)
assert result[0].resource == {}
assert result[0].resource_name == "Conditional Access Policies"
assert result[0].resource_id == "conditionalAccessPolicies"
assert result[0].location == "global"
def test_policy_disabled(self):
"""Test FAIL when matching policy is disabled."""
id = str(uuid4())
display_name = "Block Insider Risk O365"
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_conditional_access_policy_block_o365_elevated_insider_risk.entra_conditional_access_policy_block_o365_elevated_insider_risk.entra_client",
new=entra_client,
),
):
from prowler.providers.m365.services.entra.entra_conditional_access_policy_block_o365_elevated_insider_risk.entra_conditional_access_policy_block_o365_elevated_insider_risk import (
entra_conditional_access_policy_block_o365_elevated_insider_risk,
)
from prowler.providers.m365.services.entra.entra_service import (
ConditionalAccessPolicy,
)
entra_client.conditional_access_policies = {
id: ConditionalAccessPolicy(
id=id,
display_name=display_name,
conditions=Conditions(
application_conditions=ApplicationsConditions(
included_applications=["Office365"],
excluded_applications=[],
included_user_actions=[],
),
user_conditions=UsersConditions(
included_groups=[],
excluded_groups=[],
included_users=["All"],
excluded_users=[],
included_roles=[],
excluded_roles=[],
),
client_app_types=[],
user_risk_levels=[],
insider_risk_levels=InsiderRiskLevel.ELEVATED,
),
grant_controls=GrantControls(
built_in_controls=[ConditionalAccessGrantControl.BLOCK],
operator=GrantControlOperator.OR,
authentication_strength=None,
),
session_controls=SessionControls(
persistent_browser=PersistentBrowser(
is_enabled=False, mode="always"
),
sign_in_frequency=SignInFrequency(
is_enabled=False,
frequency=None,
type=None,
interval=SignInFrequencyInterval.TIME_BASED,
),
),
state=ConditionalAccessPolicyState.DISABLED,
)
}
check = entra_conditional_access_policy_block_o365_elevated_insider_risk()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== "No Conditional Access Policy blocks Office 365 access for users with elevated insider risk."
)
assert result[0].resource == {}
assert result[0].resource_name == "Conditional Access Policies"
assert result[0].resource_id == "conditionalAccessPolicies"
assert result[0].location == "global"
def test_policy_enabled_for_reporting(self):
"""Test FAIL when policy is in report-only mode."""
id = str(uuid4())
display_name = "Block Insider Risk O365 Reporting"
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_conditional_access_policy_block_o365_elevated_insider_risk.entra_conditional_access_policy_block_o365_elevated_insider_risk.entra_client",
new=entra_client,
),
):
from prowler.providers.m365.services.entra.entra_conditional_access_policy_block_o365_elevated_insider_risk.entra_conditional_access_policy_block_o365_elevated_insider_risk import (
entra_conditional_access_policy_block_o365_elevated_insider_risk,
)
from prowler.providers.m365.services.entra.entra_service import (
ConditionalAccessPolicy,
)
entra_client.conditional_access_policies = {
id: ConditionalAccessPolicy(
id=id,
display_name=display_name,
conditions=Conditions(
application_conditions=ApplicationsConditions(
included_applications=["Office365"],
excluded_applications=[],
included_user_actions=[],
),
user_conditions=UsersConditions(
included_groups=[],
excluded_groups=[],
included_users=["All"],
excluded_users=[],
included_roles=[],
excluded_roles=[],
),
client_app_types=[],
user_risk_levels=[],
insider_risk_levels=InsiderRiskLevel.ELEVATED,
),
grant_controls=GrantControls(
built_in_controls=[ConditionalAccessGrantControl.BLOCK],
operator=GrantControlOperator.OR,
authentication_strength=None,
),
session_controls=SessionControls(
persistent_browser=PersistentBrowser(
is_enabled=False, mode="always"
),
sign_in_frequency=SignInFrequency(
is_enabled=False,
frequency=None,
type=None,
interval=SignInFrequencyInterval.TIME_BASED,
),
),
state=ConditionalAccessPolicyState.ENABLED_FOR_REPORTING,
)
}
check = entra_conditional_access_policy_block_o365_elevated_insider_risk()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== f"Conditional Access Policy {display_name} reports blocking Office 365 for elevated insider risk users but does not enforce it."
)
assert result[0].resource_name == display_name
assert result[0].resource_id == id
assert result[0].location == "global"
def test_policy_no_block_control(self):
"""Test FAIL when policy has insider risk but does not block."""
id = str(uuid4())
display_name = "Insider Risk MFA Only"
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_conditional_access_policy_block_o365_elevated_insider_risk.entra_conditional_access_policy_block_o365_elevated_insider_risk.entra_client",
new=entra_client,
),
):
from prowler.providers.m365.services.entra.entra_conditional_access_policy_block_o365_elevated_insider_risk.entra_conditional_access_policy_block_o365_elevated_insider_risk import (
entra_conditional_access_policy_block_o365_elevated_insider_risk,
)
from prowler.providers.m365.services.entra.entra_service import (
ConditionalAccessPolicy,
)
entra_client.conditional_access_policies = {
id: ConditionalAccessPolicy(
id=id,
display_name=display_name,
conditions=Conditions(
application_conditions=ApplicationsConditions(
included_applications=["Office365"],
excluded_applications=[],
included_user_actions=[],
),
user_conditions=UsersConditions(
included_groups=[],
excluded_groups=[],
included_users=["All"],
excluded_users=[],
included_roles=[],
excluded_roles=[],
),
client_app_types=[],
user_risk_levels=[],
insider_risk_levels=InsiderRiskLevel.ELEVATED,
),
grant_controls=GrantControls(
built_in_controls=[ConditionalAccessGrantControl.MFA],
operator=GrantControlOperator.OR,
authentication_strength=None,
),
session_controls=SessionControls(
persistent_browser=PersistentBrowser(
is_enabled=False, mode="always"
),
sign_in_frequency=SignInFrequency(
is_enabled=False,
frequency=None,
type=None,
interval=SignInFrequencyInterval.TIME_BASED,
),
),
state=ConditionalAccessPolicyState.ENABLED,
)
}
check = entra_conditional_access_policy_block_o365_elevated_insider_risk()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== "No Conditional Access Policy blocks Office 365 access for users with elevated insider risk."
)
assert result[0].resource == {}
assert result[0].resource_name == "Conditional Access Policies"
assert result[0].resource_id == "conditionalAccessPolicies"
assert result[0].location == "global"
def test_policy_no_insider_risk_levels_adaptive_protection_not_configured(self):
"""Test FAIL when policy matches but Adaptive Protection is not configured (insider_risk_levels is None)."""
id = str(uuid4())
display_name = "Block O365 No Insider Risk"
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_conditional_access_policy_block_o365_elevated_insider_risk.entra_conditional_access_policy_block_o365_elevated_insider_risk.entra_client",
new=entra_client,
),
):
from prowler.providers.m365.services.entra.entra_conditional_access_policy_block_o365_elevated_insider_risk.entra_conditional_access_policy_block_o365_elevated_insider_risk import (
entra_conditional_access_policy_block_o365_elevated_insider_risk,
)
from prowler.providers.m365.services.entra.entra_service import (
ConditionalAccessPolicy,
)
entra_client.conditional_access_policies = {
id: ConditionalAccessPolicy(
id=id,
display_name=display_name,
conditions=Conditions(
application_conditions=ApplicationsConditions(
included_applications=["Office365"],
excluded_applications=[],
included_user_actions=[],
),
user_conditions=UsersConditions(
included_groups=[],
excluded_groups=[],
included_users=["All"],
excluded_users=[],
included_roles=[],
excluded_roles=[],
),
client_app_types=[],
user_risk_levels=[],
insider_risk_levels=None,
),
grant_controls=GrantControls(
built_in_controls=[ConditionalAccessGrantControl.BLOCK],
operator=GrantControlOperator.OR,
authentication_strength=None,
),
session_controls=SessionControls(
persistent_browser=PersistentBrowser(
is_enabled=False, mode="always"
),
sign_in_frequency=SignInFrequency(
is_enabled=False,
frequency=None,
type=None,
interval=SignInFrequencyInterval.TIME_BASED,
),
),
state=ConditionalAccessPolicyState.ENABLED,
)
}
check = entra_conditional_access_policy_block_o365_elevated_insider_risk()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== f"Conditional Access Policy {display_name} is configured to block Office 365 and Microsoft Purview Adaptive Protection is not providing insider risk signals."
)
assert result[0].resource_name == display_name
assert result[0].resource_id == id
assert result[0].location == "global"
def test_policy_report_only_adaptive_protection_not_configured(self):
"""Test FAIL when policy is report-only and Adaptive Protection is not configured."""
id = str(uuid4())
display_name = "Block O365 Report Only No Purview"
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_conditional_access_policy_block_o365_elevated_insider_risk.entra_conditional_access_policy_block_o365_elevated_insider_risk.entra_client",
new=entra_client,
),
):
from prowler.providers.m365.services.entra.entra_conditional_access_policy_block_o365_elevated_insider_risk.entra_conditional_access_policy_block_o365_elevated_insider_risk import (
entra_conditional_access_policy_block_o365_elevated_insider_risk,
)
from prowler.providers.m365.services.entra.entra_service import (
ConditionalAccessPolicy,
)
entra_client.conditional_access_policies = {
id: ConditionalAccessPolicy(
id=id,
display_name=display_name,
conditions=Conditions(
application_conditions=ApplicationsConditions(
included_applications=["Office365"],
excluded_applications=[],
included_user_actions=[],
),
user_conditions=UsersConditions(
included_groups=[],
excluded_groups=[],
included_users=["All"],
excluded_users=[],
included_roles=[],
excluded_roles=[],
),
client_app_types=[],
user_risk_levels=[],
insider_risk_levels=None,
),
grant_controls=GrantControls(
built_in_controls=[ConditionalAccessGrantControl.BLOCK],
operator=GrantControlOperator.OR,
authentication_strength=None,
),
session_controls=SessionControls(
persistent_browser=PersistentBrowser(
is_enabled=False, mode="always"
),
sign_in_frequency=SignInFrequency(
is_enabled=False,
frequency=None,
type=None,
interval=SignInFrequencyInterval.TIME_BASED,
),
),
state=ConditionalAccessPolicyState.ENABLED_FOR_REPORTING,
)
}
check = entra_conditional_access_policy_block_o365_elevated_insider_risk()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== f"Conditional Access Policy {display_name} is configured in report-only mode to block Office 365 and Microsoft Purview Adaptive Protection is not providing insider risk signals."
)
assert result[0].resource_name == display_name
assert result[0].resource_id == id
assert result[0].location == "global"
def test_policy_not_targeting_all_users(self):
"""Test FAIL when policy does not target all users."""
id = str(uuid4())
display_name = "Block Insider Risk Limited Users"
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_conditional_access_policy_block_o365_elevated_insider_risk.entra_conditional_access_policy_block_o365_elevated_insider_risk.entra_client",
new=entra_client,
),
):
from prowler.providers.m365.services.entra.entra_conditional_access_policy_block_o365_elevated_insider_risk.entra_conditional_access_policy_block_o365_elevated_insider_risk import (
entra_conditional_access_policy_block_o365_elevated_insider_risk,
)
from prowler.providers.m365.services.entra.entra_service import (
ConditionalAccessPolicy,
)
entra_client.conditional_access_policies = {
id: ConditionalAccessPolicy(
id=id,
display_name=display_name,
conditions=Conditions(
application_conditions=ApplicationsConditions(
included_applications=["Office365"],
excluded_applications=[],
included_user_actions=[],
),
user_conditions=UsersConditions(
included_groups=[str(uuid4())],
excluded_groups=[],
included_users=[],
excluded_users=[],
included_roles=[],
excluded_roles=[],
),
client_app_types=[],
user_risk_levels=[],
insider_risk_levels=InsiderRiskLevel.ELEVATED,
),
grant_controls=GrantControls(
built_in_controls=[ConditionalAccessGrantControl.BLOCK],
operator=GrantControlOperator.OR,
authentication_strength=None,
),
session_controls=SessionControls(
persistent_browser=PersistentBrowser(
is_enabled=False, mode="always"
),
sign_in_frequency=SignInFrequency(
is_enabled=False,
frequency=None,
type=None,
interval=SignInFrequencyInterval.TIME_BASED,
),
),
state=ConditionalAccessPolicyState.ENABLED,
)
}
check = entra_conditional_access_policy_block_o365_elevated_insider_risk()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== "No Conditional Access Policy blocks Office 365 access for users with elevated insider risk."
)
assert result[0].resource == {}
assert result[0].resource_name == "Conditional Access Policies"
assert result[0].resource_id == "conditionalAccessPolicies"
assert result[0].location == "global"
def test_policy_not_targeting_o365(self):
"""Test FAIL when policy does not target Office 365 applications."""
id = str(uuid4())
display_name = "Block Insider Risk Other Apps"
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_conditional_access_policy_block_o365_elevated_insider_risk.entra_conditional_access_policy_block_o365_elevated_insider_risk.entra_client",
new=entra_client,
),
):
from prowler.providers.m365.services.entra.entra_conditional_access_policy_block_o365_elevated_insider_risk.entra_conditional_access_policy_block_o365_elevated_insider_risk import (
entra_conditional_access_policy_block_o365_elevated_insider_risk,
)
from prowler.providers.m365.services.entra.entra_service import (
ConditionalAccessPolicy,
)
entra_client.conditional_access_policies = {
id: ConditionalAccessPolicy(
id=id,
display_name=display_name,
conditions=Conditions(
application_conditions=ApplicationsConditions(
included_applications=["some-other-app-id"],
excluded_applications=[],
included_user_actions=[],
),
user_conditions=UsersConditions(
included_groups=[],
excluded_groups=[],
included_users=["All"],
excluded_users=[],
included_roles=[],
excluded_roles=[],
),
client_app_types=[],
user_risk_levels=[],
insider_risk_levels=InsiderRiskLevel.ELEVATED,
),
grant_controls=GrantControls(
built_in_controls=[ConditionalAccessGrantControl.BLOCK],
operator=GrantControlOperator.OR,
authentication_strength=None,
),
session_controls=SessionControls(
persistent_browser=PersistentBrowser(
is_enabled=False, mode="always"
),
sign_in_frequency=SignInFrequency(
is_enabled=False,
frequency=None,
type=None,
interval=SignInFrequencyInterval.TIME_BASED,
),
),
state=ConditionalAccessPolicyState.ENABLED,
)
}
check = entra_conditional_access_policy_block_o365_elevated_insider_risk()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== "No Conditional Access Policy blocks Office 365 access for users with elevated insider risk."
)
assert result[0].resource == {}
assert result[0].resource_name == "Conditional Access Policies"
assert result[0].resource_id == "conditionalAccessPolicies"
assert result[0].location == "global"
def test_policy_minor_insider_risk(self):
"""Test FAIL when policy only targets minor insider risk instead of elevated."""
id = str(uuid4())
display_name = "Block Minor Insider Risk O365"
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_conditional_access_policy_block_o365_elevated_insider_risk.entra_conditional_access_policy_block_o365_elevated_insider_risk.entra_client",
new=entra_client,
),
):
from prowler.providers.m365.services.entra.entra_conditional_access_policy_block_o365_elevated_insider_risk.entra_conditional_access_policy_block_o365_elevated_insider_risk import (
entra_conditional_access_policy_block_o365_elevated_insider_risk,
)
from prowler.providers.m365.services.entra.entra_service import (
ConditionalAccessPolicy,
)
entra_client.conditional_access_policies = {
id: ConditionalAccessPolicy(
id=id,
display_name=display_name,
conditions=Conditions(
application_conditions=ApplicationsConditions(
included_applications=["Office365"],
excluded_applications=[],
included_user_actions=[],
),
user_conditions=UsersConditions(
included_groups=[],
excluded_groups=[],
included_users=["All"],
excluded_users=[],
included_roles=[],
excluded_roles=[],
),
client_app_types=[],
user_risk_levels=[],
insider_risk_levels=InsiderRiskLevel.MINOR,
),
grant_controls=GrantControls(
built_in_controls=[ConditionalAccessGrantControl.BLOCK],
operator=GrantControlOperator.OR,
authentication_strength=None,
),
session_controls=SessionControls(
persistent_browser=PersistentBrowser(
is_enabled=False, mode="always"
),
sign_in_frequency=SignInFrequency(
is_enabled=False,
frequency=None,
type=None,
interval=SignInFrequencyInterval.TIME_BASED,
),
),
state=ConditionalAccessPolicyState.ENABLED,
)
}
check = entra_conditional_access_policy_block_o365_elevated_insider_risk()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== "No Conditional Access Policy blocks Office 365 access for users with elevated insider risk."
)
assert result[0].resource == {}
assert result[0].resource_name == "Conditional Access Policies"
assert result[0].resource_id == "conditionalAccessPolicies"
assert result[0].location == "global"
def test_policy_enabled_pass(self):
"""Test PASS when policy is enabled and blocks O365 for elevated insider risk."""
id = str(uuid4())
display_name = "Block Insider Risk O365"
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_conditional_access_policy_block_o365_elevated_insider_risk.entra_conditional_access_policy_block_o365_elevated_insider_risk.entra_client",
new=entra_client,
),
):
from prowler.providers.m365.services.entra.entra_conditional_access_policy_block_o365_elevated_insider_risk.entra_conditional_access_policy_block_o365_elevated_insider_risk import (
entra_conditional_access_policy_block_o365_elevated_insider_risk,
)
from prowler.providers.m365.services.entra.entra_service import (
ConditionalAccessPolicy,
)
entra_client.conditional_access_policies = {
id: ConditionalAccessPolicy(
id=id,
display_name=display_name,
conditions=Conditions(
application_conditions=ApplicationsConditions(
included_applications=["Office365"],
excluded_applications=[],
included_user_actions=[],
),
user_conditions=UsersConditions(
included_groups=[],
excluded_groups=[],
included_users=["All"],
excluded_users=[],
included_roles=[],
excluded_roles=[],
),
client_app_types=[],
user_risk_levels=[],
insider_risk_levels=InsiderRiskLevel.ELEVATED,
),
grant_controls=GrantControls(
built_in_controls=[ConditionalAccessGrantControl.BLOCK],
operator=GrantControlOperator.OR,
authentication_strength=None,
),
session_controls=SessionControls(
persistent_browser=PersistentBrowser(
is_enabled=False, mode="always"
),
sign_in_frequency=SignInFrequency(
is_enabled=False,
frequency=None,
type=None,
interval=SignInFrequencyInterval.TIME_BASED,
),
),
state=ConditionalAccessPolicyState.ENABLED,
)
}
check = entra_conditional_access_policy_block_o365_elevated_insider_risk()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert (
result[0].status_extended
== f"Conditional Access Policy {display_name} blocks Office 365 access for users with elevated insider risk."
)
assert result[0].resource_name == display_name
assert result[0].resource_id == id
assert result[0].location == "global"
def test_mixed_policies_report_only_and_enabled_pass(self):
"""Test PASS when both a report-only and an enabled policy exist."""
report_only_id = str(uuid4())
enabled_id = str(uuid4())
enabled_display_name = "Block Insider Risk O365 Enabled"
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_conditional_access_policy_block_o365_elevated_insider_risk.entra_conditional_access_policy_block_o365_elevated_insider_risk.entra_client",
new=entra_client,
),
):
from prowler.providers.m365.services.entra.entra_conditional_access_policy_block_o365_elevated_insider_risk.entra_conditional_access_policy_block_o365_elevated_insider_risk import (
entra_conditional_access_policy_block_o365_elevated_insider_risk,
)
from prowler.providers.m365.services.entra.entra_service import (
ConditionalAccessPolicy,
)
entra_client.conditional_access_policies = {
report_only_id: ConditionalAccessPolicy(
id=report_only_id,
display_name="Block Insider Risk O365 Report Only",
conditions=Conditions(
application_conditions=ApplicationsConditions(
included_applications=["Office365"],
excluded_applications=[],
included_user_actions=[],
),
user_conditions=UsersConditions(
included_groups=[],
excluded_groups=[],
included_users=["All"],
excluded_users=[],
included_roles=[],
excluded_roles=[],
),
client_app_types=[],
user_risk_levels=[],
insider_risk_levels=InsiderRiskLevel.ELEVATED,
),
grant_controls=GrantControls(
built_in_controls=[ConditionalAccessGrantControl.BLOCK],
operator=GrantControlOperator.OR,
authentication_strength=None,
),
session_controls=SessionControls(
persistent_browser=PersistentBrowser(
is_enabled=False, mode="always"
),
sign_in_frequency=SignInFrequency(
is_enabled=False,
frequency=None,
type=None,
interval=SignInFrequencyInterval.TIME_BASED,
),
),
state=ConditionalAccessPolicyState.ENABLED_FOR_REPORTING,
),
enabled_id: ConditionalAccessPolicy(
id=enabled_id,
display_name=enabled_display_name,
conditions=Conditions(
application_conditions=ApplicationsConditions(
included_applications=["Office365"],
excluded_applications=[],
included_user_actions=[],
),
user_conditions=UsersConditions(
included_groups=[],
excluded_groups=[],
included_users=["All"],
excluded_users=[],
included_roles=[],
excluded_roles=[],
),
client_app_types=[],
user_risk_levels=[],
insider_risk_levels=InsiderRiskLevel.ELEVATED,
),
grant_controls=GrantControls(
built_in_controls=[ConditionalAccessGrantControl.BLOCK],
operator=GrantControlOperator.OR,
authentication_strength=None,
),
session_controls=SessionControls(
persistent_browser=PersistentBrowser(
is_enabled=False, mode="always"
),
sign_in_frequency=SignInFrequency(
is_enabled=False,
frequency=None,
type=None,
interval=SignInFrequencyInterval.TIME_BASED,
),
),
state=ConditionalAccessPolicyState.ENABLED,
),
}
check = entra_conditional_access_policy_block_o365_elevated_insider_risk()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert (
result[0].status_extended
== f"Conditional Access Policy {enabled_display_name} blocks Office 365 access for users with elevated insider risk."
)
assert result[0].resource_name == enabled_display_name
assert result[0].resource_id == enabled_id
assert result[0].location == "global"
def test_policy_all_apps_pass(self):
"""Test PASS when policy targets all apps (which includes Office 365)."""
id = str(uuid4())
display_name = "Block Insider Risk All Apps"
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_conditional_access_policy_block_o365_elevated_insider_risk.entra_conditional_access_policy_block_o365_elevated_insider_risk.entra_client",
new=entra_client,
),
):
from prowler.providers.m365.services.entra.entra_conditional_access_policy_block_o365_elevated_insider_risk.entra_conditional_access_policy_block_o365_elevated_insider_risk import (
entra_conditional_access_policy_block_o365_elevated_insider_risk,
)
from prowler.providers.m365.services.entra.entra_service import (
ConditionalAccessPolicy,
)
entra_client.conditional_access_policies = {
id: ConditionalAccessPolicy(
id=id,
display_name=display_name,
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=[],
),
client_app_types=[],
user_risk_levels=[],
insider_risk_levels=InsiderRiskLevel.ELEVATED,
),
grant_controls=GrantControls(
built_in_controls=[ConditionalAccessGrantControl.BLOCK],
operator=GrantControlOperator.OR,
authentication_strength=None,
),
session_controls=SessionControls(
persistent_browser=PersistentBrowser(
is_enabled=False, mode="always"
),
sign_in_frequency=SignInFrequency(
is_enabled=False,
frequency=None,
type=None,
interval=SignInFrequencyInterval.TIME_BASED,
),
),
state=ConditionalAccessPolicyState.ENABLED,
)
}
check = entra_conditional_access_policy_block_o365_elevated_insider_risk()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert (
result[0].status_extended
== f"Conditional Access Policy {display_name} blocks Office 365 access for users with elevated insider risk."
)
assert result[0].resource_name == display_name
assert result[0].resource_id == id
assert result[0].location == "global"