feat(sharepoint): add new check related with OneDrive Sync (#7589)

Co-authored-by: Andoni A. <14891798+andoniaf@users.noreply.github.com>
This commit is contained in:
Pedro Martín
2025-04-30 11:43:41 +02:00
committed by GitHub
parent ceacd077d2
commit 82eecec277
12 changed files with 233 additions and 2 deletions

View File

@@ -75,7 +75,7 @@ It contains hundreds of controls covering CIS, NIST 800, NIST CSF, CISA, RBI, Fe
| GCP | 79 | 13 | 7 | 3 |
| Azure | 140 | 18 | 8 | 3 |
| Kubernetes | 83 | 7 | 4 | 7 |
| M365 | 5 | 2 | 1 | 0 |
| M365 | 44 | 2 | 1 | 0 |
| NHN (Unofficial) | 6 | 2 | 1 | 0 |
> You can list the checks, services, compliance frameworks and categories with `prowler <provider> --list-checks`, `prowler <provider> --list-services`, `prowler <provider> --list-compliance` and `prowler <provider> --list-categories`.

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 `sharepoint_onedrive_sync_restricted_unmanaged_devices` [(#7589)](https://github.com/prowler-cloud/prowler/pull/7589)
### Fixed

View File

@@ -0,0 +1,30 @@
{
"Provider": "m365",
"CheckID": "sharepoint_onedrive_sync_restricted_unmanaged_devices",
"CheckTitle": "Ensure OneDrive sync is restricted for unmanaged devices.",
"CheckType": [],
"ServiceName": "sharepoint",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "critical",
"ResourceType": "Sharepoint Settings",
"Description": "Microsoft OneDrive allows users to sign in their cloud tenant account and begin syncing select folders or the entire contents of OneDrive to a local computer. By default, this includes any computer with OneDrive already installed, whether it is Entra Joined, Entra Hybrid Joined or Active Directory Domain joined. The recommended state for this setting is Allow syncing only on computers joined to specific domains Enabled: Specify the AD domain GUID(s).",
"Risk": "Unmanaged devices can pose a security risk by allowing users to sync sensitive data to unauthorized devices, potentially leading to data leakage or unauthorized access.",
"RelatedUrl": "https://learn.microsoft.com/en-us/graph/api/resources/sharepoint?view=graph-rest-1.0",
"Remediation": {
"Code": {
"CLI": "Set-SPOTenantSyncClientRestriction -Enable -DomainGuids '<domain_guid_1>; <domain_guid_2>; ...'",
"NativeIaC": "",
"Other": "1. Navigate to SharePoint admin center https://admin.microsoft.com/sharepoint 2. Click Settings then select OneDrive - Sync. 3. Check the Allow syncing only on computers joined to specific domains. 4. Use the Get-ADDomain PowerShell command on the on-premises server to obtain the GUID for each on-premises domain. 5. Click Save.",
"Terraform": ""
},
"Recommendation": {
"Text": "Restrict OneDrive sync to managed devices to prevent unauthorized access to sensitive data.",
"Url": "https://learn.microsoft.com/en-us/sharepoint/allow-syncing-only-on-specific-domains"
}
},
"Categories": [],
"DependsOn": [],
"RelatedTo": [],
"Notes": ""
}

View File

@@ -0,0 +1,48 @@
from typing import List
from prowler.lib.check.models import Check, CheckReportM365
from prowler.providers.m365.services.sharepoint.sharepoint_client import (
sharepoint_client,
)
class sharepoint_onedrive_sync_restricted_unmanaged_devices(Check):
"""
Check if OneDrive sync is restricted for unmanaged devices.
This check verifies that OneDrive sync is restricted to managed devices only.
Unmanaged devices can pose a security risk by allowing users to sync sensitive data to unauthorized devices,
potentially leading to data leakage or unauthorized access.
The check fails if OneDrive sync is not restricted to managed devices (AllowedDomainGuidsForSyncApp is empty).
"""
def execute(self) -> List[CheckReportM365]:
"""
Execute the OneDrive sync restriction check.
Retrieves the OneDrive sync settings from the Microsoft 365 SharePoint client and
generates a report indicating whether OneDrive sync is restricted to managed devices only.
Returns:
List[CheckReportM365]: A list containing the report object with the result of the check.
"""
findings = []
settings = sharepoint_client.settings
if settings:
report = CheckReportM365(
self.metadata(),
resource=settings if settings else {},
resource_name="SharePoint Settings",
resource_id=sharepoint_client.tenant_domain,
)
report.status = "PASS"
report.status_extended = "Microsoft 365 SharePoint does not allow OneDrive sync to unmanaged devices."
if len(settings.allowedDomainGuidsForSyncApp) == 0:
report.status = "FAIL"
report.status_extended = "Microsoft 365 SharePoint allows OneDrive sync to unmanaged devices."
findings.append(report)
return findings

View File

@@ -1,3 +1,4 @@
import uuid
from asyncio import gather, get_event_loop
from typing import List, Optional
@@ -26,7 +27,6 @@ class SharePoint(M365Service):
settings = None
try:
global_settings = await self.client.admin.sharepoint.settings.get()
settings = SharePointSettings(
sharingCapability=(
str(global_settings.sharing_capability).split(".")[-1]
@@ -38,6 +38,7 @@ class SharePoint(M365Service):
sharingDomainRestrictionMode=global_settings.sharing_domain_restriction_mode,
legacyAuth=global_settings.is_legacy_auth_protocols_enabled,
resharingEnabled=global_settings.is_resharing_by_external_users_enabled,
allowedDomainGuidsForSyncApp=global_settings.allowed_domain_guids_for_sync_app,
)
except ODataError as error:
@@ -60,3 +61,4 @@ class SharePointSettings(BaseModel):
sharingDomainRestrictionMode: str
resharingEnabled: bool
legacyAuth: bool
allowedDomainGuidsForSyncApp: List[uuid.UUID]

View File

@@ -1,3 +1,4 @@
import uuid
from unittest import mock
from prowler.providers.m365.services.sharepoint.sharepoint_service import (
@@ -35,6 +36,7 @@ class Test_sharepoint_external_sharing_managed:
legacyAuth=True,
resharingEnabled=False,
sharingDomainRestrictionMode="none",
allowedDomainGuidsForSyncApp=[uuid.uuid4()],
)
sharepoint_client.tenant_domain = DOMAIN
@@ -80,6 +82,7 @@ class Test_sharepoint_external_sharing_managed:
legacyAuth=True,
resharingEnabled=False,
sharingDomainRestrictionMode="allowList",
allowedDomainGuidsForSyncApp=[uuid.uuid4()],
)
sharepoint_client.tenant_domain = DOMAIN
@@ -125,6 +128,7 @@ class Test_sharepoint_external_sharing_managed:
legacyAuth=True,
resharingEnabled=False,
sharingDomainRestrictionMode="blockList",
allowedDomainGuidsForSyncApp=[uuid.uuid4()],
)
sharepoint_client.tenant_domain = DOMAIN
@@ -170,6 +174,7 @@ class Test_sharepoint_external_sharing_managed:
legacyAuth=True,
resharingEnabled=False,
sharingDomainRestrictionMode="allowList",
allowedDomainGuidsForSyncApp=[uuid.uuid4()],
)
sharepoint_client.tenant_domain = DOMAIN
@@ -215,6 +220,7 @@ class Test_sharepoint_external_sharing_managed:
legacyAuth=True,
resharingEnabled=False,
sharingDomainRestrictionMode="blockList",
allowedDomainGuidsForSyncApp=[uuid.uuid4()],
)
sharepoint_client.tenant_domain = DOMAIN

View File

@@ -1,3 +1,4 @@
import uuid
from unittest import mock
from prowler.providers.m365.services.sharepoint.sharepoint_service import (
@@ -35,6 +36,7 @@ class Test_sharepoint_external_sharing_restricted:
sharingDomainRestrictionMode="allowList",
resharingEnabled=False,
legacyAuth=True,
allowedDomainGuidsForSyncApp=[uuid.uuid4()],
)
sharepoint_client.tenant_domain = DOMAIN
@@ -78,6 +80,7 @@ class Test_sharepoint_external_sharing_restricted:
sharingDomainRestrictionMode="allowList",
resharingEnabled=False,
legacyAuth=True,
allowedDomainGuidsForSyncApp=[uuid.uuid4()],
)
sharepoint_client.tenant_domain = DOMAIN

View File

@@ -1,3 +1,4 @@
import uuid
from unittest import mock
from prowler.providers.m365.services.sharepoint.sharepoint_service import (
@@ -35,6 +36,7 @@ class Test_sharepoint_guest_sharing_restricted:
sharingDomainRestrictionMode="allowList",
legacyAuth=True,
resharingEnabled=False,
allowedDomainGuidsForSyncApp=[uuid.uuid4()],
)
sharepoint_client.tenant_domain = DOMAIN
@@ -79,6 +81,7 @@ class Test_sharepoint_guest_sharing_restricted:
sharingDomainRestrictionMode="allowList",
legacyAuth=True,
resharingEnabled=True,
allowedDomainGuidsForSyncApp=[uuid.uuid4()],
)
sharepoint_client.tenant_domain = DOMAIN

View File

@@ -1,3 +1,4 @@
import uuid
from unittest import mock
from tests.providers.m365.m365_fixtures import DOMAIN, set_mocked_m365_provider
@@ -35,6 +36,7 @@ class Test_sharepoint_modern_authentication_required:
sharingDomainRestrictionMode="allowList",
resharingEnabled=False,
legacyAuth=False,
allowedDomainGuidsForSyncApp=[uuid.uuid4()],
)
sharepoint_client.tenant_domain = DOMAIN
@@ -81,6 +83,7 @@ class Test_sharepoint_modern_authentication_required:
sharingDomainRestrictionMode="allowList",
resharingEnabled=False,
legacyAuth=True,
allowedDomainGuidsForSyncApp=[uuid.uuid4()],
)
sharepoint_client.tenant_domain = DOMAIN

View File

@@ -0,0 +1,129 @@
import uuid
from unittest import mock
from prowler.providers.m365.services.sharepoint.sharepoint_service import (
SharePointSettings,
)
from tests.providers.m365.m365_fixtures import DOMAIN, set_mocked_m365_provider
class Test_sharepoint_onedrive_sync_restricted_unmanaged_devices:
def test_no_allowed_domain_guids(self):
"""
Test when there are no allowed domain guids for OneDrive sync app
"""
sharepoint_client = mock.MagicMock
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_m365_provider(),
),
mock.patch(
"prowler.providers.m365.services.sharepoint.sharepoint_onedrive_sync_restricted_unmanaged_devices.sharepoint_onedrive_sync_restricted_unmanaged_devices.sharepoint_client",
new=sharepoint_client,
),
):
from prowler.providers.m365.services.sharepoint.sharepoint_onedrive_sync_restricted_unmanaged_devices.sharepoint_onedrive_sync_restricted_unmanaged_devices import (
sharepoint_onedrive_sync_restricted_unmanaged_devices,
)
sharepoint_client.settings = SharePointSettings(
sharingCapability="ExternalUserSharingOnly",
sharingAllowedDomainList=["allowed-domain.com"],
sharingBlockedDomainList=["blocked-domain.com"],
legacyAuth=True,
resharingEnabled=False,
sharingDomainRestrictionMode="none",
allowedDomainGuidsForSyncApp=[],
)
sharepoint_client.tenant_domain = DOMAIN
check = sharepoint_onedrive_sync_restricted_unmanaged_devices()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== "Microsoft 365 SharePoint allows OneDrive sync to unmanaged devices."
)
assert result[0].resource_id == DOMAIN
assert result[0].location == "global"
assert result[0].resource_name == "SharePoint Settings"
assert result[0].resource == sharepoint_client.settings.dict()
def test_allowed_domain_guids(self):
"""
Test when there are allowed domain guids for OneDrive sync app
"""
sharepoint_client = mock.MagicMock
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_m365_provider(),
),
mock.patch(
"prowler.providers.m365.services.sharepoint.sharepoint_onedrive_sync_restricted_unmanaged_devices.sharepoint_onedrive_sync_restricted_unmanaged_devices.sharepoint_client",
new=sharepoint_client,
),
):
from prowler.providers.m365.services.sharepoint.sharepoint_onedrive_sync_restricted_unmanaged_devices.sharepoint_onedrive_sync_restricted_unmanaged_devices import (
sharepoint_onedrive_sync_restricted_unmanaged_devices,
)
sharepoint_client.settings = SharePointSettings(
sharingCapability="ExternalUserSharingOnly",
sharingAllowedDomainList=[],
sharingBlockedDomainList=["blocked-domain.com"],
legacyAuth=True,
resharingEnabled=False,
sharingDomainRestrictionMode="allowList",
allowedDomainGuidsForSyncApp=[uuid.uuid4()],
)
sharepoint_client.tenant_domain = DOMAIN
check = sharepoint_onedrive_sync_restricted_unmanaged_devices()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert (
result[0].status_extended
== "Microsoft 365 SharePoint does not allow OneDrive sync to unmanaged devices."
)
assert result[0].resource_id == DOMAIN
assert result[0].location == "global"
assert result[0].resource_name == "SharePoint Settings"
assert result[0].resource == sharepoint_client.settings.dict()
def test_empty_settings(self):
"""
Test when sharepoint_client.settings is empty:
The check should return an empty list of findings.
"""
sharepoint_client = mock.MagicMock
sharepoint_client.settings = {}
sharepoint_client.tenant_domain = DOMAIN
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_m365_provider(),
),
mock.patch(
"prowler.providers.m365.services.sharepoint.sharepoint_onedrive_sync_restricted_unmanaged_devices.sharepoint_onedrive_sync_restricted_unmanaged_devices.sharepoint_client",
new=sharepoint_client,
),
):
from prowler.providers.m365.services.sharepoint.sharepoint_onedrive_sync_restricted_unmanaged_devices.sharepoint_onedrive_sync_restricted_unmanaged_devices import (
sharepoint_onedrive_sync_restricted_unmanaged_devices,
)
check = sharepoint_onedrive_sync_restricted_unmanaged_devices()
result = check.execute()
assert len(result) == 0

View File

@@ -1,3 +1,4 @@
import uuid
from unittest.mock import patch
from prowler.providers.m365.models import M365IdentityInfo
@@ -7,6 +8,8 @@ from prowler.providers.m365.services.sharepoint.sharepoint_service import (
)
from tests.providers.m365.m365_fixtures import DOMAIN, set_mocked_m365_provider
uuid_value = uuid.uuid4()
async def mock_sharepoint_get_settings(_):
return SharePointSettings(
@@ -16,6 +19,7 @@ async def mock_sharepoint_get_settings(_):
sharingDomainRestrictionMode="allowList",
resharingEnabled=False,
legacyAuth=True,
allowedDomainGuidsForSyncApp=[uuid_value],
)
@@ -39,3 +43,5 @@ class Test_SharePoint_Service:
assert settings.sharingDomainRestrictionMode == "allowList"
assert settings.resharingEnabled is False
assert settings.legacyAuth is True
assert settings.allowedDomainGuidsForSyncApp == [uuid_value]
assert len(settings.allowedDomainGuidsForSyncApp) == 1