fix(admincenter): restrict admincenter group visibility check to Unified groups (#10899)

This commit is contained in:
Daniel Barranquero
2026-04-27 13:23:03 +02:00
committed by GitHub
parent fb6da427f8
commit 368d9c1519
5 changed files with 127 additions and 4 deletions
+1
View File
@@ -14,6 +14,7 @@ All notable changes to the **Prowler SDK** are documented in this file.
### 🐞 Fixed
- Alibaba Cloud CS service SDK compatibility, harden other services and improve documentation [(#10871)](https://github.com/prowler-cloud/prowler/pull/10871)
- `admincenter_groups_not_public_visibility` check for M365 provider evaluating Security and Distribution groups, now restricted to Microsoft 365 (Unified) groups per CIS M365 Foundations 1.2.1 [(#10899)](https://github.com/prowler-cloud/prowler/pull/10899)
---
@@ -27,6 +27,11 @@ class admincenter_groups_not_public_visibility(Check):
"""
findings = []
for group in admincenter_client.groups.values():
# Only Microsoft 365 Groups (identified by the "Unified" group type) are in
# scope for this check per CIS M365 Foundations 1.2.1. Security,
# Distribution, and other group types are skipped.
if "Unified" not in group.group_types:
continue
report = CheckReportM365(
metadata=self.metadata(),
resource=group,
@@ -182,6 +182,7 @@ class AdminCenter(M365Service):
id=group.id,
name=getattr(group, "display_name", ""),
visibility=getattr(group, "visibility", ""),
group_types=getattr(group, "group_types", []) or [],
)
}
)
@@ -237,6 +238,7 @@ class Group(BaseModel):
id: str
name: str
visibility: Optional[str]
group_types: List[str] = []
class PasswordPolicy(BaseModel):
@@ -61,7 +61,12 @@ class Test_admincenter_groups_not_public_visibility:
id_group1 = str(uuid4())
admincenter_client.groups = {
id_group1: Group(id=id_group1, name="Group1", visibility="Private"),
id_group1: Group(
id=id_group1,
name="Group1",
visibility="Private",
group_types=["Unified"],
),
}
check = admincenter_groups_not_public_visibility()
@@ -102,7 +107,12 @@ class Test_admincenter_groups_not_public_visibility:
id_group1 = str(uuid4())
admincenter_client.groups = {
id_group1: Group(id=id_group1, name="Group1", visibility="Private"),
id_group1: Group(
id=id_group1,
name="Group1",
visibility="Private",
group_types=["Unified"],
),
}
check = admincenter_groups_not_public_visibility()
@@ -143,7 +153,12 @@ class Test_admincenter_groups_not_public_visibility:
id_group1 = str(uuid4())
admincenter_client.groups = {
id_group1: Group(id=id_group1, name="Group1", visibility="Public"),
id_group1: Group(
id=id_group1,
name="Group1",
visibility="Public",
group_types=["Unified"],
),
}
check = admincenter_groups_not_public_visibility()
@@ -187,7 +202,12 @@ class Test_admincenter_groups_not_public_visibility:
id_group1 = str(uuid4())
admincenter_client.groups = {
id_group1: Group(id=id_group1, name="Group1", visibility=None),
id_group1: Group(
id=id_group1,
name="Group1",
visibility=None,
group_types=["Unified"],
),
}
check = admincenter_groups_not_public_visibility()
@@ -202,3 +222,60 @@ class Test_admincenter_groups_not_public_visibility:
assert result[0].resource_name == "Group1"
assert result[0].resource_id == id_group1
assert result[0].location == "global"
def test_admincenter_security_group_ignored(self):
admincenter_client = mock.MagicMock
admincenter_client.audited_tenant = "audited_tenant"
admincenter_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.admincenter.admincenter_groups_not_public_visibility.admincenter_groups_not_public_visibility.admincenter_client",
new=admincenter_client,
),
):
from prowler.providers.m365.services.admincenter.admincenter_groups_not_public_visibility.admincenter_groups_not_public_visibility import (
admincenter_groups_not_public_visibility,
)
from prowler.providers.m365.services.admincenter.admincenter_service import (
Group,
)
id_security_group = str(uuid4())
id_distribution_group = str(uuid4())
id_m365_group = str(uuid4())
admincenter_client.groups = {
id_security_group: Group(
id=id_security_group,
name="SecurityGroup",
visibility=None,
group_types=[],
),
id_distribution_group: Group(
id=id_distribution_group,
name="DistributionGroup",
visibility=None,
group_types=[],
),
id_m365_group: Group(
id=id_m365_group,
name="M365Group",
visibility="Private",
group_types=["Unified"],
),
}
check = admincenter_groups_not_public_visibility()
result = check.execute()
assert len(result) == 1
assert result[0].resource_id == id_m365_group
assert result[0].status == "PASS"
assert result[0].resource_name == "M365Group"
@@ -214,3 +214,41 @@ def test_admincenter__get_users_handles_pagination():
with_url_mock.assert_called_once_with("next-link")
assert users["user-1"].license == "SKU-user-1"
assert users["user-3"].license == "SKU-user-3"
def test_admincenter__get_groups_maps_group_types():
admincenter_service = AdminCenter.__new__(AdminCenter)
groups_response = SimpleNamespace(
value=[
SimpleNamespace(
id="id-1",
display_name="Unified Group",
visibility="Private",
group_types=["Unified"],
),
SimpleNamespace(
id="id-2",
display_name="Security Group",
visibility=None,
group_types=[],
),
SimpleNamespace(
id="id-3",
display_name="Legacy Group",
visibility="Public",
),
]
)
groups_builder = SimpleNamespace(get=AsyncMock(return_value=groups_response))
admincenter_service.client = SimpleNamespace(groups=groups_builder)
groups = asyncio.run(admincenter_service._get_groups())
assert len(groups) == 3
assert groups_builder.get.await_count == 1
assert groups["id-1"].group_types == ["Unified"]
assert groups["id-2"].group_types == []
assert groups["id-3"].group_types == []
assert groups["id-3"].visibility == "Public"