mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-07-24 13:01:56 +00:00
feat(exchange): add new check exchange_roles_assignment_policy_addins_disabled (#7644)
Co-authored-by: Andoni A. <14891798+andoniaf@users.noreply.github.com>
This commit is contained in:
committed by
Pepe Fagoaga
parent
dbffcedc49
commit
80c8cb9b6c
@@ -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 Roles Assignment Policy with no AddIns for Exchange in M365 [(#7644)](https://github.com/prowler-cloud/prowler/pull/7644)
|
||||
- Add new check for Auditing Mailbox on E3 users is enabled for Exchange in M365 [(#7642)](https://github.com/prowler-cloud/prowler/pull/7642)
|
||||
- Add new check for SMTP Auth disabled for Exchange in M365 [(#7640)](https://github.com/prowler-cloud/prowler/pull/7640)
|
||||
- Add new check for MailTips full enabled for Exchange in M365 [(#7637)](https://github.com/prowler-cloud/prowler/pull/7637)
|
||||
|
||||
@@ -487,6 +487,27 @@ class M365PowerShell(PowerShellSession):
|
||||
"Get-HostedContentFilterPolicy | ConvertTo-Json", json_parse=True
|
||||
)
|
||||
|
||||
def get_role_assignment_policies(self) -> dict:
|
||||
"""
|
||||
Get Role Assignment Policies.
|
||||
|
||||
Retrieves the current role assignment policies for Exchange Online.
|
||||
|
||||
Returns:
|
||||
dict: Role assignment policies in JSON format.
|
||||
|
||||
Example:
|
||||
>>> get_role_assignment_policies()
|
||||
{
|
||||
"Name": "Default Role Assignment Policy",
|
||||
"Guid": "12345678-1234-1234-1234-123456789012",
|
||||
"AssignedRoles": ["MyRole"]
|
||||
}
|
||||
"""
|
||||
return self.execute(
|
||||
"Get-RoleAssignmentPolicy | ConvertTo-Json", json_parse=True
|
||||
)
|
||||
|
||||
def get_mailbox_audit_properties(self) -> dict:
|
||||
"""
|
||||
Get Mailbox Properties.
|
||||
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"Provider": "m365",
|
||||
"CheckID": "exchange_roles_assignment_policy_addins_disabled",
|
||||
"CheckTitle": "Ensure there is no policy with Outlook add-ins allowed.",
|
||||
"CheckType": [],
|
||||
"ServiceName": "exchange",
|
||||
"SubServiceName": "",
|
||||
"ResourceIdTemplate": "",
|
||||
"Severity": "high",
|
||||
"ResourceType": "Exchange Role Assignment Policy",
|
||||
"Description": "Restricting users from installing Outlook add-ins reduces the risk of data exposure or exploitation through unapproved or vulnerable add-ins.",
|
||||
"Risk": "Allowing users to install add-ins may expose sensitive information or introduce malicious behavior through third-party integrations. Disabling this capability mitigates the risk of unauthorized data access.",
|
||||
"RelatedUrl": "https://learn.microsoft.com/en-us/exchange/clients-and-mobile-in-exchange-online/add-ins-for-outlook/specify-who-can-install-and-manage-add-ins",
|
||||
"Remediation": {
|
||||
"Code": {
|
||||
"CLI": "$policy = \"Role Assignment Policy - Prevent Add-ins\"; $roles = \"MyTextMessaging\", \"MyDistributionGroups\", \"MyMailSubscriptions\", \"MyBaseOptions\", \"MyVoiceMail\", \"MyProfileInformation\", \"MyContactInformation\", \"MyRetentionPolicies\", \"MyDistributionGroupMembership\"; New-RoleAssignmentPolicy -Name $policy -Roles $roles; Set-RoleAssignmentPolicy -id $policy -IsDefault; Get-EXOMailbox -ResultSize Unlimited | Set-Mailbox -RoleAssignmentPolicy $policy",
|
||||
"NativeIaC": "",
|
||||
"Other": "1. Navigate to Exchange admin center https://admin.exchange.microsoft.com. 2. Click to expand Roles > User roles. 3. Select Default Role Assignment Policy. 4. In the right pane, click Manage permissions. 5. Uncheck My Custom Apps, My Marketplace Apps and My ReadWriteMailboxApps under Other roles. 6. Save changes.",
|
||||
"Terraform": ""
|
||||
},
|
||||
"Recommendation": {
|
||||
"Text": "Restrict Outlook add-in installation by updating the Role Assignment Policy to exclude roles that allow app installation.",
|
||||
"Url": "https://learn.microsoft.com/en-us/exchange/permissions-exo/role-assignment-policies"
|
||||
}
|
||||
},
|
||||
"Categories": [],
|
||||
"DependsOn": [],
|
||||
"RelatedTo": [],
|
||||
"Notes": ""
|
||||
}
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
from typing import List
|
||||
|
||||
from prowler.lib.check.models import Check, CheckReportM365
|
||||
from prowler.providers.m365.services.exchange.exchange_client import exchange_client
|
||||
from prowler.providers.m365.services.exchange.exchange_service import AddinRoles
|
||||
|
||||
|
||||
class exchange_roles_assignment_policy_addins_disabled(Check):
|
||||
"""Check if any Exchange role assignment policy allows Outlook add-ins.
|
||||
|
||||
Attributes:
|
||||
metadata: Metadata associated with the check (inherited from Check).
|
||||
"""
|
||||
|
||||
def execute(self) -> List[CheckReportM365]:
|
||||
"""Execute the check for role assignment policies that allow Outlook add-ins.
|
||||
|
||||
This method checks all Exchange Online Role Assignment Policies to verify
|
||||
whether any of them allow the installation of add-ins by including risky roles.
|
||||
|
||||
Returns:
|
||||
List[CheckReportM365]: A list of reports containing the result of the check.
|
||||
"""
|
||||
findings = []
|
||||
|
||||
addin_roles = [e.value for e in AddinRoles]
|
||||
|
||||
for policy in exchange_client.role_assignment_policies:
|
||||
report = CheckReportM365(
|
||||
metadata=self.metadata(),
|
||||
resource=policy,
|
||||
resource_name=policy.name,
|
||||
resource_id=policy.id,
|
||||
)
|
||||
|
||||
report.status = "PASS"
|
||||
report.status_extended = f"Role assignment policy '{policy.name}' does not allow Outlook add-ins."
|
||||
|
||||
risky_roles_found = []
|
||||
for role in policy.assigned_roles:
|
||||
if role in addin_roles:
|
||||
risky_roles_found.append(role)
|
||||
|
||||
if risky_roles_found:
|
||||
report.status = "FAIL"
|
||||
report.status_extended = f"Role assignment policy '{policy.name}' allows Outlook add-ins via roles: {', '.join(risky_roles_found)}."
|
||||
|
||||
findings.append(report)
|
||||
|
||||
return findings
|
||||
@@ -17,6 +17,7 @@ class Exchange(M365Service):
|
||||
self.transport_rules = []
|
||||
self.transport_config = None
|
||||
self.mailbox_policy = None
|
||||
self.role_assignment_policies = []
|
||||
self.mailbox_audit_properties = []
|
||||
|
||||
if self.powershell:
|
||||
@@ -27,6 +28,7 @@ class Exchange(M365Service):
|
||||
self.transport_rules = self._get_transport_rules()
|
||||
self.transport_config = self._get_transport_config()
|
||||
self.mailbox_policy = 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()
|
||||
|
||||
@@ -166,6 +168,30 @@ class Exchange(M365Service):
|
||||
)
|
||||
return mailboxes_policy
|
||||
|
||||
def _get_role_assignment_policies(self):
|
||||
logger.info("Microsoft365 - Getting role assignment policies...")
|
||||
role_assignment_policies = []
|
||||
try:
|
||||
policies_data = self.powershell.get_role_assignment_policies()
|
||||
if not policies_data:
|
||||
return role_assignment_policies
|
||||
if isinstance(policies_data, dict):
|
||||
policies_data = [policies_data]
|
||||
for policy in policies_data:
|
||||
if policy:
|
||||
role_assignment_policies.append(
|
||||
RoleAssignmentPolicy(
|
||||
name=policy.get("Name", ""),
|
||||
id=policy.get("Guid", ""),
|
||||
assigned_roles=policy.get("AssignedRoles", []),
|
||||
)
|
||||
)
|
||||
except Exception as error:
|
||||
logger.error(
|
||||
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
|
||||
)
|
||||
return role_assignment_policies
|
||||
|
||||
def _get_mailbox_audit_properties(self):
|
||||
logger.info("Microsoft365 - Getting mailbox audit properties...")
|
||||
mailbox_audit_properties = []
|
||||
@@ -241,6 +267,18 @@ class MailboxPolicy(BaseModel):
|
||||
additional_storage_enabled: bool
|
||||
|
||||
|
||||
class RoleAssignmentPolicy(BaseModel):
|
||||
name: str
|
||||
id: str
|
||||
assigned_roles: list[str]
|
||||
|
||||
|
||||
class AddinRoles(Enum):
|
||||
MY_CUSTOM_APPS = "My Custom Apps"
|
||||
MY_MARKETPLACE_APPS = "My Marketplace Apps"
|
||||
MY_READWRITE_MAILBOX_APPS = "My ReadWriteMailbox Apps"
|
||||
|
||||
|
||||
class MailboxAuditProperties(BaseModel):
|
||||
name: str
|
||||
audit_enabled: bool
|
||||
|
||||
+129
@@ -0,0 +1,129 @@
|
||||
from unittest import mock
|
||||
|
||||
from prowler.providers.m365.services.exchange.exchange_service import (
|
||||
RoleAssignmentPolicy,
|
||||
)
|
||||
from tests.providers.m365.m365_fixtures import DOMAIN, set_mocked_m365_provider
|
||||
|
||||
|
||||
class Test_exchange_roles_assignment_policy_addins_disabled:
|
||||
def test_no_policies(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_roles_assignment_policy_addins_disabled.exchange_roles_assignment_policy_addins_disabled.exchange_client",
|
||||
new=exchange_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.m365.services.exchange.exchange_roles_assignment_policy_addins_disabled.exchange_roles_assignment_policy_addins_disabled import (
|
||||
exchange_roles_assignment_policy_addins_disabled,
|
||||
)
|
||||
|
||||
exchange_client.role_assignment_policies = []
|
||||
|
||||
check = exchange_roles_assignment_policy_addins_disabled()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 0
|
||||
|
||||
def test_policy_with_no_addin_roles(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_roles_assignment_policy_addins_disabled.exchange_roles_assignment_policy_addins_disabled.exchange_client",
|
||||
new=exchange_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.m365.services.exchange.exchange_roles_assignment_policy_addins_disabled.exchange_roles_assignment_policy_addins_disabled import (
|
||||
exchange_roles_assignment_policy_addins_disabled,
|
||||
)
|
||||
|
||||
exchange_client.role_assignment_policies = [
|
||||
RoleAssignmentPolicy(
|
||||
name="Policy1",
|
||||
id="id-policy1",
|
||||
assigned_roles=["My Base Options", "My Voice Mail"],
|
||||
)
|
||||
]
|
||||
|
||||
check = exchange_roles_assignment_policy_addins_disabled()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== "Role assignment policy 'Policy1' does not allow Outlook add-ins."
|
||||
)
|
||||
assert result[0].resource_name == "Policy1"
|
||||
assert result[0].resource_id == "id-policy1"
|
||||
assert result[0].location == "global"
|
||||
assert (
|
||||
result[0].resource == exchange_client.role_assignment_policies[0].dict()
|
||||
)
|
||||
|
||||
def test_policy_with_addin_roles(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_roles_assignment_policy_addins_disabled.exchange_roles_assignment_policy_addins_disabled.exchange_client",
|
||||
new=exchange_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.m365.services.exchange.exchange_roles_assignment_policy_addins_disabled.exchange_roles_assignment_policy_addins_disabled import (
|
||||
exchange_roles_assignment_policy_addins_disabled,
|
||||
)
|
||||
|
||||
exchange_client.role_assignment_policies = [
|
||||
RoleAssignmentPolicy(
|
||||
name="Policy2",
|
||||
id="id-policy2",
|
||||
assigned_roles=["My Custom Apps", "My Voice Mail"],
|
||||
)
|
||||
]
|
||||
|
||||
check = exchange_roles_assignment_policy_addins_disabled()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== "Role assignment policy 'Policy2' allows Outlook add-ins via roles: My Custom Apps."
|
||||
)
|
||||
assert result[0].resource_name == "Policy2"
|
||||
assert result[0].resource_id == "id-policy2"
|
||||
assert result[0].location == "global"
|
||||
assert (
|
||||
result[0].resource == exchange_client.role_assignment_policies[0].dict()
|
||||
)
|
||||
@@ -9,6 +9,7 @@ from prowler.providers.m365.services.exchange.exchange_service import (
|
||||
MailboxAuditProperties,
|
||||
MailboxPolicy,
|
||||
Organization,
|
||||
RoleAssignmentPolicy,
|
||||
TransportConfig,
|
||||
TransportRule,
|
||||
)
|
||||
@@ -75,6 +76,27 @@ def mock_exchange_get_mailbox_policy(_):
|
||||
)
|
||||
|
||||
|
||||
def mock_exchange_get_role_assignment_policies(_):
|
||||
return [
|
||||
RoleAssignmentPolicy(
|
||||
name="Default Role Assignment Policy",
|
||||
id="12345678-1234-1234-1234",
|
||||
assigned_roles=[
|
||||
"MyProfileInformation",
|
||||
"MyDistributionGroupMembership",
|
||||
"MyRetentionPolicies",
|
||||
"MyDistributionGroups",
|
||||
"MyVoiceMail",
|
||||
],
|
||||
),
|
||||
RoleAssignmentPolicy(
|
||||
name="Test Policy",
|
||||
id="12345678-1234-1234",
|
||||
assigned_roles=[],
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
def mock_exchange_get_mailbox_audit_properties(_):
|
||||
return [
|
||||
MailboxAuditProperties(
|
||||
@@ -345,3 +367,35 @@ class Test_Exchange_Service:
|
||||
assert mailbox_audit_properties[0].audit_log_age == 90
|
||||
assert mailbox_audit_properties[0].identity == "test"
|
||||
exchange_client.powershell.close()
|
||||
|
||||
@patch(
|
||||
"prowler.providers.m365.services.exchange.exchange_service.Exchange._get_role_assignment_policies",
|
||||
new=mock_exchange_get_role_assignment_policies,
|
||||
)
|
||||
def test_get_role_assignment_policies(self):
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.m365.lib.powershell.m365_powershell.M365PowerShell.connect_exchange_online"
|
||||
),
|
||||
):
|
||||
exchange_client = Exchange(
|
||||
set_mocked_m365_provider(
|
||||
identity=M365IdentityInfo(tenant_domain=DOMAIN)
|
||||
)
|
||||
)
|
||||
role_assignment_policies = exchange_client.role_assignment_policies
|
||||
assert len(role_assignment_policies) == 2
|
||||
assert role_assignment_policies[0].name == "Default Role Assignment Policy"
|
||||
assert role_assignment_policies[0].id == "12345678-1234-1234-1234"
|
||||
assert role_assignment_policies[0].assigned_roles == [
|
||||
"MyProfileInformation",
|
||||
"MyDistributionGroupMembership",
|
||||
"MyRetentionPolicies",
|
||||
"MyDistributionGroups",
|
||||
"MyVoiceMail",
|
||||
]
|
||||
assert role_assignment_policies[1].name == "Test Policy"
|
||||
assert role_assignment_policies[1].id == "12345678-1234-1234"
|
||||
assert role_assignment_policies[1].assigned_roles == []
|
||||
|
||||
exchange_client.powershell.close()
|
||||
|
||||
Reference in New Issue
Block a user