Skip to content

Commit 5aef1cc

Browse files
pedroootandoniaf
andcommitted
feat(sharepoint): add new check related with OneDrive Sync (#7589)
Co-authored-by: Andoni A. <14891798+andoniaf@users.noreply.github.com>
1 parent d4c235e commit 5aef1cc

File tree

12 files changed

+233
-2
lines changed

12 files changed

+233
-2
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ It contains hundreds of controls covering CIS, NIST 800, NIST CSF, CISA, RBI, Fe
7575
| GCP | 79 | 13 | 7 | 3 |
7676
| Azure | 140 | 18 | 8 | 3 |
7777
| Kubernetes | 83 | 7 | 4 | 7 |
78-
| M365 | 5 | 2 | 1 | 0 |
78+
| M365 | 44 | 2 | 1 | 0 |
7979
| NHN (Unofficial) | 6 | 2 | 1 | 0 |
8080

8181
> 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`.

prowler/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ All notable changes to the **Prowler SDK** are documented in this file.
3636
- Add new check `teams_meeting_presenters_restricted` [(#7613)](https://github.com/prowler-cloud/prowler/pull/7613)
3737
- Add new check `teams_meeting_chat_anonymous_users_disabled` [(#7579)](https://github.com/prowler-cloud/prowler/pull/7579)
3838
- Add Prowler Threat Score Compliance Framework [(#7603)](https://github.com/prowler-cloud/prowler/pull/7603)
39+
- Add new check `sharepoint_onedrive_sync_restricted_unmanaged_devices` [(#7589)](https://github.com/prowler-cloud/prowler/pull/7589)
3940

4041
### Fixed
4142

prowler/providers/m365/services/sharepoint/sharepoint_onedrive_sync_restricted_unmanaged_devices/__init__.py

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
{
2+
"Provider": "m365",
3+
"CheckID": "sharepoint_onedrive_sync_restricted_unmanaged_devices",
4+
"CheckTitle": "Ensure OneDrive sync is restricted for unmanaged devices.",
5+
"CheckType": [],
6+
"ServiceName": "sharepoint",
7+
"SubServiceName": "",
8+
"ResourceIdTemplate": "",
9+
"Severity": "critical",
10+
"ResourceType": "Sharepoint Settings",
11+
"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).",
12+
"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.",
13+
"RelatedUrl": "https://learn.microsoft.com/en-us/graph/api/resources/sharepoint?view=graph-rest-1.0",
14+
"Remediation": {
15+
"Code": {
16+
"CLI": "Set-SPOTenantSyncClientRestriction -Enable -DomainGuids '<domain_guid_1>; <domain_guid_2>; ...'",
17+
"NativeIaC": "",
18+
"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.",
19+
"Terraform": ""
20+
},
21+
"Recommendation": {
22+
"Text": "Restrict OneDrive sync to managed devices to prevent unauthorized access to sensitive data.",
23+
"Url": "https://learn.microsoft.com/en-us/sharepoint/allow-syncing-only-on-specific-domains"
24+
}
25+
},
26+
"Categories": [],
27+
"DependsOn": [],
28+
"RelatedTo": [],
29+
"Notes": ""
30+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
from typing import List
2+
3+
from prowler.lib.check.models import Check, CheckReportM365
4+
from prowler.providers.m365.services.sharepoint.sharepoint_client import (
5+
sharepoint_client,
6+
)
7+
8+
9+
class sharepoint_onedrive_sync_restricted_unmanaged_devices(Check):
10+
"""
11+
Check if OneDrive sync is restricted for unmanaged devices.
12+
13+
This check verifies that OneDrive sync is restricted to managed devices only.
14+
Unmanaged devices can pose a security risk by allowing users to sync sensitive data to unauthorized devices,
15+
potentially leading to data leakage or unauthorized access.
16+
17+
The check fails if OneDrive sync is not restricted to managed devices (AllowedDomainGuidsForSyncApp is empty).
18+
"""
19+
20+
def execute(self) -> List[CheckReportM365]:
21+
"""
22+
Execute the OneDrive sync restriction check.
23+
24+
Retrieves the OneDrive sync settings from the Microsoft 365 SharePoint client and
25+
generates a report indicating whether OneDrive sync is restricted to managed devices only.
26+
27+
Returns:
28+
List[CheckReportM365]: A list containing the report object with the result of the check.
29+
"""
30+
findings = []
31+
settings = sharepoint_client.settings
32+
if settings:
33+
report = CheckReportM365(
34+
self.metadata(),
35+
resource=settings if settings else {},
36+
resource_name="SharePoint Settings",
37+
resource_id=sharepoint_client.tenant_domain,
38+
)
39+
report.status = "PASS"
40+
report.status_extended = "Microsoft 365 SharePoint does not allow OneDrive sync to unmanaged devices."
41+
42+
if len(settings.allowedDomainGuidsForSyncApp) == 0:
43+
report.status = "FAIL"
44+
report.status_extended = "Microsoft 365 SharePoint allows OneDrive sync to unmanaged devices."
45+
46+
findings.append(report)
47+
48+
return findings

prowler/providers/m365/services/sharepoint/sharepoint_service.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import uuid
12
from asyncio import gather, get_event_loop
23
from typing import List, Optional
34

@@ -26,7 +27,6 @@ async def _get_settings(self):
2627
settings = None
2728
try:
2829
global_settings = await self.client.admin.sharepoint.settings.get()
29-
3030
settings = SharePointSettings(
3131
sharingCapability=(
3232
str(global_settings.sharing_capability).split(".")[-1]
@@ -38,6 +38,7 @@ async def _get_settings(self):
3838
sharingDomainRestrictionMode=global_settings.sharing_domain_restriction_mode,
3939
legacyAuth=global_settings.is_legacy_auth_protocols_enabled,
4040
resharingEnabled=global_settings.is_resharing_by_external_users_enabled,
41+
allowedDomainGuidsForSyncApp=global_settings.allowed_domain_guids_for_sync_app,
4142
)
4243

4344
except ODataError as error:
@@ -60,3 +61,4 @@ class SharePointSettings(BaseModel):
6061
sharingDomainRestrictionMode: str
6162
resharingEnabled: bool
6263
legacyAuth: bool
64+
allowedDomainGuidsForSyncApp: List[uuid.UUID]

tests/providers/m365/services/sharepoint/sharepoint_external_sharing_managed/sharepoint_external_sharing_managed_test.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import uuid
12
from unittest import mock
23

34
from prowler.providers.m365.services.sharepoint.sharepoint_service import (
@@ -35,6 +36,7 @@ def test_external_sharing_invalid_mode(self):
3536
legacyAuth=True,
3637
resharingEnabled=False,
3738
sharingDomainRestrictionMode="none",
39+
allowedDomainGuidsForSyncApp=[uuid.uuid4()],
3840
)
3941
sharepoint_client.tenant_domain = DOMAIN
4042

@@ -80,6 +82,7 @@ def test_allow_list_empty(self):
8082
legacyAuth=True,
8183
resharingEnabled=False,
8284
sharingDomainRestrictionMode="allowList",
85+
allowedDomainGuidsForSyncApp=[uuid.uuid4()],
8386
)
8487
sharepoint_client.tenant_domain = DOMAIN
8588

@@ -125,6 +128,7 @@ def test_block_list_empty(self):
125128
legacyAuth=True,
126129
resharingEnabled=False,
127130
sharingDomainRestrictionMode="blockList",
131+
allowedDomainGuidsForSyncApp=[uuid.uuid4()],
128132
)
129133
sharepoint_client.tenant_domain = DOMAIN
130134

@@ -170,6 +174,7 @@ def test_allow_list_non_empty(self):
170174
legacyAuth=True,
171175
resharingEnabled=False,
172176
sharingDomainRestrictionMode="allowList",
177+
allowedDomainGuidsForSyncApp=[uuid.uuid4()],
173178
)
174179
sharepoint_client.tenant_domain = DOMAIN
175180

@@ -215,6 +220,7 @@ def test_block_list_non_empty(self):
215220
legacyAuth=True,
216221
resharingEnabled=False,
217222
sharingDomainRestrictionMode="blockList",
223+
allowedDomainGuidsForSyncApp=[uuid.uuid4()],
218224
)
219225
sharepoint_client.tenant_domain = DOMAIN
220226

tests/providers/m365/services/sharepoint/sharepoint_external_sharing_restricted/sharepoint_external_sharing_restricted_test.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import uuid
12
from unittest import mock
23

34
from prowler.providers.m365.services.sharepoint.sharepoint_service import (
@@ -35,6 +36,7 @@ def test_external_sharing_restricted(self):
3536
sharingDomainRestrictionMode="allowList",
3637
resharingEnabled=False,
3738
legacyAuth=True,
39+
allowedDomainGuidsForSyncApp=[uuid.uuid4()],
3840
)
3941
sharepoint_client.tenant_domain = DOMAIN
4042

@@ -78,6 +80,7 @@ def test_external_sharing_not_restricted(self):
7880
sharingDomainRestrictionMode="allowList",
7981
resharingEnabled=False,
8082
legacyAuth=True,
83+
allowedDomainGuidsForSyncApp=[uuid.uuid4()],
8184
)
8285
sharepoint_client.tenant_domain = DOMAIN
8386

tests/providers/m365/services/sharepoint/sharepoint_guest_sharing_restricted/sharepoint_guest_sharing_restricted_test.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import uuid
12
from unittest import mock
23

34
from prowler.providers.m365.services.sharepoint.sharepoint_service import (
@@ -35,6 +36,7 @@ def test_guest_sharing_restricted(self):
3536
sharingDomainRestrictionMode="allowList",
3637
legacyAuth=True,
3738
resharingEnabled=False,
39+
allowedDomainGuidsForSyncApp=[uuid.uuid4()],
3840
)
3941
sharepoint_client.tenant_domain = DOMAIN
4042

@@ -79,6 +81,7 @@ def test_guest_sharing_not_restricted(self):
7981
sharingDomainRestrictionMode="allowList",
8082
legacyAuth=True,
8183
resharingEnabled=True,
84+
allowedDomainGuidsForSyncApp=[uuid.uuid4()],
8285
)
8386
sharepoint_client.tenant_domain = DOMAIN
8487

tests/providers/m365/services/sharepoint/sharepoint_modern_authentication_required/sharepoint_modern_authentication_required_test.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import uuid
12
from unittest import mock
23

34
from tests.providers.m365.m365_fixtures import DOMAIN, set_mocked_m365_provider
@@ -35,6 +36,7 @@ def test_sharepoint_modern_authentication_disabled(self):
3536
sharingDomainRestrictionMode="allowList",
3637
resharingEnabled=False,
3738
legacyAuth=False,
39+
allowedDomainGuidsForSyncApp=[uuid.uuid4()],
3840
)
3941
sharepoint_client.tenant_domain = DOMAIN
4042

@@ -81,6 +83,7 @@ def test_sharepoint_modern_authentication_enabled(self):
8183
sharingDomainRestrictionMode="allowList",
8284
resharingEnabled=False,
8385
legacyAuth=True,
86+
allowedDomainGuidsForSyncApp=[uuid.uuid4()],
8487
)
8588
sharepoint_client.tenant_domain = DOMAIN
8689

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
import uuid
2+
from unittest import mock
3+
4+
from prowler.providers.m365.services.sharepoint.sharepoint_service import (
5+
SharePointSettings,
6+
)
7+
from tests.providers.m365.m365_fixtures import DOMAIN, set_mocked_m365_provider
8+
9+
10+
class Test_sharepoint_onedrive_sync_restricted_unmanaged_devices:
11+
def test_no_allowed_domain_guids(self):
12+
"""
13+
Test when there are no allowed domain guids for OneDrive sync app
14+
15+
16+
"""
17+
sharepoint_client = mock.MagicMock
18+
19+
with (
20+
mock.patch(
21+
"prowler.providers.common.provider.Provider.get_global_provider",
22+
return_value=set_mocked_m365_provider(),
23+
),
24+
mock.patch(
25+
"prowler.providers.m365.services.sharepoint.sharepoint_onedrive_sync_restricted_unmanaged_devices.sharepoint_onedrive_sync_restricted_unmanaged_devices.sharepoint_client",
26+
new=sharepoint_client,
27+
),
28+
):
29+
from prowler.providers.m365.services.sharepoint.sharepoint_onedrive_sync_restricted_unmanaged_devices.sharepoint_onedrive_sync_restricted_unmanaged_devices import (
30+
sharepoint_onedrive_sync_restricted_unmanaged_devices,
31+
)
32+
33+
sharepoint_client.settings = SharePointSettings(
34+
sharingCapability="ExternalUserSharingOnly",
35+
sharingAllowedDomainList=["allowed-domain.com"],
36+
sharingBlockedDomainList=["blocked-domain.com"],
37+
legacyAuth=True,
38+
resharingEnabled=False,
39+
sharingDomainRestrictionMode="none",
40+
allowedDomainGuidsForSyncApp=[],
41+
)
42+
sharepoint_client.tenant_domain = DOMAIN
43+
44+
check = sharepoint_onedrive_sync_restricted_unmanaged_devices()
45+
result = check.execute()
46+
47+
assert len(result) == 1
48+
assert result[0].status == "FAIL"
49+
assert (
50+
result[0].status_extended
51+
== "Microsoft 365 SharePoint allows OneDrive sync to unmanaged devices."
52+
)
53+
assert result[0].resource_id == DOMAIN
54+
assert result[0].location == "global"
55+
assert result[0].resource_name == "SharePoint Settings"
56+
assert result[0].resource == sharepoint_client.settings.dict()
57+
58+
def test_allowed_domain_guids(self):
59+
"""
60+
Test when there are allowed domain guids for OneDrive sync app
61+
"""
62+
sharepoint_client = mock.MagicMock
63+
64+
with (
65+
mock.patch(
66+
"prowler.providers.common.provider.Provider.get_global_provider",
67+
return_value=set_mocked_m365_provider(),
68+
),
69+
mock.patch(
70+
"prowler.providers.m365.services.sharepoint.sharepoint_onedrive_sync_restricted_unmanaged_devices.sharepoint_onedrive_sync_restricted_unmanaged_devices.sharepoint_client",
71+
new=sharepoint_client,
72+
),
73+
):
74+
from prowler.providers.m365.services.sharepoint.sharepoint_onedrive_sync_restricted_unmanaged_devices.sharepoint_onedrive_sync_restricted_unmanaged_devices import (
75+
sharepoint_onedrive_sync_restricted_unmanaged_devices,
76+
)
77+
78+
sharepoint_client.settings = SharePointSettings(
79+
sharingCapability="ExternalUserSharingOnly",
80+
sharingAllowedDomainList=[],
81+
sharingBlockedDomainList=["blocked-domain.com"],
82+
legacyAuth=True,
83+
resharingEnabled=False,
84+
sharingDomainRestrictionMode="allowList",
85+
allowedDomainGuidsForSyncApp=[uuid.uuid4()],
86+
)
87+
sharepoint_client.tenant_domain = DOMAIN
88+
89+
check = sharepoint_onedrive_sync_restricted_unmanaged_devices()
90+
result = check.execute()
91+
92+
assert len(result) == 1
93+
assert result[0].status == "PASS"
94+
assert (
95+
result[0].status_extended
96+
== "Microsoft 365 SharePoint does not allow OneDrive sync to unmanaged devices."
97+
)
98+
assert result[0].resource_id == DOMAIN
99+
assert result[0].location == "global"
100+
assert result[0].resource_name == "SharePoint Settings"
101+
assert result[0].resource == sharepoint_client.settings.dict()
102+
103+
def test_empty_settings(self):
104+
"""
105+
Test when sharepoint_client.settings is empty:
106+
The check should return an empty list of findings.
107+
"""
108+
sharepoint_client = mock.MagicMock
109+
sharepoint_client.settings = {}
110+
sharepoint_client.tenant_domain = DOMAIN
111+
112+
with (
113+
mock.patch(
114+
"prowler.providers.common.provider.Provider.get_global_provider",
115+
return_value=set_mocked_m365_provider(),
116+
),
117+
mock.patch(
118+
"prowler.providers.m365.services.sharepoint.sharepoint_onedrive_sync_restricted_unmanaged_devices.sharepoint_onedrive_sync_restricted_unmanaged_devices.sharepoint_client",
119+
new=sharepoint_client,
120+
),
121+
):
122+
from prowler.providers.m365.services.sharepoint.sharepoint_onedrive_sync_restricted_unmanaged_devices.sharepoint_onedrive_sync_restricted_unmanaged_devices import (
123+
sharepoint_onedrive_sync_restricted_unmanaged_devices,
124+
)
125+
126+
check = sharepoint_onedrive_sync_restricted_unmanaged_devices()
127+
result = check.execute()
128+
129+
assert len(result) == 0

tests/providers/m365/services/sharepoint/sharepoint_service_test.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import uuid
12
from unittest.mock import patch
23

34
from prowler.providers.m365.models import M365IdentityInfo
@@ -7,6 +8,8 @@
78
)
89
from tests.providers.m365.m365_fixtures import DOMAIN, set_mocked_m365_provider
910

11+
uuid_value = uuid.uuid4()
12+
1013

1114
async def mock_sharepoint_get_settings(_):
1215
return SharePointSettings(
@@ -16,6 +19,7 @@ async def mock_sharepoint_get_settings(_):
1619
sharingDomainRestrictionMode="allowList",
1720
resharingEnabled=False,
1821
legacyAuth=True,
22+
allowedDomainGuidsForSyncApp=[uuid_value],
1923
)
2024

2125

@@ -39,3 +43,5 @@ def test_get_settings(self):
3943
assert settings.sharingDomainRestrictionMode == "allowList"
4044
assert settings.resharingEnabled is False
4145
assert settings.legacyAuth is True
46+
assert settings.allowedDomainGuidsForSyncApp == [uuid_value]
47+
assert len(settings.allowedDomainGuidsForSyncApp) == 1

0 commit comments

Comments
 (0)