feat(exchange): add new check exchange_organization_modern_authentication_enabled (#7636)

Co-authored-by: Andoni A. <14891798+andoniaf@users.noreply.github.com>
This commit is contained in:
Daniel Barranquero
2025-05-02 12:44:39 +02:00
committed by GitHub
parent cccd69f27c
commit c938a25693
9 changed files with 217 additions and 0 deletions
+1
View File
@@ -36,6 +36,7 @@ All notable changes to the **Prowler SDK** are documented in this file.
- Add new check `teams_meeting_presenters_restricted` [(#7613)](https://github.com/prowler-cloud/prowler/pull/7613)
- Add new check `teams_meeting_chat_anonymous_users_disabled` [(#7579)](https://github.com/prowler-cloud/prowler/pull/7579)
- Add Prowler Threat Score Compliance Framework [(#7603)](https://github.com/prowler-cloud/prowler/pull/7603)
- Add new check for Modern Authentication enabled for Exchange Online in M365 [(#7636)](https://github.com/prowler-cloud/prowler/pull/7636)
- 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 Roles Assignment Policy with no AddIns for Exchange in M365 [(#7644)](https://github.com/prowler-cloud/prowler/pull/7644)
@@ -0,0 +1,30 @@
{
"Provider": "m365",
"CheckID": "exchange_organization_modern_authentication_enabled",
"CheckTitle": "Ensure Modern Authentication for Exchange Online is enabled.",
"CheckType": [],
"ServiceName": "exchange",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "critical",
"ResourceType": "Exchange Organization Configuration",
"Description": "Ensure that modern authentication is enabled for Exchange Online, requiring exchange and mailboxes clients to use strong authentication mechanisms instead of basic authentication.",
"Risk": "If modern authentication is not enabled, Exchange Online email clients may fall back to basic authentication, making it easier for attackers to bypass multifactor authentication and compromise user credentials.",
"RelatedUrl": "https://learn.microsoft.com/en-us/exchange/clients-and-mobile-in-exchange-online/enable-or-disable-modern-authentication-in-exchange-online",
"Remediation": {
"Code": {
"CLI": "Set-OrganizationConfig -OAuth2ClientProfileEnabled $True",
"NativeIaC": "",
"Other": "",
"Terraform": ""
},
"Recommendation": {
"Text": "Enable modern authentication in Exchange Online to enforce secure authentication methods for email clients.",
"Url": "https://learn.microsoft.com/en-us/exchange/clients-and-mobile-in-exchange-online/enable-or-disable-modern-authentication-in-exchange-online"
}
},
"Categories": [],
"DependsOn": [],
"RelatedTo": [],
"Notes": ""
}
@@ -0,0 +1,46 @@
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_modern_authentication_enabled(Check):
"""
Check if Modern Authentication is enabled for Exchange Online.
Attributes:
metadata: Metadata associated with the check (inherited from Check).
"""
def execute(self) -> List[CheckReportM365]:
"""
Execute the check for Modern Authentication in Exchange Online.
This method checks if Modern Authentication is 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 = (
"Modern Authentication is not enabled for Exchange Online."
)
if organization_config.oauth_enabled:
report.status = "PASS"
report.status_extended = (
"Modern Authentication is enabled for Exchange Online."
)
findings.append(report)
return findings
@@ -44,6 +44,9 @@ class Exchange(M365Service):
audit_disabled=organization_configuration.get(
"AuditDisabled", False
),
oauth_enabled=organization_configuration.get(
"OAuth2ClientProfileEnabled", True
),
mailtips_enabled=organization_configuration.get(
"MailTipsAllTipsEnabled", True
),
@@ -235,6 +238,7 @@ class Organization(BaseModel):
name: str
guid: str
audit_disabled: bool
oauth_enabled: bool
mailtips_enabled: bool
mailtips_external_recipient_enabled: bool
mailtips_group_metrics_enabled: bool
@@ -60,6 +60,7 @@ class Test_exchange_organization_mailbox_auditing_enabled:
audit_disabled=True,
name="test",
guid="test",
oauth_enabled=True,
mailtips_enabled=True,
mailtips_external_recipient_enabled=True,
mailtips_group_metrics_enabled=True,
@@ -108,6 +109,7 @@ class Test_exchange_organization_mailbox_auditing_enabled:
audit_disabled=False,
name="test",
guid="test",
oauth_enabled=True,
mailtips_enabled=True,
mailtips_external_recipient_enabled=True,
mailtips_group_metrics_enabled=True,
@@ -64,6 +64,7 @@ class Test_exchange_organization_mailtips_enabled:
name="test-org",
guid="org-guid",
audit_disabled=False,
oauth_enabled=True,
mailtips_enabled=False,
mailtips_external_recipient_enabled=False,
mailtips_group_metrics_enabled=True,
@@ -116,6 +117,7 @@ class Test_exchange_organization_mailtips_enabled:
name="test-org",
guid="org-guid",
audit_disabled=False,
oauth_enabled=True,
mailtips_enabled=True,
mailtips_external_recipient_enabled=True,
mailtips_group_metrics_enabled=True,
@@ -0,0 +1,130 @@
from unittest import mock
from tests.providers.m365.m365_fixtures import DOMAIN, set_mocked_m365_provider
class Test_exchange_organization_modern_authentication_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_modern_authentication_enabled.exchange_organization_modern_authentication_enabled.exchange_client",
new=exchange_client,
),
):
from prowler.providers.m365.services.exchange.exchange_organization_modern_authentication_enabled.exchange_organization_modern_authentication_enabled import (
exchange_organization_modern_authentication_enabled,
)
check = exchange_organization_modern_authentication_enabled()
result = check.execute()
assert len(result) == 0
def test_modern_authentication_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_organization_modern_authentication_enabled.exchange_organization_modern_authentication_enabled.exchange_client",
new=exchange_client,
),
):
from prowler.providers.m365.services.exchange.exchange_organization_modern_authentication_enabled.exchange_organization_modern_authentication_enabled import (
exchange_organization_modern_authentication_enabled,
)
from prowler.providers.m365.services.exchange.exchange_service import (
Organization,
)
exchange_client.organization_config = Organization(
oauth_enabled=False,
name="test",
guid="test",
audit_disabled=False,
mailtips_enabled=False,
mailtips_external_recipient_enabled=False,
mailtips_group_metrics_enabled=True,
mailtips_large_audience_threshold=25,
)
check = exchange_organization_modern_authentication_enabled()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== "Modern Authentication is not enabled for Exchange Online."
)
assert result[0].resource == exchange_client.organization_config.dict()
assert result[0].resource_name == "test"
assert result[0].resource_id == "test"
assert result[0].location == "global"
def test_modern_authentication_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_modern_authentication_enabled.exchange_organization_modern_authentication_enabled.exchange_client",
new=exchange_client,
),
):
from prowler.providers.m365.services.exchange.exchange_organization_modern_authentication_enabled.exchange_organization_modern_authentication_enabled import (
exchange_organization_modern_authentication_enabled,
)
from prowler.providers.m365.services.exchange.exchange_service import (
Organization,
)
exchange_client.organization_config = Organization(
oauth_enabled=True,
name="test",
guid="test",
audit_disabled=False,
mailtips_enabled=False,
mailtips_external_recipient_enabled=False,
mailtips_group_metrics_enabled=True,
mailtips_large_audience_threshold=25,
)
check = exchange_organization_modern_authentication_enabled()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert (
result[0].status_extended
== "Modern Authentication is enabled for Exchange Online."
)
assert result[0].resource == exchange_client.organization_config.dict()
assert result[0].resource_name == "test"
assert result[0].resource_id == "test"
assert result[0].location == "global"
@@ -21,6 +21,7 @@ def mock_exchange_get_organization_config(_):
audit_disabled=True,
name="test",
guid="test",
oauth_enabled=True,
mailtips_enabled=True,
mailtips_external_recipient_enabled=False,
mailtips_group_metrics_enabled=True,
@@ -183,6 +184,7 @@ class Test_Exchange_Service:
assert organization_config.name == "test"
assert organization_config.guid == "test"
assert organization_config.audit_disabled is True
assert organization_config.oauth_enabled 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