diff --git a/prowler/changelog.d/m365-defender-preset-policy-keyerror.fixed.md b/prowler/changelog.d/m365-defender-preset-policy-keyerror.fixed.md new file mode 100644 index 0000000000..2a50037e84 --- /dev/null +++ b/prowler/changelog.d/m365-defender-preset-policy-keyerror.fixed.md @@ -0,0 +1 @@ +`KeyError` in M365 Defender malware, anti-phishing and inbound anti-spam checks when the tenant has Standard or Strict preset security policies diff --git a/prowler/providers/m365/services/defender/defender_antiphishing_policy_configured/defender_antiphishing_policy_configured.py b/prowler/providers/m365/services/defender/defender_antiphishing_policy_configured/defender_antiphishing_policy_configured.py index 61727b9d3c..5c467f941a 100644 --- a/prowler/providers/m365/services/defender/defender_antiphishing_policy_configured/defender_antiphishing_policy_configured.py +++ b/prowler/providers/m365/services/defender/defender_antiphishing_policy_configured/defender_antiphishing_policy_configured.py @@ -55,6 +55,12 @@ class defender_antiphishing_policy_configured(Check): policy_name, policy, ) in defender_client.antiphishing_policies.items(): + # Preset security policies are scoped by protection policy rules, not filter rules + if ( + not policy.default + and policy.name not in defender_client.antiphishing_rules + ): + continue report = CheckReportM365( metadata=self.metadata(), resource=policy, diff --git a/prowler/providers/m365/services/defender/defender_antispam_policy_inbound_no_allowed_domains/defender_antispam_policy_inbound_no_allowed_domains.py b/prowler/providers/m365/services/defender/defender_antispam_policy_inbound_no_allowed_domains/defender_antispam_policy_inbound_no_allowed_domains.py index 67b6643e9e..56616258e0 100644 --- a/prowler/providers/m365/services/defender/defender_antispam_policy_inbound_no_allowed_domains/defender_antispam_policy_inbound_no_allowed_domains.py +++ b/prowler/providers/m365/services/defender/defender_antispam_policy_inbound_no_allowed_domains/defender_antispam_policy_inbound_no_allowed_domains.py @@ -51,6 +51,12 @@ class defender_antispam_policy_inbound_no_allowed_domains(Check): default_policy_well_configured = False for policy in defender_client.inbound_spam_policies: + # Preset security policies are scoped by protection policy rules, not filter rules + if ( + not policy.default + and policy.identity not in defender_client.inbound_spam_rules + ): + continue report = CheckReportM365( metadata=self.metadata(), resource=policy, diff --git a/prowler/providers/m365/services/defender/defender_malware_policy_common_attachments_filter_enabled/defender_malware_policy_common_attachments_filter_enabled.py b/prowler/providers/m365/services/defender/defender_malware_policy_common_attachments_filter_enabled/defender_malware_policy_common_attachments_filter_enabled.py index a0b817cda3..d9a7080295 100644 --- a/prowler/providers/m365/services/defender/defender_malware_policy_common_attachments_filter_enabled/defender_malware_policy_common_attachments_filter_enabled.py +++ b/prowler/providers/m365/services/defender/defender_malware_policy_common_attachments_filter_enabled/defender_malware_policy_common_attachments_filter_enabled.py @@ -51,6 +51,12 @@ class defender_malware_policy_common_attachments_filter_enabled(Check): default_policy_well_configured = False for policy in defender_client.malware_policies: + # Preset security policies are scoped by protection policy rules, not filter rules + if ( + not policy.is_default + and policy.identity not in defender_client.malware_rules + ): + continue report = CheckReportM365( metadata=self.metadata(), resource=policy, diff --git a/prowler/providers/m365/services/defender/defender_malware_policy_comprehensive_attachments_filter_applied/defender_malware_policy_comprehensive_attachments_filter_applied.py b/prowler/providers/m365/services/defender/defender_malware_policy_comprehensive_attachments_filter_applied/defender_malware_policy_comprehensive_attachments_filter_applied.py index 87aa4ab50e..808ece11b9 100644 --- a/prowler/providers/m365/services/defender/defender_malware_policy_comprehensive_attachments_filter_applied/defender_malware_policy_comprehensive_attachments_filter_applied.py +++ b/prowler/providers/m365/services/defender/defender_malware_policy_comprehensive_attachments_filter_applied/defender_malware_policy_comprehensive_attachments_filter_applied.py @@ -114,6 +114,12 @@ class defender_malware_policy_comprehensive_attachments_filter_applied(Check): default_policy_well_configured = False for policy in defender_client.malware_policies: + # Preset security policies are scoped by protection policy rules, not filter rules + if ( + not policy.is_default + and policy.identity not in defender_client.malware_rules + ): + continue report = CheckReportM365( metadata=self.metadata(), resource=policy, diff --git a/prowler/providers/m365/services/defender/defender_malware_policy_notifications_internal_users_malware_enabled/defender_malware_policy_notifications_internal_users_malware_enabled.py b/prowler/providers/m365/services/defender/defender_malware_policy_notifications_internal_users_malware_enabled/defender_malware_policy_notifications_internal_users_malware_enabled.py index 735cc6b9e9..ab5efc8efe 100644 --- a/prowler/providers/m365/services/defender/defender_malware_policy_notifications_internal_users_malware_enabled/defender_malware_policy_notifications_internal_users_malware_enabled.py +++ b/prowler/providers/m365/services/defender/defender_malware_policy_notifications_internal_users_malware_enabled/defender_malware_policy_notifications_internal_users_malware_enabled.py @@ -51,6 +51,12 @@ class defender_malware_policy_notifications_internal_users_malware_enabled(Check default_policy_well_configured = False for policy in defender_client.malware_policies: + # Preset security policies are scoped by protection policy rules, not filter rules + if ( + not policy.is_default + and policy.identity not in defender_client.malware_rules + ): + continue report = CheckReportM365( metadata=self.metadata(), resource=policy, diff --git a/tests/providers/m365/services/defender/defender_antiphishing_policy_configured/defender_antiphishing_policy_configured_test.py b/tests/providers/m365/services/defender/defender_antiphishing_policy_configured/defender_antiphishing_policy_configured_test.py index 8610e96769..984fa648df 100644 --- a/tests/providers/m365/services/defender/defender_antiphishing_policy_configured/defender_antiphishing_policy_configured_test.py +++ b/tests/providers/m365/services/defender/defender_antiphishing_policy_configured/defender_antiphishing_policy_configured_test.py @@ -521,3 +521,86 @@ class Test_defender_antiphishing_policy_configured: check = defender_antiphishing_policy_configured() result = check.execute() assert len(result) == 0 + + def test_preset_policy_without_rule_is_skipped(self): + defender_client = mock.MagicMock() + defender_client.audited_tenant = "audited_tenant" + defender_client.audited_domain = DOMAIN + defender_client.audit_config = {} + + 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_antiphishing_policy_configured.defender_antiphishing_policy_configured.defender_client", + new=defender_client, + ), + ): + from prowler.providers.m365.services.defender.defender_antiphishing_policy_configured.defender_antiphishing_policy_configured import ( + defender_antiphishing_policy_configured, + ) + from prowler.providers.m365.services.defender.defender_service import ( + AntiphishingPolicy, + AntiphishingRule, + ) + + defender_client.antiphishing_policies = { + "Default": AntiphishingPolicy( + name="Default", + spoof_intelligence=True, + spoof_intelligence_action="Quarantine", + dmarc_reject_action="Quarantine", + dmarc_quarantine_action="Quarantine", + safety_tips=True, + unauthenticated_sender_action=True, + show_tag=True, + honor_dmarc_policy=True, + default=True, + ), + "Standard Preset Security Policy1663355404982": AntiphishingPolicy( + name="Standard Preset Security Policy1663355404982", + spoof_intelligence=True, + spoof_intelligence_action="Quarantine", + dmarc_reject_action="Quarantine", + dmarc_quarantine_action="Quarantine", + safety_tips=True, + unauthenticated_sender_action=True, + show_tag=True, + honor_dmarc_policy=True, + default=False, + ), + "Custom1": AntiphishingPolicy( + name="Custom1", + spoof_intelligence=True, + spoof_intelligence_action="Quarantine", + dmarc_reject_action="Quarantine", + dmarc_quarantine_action="Quarantine", + safety_tips=True, + unauthenticated_sender_action=True, + show_tag=True, + honor_dmarc_policy=True, + default=False, + ), + } + defender_client.antiphishing_rules = { + "Custom1": AntiphishingRule( + state="Enabled", + priority=1, + users=["user1@example.com"], + groups=None, + domains=None, + ) + } + + check = defender_antiphishing_policy_configured() + result = check.execute() + + assert len(result) == 2 + assert "Standard Preset Security Policy1663355404982" not in [ + finding.resource_id for finding in result + ] diff --git a/tests/providers/m365/services/defender/defender_antispam_policy_inbound_no_allowed_domains/defender_antispam_policy_inbound_no_allowed_domains_test.py b/tests/providers/m365/services/defender/defender_antispam_policy_inbound_no_allowed_domains/defender_antispam_policy_inbound_no_allowed_domains_test.py index 625fc8ded3..4e0a5482a1 100644 --- a/tests/providers/m365/services/defender/defender_antispam_policy_inbound_no_allowed_domains/defender_antispam_policy_inbound_no_allowed_domains_test.py +++ b/tests/providers/m365/services/defender/defender_antispam_policy_inbound_no_allowed_domains/defender_antispam_policy_inbound_no_allowed_domains_test.py @@ -410,3 +410,65 @@ class Test_defender_antispam_policy_inbound_no_allowed_domains: check = defender_antispam_policy_inbound_no_allowed_domains() result = check.execute() assert len(result) == 0 + + def test_preset_policy_without_rule_is_skipped(self): + defender_client = mock.MagicMock() + defender_client.audited_tenant = "audited_tenant" + defender_client.audited_domain = DOMAIN + defender_client.audit_config = {} + + 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_antispam_policy_inbound_no_allowed_domains.defender_antispam_policy_inbound_no_allowed_domains.defender_client", + new=defender_client, + ), + ): + from prowler.providers.m365.services.defender.defender_antispam_policy_inbound_no_allowed_domains.defender_antispam_policy_inbound_no_allowed_domains import ( + defender_antispam_policy_inbound_no_allowed_domains, + ) + from prowler.providers.m365.services.defender.defender_service import ( + DefenderInboundSpamPolicy, + InboundSpamRule, + ) + + defender_client.inbound_spam_policies = [ + DefenderInboundSpamPolicy( + identity="Default", + allowed_sender_domains=[], + default=True, + ), + DefenderInboundSpamPolicy( + identity="Standard Preset Security Policy1663355404982", + allowed_sender_domains=[], + default=False, + ), + DefenderInboundSpamPolicy( + identity="Custom1", + allowed_sender_domains=[], + default=False, + ), + ] + defender_client.inbound_spam_rules = { + "Custom1": InboundSpamRule( + state="Enabled", + priority=1, + users=["user1@example.com"], + groups=None, + domains=None, + ) + } + + check = defender_antispam_policy_inbound_no_allowed_domains() + result = check.execute() + + assert len(result) == 2 + assert "Standard Preset Security Policy1663355404982" not in [ + finding.resource_id for finding in result + ] diff --git a/tests/providers/m365/services/defender/defender_malware_policy_common_attachments_filter_enabled/defender_malware_policy_common_attachments_filter_enabled_test.py b/tests/providers/m365/services/defender/defender_malware_policy_common_attachments_filter_enabled/defender_malware_policy_common_attachments_filter_enabled_test.py index adb62b213d..f2b9fdb402 100644 --- a/tests/providers/m365/services/defender/defender_malware_policy_common_attachments_filter_enabled/defender_malware_policy_common_attachments_filter_enabled_test.py +++ b/tests/providers/m365/services/defender/defender_malware_policy_common_attachments_filter_enabled/defender_malware_policy_common_attachments_filter_enabled_test.py @@ -442,3 +442,77 @@ class Test_defender_malware_policy_common_attachments_filter_enabled: check = defender_malware_policy_common_attachments_filter_enabled() result = check.execute() assert len(result) == 0 + + def test_preset_policy_without_rule_is_skipped(self): + defender_client = mock.MagicMock() + defender_client.audited_tenant = "audited_tenant" + defender_client.audited_domain = DOMAIN + defender_client.audit_config = {} + + 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_malware_policy_common_attachments_filter_enabled.defender_malware_policy_common_attachments_filter_enabled.defender_client", + new=defender_client, + ), + ): + from prowler.providers.m365.services.defender.defender_malware_policy_common_attachments_filter_enabled.defender_malware_policy_common_attachments_filter_enabled import ( + defender_malware_policy_common_attachments_filter_enabled, + ) + from prowler.providers.m365.services.defender.defender_service import ( + MalwarePolicy, + MalwareRule, + ) + + defender_client.audit_config = { + "recommended_blocked_file_types": ["exe", "bat"] + } + defender_client.malware_policies = [ + MalwarePolicy( + identity="Default", + enable_file_filter=True, + enable_internal_sender_admin_notifications=True, + internal_sender_admin_address="admin@example.com", + file_types=["exe", "bat"], + is_default=True, + ), + MalwarePolicy( + identity="Standard Preset Security Policy1663355404982", + enable_file_filter=True, + enable_internal_sender_admin_notifications=True, + internal_sender_admin_address="admin@example.com", + file_types=["exe", "bat"], + is_default=False, + ), + MalwarePolicy( + identity="Custom1", + enable_file_filter=True, + enable_internal_sender_admin_notifications=True, + internal_sender_admin_address="admin@example.com", + file_types=["exe", "bat"], + is_default=False, + ), + ] + defender_client.malware_rules = { + "Custom1": MalwareRule( + state="Enabled", + priority=1, + users=["user1@example.com"], + groups=None, + domains=None, + ) + } + + check = defender_malware_policy_common_attachments_filter_enabled() + result = check.execute() + + assert len(result) == 2 + assert "Standard Preset Security Policy1663355404982" not in [ + finding.resource_id for finding in result + ] diff --git a/tests/providers/m365/services/defender/defender_malware_policy_comprehensive_attachments_filter_applied/defender_malware_policy_comprehensive_attachments_filter_applied_test.py b/tests/providers/m365/services/defender/defender_malware_policy_comprehensive_attachments_filter_applied/defender_malware_policy_comprehensive_attachments_filter_applied_test.py index 682f86c768..7a9cfbbd90 100644 --- a/tests/providers/m365/services/defender/defender_malware_policy_comprehensive_attachments_filter_applied/defender_malware_policy_comprehensive_attachments_filter_applied_test.py +++ b/tests/providers/m365/services/defender/defender_malware_policy_comprehensive_attachments_filter_applied/defender_malware_policy_comprehensive_attachments_filter_applied_test.py @@ -452,3 +452,77 @@ class Test_defender_malware_policy_comprehensive_attachments_filter_applied: check = defender_malware_policy_comprehensive_attachments_filter_applied() result = check.execute() assert len(result) == 0 + + def test_preset_policy_without_rule_is_skipped(self): + defender_client = mock.MagicMock() + defender_client.audited_tenant = "audited_tenant" + defender_client.audited_domain = DOMAIN + defender_client.audit_config = {} + + 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_malware_policy_comprehensive_attachments_filter_applied.defender_malware_policy_comprehensive_attachments_filter_applied.defender_client", + new=defender_client, + ), + ): + from prowler.providers.m365.services.defender.defender_malware_policy_comprehensive_attachments_filter_applied.defender_malware_policy_comprehensive_attachments_filter_applied import ( + defender_malware_policy_comprehensive_attachments_filter_applied, + ) + from prowler.providers.m365.services.defender.defender_service import ( + MalwarePolicy, + MalwareRule, + ) + + defender_client.audit_config = { + "recommended_blocked_file_types": ["exe", "bat"] + } + defender_client.malware_policies = [ + MalwarePolicy( + identity="Default", + enable_file_filter=True, + enable_internal_sender_admin_notifications=True, + internal_sender_admin_address="admin@example.com", + file_types=["exe", "bat"], + is_default=True, + ), + MalwarePolicy( + identity="Standard Preset Security Policy1663355404982", + enable_file_filter=True, + enable_internal_sender_admin_notifications=True, + internal_sender_admin_address="admin@example.com", + file_types=["exe", "bat"], + is_default=False, + ), + MalwarePolicy( + identity="Custom1", + enable_file_filter=True, + enable_internal_sender_admin_notifications=True, + internal_sender_admin_address="admin@example.com", + file_types=["exe", "bat"], + is_default=False, + ), + ] + defender_client.malware_rules = { + "Custom1": MalwareRule( + state="Enabled", + priority=1, + users=["user1@example.com"], + groups=None, + domains=None, + ) + } + + check = defender_malware_policy_comprehensive_attachments_filter_applied() + result = check.execute() + + assert len(result) == 2 + assert "Standard Preset Security Policy1663355404982" not in [ + finding.resource_id for finding in result + ] diff --git a/tests/providers/m365/services/defender/defender_malware_policy_notifications_internal_users_malware_enabled/defender_malware_policy_notifications_internal_users_malware_enabled_test.py b/tests/providers/m365/services/defender/defender_malware_policy_notifications_internal_users_malware_enabled/defender_malware_policy_notifications_internal_users_malware_enabled_test.py index 16fe656c2b..7764da21e4 100644 --- a/tests/providers/m365/services/defender/defender_malware_policy_notifications_internal_users_malware_enabled/defender_malware_policy_notifications_internal_users_malware_enabled_test.py +++ b/tests/providers/m365/services/defender/defender_malware_policy_notifications_internal_users_malware_enabled/defender_malware_policy_notifications_internal_users_malware_enabled_test.py @@ -456,3 +456,79 @@ class Test_defender_malware_policy_notifications_internal_users_malware_enabled: ) result = check.execute() assert len(result) == 0 + + def test_preset_policy_without_rule_is_skipped(self): + defender_client = mock.MagicMock() + defender_client.audited_tenant = "audited_tenant" + defender_client.audited_domain = DOMAIN + defender_client.audit_config = {} + + 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_malware_policy_notifications_internal_users_malware_enabled.defender_malware_policy_notifications_internal_users_malware_enabled.defender_client", + new=defender_client, + ), + ): + from prowler.providers.m365.services.defender.defender_malware_policy_notifications_internal_users_malware_enabled.defender_malware_policy_notifications_internal_users_malware_enabled import ( + defender_malware_policy_notifications_internal_users_malware_enabled, + ) + from prowler.providers.m365.services.defender.defender_service import ( + MalwarePolicy, + MalwareRule, + ) + + defender_client.audit_config = { + "recommended_blocked_file_types": ["exe", "bat"] + } + defender_client.malware_policies = [ + MalwarePolicy( + identity="Default", + enable_file_filter=True, + enable_internal_sender_admin_notifications=True, + internal_sender_admin_address="admin@example.com", + file_types=["exe", "bat"], + is_default=True, + ), + MalwarePolicy( + identity="Standard Preset Security Policy1663355404982", + enable_file_filter=True, + enable_internal_sender_admin_notifications=True, + internal_sender_admin_address="admin@example.com", + file_types=["exe", "bat"], + is_default=False, + ), + MalwarePolicy( + identity="Custom1", + enable_file_filter=True, + enable_internal_sender_admin_notifications=True, + internal_sender_admin_address="admin@example.com", + file_types=["exe", "bat"], + is_default=False, + ), + ] + defender_client.malware_rules = { + "Custom1": MalwareRule( + state="Enabled", + priority=1, + users=["user1@example.com"], + groups=None, + domains=None, + ) + } + + check = ( + defender_malware_policy_notifications_internal_users_malware_enabled() + ) + result = check.execute() + + assert len(result) == 2 + assert "Standard Preset Security Policy1663355404982" not in [ + finding.resource_id for finding in result + ]