feat(m365): add CIS M365 v7.0.0 defender preset policy checks (#12148)

Co-authored-by: Daniel Barranquero <danielbo2001@gmail.com>
This commit is contained in:
Pedro Martín
2026-08-04 11:31:50 +02:00
committed by GitHub
co-authored by Daniel Barranquero
parent 162c6560d9
commit 0f39665ece
13 changed files with 985 additions and 2 deletions
@@ -0,0 +1 @@
`defender_priority_account_protection_enabled` and `defender_strict_preset_security_policy_enabled` checks for M365 provider, covering CIS Microsoft 365 Foundations Benchmark v7.0.0 controls 2.4.1 and 2.4.2
+6 -2
View File
@@ -770,7 +770,9 @@
{
"Id": "2.4.1",
"Description": "Identify priority accounts to utilize Microsoft 365's advanced custom security features. This is an essential tool to bolster protection for users who are frequently targeted due to their critical positions, such as executives, leaders, managers, or others who have access to sensitive, confidential, financial, or high-priority information. Once these accounts are identified, several services and features can be enabled, including threat policies, enhanced sign-in protection through conditional access policies, and alert policies, enabling faster response times for incident response teams.",
"Checks": [],
"Checks": [
"defender_priority_account_protection_enabled"
],
"Attributes": [
{
"Section": "2 Microsoft Defender",
@@ -791,7 +793,9 @@
{
"Id": "2.4.2",
"Description": "Preset security policies have been established by Microsoft, utilizing observations and experiences within datacenters to strike a balance between the exclusion of malicious content from users and limiting unwarranted disruptions. These policies can apply to all, or select users and encompass recommendations for addressing spam, malware, and phishing threats. The policy parameters are pre-determined and non-adjustable. Strict protection has the most aggressive protection of the 3 presets. - EOP: Anti-spam, Anti-malware and Anti-phishing - Defender: Spoof protection, Impersonation protection and Advanced phishing - Defender: Safe Links and Safe Attachments NOTE: The preset security polices cannot target Priority account TAGS currently, groups should be used instead.",
"Checks": [],
"Checks": [
"defender_strict_preset_security_policy_enabled"
],
"Attributes": [
{
"Section": "2 Microsoft Defender",
@@ -402,6 +402,39 @@ class M365PowerShell(PowerShellSession):
"Get-MalwareFilterPolicy | ConvertTo-Json -Depth 10", json_parse=True
)
def get_eop_protection_policy_rule(self) -> dict:
"""
Get Exchange Online Protection (EOP) preset security policy rules.
Returns:
dict: EOP protection policy rules in JSON format.
"""
return self.execute(
"Get-EOPProtectionPolicyRule | ConvertTo-Json -Depth 10", json_parse=True
)
def get_atp_protection_policy_rule(self) -> dict:
"""
Get Defender for Office 365 (ATP) preset security policy rules.
Returns:
dict: ATP protection policy rules in JSON format.
"""
return self.execute(
"Get-ATPProtectionPolicyRule | ConvertTo-Json -Depth 10", json_parse=True
)
def get_email_tenant_settings(self) -> dict:
"""
Get Defender email tenant settings.
Returns:
dict: Email tenant settings (e.g. EnablePriorityAccountProtection).
"""
return self.execute(
"Get-EmailTenantSettings | ConvertTo-Json -Depth 10", json_parse=True
)
def get_malware_filter_rule(self) -> dict:
"""
Get Defender Malware Filter Rule.
@@ -0,0 +1,37 @@
{
"Provider": "m365",
"CheckID": "defender_priority_account_protection_enabled",
"CheckTitle": "Priority account protection is enabled",
"CheckType": [],
"ServiceName": "defender",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "medium",
"ResourceType": "NotDefined",
"ResourceGroup": "security",
"Description": "Priority account protection applies enhanced monitoring and protection to high-value accounts. The tenant-level flag **EnablePriorityAccountProtection** (from Get-EmailTenantSettings) should be enabled. This check evaluates the tenant-level enablement flag; tagging priority accounts and configuring alert policies must be verified separately.",
"Risk": "Without **priority account** protection, high-value targets such as executives receive the same protection as standard users, despite being far more likely to be targeted by **phishing** and **business email compromise**.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://learn.microsoft.com/en-us/defender-office-365/priority-accounts-security-recommendations"
],
"Remediation": {
"Code": {
"CLI": "",
"NativeIaC": "",
"Other": "1. Navigate to Microsoft Defender at https://security.microsoft.com/\n2. Go to **System** > **Settings** > **Email & collaboration** > **Priority account protection**\n3. Set **Priority account protection** to **On**\n4. Tag priority accounts and configure the associated alert policies",
"Terraform": ""
},
"Recommendation": {
"Text": "Enable priority account protection, tag your high-value accounts as priority accounts, and configure the associated alert policies for enhanced monitoring.",
"Url": "https://hub.prowler.com/check/defender_priority_account_protection_enabled"
}
},
"Categories": [
"email-security",
"e5"
],
"DependsOn": [],
"RelatedTo": [],
"Notes": "Covers the tenant-level EnablePriorityAccountProtection flag only; alert-policy verification (Get-ProtectionAlert) requires a Security & Compliance PowerShell session that Prowler does not currently establish."
}
@@ -0,0 +1,56 @@
from typing import List
from prowler.lib.check.models import Check, CheckReportM365
from prowler.providers.m365.services.defender.defender_client import defender_client
class defender_priority_account_protection_enabled(Check):
"""Check if priority account protection is enabled.
Priority account protection applies enhanced monitoring and protection to
high-value accounts. Its tenant-level flag ``EnablePriorityAccountProtection``
(from Get-EmailTenantSettings) should be enabled.
Note: This check covers the tenant-level enablement flag only. The full control
also requires priority accounts to be tagged and alert policies to be configured,
which must be verified manually.
- PASS: Priority account protection is enabled at the tenant level.
- FAIL: Priority account protection is disabled at the tenant level.
"""
def execute(self) -> List[CheckReportM365]:
"""Execute the priority account protection check.
Evaluates the tenant-level ``EnablePriorityAccountProtection`` flag from
the Defender email tenant settings, producing PASS when enabled and FAIL
when disabled. Returns no findings when settings are unavailable.
Returns:
List[CheckReportM365]: A list with the check report, or empty when no
email tenant settings are available.
"""
findings = []
settings = defender_client.email_tenant_settings
if not settings:
return findings
report = CheckReportM365(
metadata=self.metadata(),
resource=settings,
resource_name="Email Tenant Settings",
resource_id="emailTenantSettings",
)
report.status = "FAIL"
report.status_extended = (
"Priority account protection is not enabled at the tenant level."
)
if settings.priority_account_protection_enabled:
report.status = "PASS"
report.status_extended = (
"Priority account protection is enabled at the tenant level."
)
findings.append(report)
return findings
@@ -59,6 +59,9 @@ class Defender(M365Service):
self.safe_links_policies = {}
self.safe_links_rules = {}
self.teams_protection_policy = None
self.eop_protection_policy_rules = None
self.atp_protection_policy_rules = None
self.email_tenant_settings = None
if self.powershell:
if self.powershell.connect_exchange_online():
self.malware_policies = self._get_malware_filter_policy()
@@ -80,8 +83,102 @@ class Defender(M365Service):
self.safe_links_policies = self._get_safe_links_policy()
self.safe_links_rules = self._get_safe_links_rule()
self.teams_protection_policy = self._get_teams_protection_policy()
self.eop_protection_policy_rules = (
self._get_eop_protection_policy_rules()
)
self.atp_protection_policy_rules = (
self._get_atp_protection_policy_rules()
)
self.email_tenant_settings = self._get_email_tenant_settings()
self.powershell.close()
def _parse_protection_policy_rules(self, rules_data):
"""Parse preset security policy rules into PresetSecurityPolicyRule models."""
rules = []
if not rules_data:
return rules
if isinstance(rules_data, dict):
rules_data = [rules_data]
for rule in rules_data:
if rule:
rules.append(
PresetSecurityPolicyRule(
name=rule.get("Name", rule.get("Identity", "")),
state=rule.get("State", ""),
sent_to=self._normalize_list(rule.get("SentTo")),
sent_to_member_of=self._normalize_list(
rule.get("SentToMemberOf")
),
recipient_domain_is=self._normalize_list(
rule.get("RecipientDomainIs")
),
)
)
return rules
@staticmethod
def _normalize_list(value):
"""Normalize a PowerShell scalar/list/None value into a list."""
if value is None:
return []
if isinstance(value, list):
return value
return [value]
def _get_eop_protection_policy_rules(self):
"""Retrieve the EOP preset security policy rules.
Returns:
Optional[List[PresetSecurityPolicyRule]]: The parsed rules (empty when
the tenant has none), or None on error so checks can skip instead of
reporting on missing data.
"""
logger.info("M365 - Getting Defender EOP protection policy rules...")
try:
return self._parse_protection_policy_rules(
self.powershell.get_eop_protection_policy_rule()
)
except Exception as error:
logger.error(
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
return None
def _get_atp_protection_policy_rules(self):
"""Retrieve the Defender for Office 365 (ATP) preset security policy rules.
Returns:
Optional[List[PresetSecurityPolicyRule]]: The parsed rules (empty when
the tenant has none), or None on error so checks can skip instead of
reporting on missing data.
"""
logger.info("M365 - Getting Defender ATP protection policy rules...")
try:
return self._parse_protection_policy_rules(
self.powershell.get_atp_protection_policy_rule()
)
except Exception as error:
logger.error(
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
return None
def _get_email_tenant_settings(self):
logger.info("M365 - Getting Defender email tenant settings...")
try:
data = self.powershell.get_email_tenant_settings()
if data:
return EmailTenantSettings(
priority_account_protection_enabled=data.get(
"EnablePriorityAccountProtection", False
),
)
except Exception as error:
logger.error(
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
return None
def _get_malware_filter_policy(self):
logger.info("M365 - Getting Defender malware filter policy...")
malware_policies = []
@@ -826,3 +923,22 @@ class TeamsProtectionPolicy(BaseModel):
identity: str
zap_enabled: bool
class PresetSecurityPolicyRule(BaseModel):
"""Model for a preset security policy rule (EOP or ATP).
Empty recipient conditions mean the rule applies to all recipients.
"""
name: str = ""
state: str = ""
sent_to: list = []
sent_to_member_of: list = []
recipient_domain_is: list = []
class EmailTenantSettings(BaseModel):
"""Model for Defender email tenant settings."""
priority_account_protection_enabled: bool = False
@@ -0,0 +1,37 @@
{
"Provider": "m365",
"CheckID": "defender_strict_preset_security_policy_enabled",
"CheckTitle": "Strict Preset Security Policy is enabled",
"CheckType": [],
"ServiceName": "defender",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "medium",
"ResourceType": "NotDefined",
"ResourceGroup": "security",
"Description": "The **Strict Preset Security Policy** applies Microsoft's recommended strict protection settings and should be enabled for both **Exchange Online Protection** (anti-phishing, anti-spam, anti-malware) and **Defender for Office 365** (Safe Attachments, Safe Links). The rules are exposed via Get-EOPProtectionPolicyRule and Get-ATPProtectionPolicyRule.",
"Risk": "Without the **Strict Preset Security Policy** enabled, mailboxes rely on weaker default or custom protection settings, increasing exposure to **phishing**, malware, and malicious links and attachments.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://learn.microsoft.com/en-us/defender-office-365/preset-security-policies"
],
"Remediation": {
"Code": {
"CLI": "",
"NativeIaC": "",
"Other": "1. Navigate to Microsoft Defender at https://security.microsoft.com/\n2. Go to **Email & collaboration** > **Policies & rules** > **Threat policies** > **Preset security policies**\n3. Turn on the **Strict protection** preset and assign it to the appropriate users, groups, or domains (including priority accounts)",
"Terraform": ""
},
"Recommendation": {
"Text": "Enable the Strict Preset Security Policy for both Exchange Online Protection and Defender for Office 365 and assign it to your users and priority accounts.",
"Url": "https://hub.prowler.com/check/defender_strict_preset_security_policy_enabled"
}
},
"Categories": [
"email-security",
"e5"
],
"DependsOn": [],
"RelatedTo": [],
"Notes": ""
}
@@ -0,0 +1,91 @@
from typing import List
from prowler.lib.check.models import Check, CheckReportM365
from prowler.providers.m365.services.defender.defender_client import defender_client
STRICT_PRESET_NAME = "Strict Preset Security Policy"
class defender_strict_preset_security_policy_enabled(Check):
"""Check if the Strict Preset Security Policy is enabled for EOP and Defender.
The Strict Preset Security Policy applies Microsoft's recommended strict
protection settings. It should be enabled for both Exchange Online Protection
(anti-phishing, anti-spam, anti-malware) and Defender for Office 365 (Safe
Attachments, Safe Links). A rule with no recipient conditions applies to all
recipients.
- PASS: The Strict Preset Security Policy is enabled for both EOP and Defender.
- FAIL: The Strict Preset Security Policy is not enabled for EOP and/or Defender.
"""
def _has_enabled_strict_preset(self, rules) -> bool:
"""Check whether any rule enables the Strict Preset Security Policy.
A rule qualifies when it is named the Strict Preset Security Policy and is
in the ``Enabled`` state. Recipient conditions are not evaluated because
empty conditions mean the rule applies to all recipients.
Args:
rules: Iterable of preset security policy rules (EOP or ATP).
Returns:
bool: True if at least one rule enables the Strict Preset Security
Policy, False otherwise.
"""
return any(
rule.name == STRICT_PRESET_NAME and rule.state == "Enabled"
for rule in rules
)
def execute(self) -> List[CheckReportM365]:
"""Execute the Strict Preset Security Policy check.
Evaluates whether the Strict Preset Security Policy is enabled for both
Exchange Online Protection (EOP) and Defender for Office 365 (ATP),
producing PASS only when both are enabled. Returns no findings when the
policy rules could not be collected.
Returns:
List[CheckReportM365]: A list with the check report, or empty when the
preset policy rules are unavailable.
"""
findings = []
eop_rules = defender_client.eop_protection_policy_rules
atp_rules = defender_client.atp_protection_policy_rules
if eop_rules is None or atp_rules is None:
return findings
report = CheckReportM365(
metadata=self.metadata(),
resource={
"eop": [rule.dict() for rule in eop_rules],
"atp": [rule.dict() for rule in atp_rules],
},
resource_name="Strict Preset Security Policy",
resource_id="strictPresetSecurityPolicy",
)
eop_enabled = self._has_enabled_strict_preset(eop_rules)
atp_enabled = self._has_enabled_strict_preset(atp_rules)
if eop_enabled and atp_enabled:
report.status = "PASS"
report.status_extended = (
"The Strict Preset Security Policy is enabled for both Exchange "
"Online Protection and Defender for Office 365."
)
else:
missing = []
if not eop_enabled:
missing.append("Exchange Online Protection")
if not atp_enabled:
missing.append("Defender for Office 365")
report.status = "FAIL"
report.status_extended = (
"The Strict Preset Security Policy is not enabled for "
f"{' or '.join(missing)}."
)
findings.append(report)
return findings
@@ -0,0 +1,116 @@
from unittest import mock
from tests.providers.m365.m365_fixtures import DOMAIN, set_mocked_m365_provider
class Test_defender_priority_account_protection_enabled:
def test_defender_no_email_tenant_settings(self):
defender_client = mock.MagicMock()
defender_client.audited_tenant = "audited_tenant"
defender_client.audited_domain = DOMAIN
defender_client.email_tenant_settings = None
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_m365_provider(),
),
mock.patch(
"prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.connect_exchange_online"
),
mock.patch(
"prowler.providers.m365.services.defender.defender_priority_account_protection_enabled.defender_priority_account_protection_enabled.defender_client",
new=defender_client,
),
):
from prowler.providers.m365.services.defender.defender_priority_account_protection_enabled.defender_priority_account_protection_enabled import (
defender_priority_account_protection_enabled,
)
check = defender_priority_account_protection_enabled()
result = check.execute()
assert len(result) == 0
def test_defender_priority_account_protection_enabled(self):
defender_client = mock.MagicMock()
defender_client.audited_tenant = "audited_tenant"
defender_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.lib.powershell.m365_powershell.M365PowerShell.connect_exchange_online"
),
mock.patch(
"prowler.providers.m365.services.defender.defender_priority_account_protection_enabled.defender_priority_account_protection_enabled.defender_client",
new=defender_client,
),
):
from prowler.providers.m365.services.defender.defender_priority_account_protection_enabled.defender_priority_account_protection_enabled import (
defender_priority_account_protection_enabled,
)
from prowler.providers.m365.services.defender.defender_service import (
EmailTenantSettings,
)
defender_client.email_tenant_settings = EmailTenantSettings(
priority_account_protection_enabled=True,
)
check = defender_priority_account_protection_enabled()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert (
result[0].status_extended
== "Priority account protection is enabled at the tenant level."
)
assert result[0].resource == defender_client.email_tenant_settings.dict()
assert result[0].resource_name == "Email Tenant Settings"
assert result[0].resource_id == "emailTenantSettings"
assert result[0].location == "global"
def test_defender_priority_account_protection_disabled(self):
defender_client = mock.MagicMock()
defender_client.audited_tenant = "audited_tenant"
defender_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.lib.powershell.m365_powershell.M365PowerShell.connect_exchange_online"
),
mock.patch(
"prowler.providers.m365.services.defender.defender_priority_account_protection_enabled.defender_priority_account_protection_enabled.defender_client",
new=defender_client,
),
):
from prowler.providers.m365.services.defender.defender_priority_account_protection_enabled.defender_priority_account_protection_enabled import (
defender_priority_account_protection_enabled,
)
from prowler.providers.m365.services.defender.defender_service import (
EmailTenantSettings,
)
defender_client.email_tenant_settings = EmailTenantSettings(
priority_account_protection_enabled=False,
)
check = defender_priority_account_protection_enabled()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== "Priority account protection is not enabled at the tenant level."
)
assert result[0].resource == defender_client.email_tenant_settings.dict()
assert result[0].resource_name == "Email Tenant Settings"
assert result[0].resource_id == "emailTenantSettings"
assert result[0].location == "global"
@@ -0,0 +1,395 @@
from unittest import mock
from tests.providers.m365.m365_fixtures import DOMAIN, set_mocked_m365_provider
class Test_defender_strict_preset_security_policy_enabled:
def test_defender_no_preset_policy_rules_data(self):
defender_client = mock.MagicMock()
defender_client.audited_tenant = "audited_tenant"
defender_client.audited_domain = DOMAIN
defender_client.eop_protection_policy_rules = None
defender_client.atp_protection_policy_rules = None
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_m365_provider(),
),
mock.patch(
"prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.connect_exchange_online"
),
mock.patch(
"prowler.providers.m365.services.defender.defender_strict_preset_security_policy_enabled.defender_strict_preset_security_policy_enabled.defender_client",
new=defender_client,
),
):
from prowler.providers.m365.services.defender.defender_strict_preset_security_policy_enabled.defender_strict_preset_security_policy_enabled import (
defender_strict_preset_security_policy_enabled,
)
check = defender_strict_preset_security_policy_enabled()
result = check.execute()
assert len(result) == 0
def test_defender_eop_rules_unavailable(self):
defender_client = mock.MagicMock()
defender_client.audited_tenant = "audited_tenant"
defender_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.lib.powershell.m365_powershell.M365PowerShell.connect_exchange_online"
),
mock.patch(
"prowler.providers.m365.services.defender.defender_strict_preset_security_policy_enabled.defender_strict_preset_security_policy_enabled.defender_client",
new=defender_client,
),
):
from prowler.providers.m365.services.defender.defender_service import (
PresetSecurityPolicyRule,
)
from prowler.providers.m365.services.defender.defender_strict_preset_security_policy_enabled.defender_strict_preset_security_policy_enabled import (
defender_strict_preset_security_policy_enabled,
)
defender_client.eop_protection_policy_rules = None
defender_client.atp_protection_policy_rules = [
PresetSecurityPolicyRule(
name="Strict Preset Security Policy",
state="Enabled",
)
]
check = defender_strict_preset_security_policy_enabled()
result = check.execute()
assert len(result) == 0
def test_defender_strict_preset_enabled_for_both(self):
defender_client = mock.MagicMock()
defender_client.audited_tenant = "audited_tenant"
defender_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.lib.powershell.m365_powershell.M365PowerShell.connect_exchange_online"
),
mock.patch(
"prowler.providers.m365.services.defender.defender_strict_preset_security_policy_enabled.defender_strict_preset_security_policy_enabled.defender_client",
new=defender_client,
),
):
from prowler.providers.m365.services.defender.defender_service import (
PresetSecurityPolicyRule,
)
from prowler.providers.m365.services.defender.defender_strict_preset_security_policy_enabled.defender_strict_preset_security_policy_enabled import (
defender_strict_preset_security_policy_enabled,
)
defender_client.eop_protection_policy_rules = [
PresetSecurityPolicyRule(
name="Strict Preset Security Policy",
state="Enabled",
recipient_domain_is=["contoso.com"],
)
]
defender_client.atp_protection_policy_rules = [
PresetSecurityPolicyRule(
name="Strict Preset Security Policy",
state="Enabled",
recipient_domain_is=["contoso.com"],
)
]
check = defender_strict_preset_security_policy_enabled()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert (
result[0].status_extended
== "The Strict Preset Security Policy is enabled for both Exchange Online Protection and Defender for Office 365."
)
assert result[0].resource == {
"eop": [
rule.dict() for rule in defender_client.eop_protection_policy_rules
],
"atp": [
rule.dict() for rule in defender_client.atp_protection_policy_rules
],
}
assert result[0].resource_name == "Strict Preset Security Policy"
assert result[0].resource_id == "strictPresetSecurityPolicy"
assert result[0].location == "global"
def test_defender_strict_preset_enabled_all_recipients(self):
defender_client = mock.MagicMock()
defender_client.audited_tenant = "audited_tenant"
defender_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.lib.powershell.m365_powershell.M365PowerShell.connect_exchange_online"
),
mock.patch(
"prowler.providers.m365.services.defender.defender_strict_preset_security_policy_enabled.defender_strict_preset_security_policy_enabled.defender_client",
new=defender_client,
),
):
from prowler.providers.m365.services.defender.defender_service import (
PresetSecurityPolicyRule,
)
from prowler.providers.m365.services.defender.defender_strict_preset_security_policy_enabled.defender_strict_preset_security_policy_enabled import (
defender_strict_preset_security_policy_enabled,
)
# Empty recipient conditions mean the rule applies to all recipients.
defender_client.eop_protection_policy_rules = [
PresetSecurityPolicyRule(
name="Strict Preset Security Policy",
state="Enabled",
)
]
defender_client.atp_protection_policy_rules = [
PresetSecurityPolicyRule(
name="Strict Preset Security Policy",
state="Enabled",
)
]
check = defender_strict_preset_security_policy_enabled()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert (
result[0].status_extended
== "The Strict Preset Security Policy is enabled for both Exchange Online Protection and Defender for Office 365."
)
def test_defender_strict_preset_only_eop(self):
defender_client = mock.MagicMock()
defender_client.audited_tenant = "audited_tenant"
defender_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.lib.powershell.m365_powershell.M365PowerShell.connect_exchange_online"
),
mock.patch(
"prowler.providers.m365.services.defender.defender_strict_preset_security_policy_enabled.defender_strict_preset_security_policy_enabled.defender_client",
new=defender_client,
),
):
from prowler.providers.m365.services.defender.defender_service import (
PresetSecurityPolicyRule,
)
from prowler.providers.m365.services.defender.defender_strict_preset_security_policy_enabled.defender_strict_preset_security_policy_enabled import (
defender_strict_preset_security_policy_enabled,
)
defender_client.eop_protection_policy_rules = [
PresetSecurityPolicyRule(
name="Strict Preset Security Policy",
state="Enabled",
)
]
defender_client.atp_protection_policy_rules = []
check = defender_strict_preset_security_policy_enabled()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== "The Strict Preset Security Policy is not enabled for Defender for Office 365."
)
def test_defender_strict_preset_only_atp(self):
defender_client = mock.MagicMock()
defender_client.audited_tenant = "audited_tenant"
defender_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.lib.powershell.m365_powershell.M365PowerShell.connect_exchange_online"
),
mock.patch(
"prowler.providers.m365.services.defender.defender_strict_preset_security_policy_enabled.defender_strict_preset_security_policy_enabled.defender_client",
new=defender_client,
),
):
from prowler.providers.m365.services.defender.defender_service import (
PresetSecurityPolicyRule,
)
from prowler.providers.m365.services.defender.defender_strict_preset_security_policy_enabled.defender_strict_preset_security_policy_enabled import (
defender_strict_preset_security_policy_enabled,
)
defender_client.eop_protection_policy_rules = []
defender_client.atp_protection_policy_rules = [
PresetSecurityPolicyRule(
name="Strict Preset Security Policy",
state="Enabled",
)
]
check = defender_strict_preset_security_policy_enabled()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== "The Strict Preset Security Policy is not enabled for Exchange Online Protection."
)
def test_defender_strict_preset_disabled_state(self):
defender_client = mock.MagicMock()
defender_client.audited_tenant = "audited_tenant"
defender_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.lib.powershell.m365_powershell.M365PowerShell.connect_exchange_online"
),
mock.patch(
"prowler.providers.m365.services.defender.defender_strict_preset_security_policy_enabled.defender_strict_preset_security_policy_enabled.defender_client",
new=defender_client,
),
):
from prowler.providers.m365.services.defender.defender_service import (
PresetSecurityPolicyRule,
)
from prowler.providers.m365.services.defender.defender_strict_preset_security_policy_enabled.defender_strict_preset_security_policy_enabled import (
defender_strict_preset_security_policy_enabled,
)
defender_client.eop_protection_policy_rules = [
PresetSecurityPolicyRule(
name="Strict Preset Security Policy",
state="Disabled",
)
]
defender_client.atp_protection_policy_rules = [
PresetSecurityPolicyRule(
name="Strict Preset Security Policy",
state="Disabled",
)
]
check = defender_strict_preset_security_policy_enabled()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== "The Strict Preset Security Policy is not enabled for Exchange Online Protection or Defender for Office 365."
)
def test_defender_standard_preset_only(self):
defender_client = mock.MagicMock()
defender_client.audited_tenant = "audited_tenant"
defender_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.lib.powershell.m365_powershell.M365PowerShell.connect_exchange_online"
),
mock.patch(
"prowler.providers.m365.services.defender.defender_strict_preset_security_policy_enabled.defender_strict_preset_security_policy_enabled.defender_client",
new=defender_client,
),
):
from prowler.providers.m365.services.defender.defender_service import (
PresetSecurityPolicyRule,
)
from prowler.providers.m365.services.defender.defender_strict_preset_security_policy_enabled.defender_strict_preset_security_policy_enabled import (
defender_strict_preset_security_policy_enabled,
)
defender_client.eop_protection_policy_rules = [
PresetSecurityPolicyRule(
name="Standard Preset Security Policy",
state="Enabled",
)
]
defender_client.atp_protection_policy_rules = [
PresetSecurityPolicyRule(
name="Standard Preset Security Policy",
state="Enabled",
)
]
check = defender_strict_preset_security_policy_enabled()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== "The Strict Preset Security Policy is not enabled for Exchange Online Protection or Defender for Office 365."
)
def test_defender_no_preset_rules(self):
defender_client = mock.MagicMock()
defender_client.audited_tenant = "audited_tenant"
defender_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.lib.powershell.m365_powershell.M365PowerShell.connect_exchange_online"
),
mock.patch(
"prowler.providers.m365.services.defender.defender_strict_preset_security_policy_enabled.defender_strict_preset_security_policy_enabled.defender_client",
new=defender_client,
),
):
from prowler.providers.m365.services.defender.defender_strict_preset_security_policy_enabled.defender_strict_preset_security_policy_enabled import (
defender_strict_preset_security_policy_enabled,
)
# Rules were collected successfully but the presets were never enabled.
defender_client.eop_protection_policy_rules = []
defender_client.atp_protection_policy_rules = []
check = defender_strict_preset_security_policy_enabled()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== "The Strict Preset Security Policy is not enabled for Exchange Online Protection or Defender for Office 365."
)
assert result[0].resource == {"eop": [], "atp": []}
assert result[0].resource_name == "Strict Preset Security Policy"
assert result[0].resource_id == "strictPresetSecurityPolicy"
assert result[0].location == "global"
@@ -554,3 +554,100 @@ class Test_Defender_Service:
assert report_submission_policy.report_not_junk_addresses == []
assert report_submission_policy.report_phish_addresses == []
assert report_submission_policy.report_chat_message_enabled is True
@patch(
"prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.get_eop_protection_policy_rule",
return_value=[
{
"Name": "Standard Preset Security Policy",
"State": "Disabled",
"SentTo": None,
"SentToMemberOf": None,
"RecipientDomainIs": "contoso.com",
},
{
"Name": "Strict Preset Security Policy",
"State": "Enabled",
"SentTo": ["user@contoso.com"],
"SentToMemberOf": None,
"RecipientDomainIs": None,
},
],
)
def test__get_eop_protection_policy_rules(self, _mock_get_eop_rules):
with (
mock.patch(
"prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.connect_exchange_online",
return_value=True,
),
):
defender_client = Defender(
set_mocked_m365_provider(
identity=M365IdentityInfo(tenant_domain=DOMAIN)
)
)
eop_rules = defender_client.eop_protection_policy_rules
assert len(eop_rules) == 2
assert eop_rules[0].name == "Standard Preset Security Policy"
assert eop_rules[0].state == "Disabled"
assert eop_rules[0].sent_to == []
assert eop_rules[0].recipient_domain_is == ["contoso.com"]
assert eop_rules[1].name == "Strict Preset Security Policy"
assert eop_rules[1].state == "Enabled"
assert eop_rules[1].sent_to == ["user@contoso.com"]
assert eop_rules[1].recipient_domain_is == []
defender_client.powershell.close()
@patch(
"prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.get_atp_protection_policy_rule",
return_value={
"Name": "Strict Preset Security Policy",
"State": "Enabled",
"SentTo": None,
"SentToMemberOf": None,
"RecipientDomainIs": None,
},
)
def test__get_atp_protection_policy_rules_single_dict(self, _mock_get_atp_rules):
with (
mock.patch(
"prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.connect_exchange_online",
return_value=True,
),
):
defender_client = Defender(
set_mocked_m365_provider(
identity=M365IdentityInfo(tenant_domain=DOMAIN)
)
)
atp_rules = defender_client.atp_protection_policy_rules
assert len(atp_rules) == 1
assert atp_rules[0].name == "Strict Preset Security Policy"
assert atp_rules[0].state == "Enabled"
assert atp_rules[0].sent_to == []
assert atp_rules[0].sent_to_member_of == []
assert atp_rules[0].recipient_domain_is == []
defender_client.powershell.close()
@patch(
"prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.get_email_tenant_settings",
return_value={
"Identity": "Default",
"EnablePriorityAccountProtection": True,
},
)
def test__get_email_tenant_settings(self, _mock_get_email_tenant_settings):
with (
mock.patch(
"prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.connect_exchange_online",
return_value=True,
),
):
defender_client = Defender(
set_mocked_m365_provider(
identity=M365IdentityInfo(tenant_domain=DOMAIN)
)
)
email_tenant_settings = defender_client.email_tenant_settings
assert email_tenant_settings.priority_account_protection_enabled is True
defender_client.powershell.close()