mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-03-21 18:58:04 +00:00
feat(m365): add entra_seamless_sso_disabled security check (#10086)
Co-authored-by: HugoPBrito <hugopbrit@gmail.com> Co-authored-by: Hugo Pereira Brito <101209179+HugoPBrito@users.noreply.github.com>
This commit is contained in:
@@ -41,6 +41,7 @@ When using service principal authentication, add these **Application Permissions
|
||||
|
||||
- `AuditLog.Read.All`: Required for Entra service.
|
||||
- `Directory.Read.All`: Required for all services.
|
||||
- `OnPremDirectorySynchronization.Read.All`: Required for `entra_seamless_sso_disabled` check (hybrid deployments).
|
||||
- `Policy.Read.All`: Required for all services.
|
||||
- `SecurityIdentitiesHealth.Read.All`: Required for `defenderidentity_health_issues_no_open` check.
|
||||
- `SecurityIdentitiesSensors.Read.All`: Required for `defenderidentity_health_issues_no_open` check.
|
||||
@@ -108,6 +109,7 @@ Browser and Azure CLI authentication methods limit scanning capabilities to chec
|
||||
|
||||
- `AuditLog.Read.All`: Required for Entra service
|
||||
- `Directory.Read.All`: Required for all services
|
||||
- `OnPremDirectorySynchronization.Read.All`: Required for `entra_seamless_sso_disabled` check (hybrid deployments)
|
||||
- `Policy.Read.All`: Required for all services
|
||||
- `SecurityIdentitiesHealth.Read.All`: Required for `defenderidentity_health_issues_no_open` check
|
||||
- `SecurityIdentitiesSensors.Read.All`: Required for `defenderidentity_health_issues_no_open` check
|
||||
|
||||
@@ -23,6 +23,7 @@ All notable changes to the **Prowler SDK** are documented in this file.
|
||||
- CSA CCM 4.0 for the Alibaba Cloud provider [(#10061)](https://github.com/prowler-cloud/prowler/pull/10061)
|
||||
- ECS Exec (ECS-006) privilege escalation detection via `ecs:ExecuteCommand` + `ecs:DescribeTasks` [(#10066)](https://github.com/prowler-cloud/prowler/pull/10066)
|
||||
- `defenderxdr_endpoint_privileged_user_exposed_credentials` check for M365 provider [(#10084)](https://github.com/prowler-cloud/prowler/pull/10084)
|
||||
- `entra_seamless_sso_disabled` check for m365 provider [(#10086)](https://github.com/prowler-cloud/prowler/pull/10086)
|
||||
- Registry scan mode for `image` provider: enumerate and scan all images from OCI standard, Docker Hub, and ECR [(#9985)](https://github.com/prowler-cloud/prowler/pull/9985)
|
||||
- Add file descriptor limits (`ulimits`) to Docker Compose worker services to prevent `Too many open files` errors [(#10107)](https://github.com/prowler-cloud/prowler/pull/10107)
|
||||
|
||||
|
||||
@@ -201,7 +201,8 @@
|
||||
"admincenter_users_admins_reduced_license_footprint",
|
||||
"entra_admin_portals_access_restriction",
|
||||
"entra_admin_users_phishing_resistant_mfa_enabled",
|
||||
"entra_policy_guest_users_access_restrictions"
|
||||
"entra_policy_guest_users_access_restrictions",
|
||||
"entra_seamless_sso_disabled"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -217,7 +218,8 @@
|
||||
}
|
||||
],
|
||||
"Checks": [
|
||||
"admincenter_settings_password_never_expire"
|
||||
"admincenter_settings_password_never_expire",
|
||||
"entra_seamless_sso_disabled"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -239,6 +241,7 @@
|
||||
"entra_admin_users_sign_in_frequency_enabled",
|
||||
"entra_legacy_authentication_blocked",
|
||||
"entra_managed_device_required_for_authentication",
|
||||
"entra_seamless_sso_disabled",
|
||||
"entra_users_mfa_enabled",
|
||||
"exchange_organization_modern_authentication_enabled",
|
||||
"exchange_transport_config_smtp_auth_disabled",
|
||||
@@ -643,7 +646,8 @@
|
||||
"entra_admin_users_sign_in_frequency_enabled",
|
||||
"entra_app_registration_no_unused_privileged_permissions",
|
||||
"entra_policy_ensure_default_user_cannot_create_tenants",
|
||||
"entra_policy_guest_invite_only_for_admin_roles"
|
||||
"entra_policy_guest_invite_only_for_admin_roles",
|
||||
"entra_seamless_sso_disabled"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -680,6 +684,7 @@
|
||||
"entra_admin_users_sign_in_frequency_enabled",
|
||||
"entra_admin_users_mfa_enabled",
|
||||
"entra_managed_device_required_for_authentication",
|
||||
"entra_seamless_sso_disabled",
|
||||
"entra_users_mfa_enabled",
|
||||
"entra_identity_protection_sign_in_risk_enabled"
|
||||
]
|
||||
|
||||
@@ -1148,7 +1148,8 @@
|
||||
"Id": "4.1.2",
|
||||
"Description": "Ensure that password hash sync is enabled for hybrid deployments",
|
||||
"Checks": [
|
||||
"entra_password_hash_sync_enabled"
|
||||
"entra_password_hash_sync_enabled",
|
||||
"entra_seamless_sso_disabled"
|
||||
],
|
||||
"Attributes": [
|
||||
{
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"Provider": "m365",
|
||||
"CheckID": "entra_seamless_sso_disabled",
|
||||
"CheckTitle": "Entra hybrid deployment does not have Seamless SSO enabled",
|
||||
"CheckType": [],
|
||||
"ServiceName": "entra",
|
||||
"SubServiceName": "",
|
||||
"ResourceIdTemplate": "",
|
||||
"Severity": "medium",
|
||||
"ResourceType": "Directory Sync Settings",
|
||||
"ResourceGroup": "IAM",
|
||||
"Description": "**Seamless Single Sign-On (SSO)** in hybrid Microsoft Entra deployments allows automatic authentication for domain-joined devices on the corporate network.\n\nThis check verifies the actual Seamless SSO configuration in directory synchronization settings. Modern devices with **Primary Refresh Token** (PRT) support no longer require Seamless SSO.",
|
||||
"Risk": "Seamless SSO can be exploited for **lateral movement** between on-premises domains and Entra ID when an Entra Connect server is compromised. It can also be used to perform **brute force attacks** against Entra ID, as authentication through the AZUREADSSOACC account bypasses standard protections.",
|
||||
"RelatedUrl": "",
|
||||
"AdditionalURLs": [
|
||||
"https://learn.microsoft.com/en-us/entra/identity/hybrid/connect/how-to-connect-sso"
|
||||
],
|
||||
"Remediation": {
|
||||
"Code": {
|
||||
"CLI": "",
|
||||
"NativeIaC": "",
|
||||
"Other": "1. Open Microsoft Entra Connect configuration tool on the on-premises server.\n2. Navigate to **Change User Sign In**.\n3. Uncheck **Enable single sign-on**.\n4. Complete the configuration wizard.\n5. In Active Directory, run `Get-AzureADSSOStatus` to verify Seamless SSO shows `\"enable\":false`.\n6. Run `Disable-AzureADSSOForest` with domain admin credentials to remove the AZUREADSSOACC account.",
|
||||
"Terraform": ""
|
||||
},
|
||||
"Recommendation": {
|
||||
"Text": "Disable **Seamless SSO** in hybrid environments where modern devices support *Primary Refresh Token (PRT)*. Regularly audit Entra Connect settings and verify that the AZUREADSSOACC computer account is removed from Active Directory.",
|
||||
"Url": "https://hub.prowler.com/check/entra_seamless_sso_disabled"
|
||||
}
|
||||
},
|
||||
"Categories": [
|
||||
"e3"
|
||||
],
|
||||
"DependsOn": [],
|
||||
"RelatedTo": [
|
||||
"entra_password_hash_sync_enabled"
|
||||
],
|
||||
"Notes": "Applies only to hybrid Microsoft Entra deployments using Entra Connect sync. The check reads the seamless_sso_enabled flag from the directory on-premises synchronization settings via Microsoft Graph API."
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
from typing import List
|
||||
|
||||
from prowler.lib.check.models import Check, CheckReportM365
|
||||
from prowler.providers.m365.services.entra.entra_client import entra_client
|
||||
|
||||
|
||||
class entra_seamless_sso_disabled(Check):
|
||||
"""Check that Seamless Single Sign-On (SSO) is disabled for Microsoft Entra hybrid deployments.
|
||||
|
||||
Seamless SSO allows users to sign in without typing their passwords when on
|
||||
corporate devices connected to the corporate network. When an Entra Connect server
|
||||
is compromised, Seamless SSO can enable lateral movement between on-premises domains
|
||||
and Entra ID, and it can also be exploited for brute force attacks. Modern devices with
|
||||
Primary Refresh Token (PRT) support make this feature unnecessary for most organizations.
|
||||
|
||||
- PASS: Seamless SSO is disabled or on-premises sync is not enabled (cloud-only).
|
||||
- FAIL: Seamless SSO is enabled in a hybrid deployment, or cannot verify due to insufficient permissions.
|
||||
"""
|
||||
|
||||
def execute(self) -> List[CheckReportM365]:
|
||||
"""Execute the Seamless SSO disabled check.
|
||||
|
||||
Checks the directory sync settings to determine if Seamless SSO is enabled.
|
||||
For hybrid environments, this check verifies the actual Seamless SSO configuration
|
||||
rather than inferring from on-premises sync status.
|
||||
|
||||
Returns:
|
||||
A list of CheckReportM365 objects with the result of the check.
|
||||
"""
|
||||
findings = []
|
||||
|
||||
# Check if there was an error retrieving directory sync settings
|
||||
if entra_client.directory_sync_error:
|
||||
for organization in entra_client.organizations:
|
||||
report = CheckReportM365(
|
||||
self.metadata(),
|
||||
resource=organization,
|
||||
resource_id=organization.id,
|
||||
resource_name=organization.name,
|
||||
)
|
||||
# Only FAIL for hybrid orgs; cloud-only orgs don't need this permission
|
||||
if organization.on_premises_sync_enabled:
|
||||
report.status = "FAIL"
|
||||
report.status_extended = f"Cannot verify Seamless SSO status for {organization.name}: {entra_client.directory_sync_error}."
|
||||
else:
|
||||
report.status = "PASS"
|
||||
report.status_extended = f"Entra organization {organization.name} is cloud-only (no on-premises sync), Seamless SSO is not applicable."
|
||||
findings.append(report)
|
||||
return findings
|
||||
|
||||
# Process directory sync settings if available
|
||||
for sync_settings in entra_client.directory_sync_settings:
|
||||
report = CheckReportM365(
|
||||
self.metadata(),
|
||||
resource=sync_settings,
|
||||
resource_id=sync_settings.id,
|
||||
resource_name=f"Directory Sync {sync_settings.id}",
|
||||
)
|
||||
|
||||
if sync_settings.seamless_sso_enabled:
|
||||
report.status = "FAIL"
|
||||
report.status_extended = f"Entra directory sync {sync_settings.id} has Seamless SSO enabled, which can be exploited for lateral movement and brute force attacks."
|
||||
else:
|
||||
report.status = "PASS"
|
||||
report.status_extended = f"Entra directory sync {sync_settings.id} has Seamless SSO disabled."
|
||||
|
||||
findings.append(report)
|
||||
|
||||
# If no directory sync settings and no error, it's a cloud-only tenant
|
||||
if not entra_client.directory_sync_settings:
|
||||
for organization in entra_client.organizations:
|
||||
report = CheckReportM365(
|
||||
self.metadata(),
|
||||
resource=organization,
|
||||
resource_id=organization.id,
|
||||
resource_name=organization.name,
|
||||
)
|
||||
report.status = "PASS"
|
||||
report.status_extended = f"Entra organization {organization.name} is cloud-only (no on-premises sync), Seamless SSO is not applicable."
|
||||
findings.append(report)
|
||||
|
||||
return findings
|
||||
@@ -5,6 +5,7 @@ from enum import Enum
|
||||
from typing import Dict, List, Optional
|
||||
from uuid import UUID
|
||||
|
||||
from msgraph.generated.models.o_data_errors.o_data_error import ODataError
|
||||
from msgraph.generated.security.microsoft_graph_security_run_hunting_query.run_hunting_query_post_request_body import (
|
||||
RunHuntingQueryPostRequestBody,
|
||||
)
|
||||
@@ -78,6 +79,7 @@ class Entra(M365Service):
|
||||
self._get_organization(),
|
||||
self._get_users(),
|
||||
self._get_oauth_apps(),
|
||||
self._get_directory_sync_settings(),
|
||||
)
|
||||
)
|
||||
|
||||
@@ -88,6 +90,7 @@ class Entra(M365Service):
|
||||
self.organizations = attributes[4]
|
||||
self.users = attributes[5]
|
||||
self.oauth_apps: Optional[Dict[str, OAuthApp]] = attributes[6]
|
||||
self.directory_sync_settings, self.directory_sync_error = attributes[7]
|
||||
self.user_accounts_status = {}
|
||||
|
||||
if created_loop:
|
||||
@@ -411,6 +414,57 @@ class Entra(M365Service):
|
||||
|
||||
return organizations
|
||||
|
||||
async def _get_directory_sync_settings(self):
|
||||
"""Retrieve on-premises directory synchronization settings.
|
||||
|
||||
Fetches the directory synchronization configuration from Microsoft Graph API
|
||||
to determine the state of synchronization features such as password sync,
|
||||
device writeback, and other hybrid identity settings.
|
||||
|
||||
Returns:
|
||||
A tuple containing:
|
||||
- A list of DirectorySyncSettings objects, or an empty list if retrieval fails.
|
||||
- An error message string if there was an access error, None otherwise.
|
||||
"""
|
||||
logger.info("Entra - Getting directory sync settings...")
|
||||
directory_sync_settings = []
|
||||
error_message = None
|
||||
try:
|
||||
sync_data = await self.client.directory.on_premises_synchronization.get()
|
||||
for sync in getattr(sync_data, "value", []) or []:
|
||||
features = getattr(sync, "features", None)
|
||||
directory_sync_settings.append(
|
||||
DirectorySyncSettings(
|
||||
id=sync.id,
|
||||
password_sync_enabled=getattr(
|
||||
features, "password_sync_enabled", False
|
||||
)
|
||||
or False,
|
||||
seamless_sso_enabled=getattr(
|
||||
features, "seamless_sso_enabled", False
|
||||
)
|
||||
or False,
|
||||
)
|
||||
)
|
||||
except ODataError as error:
|
||||
error_code = getattr(error.error, "code", None) if error.error else None
|
||||
if error_code == "Authorization_RequestDenied":
|
||||
error_message = "Insufficient privileges to read directory sync settings. Required permission: OnPremDirectorySynchronization.Read.All or OnPremDirectorySynchronization.ReadWrite.All"
|
||||
logger.error(
|
||||
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error_message}"
|
||||
)
|
||||
else:
|
||||
logger.error(
|
||||
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
|
||||
)
|
||||
error_message = str(error)
|
||||
except Exception as error:
|
||||
logger.error(
|
||||
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
|
||||
)
|
||||
error_message = str(error)
|
||||
return directory_sync_settings, error_message
|
||||
|
||||
async def _get_users(self):
|
||||
logger.info("Entra - Getting users...")
|
||||
users = {}
|
||||
@@ -747,6 +801,19 @@ class Organization(BaseModel):
|
||||
on_premises_sync_enabled: bool
|
||||
|
||||
|
||||
class DirectorySyncSettings(BaseModel):
|
||||
"""On-premises directory synchronization settings.
|
||||
|
||||
Represents the synchronization configuration for a tenant, including feature
|
||||
flags that control hybrid identity behaviors such as password synchronization
|
||||
and Seamless SSO.
|
||||
"""
|
||||
|
||||
id: str
|
||||
password_sync_enabled: bool = False
|
||||
seamless_sso_enabled: bool = False
|
||||
|
||||
|
||||
class Group(BaseModel):
|
||||
id: str
|
||||
name: str
|
||||
|
||||
@@ -0,0 +1,274 @@
|
||||
from unittest import mock
|
||||
|
||||
from prowler.providers.m365.services.entra.entra_service import (
|
||||
DirectorySyncSettings,
|
||||
Organization,
|
||||
)
|
||||
from tests.providers.m365.m365_fixtures import set_mocked_m365_provider
|
||||
|
||||
|
||||
class Test_entra_seamless_sso_disabled:
|
||||
def test_seamless_sso_disabled(self):
|
||||
"""Test PASS when Seamless SSO is disabled in directory sync settings."""
|
||||
entra_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.entra.entra_seamless_sso_disabled.entra_seamless_sso_disabled.entra_client",
|
||||
new=entra_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.m365.services.entra.entra_seamless_sso_disabled.entra_seamless_sso_disabled import (
|
||||
entra_seamless_sso_disabled,
|
||||
)
|
||||
|
||||
sync_settings = DirectorySyncSettings(
|
||||
id="sync-001",
|
||||
password_sync_enabled=True,
|
||||
seamless_sso_enabled=False,
|
||||
)
|
||||
entra_client.directory_sync_settings = [sync_settings]
|
||||
entra_client.directory_sync_error = None
|
||||
entra_client.organizations = []
|
||||
|
||||
check = entra_seamless_sso_disabled()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== "Entra directory sync sync-001 has Seamless SSO disabled."
|
||||
)
|
||||
assert result[0].resource_id == "sync-001"
|
||||
assert result[0].resource_name == "Directory Sync sync-001"
|
||||
assert result[0].location == "global"
|
||||
|
||||
def test_seamless_sso_enabled(self):
|
||||
"""Test FAIL when Seamless SSO is enabled in directory sync settings."""
|
||||
entra_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.entra.entra_seamless_sso_disabled.entra_seamless_sso_disabled.entra_client",
|
||||
new=entra_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.m365.services.entra.entra_seamless_sso_disabled.entra_seamless_sso_disabled import (
|
||||
entra_seamless_sso_disabled,
|
||||
)
|
||||
|
||||
sync_settings = DirectorySyncSettings(
|
||||
id="sync-001",
|
||||
password_sync_enabled=True,
|
||||
seamless_sso_enabled=True,
|
||||
)
|
||||
entra_client.directory_sync_settings = [sync_settings]
|
||||
entra_client.directory_sync_error = None
|
||||
entra_client.organizations = []
|
||||
|
||||
check = entra_seamless_sso_disabled()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== "Entra directory sync sync-001 has Seamless SSO enabled, which can be exploited for lateral movement and brute force attacks."
|
||||
)
|
||||
assert result[0].resource_id == "sync-001"
|
||||
assert result[0].resource_name == "Directory Sync sync-001"
|
||||
assert result[0].location == "global"
|
||||
|
||||
def test_multiple_sync_settings_mixed(self):
|
||||
"""Test mixed results with multiple directory sync configurations."""
|
||||
entra_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.entra.entra_seamless_sso_disabled.entra_seamless_sso_disabled.entra_client",
|
||||
new=entra_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.m365.services.entra.entra_seamless_sso_disabled.entra_seamless_sso_disabled import (
|
||||
entra_seamless_sso_disabled,
|
||||
)
|
||||
|
||||
sync_settings_1 = DirectorySyncSettings(
|
||||
id="sync-001",
|
||||
password_sync_enabled=True,
|
||||
seamless_sso_enabled=True,
|
||||
)
|
||||
sync_settings_2 = DirectorySyncSettings(
|
||||
id="sync-002",
|
||||
password_sync_enabled=True,
|
||||
seamless_sso_enabled=False,
|
||||
)
|
||||
entra_client.directory_sync_settings = [sync_settings_1, sync_settings_2]
|
||||
entra_client.directory_sync_error = None
|
||||
entra_client.organizations = []
|
||||
|
||||
check = entra_seamless_sso_disabled()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 2
|
||||
assert result[0].status == "FAIL"
|
||||
assert result[0].resource_id == "sync-001"
|
||||
assert result[1].status == "PASS"
|
||||
assert result[1].resource_id == "sync-002"
|
||||
|
||||
def test_cloud_only_no_sync_settings(self):
|
||||
"""Test PASS for cloud-only tenant with no directory sync settings."""
|
||||
entra_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.entra.entra_seamless_sso_disabled.entra_seamless_sso_disabled.entra_client",
|
||||
new=entra_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.m365.services.entra.entra_seamless_sso_disabled.entra_seamless_sso_disabled import (
|
||||
entra_seamless_sso_disabled,
|
||||
)
|
||||
|
||||
org = Organization(
|
||||
id="org1",
|
||||
name="Cloud Only Org",
|
||||
on_premises_sync_enabled=False,
|
||||
)
|
||||
entra_client.directory_sync_settings = []
|
||||
entra_client.directory_sync_error = None
|
||||
entra_client.organizations = [org]
|
||||
|
||||
check = entra_seamless_sso_disabled()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== "Entra organization Cloud Only Org is cloud-only (no on-premises sync), Seamless SSO is not applicable."
|
||||
)
|
||||
assert result[0].resource_id == "org1"
|
||||
assert result[0].resource_name == "Cloud Only Org"
|
||||
|
||||
def test_insufficient_permissions_error(self):
|
||||
"""Test FAIL when there's a permission error reading directory sync settings."""
|
||||
entra_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.entra.entra_seamless_sso_disabled.entra_seamless_sso_disabled.entra_client",
|
||||
new=entra_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.m365.services.entra.entra_seamless_sso_disabled.entra_seamless_sso_disabled import (
|
||||
entra_seamless_sso_disabled,
|
||||
)
|
||||
|
||||
org = Organization(
|
||||
id="org1",
|
||||
name="Prowler Org",
|
||||
on_premises_sync_enabled=True,
|
||||
)
|
||||
entra_client.directory_sync_settings = []
|
||||
entra_client.directory_sync_error = "Insufficient privileges to read directory sync settings. Required permission: OnPremDirectorySynchronization.Read.All or OnPremDirectorySynchronization.ReadWrite.All"
|
||||
entra_client.organizations = [org]
|
||||
|
||||
check = entra_seamless_sso_disabled()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert "Cannot verify Seamless SSO status" in result[0].status_extended
|
||||
assert "Insufficient privileges" in result[0].status_extended
|
||||
assert (
|
||||
"OnPremDirectorySynchronization.Read.All" in result[0].status_extended
|
||||
)
|
||||
assert result[0].resource_id == "org1"
|
||||
assert result[0].resource_name == "Prowler Org"
|
||||
|
||||
def test_insufficient_permissions_cloud_only_passes(self):
|
||||
"""Test PASS for cloud-only org even when there's a permission error."""
|
||||
entra_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.entra.entra_seamless_sso_disabled.entra_seamless_sso_disabled.entra_client",
|
||||
new=entra_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.m365.services.entra.entra_seamless_sso_disabled.entra_seamless_sso_disabled import (
|
||||
entra_seamless_sso_disabled,
|
||||
)
|
||||
|
||||
# Cloud-only org (on_premises_sync_enabled=False)
|
||||
org = Organization(
|
||||
id="org1",
|
||||
name="Cloud Only Org",
|
||||
on_premises_sync_enabled=False,
|
||||
)
|
||||
entra_client.directory_sync_settings = []
|
||||
entra_client.directory_sync_error = (
|
||||
"Insufficient privileges to read directory sync settings."
|
||||
)
|
||||
entra_client.organizations = [org]
|
||||
|
||||
check = entra_seamless_sso_disabled()
|
||||
result = check.execute()
|
||||
|
||||
# Should PASS because cloud-only orgs don't need this permission
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
assert "cloud-only" in result[0].status_extended
|
||||
assert result[0].resource_id == "org1"
|
||||
|
||||
def test_empty_everything(self):
|
||||
"""Test no findings when both sync settings and organizations are empty."""
|
||||
entra_client = mock.MagicMock()
|
||||
entra_client.directory_sync_settings = []
|
||||
entra_client.directory_sync_error = None
|
||||
entra_client.organizations = []
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=set_mocked_m365_provider(),
|
||||
),
|
||||
mock.patch(
|
||||
"prowler.providers.m365.services.entra.entra_seamless_sso_disabled.entra_seamless_sso_disabled.entra_client",
|
||||
new=entra_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.m365.services.entra.entra_seamless_sso_disabled.entra_seamless_sso_disabled import (
|
||||
entra_seamless_sso_disabled,
|
||||
)
|
||||
|
||||
check = entra_seamless_sso_disabled()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 0
|
||||
Reference in New Issue
Block a user