feat(exchange): add new check exchange_organization_mailtips_enabled (#7637)

Co-authored-by: Andoni A. <14891798+andoniaf@users.noreply.github.com>
This commit is contained in:
Daniel Barranquero
2025-05-02 08:46:14 +02:00
committed by Pepe Fagoaga
parent 75b3c02811
commit ab6d05637d
12 changed files with 286 additions and 4 deletions
+5
View File
@@ -106,6 +106,7 @@ The following list includes all the Microsoft 365 checks with configurable varia
|---------------------------------------------------------------|--------------------------------------------------|-----------------|
| `entra_admin_users_sign_in_frequency_enabled` | `sign_in_frequency` | Integer |
| `teams_external_file_sharing_restricted` | `allowed_cloud_storage_services` | List of Strings |
| `exchange_organization_mailtips_enabled` | `recommended_mailtips_large_audience_threshold` | Integer |
## Config YAML File Structure
@@ -520,5 +521,9 @@ m365:
#"allow_google_drive",
#"allow_share_file",
]
# Exchange Organization Settings
# m365.exchange_organization_mailtips_enabled
recommended_mailtips_large_audience_threshold: 25 # maximum number of recipients
```
+1
View File
@@ -38,6 +38,7 @@ All notable changes to the **Prowler SDK** are documented in this file.
- Add Prowler Threat Score Compliance Framework [(#7603)](https://github.com/prowler-cloud/prowler/pull/7603)
- Add new check `sharepoint_onedrive_sync_restricted_unmanaged_devices` [(#7589)](https://github.com/prowler-cloud/prowler/pull/7589)
- Add new check for Additional Storage restricted for Exchange in M365 [(#7638)](https://github.com/prowler-cloud/prowler/pull/7638)
- Add new check for MailTips full enabled for Exchange in M365 [(#7637)](https://github.com/prowler-cloud/prowler/pull/7637)
### Fixed
+1 -1
View File
@@ -132,7 +132,7 @@ def load_and_validate_config_file(provider: str, config_file_path: str) -> dict:
else:
config = config_file if config_file else {}
# Not to break Azure, K8s and GCP does not support or use the old config format
if provider in ["azure", "gcp", "kubernetes"]:
if provider in ["azure", "gcp", "kubernetes", "m365"]:
config = {}
return config
+3
View File
@@ -493,3 +493,6 @@ m365:
#"allow_google_drive",
#"allow_share_file",
]
# Exchange Organization Settings
# m365.exchange_organization_mailtips_enabled
recommended_mailtips_large_audience_threshold: 25 # maximum number of recipients
@@ -0,0 +1,30 @@
{
"Provider": "m365",
"CheckID": "exchange_organization_mailtips_enabled",
"CheckTitle": "Ensure MailTips are enabled for end users.",
"CheckType": [],
"ServiceName": "exchange",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "medium",
"ResourceType": "Exchange Organization Configuration",
"Description": "Ensure that MailTips are enabled in Exchange Online to provide users with informative messages while composing emails, helping to avoid issues such as sending to large groups or external recipients unintentionally.",
"Risk": "Without MailTips, users may inadvertently send sensitive information externally or generate non-delivery reports, leading to communication errors and potential data exposure.",
"RelatedUrl": "https://learn.microsoft.com/en-us/exchange/clients-and-mobile-in-exchange-online/mailtips/mailtips",
"Remediation": {
"Code": {
"CLI": "$TipsParams = @{ MailTipsAllTipsEnabled = $true; MailTipsExternalRecipientsTipsEnabled = $true; MailTipsGroupMetricsEnabled = $true; MailTipsLargeAudienceThreshold = '25' }; Set-OrganizationConfig @TipsParams",
"NativeIaC": "",
"Other": "",
"Terraform": ""
},
"Recommendation": {
"Text": "Enable MailTips features in Exchange Online and configure the large audience threshold appropriately to assist users when composing emails.",
"Url": "https://learn.microsoft.com/en-us/powershell/module/exchange/set-organizationconfig?view=exchange-ps"
}
},
"Categories": [],
"DependsOn": [],
"RelatedTo": [],
"Notes": ""
}
@@ -0,0 +1,54 @@
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_organization_mailtips_enabled(Check):
"""
Check if MailTips are enabled for Exchange Online.
Attributes:
metadata: Metadata associated with the check (inherited from Check).
"""
def execute(self) -> List[CheckReportM365]:
"""
Execute the check for MailTips in Exchange Online.
This method checks if MailTips are enabled in the Exchange organization configuration.
Returns:
List[CheckReportM365]: A list of reports containing the result of the check.
"""
findings = []
organization_config = exchange_client.organization_config
if organization_config:
report = CheckReportM365(
metadata=self.metadata(),
resource=organization_config,
resource_name=organization_config.name,
resource_id=organization_config.guid,
)
report.status = "FAIL"
report.status_extended = (
"MailTips are not fully enabled for Exchange Online."
)
if (
organization_config.mailtips_enabled
and organization_config.mailtips_external_recipient_enabled
and organization_config.mailtips_group_metrics_enabled
and organization_config.mailtips_large_audience_threshold
<= exchange_client.audit_config.get(
"recommended_mailtips_large_audience_threshold", 25
)
):
report.status = "PASS"
report.status_extended = (
"MailTips are fully enabled for Exchange Online."
)
findings.append(report)
return findings
@@ -37,6 +37,18 @@ class Exchange(M365Service):
audit_disabled=organization_configuration.get(
"AuditDisabled", False
),
mailtips_enabled=organization_configuration.get(
"MailTipsAllTipsEnabled", True
),
mailtips_external_recipient_enabled=organization_configuration.get(
"MailTipsExternalRecipientsTipsEnabled", False
),
mailtips_group_metrics_enabled=organization_configuration.get(
"MailTipsGroupMetricsEnabled", True
),
mailtips_large_audience_threshold=organization_configuration.get(
"MailTipsLargeAudienceThreshold", 25
),
)
except Exception as error:
logger.error(
@@ -137,6 +149,10 @@ class Organization(BaseModel):
name: str
guid: str
audit_disabled: bool
mailtips_enabled: bool
mailtips_external_recipient_enabled: bool
mailtips_group_metrics_enabled: bool
mailtips_large_audience_threshold: int
class MailboxAuditConfig(BaseModel):
+13
View File
@@ -434,3 +434,16 @@ m365:
# Conditional Access Policy
# policy.session_controls.sign_in_frequency.frequency in hours
sign_in_frequency: 4
# Teams Settings
# m365.teams_external_file_sharing_restricted
allowed_cloud_storage_services:
[
#"allow_box",
#"allow_drop_box",
#"allow_egnyte",
#"allow_google_drive",
#"allow_share_file",
]
# Exchange Organization Settings
# m365.exchange_organization_mailtips_enabled
recommended_mailtips_large_audience_threshold: 25 # maximum number of recipients
@@ -57,7 +57,13 @@ class Test_exchange_organization_mailbox_auditing_enabled:
)
exchange_client.organization_config = Organization(
audit_disabled=True, name="test", guid="test"
audit_disabled=True,
name="test",
guid="test",
mailtips_enabled=True,
mailtips_external_recipient_enabled=True,
mailtips_group_metrics_enabled=True,
mailtips_large_audience_threshold=25,
)
check = exchange_organization_mailbox_auditing_enabled()
@@ -99,7 +105,13 @@ class Test_exchange_organization_mailbox_auditing_enabled:
)
exchange_client.organization_config = Organization(
audit_disabled=False, name="test", guid="test"
audit_disabled=False,
name="test",
guid="test",
mailtips_enabled=True,
mailtips_external_recipient_enabled=True,
mailtips_group_metrics_enabled=True,
mailtips_large_audience_threshold=25,
)
check = exchange_organization_mailbox_auditing_enabled()
@@ -0,0 +1,136 @@
from unittest import mock
from tests.providers.m365.m365_fixtures import DOMAIN, set_mocked_m365_provider
class Test_exchange_organization_mailtips_enabled:
def test_no_organization(self):
exchange_client = mock.MagicMock()
exchange_client.audited_tenant = "audited_tenant"
exchange_client.audited_domain = DOMAIN
exchange_client.organization_config = 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.exchange.exchange_organization_mailtips_enabled.exchange_organization_mailtips_enabled.exchange_client",
new=exchange_client,
),
):
from prowler.providers.m365.services.exchange.exchange_organization_mailtips_enabled.exchange_organization_mailtips_enabled import (
exchange_organization_mailtips_enabled,
)
check = exchange_organization_mailtips_enabled()
result = check.execute()
assert result == []
def test_mailtips_not_fully_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_organization_mailtips_enabled.exchange_organization_mailtips_enabled.exchange_client",
new=exchange_client,
),
):
from prowler.providers.m365.services.exchange.exchange_organization_mailtips_enabled.exchange_organization_mailtips_enabled import (
exchange_organization_mailtips_enabled,
)
from prowler.providers.m365.services.exchange.exchange_service import (
Organization,
)
exchange_client.audit_config = {
"recommended_mailtips_large_audience_threshold": 25
}
exchange_client.organization_config = Organization(
name="test-org",
guid="org-guid",
audit_disabled=False,
mailtips_enabled=False,
mailtips_external_recipient_enabled=False,
mailtips_group_metrics_enabled=True,
mailtips_large_audience_threshold=25,
)
check = exchange_organization_mailtips_enabled()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== "MailTips are not fully enabled for Exchange Online."
)
assert result[0].resource_name == "test-org"
assert result[0].resource_id == "org-guid"
assert result[0].location == "global"
assert result[0].resource == exchange_client.organization_config.dict()
def test_mailtips_fully_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_organization_mailtips_enabled.exchange_organization_mailtips_enabled.exchange_client",
new=exchange_client,
),
):
from prowler.providers.m365.services.exchange.exchange_organization_mailtips_enabled.exchange_organization_mailtips_enabled import (
exchange_organization_mailtips_enabled,
)
from prowler.providers.m365.services.exchange.exchange_service import (
Organization,
)
exchange_client.audit_config = {
"recommended_mailtips_large_audience_threshold": 25
}
exchange_client.organization_config = Organization(
name="test-org",
guid="org-guid",
audit_disabled=False,
mailtips_enabled=True,
mailtips_external_recipient_enabled=True,
mailtips_group_metrics_enabled=True,
mailtips_large_audience_threshold=25,
)
check = exchange_organization_mailtips_enabled()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert (
result[0].status_extended
== "MailTips are fully enabled for Exchange Online."
)
assert result[0].resource_name == "test-org"
assert result[0].resource_id == "org-guid"
assert result[0].location == "global"
assert result[0].resource == exchange_client.organization_config.dict()
@@ -14,7 +14,15 @@ from tests.providers.m365.m365_fixtures import DOMAIN, set_mocked_m365_provider
def mock_exchange_get_organization_config(_):
return Organization(audit_disabled=True, name="test", guid="test")
return Organization(
audit_disabled=True,
name="test",
guid="test",
mailtips_enabled=True,
mailtips_external_recipient_enabled=False,
mailtips_group_metrics_enabled=True,
mailtips_large_audience_threshold=25,
)
def mock_exchange_get_mailbox_audit_config(_):
@@ -94,6 +102,10 @@ class Test_Exchange_Service:
assert organization_config.name == "test"
assert organization_config.guid == "test"
assert organization_config.audit_disabled is True
assert organization_config.mailtips_enabled is True
assert organization_config.mailtips_external_recipient_enabled is False
assert organization_config.mailtips_group_metrics_enabled is True
assert organization_config.mailtips_large_audience_threshold == 25
exchange_client.powershell.close()