diff --git a/prowler/providers/m365/services/defender/defender_antispam_outbound_policy_configured/defender_antispam_outbound_policy_configured.metadata.json b/prowler/providers/m365/services/defender/defender_antispam_outbound_policy_configured/defender_antispam_outbound_policy_configured.metadata.json index 0d7b2ecc76..171ac8d7eb 100644 --- a/prowler/providers/m365/services/defender/defender_antispam_outbound_policy_configured/defender_antispam_outbound_policy_configured.metadata.json +++ b/prowler/providers/m365/services/defender/defender_antispam_outbound_policy_configured/defender_antispam_outbound_policy_configured.metadata.json @@ -7,7 +7,7 @@ "SubServiceName": "", "ResourceIdTemplate": "", "Severity": "low", - "ResourceType": "Defender Anti-Spam Policy", + "ResourceType": "Defender Anti-Spam Outbound Policy", "Description": "Ensure that outbound anti-spam policies are configured to notify administrators and copy suspicious outbound messages to designated recipients when a sender is blocked for sending spam emails.", "Risk": "Without outbound spam notifications and message copies, compromised accounts may go undetected, increasing the risk of reputation damage or data leakage through unauthorized email activity.", "RelatedUrl": "https://learn.microsoft.com/en-us/defender-office-365/outbound-spam-protection-about", diff --git a/prowler/providers/m365/services/defender/defender_antispam_outbound_policy_forwarding_disabled/__init__.py b/prowler/providers/m365/services/defender/defender_antispam_outbound_policy_forwarding_disabled/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/m365/services/defender/defender_antispam_outbound_policy_forwarding_disabled/defender_antispam_outbound_policy_forwarding_disabled.metadata.json b/prowler/providers/m365/services/defender/defender_antispam_outbound_policy_forwarding_disabled/defender_antispam_outbound_policy_forwarding_disabled.metadata.json new file mode 100644 index 0000000000..99db6d175f --- /dev/null +++ b/prowler/providers/m365/services/defender/defender_antispam_outbound_policy_forwarding_disabled/defender_antispam_outbound_policy_forwarding_disabled.metadata.json @@ -0,0 +1,30 @@ +{ + "Provider": "m365", + "CheckID": "defender_antispam_outbound_policy_forwarding_disabled", + "CheckTitle": "Ensure Defender Outbound Spam Policies are set to disable mail forwarding.", + "CheckType": [], + "ServiceName": "defender", + "SubServiceName": "", + "ResourceIdTemplate": "", + "Severity": "high", + "ResourceType": "Defender Anti-Spam Outbound Policy", + "Description": "Ensure Defender Outbound Spam Policies are set to disable mail forwarding.", + "Risk": "Enabling email auto-forwarding can be exploited by attackers or malicious insiders to exfiltrate sensitive data outside the organization, often without detection.", + "RelatedUrl": "https://learn.microsoft.com/en-us/defender-office-365/outbound-spam-protection-about", + "Remediation": { + "Code": { + "CLI": "Set-HostedOutboundSpamFilterPolicy -Identity {policyName} -AutoForwardingMode Off", + "NativeIaC": "", + "Other": "1. Navigate to Microsoft 365 Defender https://security.microsoft.com/. 2. Expand E-mail & collaboration then select Policies & rules. 3. Select Threat policies > Anti-spam. 4. Select Anti-spam outbound policy (default). 5. Click Edit protection settings. 6. Set Automatic forwarding rules dropdown to Off - Forwarding is disabled and click Save. 7. Repeat steps 4-6 for any additional higher priority, custom policies.", + "Terraform": "" + }, + "Recommendation": { + "Text": "Block all forms of mail forwarding using Anti-spam outbound policies in Exchange Online. Apply exclusions only where justified by organizational policy.", + "Url": "https://learn.microsoft.com/en-us/defender-office-365/outbound-spam-protection-about" + } + }, + "Categories": [], + "DependsOn": [], + "RelatedTo": [], + "Notes": "Ensure settings are applied to the highest priority policy if custom policies exist." +} diff --git a/prowler/providers/m365/services/defender/defender_antispam_outbound_policy_forwarding_disabled/defender_antispam_outbound_policy_forwarding_disabled.py b/prowler/providers/m365/services/defender/defender_antispam_outbound_policy_forwarding_disabled/defender_antispam_outbound_policy_forwarding_disabled.py new file mode 100644 index 0000000000..8bb8c4fa60 --- /dev/null +++ b/prowler/providers/m365/services/defender/defender_antispam_outbound_policy_forwarding_disabled/defender_antispam_outbound_policy_forwarding_disabled.py @@ -0,0 +1,47 @@ +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_antispam_outbound_policy_forwarding_disabled(Check): + """ + Check if the Defender Outbound Spam Policies are configured to disable mail forwarding. + + Attributes: + metadata: Metadata associated with the check (inherited from Check). + """ + + def execute(self) -> List[CheckReportM365]: + """ + Execute the check to verify if the Defender Outbound Spam Policies disable mail forwarding. + + Returns: + List[CheckReportM365]: A list of reports containing the result of the check. + """ + findings = [] + for policy_name, policy in defender_client.outbound_spam_policies.items(): + report = CheckReportM365( + metadata=self.metadata(), + resource=policy, + resource_name="Defender Outbound Spam Policy", + resource_id=policy_name, + ) + report.status = "FAIL" + report.status_extended = ( + f"Outbound Spam Policy {policy_name} does allow mail forwarding." + ) + + if ( + not policy.default + and policy_name in defender_client.outbound_spam_rules + and defender_client.outbound_spam_rules[policy_name].state.lower() + == "enabled" + ) or policy.default: + if not policy.auto_forwarding_mode: + report.status = "PASS" + report.status_extended = f"Outbound Spam Policy {policy_name} does not allow mail forwarding." + + findings.append(report) + + return findings diff --git a/prowler/providers/m365/services/defender/defender_service.py b/prowler/providers/m365/services/defender/defender_service.py index 3ed0eee58b..875d78f622 100644 --- a/prowler/providers/m365/services/defender/defender_service.py +++ b/prowler/providers/m365/services/defender/defender_service.py @@ -164,6 +164,7 @@ class Defender(M365Service): notify_sender_blocked_addresses=policy.get( "NotifyOutboundSpamRecipients", [] ), + auto_forwarding_mode=policy.get("AutoForwardingMode", True), default=policy.get("IsDefault", False), ) except Exception as error: @@ -255,6 +256,7 @@ class OutboundSpamPolicy(BaseModel): notify_limit_exceeded: bool notify_limit_exceeded_addresses: List[str] notify_sender_blocked_addresses: List[str] + auto_forwarding_mode: bool default: bool diff --git a/prowler/providers/m365/services/exchange/exchange_service.py b/prowler/providers/m365/services/exchange/exchange_service.py index 67f0564e74..40dc8c3893 100644 --- a/prowler/providers/m365/services/exchange/exchange_service.py +++ b/prowler/providers/m365/services/exchange/exchange_service.py @@ -128,6 +128,7 @@ class Exchange(M365Service): name=rule.get("Name", ""), scl=rule.get("SetSCL", None), sender_domain_is=rule.get("SenderDomainIs", []), + redirect_message_to=rule.get("RedirectMessageTo", None), ) ) except Exception as error: @@ -260,6 +261,7 @@ class TransportRule(BaseModel): name: str scl: Optional[int] sender_domain_is: list[str] + redirect_message_to: Optional[list[str]] class TransportConfig(BaseModel): diff --git a/prowler/providers/m365/services/exchange/exchange_transport_rules_mail_forwarding_disabled/__init__.py b/prowler/providers/m365/services/exchange/exchange_transport_rules_mail_forwarding_disabled/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/m365/services/exchange/exchange_transport_rules_mail_forwarding_disabled/exchange_transport_rules_mail_forwarding_disabled.metadata.json b/prowler/providers/m365/services/exchange/exchange_transport_rules_mail_forwarding_disabled/exchange_transport_rules_mail_forwarding_disabled.metadata.json new file mode 100644 index 0000000000..c242607ec1 --- /dev/null +++ b/prowler/providers/m365/services/exchange/exchange_transport_rules_mail_forwarding_disabled/exchange_transport_rules_mail_forwarding_disabled.metadata.json @@ -0,0 +1,30 @@ +{ + "Provider": "m365", + "CheckID": "exchange_transport_rules_mail_forwarding_disabled", + "CheckTitle": "Ensure mail transport rules are set to disable mail forwarding.", + "CheckType": [], + "ServiceName": "exchange", + "SubServiceName": "", + "ResourceIdTemplate": "", + "Severity": "high", + "ResourceType": "Exchange Transport Rules", + "Description": "Ensure mail transport rules are set to disable mail forwarding.", + "Risk": "Enabling email auto-forwarding can be exploited by attackers or malicious insiders to exfiltrate sensitive data outside the organization, often without detection.", + "RelatedUrl": "https://learn.microsoft.com/en-us/exchange/security-and-compliance/mail-flow-rules/configuration-best-practices", + "Remediation": { + "Code": { + "CLI": "Remove-TransportRule -Identity ", + "NativeIaC": "", + "Other": "1. Select Exchange to open the Exchange admin center. 2. Select Mail Flow then Rules. 3. For each rule that redirects email to external domains, select the rule and click the 'Delete' icon.", + "Terraform": "" + }, + "Recommendation": { + "Text": "Block all forms of mail forwarding using Transport rules in Exchange Online. Apply exclusions only where justified by organizational policy.", + "Url": "https://learn.microsoft.com/en-us/exchange/security-and-compliance/mail-flow-rules/mail-flow-rules" + } + }, + "Categories": [], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/m365/services/exchange/exchange_transport_rules_mail_forwarding_disabled/exchange_transport_rules_mail_forwarding_disabled.py b/prowler/providers/m365/services/exchange/exchange_transport_rules_mail_forwarding_disabled/exchange_transport_rules_mail_forwarding_disabled.py new file mode 100644 index 0000000000..c9932af3e6 --- /dev/null +++ b/prowler/providers/m365/services/exchange/exchange_transport_rules_mail_forwarding_disabled/exchange_transport_rules_mail_forwarding_disabled.py @@ -0,0 +1,43 @@ +from typing import List + +from prowler.lib.check.models import Check, CheckReportM365 +from prowler.providers.m365.services.exchange.exchange_client import exchange_client + + +class exchange_transport_rules_mail_forwarding_disabled(Check): + """ + Check to ensure that no mail transport rules allow forwarding mail to external domains. + + Attributes: + metadata: Metadata associated with the check (inherited from Check). + """ + + def execute(self) -> List[CheckReportM365]: + """ + Execute the check to validate that no transport rules allow forwarding mail to external domains. + + This method retrieves all transport rules from the Exchange service and evaluates + whether any of them allow forwarding mail to external domains. A report is generated for each + transport rule. + + Returns: + List[CheckReportM365]: A list of findings with the status of each transport rule. + """ + findings = [] + for rule in exchange_client.transport_rules: + report = CheckReportM365( + metadata=self.metadata(), + resource=rule, + resource_name=rule.name, + resource_id="ExchangeTransportRule", + ) + report.status = "PASS" + report.status_extended = f"Transport rule {rule.name} does not allow forwarding mail to external domains." + + if rule.redirect_message_to: + report.status = "FAIL" + report.status_extended = f"Transport rule {rule.name} allows forwarding mail to external domains: {', '.join(rule.redirect_message_to)}." + + findings.append(report) + + return findings diff --git a/tests/providers/m365/services/defender/defender_antispam_outbound_policy_configured/defender_antispam_outbound_policy_configured_test.py b/tests/providers/m365/services/defender/defender_antispam_outbound_policy_configured/defender_antispam_outbound_policy_configured_test.py index fe7c338838..dbb3ee3344 100644 --- a/tests/providers/m365/services/defender/defender_antispam_outbound_policy_configured/defender_antispam_outbound_policy_configured_test.py +++ b/tests/providers/m365/services/defender/defender_antispam_outbound_policy_configured/defender_antispam_outbound_policy_configured_test.py @@ -37,6 +37,7 @@ class Test_defender_antispam_outbound_policy_configured: notify_limit_exceeded_addresses=["test@correo.com"], notify_sender_blocked_addresses=["test@correo.com"], default=False, + auto_forwarding_mode=False, ) } defender_client.outbound_spam_rules = { @@ -92,6 +93,7 @@ class Test_defender_antispam_outbound_policy_configured: notify_limit_exceeded_addresses=[], notify_sender_blocked_addresses=[], default=False, + auto_forwarding_mode=False, ) } defender_client.outbound_spam_rules = { @@ -146,6 +148,7 @@ class Test_defender_antispam_outbound_policy_configured: notify_limit_exceeded_addresses=["test@correo.com"], notify_sender_blocked_addresses=["test@correo.com"], default=True, + auto_forwarding_mode=False, ) } defender_client.outbound_spam_rules = {} @@ -198,6 +201,7 @@ class Test_defender_antispam_outbound_policy_configured: notify_limit_exceeded_addresses=["admin@org.com"], notify_sender_blocked_addresses=["admin@org.com"], default=False, + auto_forwarding_mode=False, ) } defender_client.outbound_spam_rules = {} diff --git a/tests/providers/m365/services/defender/defender_antispam_outbound_policy_forwarding_disabled/defender_antispam_outbound_policy_forwarding_disabled_test.py b/tests/providers/m365/services/defender/defender_antispam_outbound_policy_forwarding_disabled/defender_antispam_outbound_policy_forwarding_disabled_test.py new file mode 100644 index 0000000000..0e911a33b2 --- /dev/null +++ b/tests/providers/m365/services/defender/defender_antispam_outbound_policy_forwarding_disabled/defender_antispam_outbound_policy_forwarding_disabled_test.py @@ -0,0 +1,252 @@ +from unittest import mock + +from tests.providers.m365.m365_fixtures import DOMAIN, set_mocked_m365_provider + + +class Test_defender_antispam_outbound_policy_forwarding_disabled: + def test_no_outbound_spam_policies(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_antispam_outbound_policy_forwarding_disabled.defender_antispam_outbound_policy_forwarding_disabled.defender_client", + new=defender_client, + ), + ): + from prowler.providers.m365.services.defender.defender_antispam_outbound_policy_forwarding_disabled.defender_antispam_outbound_policy_forwarding_disabled import ( + defender_antispam_outbound_policy_forwarding_disabled, + ) + + defender_client.outbound_spam_policies = {} + defender_client.outbound_spam_rules = {} + + check = defender_antispam_outbound_policy_forwarding_disabled() + result = check.execute() + assert len(result) == 0 + + def test_forwarding_disabled_custom_policy(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_antispam_outbound_policy_forwarding_disabled.defender_antispam_outbound_policy_forwarding_disabled.defender_client", + new=defender_client, + ), + ): + from prowler.providers.m365.services.defender.defender_antispam_outbound_policy_forwarding_disabled.defender_antispam_outbound_policy_forwarding_disabled import ( + defender_antispam_outbound_policy_forwarding_disabled, + ) + from prowler.providers.m365.services.defender.defender_service import ( + OutboundSpamPolicy, + OutboundSpamRule, + ) + + defender_client.outbound_spam_policies = { + "Policy1": OutboundSpamPolicy( + default=False, + notify_sender_blocked=True, + notify_limit_exceeded=True, + notify_limit_exceeded_addresses=["test@correo.com"], + notify_sender_blocked_addresses=["test@correo.com"], + auto_forwarding_mode=False, + ) + } + defender_client.outbound_spam_rules = { + "Policy1": OutboundSpamRule(state="Enabled") + } + + check = defender_antispam_outbound_policy_forwarding_disabled() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == "Outbound Spam Policy Policy1 does not allow mail forwarding." + ) + assert ( + result[0].resource + == defender_client.outbound_spam_policies["Policy1"].dict() + ) + assert result[0].resource_name == "Defender Outbound Spam Policy" + assert result[0].resource_id == "Policy1" + assert result[0].location == "global" + + def test_forwarding_enabled_policy(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_antispam_outbound_policy_forwarding_disabled.defender_antispam_outbound_policy_forwarding_disabled.defender_client", + new=defender_client, + ), + ): + from prowler.providers.m365.services.defender.defender_antispam_outbound_policy_forwarding_disabled.defender_antispam_outbound_policy_forwarding_disabled import ( + defender_antispam_outbound_policy_forwarding_disabled, + ) + from prowler.providers.m365.services.defender.defender_service import ( + OutboundSpamPolicy, + OutboundSpamRule, + ) + + defender_client.outbound_spam_policies = { + "Policy2": OutboundSpamPolicy( + default=False, + notify_sender_blocked=True, + notify_limit_exceeded=True, + notify_limit_exceeded_addresses=["test@correo.com"], + notify_sender_blocked_addresses=["test@correo.com"], + auto_forwarding_mode=True, + ) + } + defender_client.outbound_spam_rules = { + "Policy2": OutboundSpamRule(state="Enabled") + } + + check = defender_antispam_outbound_policy_forwarding_disabled() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == "Outbound Spam Policy Policy2 does allow mail forwarding." + ) + assert ( + result[0].resource + == defender_client.outbound_spam_policies["Policy2"].dict() + ) + assert result[0].resource_name == "Defender Outbound Spam Policy" + assert result[0].resource_id == "Policy2" + assert result[0].location == "global" + + def test_forwarding_disabled_default_policy(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_antispam_outbound_policy_forwarding_disabled.defender_antispam_outbound_policy_forwarding_disabled.defender_client", + new=defender_client, + ), + ): + from prowler.providers.m365.services.defender.defender_antispam_outbound_policy_forwarding_disabled.defender_antispam_outbound_policy_forwarding_disabled import ( + defender_antispam_outbound_policy_forwarding_disabled, + ) + from prowler.providers.m365.services.defender.defender_service import ( + OutboundSpamPolicy, + ) + + defender_client.outbound_spam_policies = { + "Default": OutboundSpamPolicy( + default=True, + notify_sender_blocked=True, + notify_limit_exceeded=True, + notify_limit_exceeded_addresses=["test@correo.com"], + notify_sender_blocked_addresses=["test@correo.com"], + auto_forwarding_mode=False, + ) + } + defender_client.outbound_spam_rules = {} + + check = defender_antispam_outbound_policy_forwarding_disabled() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == "Outbound Spam Policy Default does not allow mail forwarding." + ) + assert ( + result[0].resource + == defender_client.outbound_spam_policies["Default"].dict() + ) + assert result[0].resource_name == "Defender Outbound Spam Policy" + assert result[0].resource_id == "Default" + assert result[0].location == "global" + + def test_policy_without_rule(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_antispam_outbound_policy_forwarding_disabled.defender_antispam_outbound_policy_forwarding_disabled.defender_client", + new=defender_client, + ), + ): + from prowler.providers.m365.services.defender.defender_antispam_outbound_policy_forwarding_disabled.defender_antispam_outbound_policy_forwarding_disabled import ( + defender_antispam_outbound_policy_forwarding_disabled, + ) + from prowler.providers.m365.services.defender.defender_service import ( + OutboundSpamPolicy, + ) + + defender_client.outbound_spam_policies = { + "PolicyX": OutboundSpamPolicy( + default=False, + notify_sender_blocked=True, + notify_limit_exceeded=True, + notify_limit_exceeded_addresses=["test@correo.com"], + notify_sender_blocked_addresses=["test@correo.com"], + auto_forwarding_mode=False, + ) + } + defender_client.outbound_spam_rules = {} + + check = defender_antispam_outbound_policy_forwarding_disabled() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == "Outbound Spam Policy PolicyX does allow mail forwarding." + ) + assert ( + result[0].resource + == defender_client.outbound_spam_policies["PolicyX"].dict() + ) + assert result[0].resource_name == "Defender Outbound Spam Policy" + assert result[0].resource_id == "PolicyX" + assert result[0].location == "global" diff --git a/tests/providers/m365/services/defender/m365_defender_service_test.py b/tests/providers/m365/services/defender/m365_defender_service_test.py index efce49f843..4c170d0da9 100644 --- a/tests/providers/m365/services/defender/m365_defender_service_test.py +++ b/tests/providers/m365/services/defender/m365_defender_service_test.py @@ -106,6 +106,7 @@ def mock_defender_get_outbound_spam_filter_policy(_): notify_limit_exceeded=True, notify_limit_exceeded_addresses=["security@example.com"], notify_sender_blocked_addresses=["security@example.com"], + auto_forwarding_mode=False, default=False, ), "Policy2": OutboundSpamPolicy( @@ -113,6 +114,7 @@ def mock_defender_get_outbound_spam_filter_policy(_): notify_limit_exceeded=False, notify_limit_exceeded_addresses=[], notify_sender_blocked_addresses=[], + auto_forwarding_mode=True, default=True, ), } @@ -308,6 +310,7 @@ class Test_Defender_Service: assert outbound_spam_policies[ "Policy1" ].notify_sender_blocked_addresses == ["security@example.com"] + assert outbound_spam_policies["Policy1"].auto_forwarding_mode is False assert outbound_spam_policies["Policy1"].default is False assert outbound_spam_policies["Policy2"].notify_sender_blocked is False assert outbound_spam_policies["Policy2"].notify_limit_exceeded is False @@ -317,6 +320,7 @@ class Test_Defender_Service: assert ( outbound_spam_policies["Policy2"].notify_sender_blocked_addresses == [] ) + assert outbound_spam_policies["Policy2"].auto_forwarding_mode is True assert outbound_spam_policies["Policy2"].default is True @patch( diff --git a/tests/providers/m365/services/exchange/exchange_service_test.py b/tests/providers/m365/services/exchange/exchange_service_test.py index afeb326e46..c3ba2a151e 100644 --- a/tests/providers/m365/services/exchange/exchange_service_test.py +++ b/tests/providers/m365/services/exchange/exchange_service_test.py @@ -55,11 +55,13 @@ def mock_exchange_get_transport_rules(_): name="test", scl=-1, sender_domain_is=["example.com"], + redirect_message_to=None, ), TransportRule( name="test2", scl=0, sender_domain_is=["example.com"], + redirect_message_to=["test@example.com"], ), ] @@ -261,9 +263,11 @@ class Test_Exchange_Service: assert transport_rules[0].name == "test" assert transport_rules[0].scl == -1 assert transport_rules[0].sender_domain_is == ["example.com"] + assert transport_rules[0].redirect_message_to is None assert transport_rules[1].name == "test2" assert transport_rules[1].scl == 0 assert transport_rules[1].sender_domain_is == ["example.com"] + assert transport_rules[1].redirect_message_to == ["test@example.com"] exchange_client.powershell.close() diff --git a/tests/providers/m365/services/exchange/exchange_transport_rules_mail_forwarding_disabled/exchange_transport_rules_mail_forwarding_disabled_test.py b/tests/providers/m365/services/exchange/exchange_transport_rules_mail_forwarding_disabled/exchange_transport_rules_mail_forwarding_disabled_test.py new file mode 100644 index 0000000000..5523e51e18 --- /dev/null +++ b/tests/providers/m365/services/exchange/exchange_transport_rules_mail_forwarding_disabled/exchange_transport_rules_mail_forwarding_disabled_test.py @@ -0,0 +1,145 @@ +from unittest import mock + +from tests.providers.m365.m365_fixtures import DOMAIN, set_mocked_m365_provider + + +class Test_exchange_transport_rules_mail_forwarding_disabled: + def test_empty_rule_list(self): + exchange_client = mock.MagicMock() + exchange_client.audited_tenant = "audited_tenant" + exchange_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.exchange.exchange_transport_rules_mail_forwarding_disabled.exchange_transport_rules_mail_forwarding_disabled.exchange_client", + new=exchange_client, + ), + ): + from prowler.providers.m365.services.exchange.exchange_transport_rules_mail_forwarding_disabled.exchange_transport_rules_mail_forwarding_disabled import ( + exchange_transport_rules_mail_forwarding_disabled, + ) + + exchange_client.transport_rules = [] + + check = exchange_transport_rules_mail_forwarding_disabled() + result = check.execute() + + assert len(result) == 0 + + def test_forwarding_disabled(self): + exchange_client = mock.MagicMock() + exchange_client.audited_tenant = "audited_tenant" + exchange_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.exchange.exchange_transport_rules_mail_forwarding_disabled.exchange_transport_rules_mail_forwarding_disabled.exchange_client", + new=exchange_client, + ), + ): + from prowler.providers.m365.services.exchange.exchange_service import ( + TransportRule, + ) + from prowler.providers.m365.services.exchange.exchange_transport_rules_mail_forwarding_disabled.exchange_transport_rules_mail_forwarding_disabled import ( + exchange_transport_rules_mail_forwarding_disabled, + ) + + exchange_client.transport_rules = [ + TransportRule( + name="Rule1", redirect_message_to=[], sender_domain_is=[] + ), + TransportRule( + name="Rule2", redirect_message_to=[], sender_domain_is=[] + ), + ] + + check = exchange_transport_rules_mail_forwarding_disabled() + result = check.execute() + + assert len(result) == 2 + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == "Transport rule Rule1 does not allow forwarding mail to external domains." + ) + assert result[0].resource_name == "Rule1" + assert result[0].resource_id == "ExchangeTransportRule" + assert result[1].status == "PASS" + assert ( + result[1].status_extended + == "Transport rule Rule2 does not allow forwarding mail to external domains." + ) + assert result[1].resource_name == "Rule2" + + def test_forwarding_enabled(self): + exchange_client = mock.MagicMock() + exchange_client.audited_tenant = "audited_tenant" + exchange_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.exchange.exchange_transport_rules_mail_forwarding_disabled.exchange_transport_rules_mail_forwarding_disabled.exchange_client", + new=exchange_client, + ), + ): + from prowler.providers.m365.services.exchange.exchange_service import ( + TransportRule, + ) + from prowler.providers.m365.services.exchange.exchange_transport_rules_mail_forwarding_disabled.exchange_transport_rules_mail_forwarding_disabled import ( + exchange_transport_rules_mail_forwarding_disabled, + ) + + exchange_client.transport_rules = [ + TransportRule( + name="ForwardingRule", + redirect_message_to=["external@example.com"], + sender_domain_is=[], + ), + TransportRule( + name="NoForwardingRule", + redirect_message_to=[], + sender_domain_is=[], + ), + ] + + check = exchange_transport_rules_mail_forwarding_disabled() + result = check.execute() + + assert len(result) == 2 + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == "Transport rule ForwardingRule allows forwarding mail to external domains: external@example.com." + ) + assert result[0].resource_name == "ForwardingRule" + assert result[0].resource_id == "ExchangeTransportRule" + assert result[0].location == "global" + assert result[1].status == "PASS" + assert ( + result[1].status_extended + == "Transport rule NoForwardingRule does not allow forwarding mail to external domains." + ) + assert result[1].resource_name == "NoForwardingRule" + assert result[1].resource_id == "ExchangeTransportRule" + assert result[1].location == "global"