diff --git a/prowler/CHANGELOG.md b/prowler/CHANGELOG.md index b456832c83..4076e01fcd 100644 --- a/prowler/CHANGELOG.md +++ b/prowler/CHANGELOG.md @@ -6,6 +6,7 @@ All notable changes to the **Prowler SDK** are documented in this file. ### Fixed - `sharepoint_external_sharing_managed` check to handle external sharing disabled at organization level [(#9298)](https://github.com/prowler-cloud/prowler/pull/9298) +- Support multiple Exchange mailbox policies in M365 `exchange_mailbox_policy_additional_storage_restricted` check [(#9241)](https://github.com/prowler-cloud/prowler/pull/9241) --- diff --git a/prowler/providers/m365/services/exchange/exchange_mailbox_policy_additional_storage_restricted/exchange_mailbox_policy_additional_storage_restricted.py b/prowler/providers/m365/services/exchange/exchange_mailbox_policy_additional_storage_restricted/exchange_mailbox_policy_additional_storage_restricted.py index 05ecd7cc0d..ba85c74c16 100644 --- a/prowler/providers/m365/services/exchange/exchange_mailbox_policy_additional_storage_restricted/exchange_mailbox_policy_additional_storage_restricted.py +++ b/prowler/providers/m365/services/exchange/exchange_mailbox_policy_additional_storage_restricted/exchange_mailbox_policy_additional_storage_restricted.py @@ -13,32 +13,28 @@ class exchange_mailbox_policy_additional_storage_restricted(Check): def execute(self) -> List[CheckReportM365]: """Run the check to validate Exchange mailbox policy restrictions. - Iterates through the mailbox policy configuration to determine if additional storage - providers are restricted and generates a report based on the policy status. + Iterates through all mailbox policies to determine if additional storage + providers are restricted and generates reports for each policy. Returns: - List[CheckReportM365]: A list of reports with the restriction status for the mailbox policy. + List[CheckReportM365]: A list of reports with the restriction status for each mailbox policy. """ findings = [] - mailbox_policy = exchange_client.mailbox_policy - if mailbox_policy: - report = CheckReportM365( - metadata=self.metadata(), - resource=mailbox_policy, - resource_name="Exchange Mailbox Policy", - resource_id=mailbox_policy.id, - ) - report.status = "FAIL" - report.status_extended = ( - "Exchange mailbox policy allows additional storage providers." - ) - - if not mailbox_policy.additional_storage_enabled: - report.status = "PASS" - report.status_extended = ( - "Exchange mailbox policy restricts additional storage providers." + for mailbox_policy in exchange_client.mailbox_policies: + if mailbox_policy: + report = CheckReportM365( + metadata=self.metadata(), + resource=mailbox_policy, + resource_name=f"Exchange Mailbox Policy - {mailbox_policy.id}", + resource_id=mailbox_policy.id, ) + report.status = "FAIL" + report.status_extended = f"Exchange mailbox policy '{mailbox_policy.id}' allows additional storage providers." - findings.append(report) + if not mailbox_policy.additional_storage_enabled: + report.status = "PASS" + report.status_extended = f"Exchange mailbox policy '{mailbox_policy.id}' restricts additional storage providers." + + findings.append(report) return findings diff --git a/prowler/providers/m365/services/exchange/exchange_service.py b/prowler/providers/m365/services/exchange/exchange_service.py index 35d4068007..1c8f1d1e87 100644 --- a/prowler/providers/m365/services/exchange/exchange_service.py +++ b/prowler/providers/m365/services/exchange/exchange_service.py @@ -16,7 +16,7 @@ class Exchange(M365Service): self.external_mail_config = [] self.transport_rules = [] self.transport_config = None - self.mailbox_policy = None + self.mailbox_policies = [] self.role_assignment_policies = [] self.mailbox_audit_properties = [] @@ -27,7 +27,7 @@ class Exchange(M365Service): self.external_mail_config = self._get_external_mail_config() self.transport_rules = self._get_transport_rules() self.transport_config = self._get_transport_config() - self.mailbox_policy = self._get_mailbox_policy() + self.mailbox_policies = self._get_mailbox_policy() self.role_assignment_policies = self._get_role_assignment_policies() self.mailbox_audit_properties = self._get_mailbox_audit_properties() self.powershell.close() @@ -164,21 +164,27 @@ class Exchange(M365Service): def _get_mailbox_policy(self): logger.info("Microsoft365 - Getting mailbox policy configuration...") - mailboxes_policy = None + mailbox_policies = [] try: - mailbox_policy = self.powershell.get_mailbox_policy() - if mailbox_policy: - mailboxes_policy = MailboxPolicy( - id=mailbox_policy.get("Id", ""), - additional_storage_enabled=mailbox_policy.get( - "AdditionalStorageProvidersAvailable", True - ), - ) + policies_data = self.powershell.get_mailbox_policy() + if policies_data: + if isinstance(policies_data, dict): + policies_data = [policies_data] + for policy in policies_data: + if policy: + mailbox_policies.append( + MailboxPolicy( + id=policy.get("Id", ""), + additional_storage_enabled=policy.get( + "AdditionalStorageProvidersAvailable", True + ), + ) + ) except Exception as error: logger.error( f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" ) - return mailboxes_policy + return mailbox_policies def _get_role_assignment_policies(self): logger.info("Microsoft365 - Getting role assignment policies...") diff --git a/tests/providers/m365/services/exchange/exchange_mailbox_policy_additional_storage_restricted/exchange_mailbox_policy_additional_storage_restricted_test.py b/tests/providers/m365/services/exchange/exchange_mailbox_policy_additional_storage_restricted/exchange_mailbox_policy_additional_storage_restricted_test.py index 688d68add6..a04a44ec64 100644 --- a/tests/providers/m365/services/exchange/exchange_mailbox_policy_additional_storage_restricted/exchange_mailbox_policy_additional_storage_restricted_test.py +++ b/tests/providers/m365/services/exchange/exchange_mailbox_policy_additional_storage_restricted/exchange_mailbox_policy_additional_storage_restricted_test.py @@ -29,9 +29,11 @@ class Test_exchange_mailbox_policy_additional_storage_restricted: MailboxPolicy, ) - exchange_client.mailbox_policy = MailboxPolicy( - id="OwaMailboxPolicy-Default", additional_storage_enabled=False - ) + exchange_client.mailbox_policies = [ + MailboxPolicy( + id="OwaMailboxPolicy-Default", additional_storage_enabled=False + ) + ] check = exchange_mailbox_policy_additional_storage_restricted() result = check.execute() @@ -40,10 +42,13 @@ class Test_exchange_mailbox_policy_additional_storage_restricted: assert result[0].status == "PASS" assert ( result[0].status_extended - == "Exchange mailbox policy restricts additional storage providers." + == "Exchange mailbox policy 'OwaMailboxPolicy-Default' restricts additional storage providers." + ) + assert result[0].resource == exchange_client.mailbox_policies[0].dict() + assert ( + result[0].resource_name + == "Exchange Mailbox Policy - OwaMailboxPolicy-Default" ) - assert result[0].resource == exchange_client.mailbox_policy.dict() - assert result[0].resource_name == "Exchange Mailbox Policy" assert result[0].resource_id == "OwaMailboxPolicy-Default" assert result[0].location == "global" @@ -72,9 +77,11 @@ class Test_exchange_mailbox_policy_additional_storage_restricted: MailboxPolicy, ) - exchange_client.mailbox_policy = MailboxPolicy( - id="OwaMailboxPolicy-Default", additional_storage_enabled=True - ) + exchange_client.mailbox_policies = [ + MailboxPolicy( + id="OwaMailboxPolicy-Default", additional_storage_enabled=True + ) + ] check = exchange_mailbox_policy_additional_storage_restricted() result = check.execute() @@ -83,10 +90,13 @@ class Test_exchange_mailbox_policy_additional_storage_restricted: assert result[0].status == "FAIL" assert ( result[0].status_extended - == "Exchange mailbox policy allows additional storage providers." + == "Exchange mailbox policy 'OwaMailboxPolicy-Default' allows additional storage providers." + ) + assert result[0].resource == exchange_client.mailbox_policies[0].dict() + assert ( + result[0].resource_name + == "Exchange Mailbox Policy - OwaMailboxPolicy-Default" ) - assert result[0].resource == exchange_client.mailbox_policy.dict() - assert result[0].resource_name == "Exchange Mailbox Policy" assert result[0].resource_id == "OwaMailboxPolicy-Default" assert result[0].location == "global" @@ -94,7 +104,7 @@ class Test_exchange_mailbox_policy_additional_storage_restricted: exchange_client = mock.MagicMock() exchange_client.audited_tenant = "audited_tenant" exchange_client.audited_domain = DOMAIN - exchange_client.mailbox_policy = None + exchange_client.mailbox_policies = [] with ( mock.patch( @@ -116,3 +126,57 @@ class Test_exchange_mailbox_policy_additional_storage_restricted: check = exchange_mailbox_policy_additional_storage_restricted() result = check.execute() assert len(result) == 0 + + def test_multiple_mailbox_policies_mixed_results(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_mailbox_policy_additional_storage_restricted.exchange_mailbox_policy_additional_storage_restricted.exchange_client", + new=exchange_client, + ), + ): + from prowler.providers.m365.services.exchange.exchange_mailbox_policy_additional_storage_restricted.exchange_mailbox_policy_additional_storage_restricted import ( + exchange_mailbox_policy_additional_storage_restricted, + ) + from prowler.providers.m365.services.exchange.exchange_service import ( + MailboxPolicy, + ) + + exchange_client.mailbox_policies = [ + MailboxPolicy( + id="OwaMailboxPolicy-Default", additional_storage_enabled=False + ), + MailboxPolicy(id="OWA-Policy-2", additional_storage_enabled=True), + MailboxPolicy(id="OWA-Policy-3", additional_storage_enabled=False), + ] + + check = exchange_mailbox_policy_additional_storage_restricted() + result = check.execute() + + # Should have 3 results, one for each policy + assert len(result) == 3 + + # First policy (Default) should PASS + assert result[0].status == "PASS" + assert result[0].resource_id == "OwaMailboxPolicy-Default" + assert "restricts additional storage providers" in result[0].status_extended + + # Second policy should FAIL + assert result[1].status == "FAIL" + assert result[1].resource_id == "OWA-Policy-2" + assert "allows additional storage providers" in result[1].status_extended + + # Third policy should PASS + assert result[2].status == "PASS" + assert result[2].resource_id == "OWA-Policy-3" + assert "restricts additional storage providers" in result[2].status_extended diff --git a/tests/providers/m365/services/exchange/exchange_service_test.py b/tests/providers/m365/services/exchange/exchange_service_test.py index c3ba2a151e..9a93e616a5 100644 --- a/tests/providers/m365/services/exchange/exchange_service_test.py +++ b/tests/providers/m365/services/exchange/exchange_service_test.py @@ -7,7 +7,6 @@ from prowler.providers.m365.services.exchange.exchange_service import ( ExternalMailConfig, MailboxAuditConfig, MailboxAuditProperties, - MailboxPolicy, Organization, RoleAssignmentPolicy, TransportConfig, @@ -72,13 +71,6 @@ def mock_exchange_get_transport_config(_): ) -def mock_exchange_get_mailbox_policy(_): - return MailboxPolicy( - id="test", - additional_storage_enabled=True, - ) - - def mock_exchange_get_role_assignment_policies(_): return [ RoleAssignmentPolicy( @@ -272,13 +264,19 @@ class Test_Exchange_Service: exchange_client.powershell.close() @patch( - "prowler.providers.m365.services.exchange.exchange_service.Exchange._get_mailbox_policy", - new=mock_exchange_get_mailbox_policy, + "prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.get_mailbox_policy", + return_value=[ + { + "Id": "test", + "AdditionalStorageProvidersAvailable": True, + } + ], ) - def test_get_mailbox_policy(self): + def test_get_mailbox_policy(self, _mock_get_mailbox_policy): with ( mock.patch( - "prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.connect_exchange_online" + "prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.connect_exchange_online", + return_value=True, ), ): exchange_client = Exchange( @@ -286,9 +284,35 @@ class Test_Exchange_Service: identity=M365IdentityInfo(tenant_domain=DOMAIN) ) ) - mailbox_policy = exchange_client.mailbox_policy - assert mailbox_policy.id == "test" - assert mailbox_policy.additional_storage_enabled is True + mailbox_policies = exchange_client.mailbox_policies + assert len(mailbox_policies) == 1 + assert mailbox_policies[0].id == "test" + assert mailbox_policies[0].additional_storage_enabled is True + exchange_client.powershell.close() + + @patch( + "prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.get_mailbox_policy", + return_value={ + "Id": "test_single", + "AdditionalStorageProvidersAvailable": False, + }, + ) + def test_get_mailbox_policy_single_dict(self, _mock_get_mailbox_policy): + with ( + mock.patch( + "prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.connect_exchange_online", + return_value=True, + ), + ): + exchange_client = Exchange( + set_mocked_m365_provider( + identity=M365IdentityInfo(tenant_domain=DOMAIN) + ) + ) + mailbox_policies = exchange_client.mailbox_policies + assert len(mailbox_policies) == 1 + assert mailbox_policies[0].id == "test_single" + assert mailbox_policies[0].additional_storage_enabled is False exchange_client.powershell.close() @patch(