mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-07-24 04:51:51 +00:00
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:
committed by
Pepe Fagoaga
parent
75b3c02811
commit
ab6d05637d
@@ -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
|
||||
|
||||
|
||||
```
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
+30
@@ -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": ""
|
||||
}
|
||||
+54
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
+14
-2
@@ -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()
|
||||
|
||||
+136
@@ -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()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user