fix(m365): Support multiple Exchange mailbox policies (#9241)

Co-authored-by: HugoPBrito <hugopbrit@gmail.com>
This commit is contained in:
mattkeeler
2025-11-27 08:10:15 -05:00
committed by GitHub
parent 59f8dfe5ae
commit dc9e91ac4e
5 changed files with 152 additions and 61 deletions

View File

@@ -14,6 +14,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)
---

View File

@@ -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

View File

@@ -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...")

View File

@@ -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

View File

@@ -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(