mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-03-21 18:58:04 +00:00
feat(m365): add entra_app_registration_no_unused_privileged_permissions security check (#10080)
Co-authored-by: Daniel Barranquero <74871504+danibarranqueroo@users.noreply.github.com> Co-authored-by: Daniel Barranquero <danielbo2001@gmail.com>
This commit is contained in:
committed by
GitHub
parent
23e51158e0
commit
20b26bc7d0
@@ -0,0 +1,895 @@
|
||||
from unittest import mock
|
||||
from uuid import uuid4
|
||||
|
||||
from prowler.providers.m365.services.entra.entra_service import (
|
||||
OAuthApp,
|
||||
OAuthAppPermission,
|
||||
)
|
||||
from tests.providers.m365.m365_fixtures import DOMAIN, set_mocked_m365_provider
|
||||
|
||||
|
||||
class Test_entra_app_registration_no_unused_privileged_permissions:
|
||||
def test_no_oauth_apps(self):
|
||||
"""No OAuth apps registered in tenant (empty dict): expected PASS."""
|
||||
entra_client = mock.MagicMock
|
||||
entra_client.audited_tenant = "audited_tenant"
|
||||
entra_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.services.entra.entra_app_registration_no_unused_privileged_permissions.entra_app_registration_no_unused_privileged_permissions.entra_client",
|
||||
new=entra_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.m365.services.entra.entra_app_registration_no_unused_privileged_permissions.entra_app_registration_no_unused_privileged_permissions import (
|
||||
entra_app_registration_no_unused_privileged_permissions,
|
||||
)
|
||||
|
||||
entra_client.oauth_apps = {}
|
||||
|
||||
check = entra_app_registration_no_unused_privileged_permissions()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== "No OAuth applications are registered in the tenant."
|
||||
)
|
||||
assert result[0].resource == {}
|
||||
assert result[0].resource_name == "OAuth Applications"
|
||||
assert result[0].resource_id == "oauthApps"
|
||||
|
||||
def test_no_oauth_apps_none(self):
|
||||
"""OAuth apps is None (App Governance not enabled): expected FAIL."""
|
||||
entra_client = mock.MagicMock
|
||||
entra_client.audited_tenant = "audited_tenant"
|
||||
entra_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.services.entra.entra_app_registration_no_unused_privileged_permissions.entra_app_registration_no_unused_privileged_permissions.entra_client",
|
||||
new=entra_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.m365.services.entra.entra_app_registration_no_unused_privileged_permissions.entra_app_registration_no_unused_privileged_permissions import (
|
||||
entra_app_registration_no_unused_privileged_permissions,
|
||||
)
|
||||
|
||||
entra_client.oauth_apps = None
|
||||
|
||||
check = entra_app_registration_no_unused_privileged_permissions()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== "OAuth App Governance data is unavailable. Enable App Governance in Microsoft Defender for Cloud Apps and grant ThreatHunting.Read.All to evaluate unused privileged permissions."
|
||||
)
|
||||
assert result[0].resource == {}
|
||||
assert result[0].resource_name == "OAuth Applications"
|
||||
assert result[0].resource_id == "oauthApps"
|
||||
|
||||
def test_app_no_permissions(self):
|
||||
"""App with no permissions: expected PASS."""
|
||||
app_id = str(uuid4())
|
||||
app_name = "Test App No Permissions"
|
||||
entra_client = mock.MagicMock
|
||||
entra_client.audited_tenant = "audited_tenant"
|
||||
entra_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.services.entra.entra_app_registration_no_unused_privileged_permissions.entra_app_registration_no_unused_privileged_permissions.entra_client",
|
||||
new=entra_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.m365.services.entra.entra_app_registration_no_unused_privileged_permissions.entra_app_registration_no_unused_privileged_permissions import (
|
||||
entra_app_registration_no_unused_privileged_permissions,
|
||||
)
|
||||
|
||||
entra_client.oauth_apps = {
|
||||
app_id: OAuthApp(
|
||||
id=app_id,
|
||||
name=app_name,
|
||||
status="Enabled",
|
||||
privilege_level="Low",
|
||||
permissions=[],
|
||||
service_principal_id=str(uuid4()),
|
||||
is_admin_consented=False,
|
||||
last_used_time=None,
|
||||
app_origin="Internal",
|
||||
)
|
||||
}
|
||||
|
||||
check = entra_app_registration_no_unused_privileged_permissions()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"App registration {app_name} has no unused privileged permissions."
|
||||
)
|
||||
assert result[0].resource_name == app_name
|
||||
assert result[0].resource_id == app_id
|
||||
|
||||
def test_app_all_permissions_in_use(self):
|
||||
"""App with all privileged permissions in use: expected PASS."""
|
||||
app_id = str(uuid4())
|
||||
app_name = "Test App All In Use"
|
||||
entra_client = mock.MagicMock
|
||||
entra_client.audited_tenant = "audited_tenant"
|
||||
entra_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.services.entra.entra_app_registration_no_unused_privileged_permissions.entra_app_registration_no_unused_privileged_permissions.entra_client",
|
||||
new=entra_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.m365.services.entra.entra_app_registration_no_unused_privileged_permissions.entra_app_registration_no_unused_privileged_permissions import (
|
||||
entra_app_registration_no_unused_privileged_permissions,
|
||||
)
|
||||
|
||||
entra_client.oauth_apps = {
|
||||
app_id: OAuthApp(
|
||||
id=app_id,
|
||||
name=app_name,
|
||||
status="Enabled",
|
||||
privilege_level="High",
|
||||
permissions=[
|
||||
OAuthAppPermission(
|
||||
name="Mail.ReadWrite.All",
|
||||
target_app_id="00000003-0000-0000-c000-000000000000",
|
||||
target_app_name="Microsoft Graph",
|
||||
permission_type="Application",
|
||||
privilege_level="High",
|
||||
usage_status="InUse",
|
||||
),
|
||||
OAuthAppPermission(
|
||||
name="User.Read.All",
|
||||
target_app_id="00000003-0000-0000-c000-000000000000",
|
||||
target_app_name="Microsoft Graph",
|
||||
permission_type="Application",
|
||||
privilege_level="High",
|
||||
usage_status="InUse",
|
||||
),
|
||||
],
|
||||
service_principal_id=str(uuid4()),
|
||||
is_admin_consented=True,
|
||||
last_used_time="2024-01-15T10:30:00Z",
|
||||
app_origin="Internal",
|
||||
)
|
||||
}
|
||||
|
||||
check = entra_app_registration_no_unused_privileged_permissions()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"App registration {app_name} has no unused privileged permissions."
|
||||
)
|
||||
assert result[0].resource_name == app_name
|
||||
assert result[0].resource_id == app_id
|
||||
|
||||
def test_app_low_privilege_unused(self):
|
||||
"""App with unused low privilege permissions (not high): expected PASS."""
|
||||
app_id = str(uuid4())
|
||||
app_name = "Test App Low Privilege Unused"
|
||||
entra_client = mock.MagicMock
|
||||
entra_client.audited_tenant = "audited_tenant"
|
||||
entra_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.services.entra.entra_app_registration_no_unused_privileged_permissions.entra_app_registration_no_unused_privileged_permissions.entra_client",
|
||||
new=entra_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.m365.services.entra.entra_app_registration_no_unused_privileged_permissions.entra_app_registration_no_unused_privileged_permissions import (
|
||||
entra_app_registration_no_unused_privileged_permissions,
|
||||
)
|
||||
|
||||
entra_client.oauth_apps = {
|
||||
app_id: OAuthApp(
|
||||
id=app_id,
|
||||
name=app_name,
|
||||
status="Enabled",
|
||||
privilege_level="Low",
|
||||
permissions=[
|
||||
OAuthAppPermission(
|
||||
name="User.Read",
|
||||
target_app_id="00000003-0000-0000-c000-000000000000",
|
||||
target_app_name="Microsoft Graph",
|
||||
permission_type="Delegated",
|
||||
privilege_level="Low",
|
||||
usage_status="NotInUse",
|
||||
),
|
||||
OAuthAppPermission(
|
||||
name="openid",
|
||||
target_app_id="00000003-0000-0000-c000-000000000000",
|
||||
target_app_name="Microsoft Graph",
|
||||
permission_type="Delegated",
|
||||
privilege_level="Low",
|
||||
usage_status="NotInUse",
|
||||
),
|
||||
],
|
||||
service_principal_id=str(uuid4()),
|
||||
is_admin_consented=False,
|
||||
last_used_time=None,
|
||||
app_origin="External",
|
||||
)
|
||||
}
|
||||
|
||||
check = entra_app_registration_no_unused_privileged_permissions()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"App registration {app_name} has no unused privileged permissions."
|
||||
)
|
||||
assert result[0].resource_name == app_name
|
||||
assert result[0].resource_id == app_id
|
||||
|
||||
def test_app_medium_privilege_unused(self):
|
||||
"""App with unused medium privilege permissions (not high): expected PASS."""
|
||||
app_id = str(uuid4())
|
||||
app_name = "Test App Medium Privilege Unused"
|
||||
entra_client = mock.MagicMock
|
||||
entra_client.audited_tenant = "audited_tenant"
|
||||
entra_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.services.entra.entra_app_registration_no_unused_privileged_permissions.entra_app_registration_no_unused_privileged_permissions.entra_client",
|
||||
new=entra_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.m365.services.entra.entra_app_registration_no_unused_privileged_permissions.entra_app_registration_no_unused_privileged_permissions import (
|
||||
entra_app_registration_no_unused_privileged_permissions,
|
||||
)
|
||||
|
||||
entra_client.oauth_apps = {
|
||||
app_id: OAuthApp(
|
||||
id=app_id,
|
||||
name=app_name,
|
||||
status="Enabled",
|
||||
privilege_level="Medium",
|
||||
permissions=[
|
||||
OAuthAppPermission(
|
||||
name="Files.Read",
|
||||
target_app_id="00000003-0000-0000-c000-000000000000",
|
||||
target_app_name="Microsoft Graph",
|
||||
permission_type="Delegated",
|
||||
privilege_level="Medium",
|
||||
usage_status="NotInUse",
|
||||
),
|
||||
],
|
||||
service_principal_id=str(uuid4()),
|
||||
is_admin_consented=False,
|
||||
last_used_time=None,
|
||||
app_origin="External",
|
||||
)
|
||||
}
|
||||
|
||||
check = entra_app_registration_no_unused_privileged_permissions()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"App registration {app_name} has no unused privileged permissions."
|
||||
)
|
||||
assert result[0].resource_name == app_name
|
||||
assert result[0].resource_id == app_id
|
||||
|
||||
def test_app_one_unused_high_privilege_permission(self):
|
||||
"""App with one unused high privilege permission: expected FAIL."""
|
||||
app_id = str(uuid4())
|
||||
app_name = "Test App One Unused High"
|
||||
entra_client = mock.MagicMock
|
||||
entra_client.audited_tenant = "audited_tenant"
|
||||
entra_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.services.entra.entra_app_registration_no_unused_privileged_permissions.entra_app_registration_no_unused_privileged_permissions.entra_client",
|
||||
new=entra_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.m365.services.entra.entra_app_registration_no_unused_privileged_permissions.entra_app_registration_no_unused_privileged_permissions import (
|
||||
entra_app_registration_no_unused_privileged_permissions,
|
||||
)
|
||||
|
||||
entra_client.oauth_apps = {
|
||||
app_id: OAuthApp(
|
||||
id=app_id,
|
||||
name=app_name,
|
||||
status="Enabled",
|
||||
privilege_level="High",
|
||||
permissions=[
|
||||
OAuthAppPermission(
|
||||
name="Mail.ReadWrite.All",
|
||||
target_app_id="00000003-0000-0000-c000-000000000000",
|
||||
target_app_name="Microsoft Graph",
|
||||
permission_type="Application",
|
||||
privilege_level="High",
|
||||
usage_status="NotInUse",
|
||||
),
|
||||
OAuthAppPermission(
|
||||
name="User.Read",
|
||||
target_app_id="00000003-0000-0000-c000-000000000000",
|
||||
target_app_name="Microsoft Graph",
|
||||
permission_type="Delegated",
|
||||
privilege_level="Low",
|
||||
usage_status="InUse",
|
||||
),
|
||||
],
|
||||
service_principal_id=str(uuid4()),
|
||||
is_admin_consented=True,
|
||||
last_used_time="2024-01-15T10:30:00Z",
|
||||
app_origin="Internal",
|
||||
)
|
||||
}
|
||||
|
||||
check = entra_app_registration_no_unused_privileged_permissions()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"App registration {app_name} has 1 unused privileged permission(s): Mail.ReadWrite.All."
|
||||
)
|
||||
assert result[0].resource_name == app_name
|
||||
assert result[0].resource_id == app_id
|
||||
|
||||
def test_app_multiple_unused_high_privilege_permissions(self):
|
||||
"""App with multiple unused high privilege permissions: expected FAIL."""
|
||||
app_id = str(uuid4())
|
||||
app_name = "Test App Multiple Unused High"
|
||||
entra_client = mock.MagicMock
|
||||
entra_client.audited_tenant = "audited_tenant"
|
||||
entra_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.services.entra.entra_app_registration_no_unused_privileged_permissions.entra_app_registration_no_unused_privileged_permissions.entra_client",
|
||||
new=entra_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.m365.services.entra.entra_app_registration_no_unused_privileged_permissions.entra_app_registration_no_unused_privileged_permissions import (
|
||||
entra_app_registration_no_unused_privileged_permissions,
|
||||
)
|
||||
|
||||
entra_client.oauth_apps = {
|
||||
app_id: OAuthApp(
|
||||
id=app_id,
|
||||
name=app_name,
|
||||
status="Enabled",
|
||||
privilege_level="High",
|
||||
permissions=[
|
||||
OAuthAppPermission(
|
||||
name="Mail.ReadWrite.All",
|
||||
target_app_id="00000003-0000-0000-c000-000000000000",
|
||||
target_app_name="Microsoft Graph",
|
||||
permission_type="Application",
|
||||
privilege_level="High",
|
||||
usage_status="NotInUse",
|
||||
),
|
||||
OAuthAppPermission(
|
||||
name="Directory.ReadWrite.All",
|
||||
target_app_id="00000003-0000-0000-c000-000000000000",
|
||||
target_app_name="Microsoft Graph",
|
||||
permission_type="Application",
|
||||
privilege_level="High",
|
||||
usage_status="NotInUse",
|
||||
),
|
||||
OAuthAppPermission(
|
||||
name="User.ReadWrite.All",
|
||||
target_app_id="00000003-0000-0000-c000-000000000000",
|
||||
target_app_name="Microsoft Graph",
|
||||
permission_type="Application",
|
||||
privilege_level="High",
|
||||
usage_status="NotInUse",
|
||||
),
|
||||
],
|
||||
service_principal_id=str(uuid4()),
|
||||
is_admin_consented=True,
|
||||
last_used_time="2024-01-15T10:30:00Z",
|
||||
app_origin="External",
|
||||
)
|
||||
}
|
||||
|
||||
check = entra_app_registration_no_unused_privileged_permissions()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"App registration {app_name} has 3 unused privileged permission(s): Mail.ReadWrite.All, Directory.ReadWrite.All, User.ReadWrite.All."
|
||||
)
|
||||
assert result[0].resource_name == app_name
|
||||
assert result[0].resource_id == app_id
|
||||
|
||||
def test_app_more_than_five_unused_high_privilege_permissions(self):
|
||||
"""App with more than 5 unused high privilege permissions: expected FAIL with truncated list."""
|
||||
app_id = str(uuid4())
|
||||
app_name = "Test App Many Unused High"
|
||||
entra_client = mock.MagicMock
|
||||
entra_client.audited_tenant = "audited_tenant"
|
||||
entra_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.services.entra.entra_app_registration_no_unused_privileged_permissions.entra_app_registration_no_unused_privileged_permissions.entra_client",
|
||||
new=entra_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.m365.services.entra.entra_app_registration_no_unused_privileged_permissions.entra_app_registration_no_unused_privileged_permissions import (
|
||||
entra_app_registration_no_unused_privileged_permissions,
|
||||
)
|
||||
|
||||
entra_client.oauth_apps = {
|
||||
app_id: OAuthApp(
|
||||
id=app_id,
|
||||
name=app_name,
|
||||
status="Enabled",
|
||||
privilege_level="High",
|
||||
permissions=[
|
||||
OAuthAppPermission(
|
||||
name="Mail.ReadWrite.All",
|
||||
target_app_id="00000003-0000-0000-c000-000000000000",
|
||||
target_app_name="Microsoft Graph",
|
||||
permission_type="Application",
|
||||
privilege_level="High",
|
||||
usage_status="NotInUse",
|
||||
),
|
||||
OAuthAppPermission(
|
||||
name="Directory.ReadWrite.All",
|
||||
target_app_id="00000003-0000-0000-c000-000000000000",
|
||||
target_app_name="Microsoft Graph",
|
||||
permission_type="Application",
|
||||
privilege_level="High",
|
||||
usage_status="NotInUse",
|
||||
),
|
||||
OAuthAppPermission(
|
||||
name="User.ReadWrite.All",
|
||||
target_app_id="00000003-0000-0000-c000-000000000000",
|
||||
target_app_name="Microsoft Graph",
|
||||
permission_type="Application",
|
||||
privilege_level="High",
|
||||
usage_status="NotInUse",
|
||||
),
|
||||
OAuthAppPermission(
|
||||
name="Group.ReadWrite.All",
|
||||
target_app_id="00000003-0000-0000-c000-000000000000",
|
||||
target_app_name="Microsoft Graph",
|
||||
permission_type="Application",
|
||||
privilege_level="High",
|
||||
usage_status="NotInUse",
|
||||
),
|
||||
OAuthAppPermission(
|
||||
name="Sites.ReadWrite.All",
|
||||
target_app_id="00000003-0000-0000-c000-000000000000",
|
||||
target_app_name="Microsoft Graph",
|
||||
permission_type="Application",
|
||||
privilege_level="High",
|
||||
usage_status="NotInUse",
|
||||
),
|
||||
OAuthAppPermission(
|
||||
name="RoleManagement.ReadWrite.All",
|
||||
target_app_id="00000003-0000-0000-c000-000000000000",
|
||||
target_app_name="Microsoft Graph",
|
||||
permission_type="Application",
|
||||
privilege_level="High",
|
||||
usage_status="NotInUse",
|
||||
),
|
||||
OAuthAppPermission(
|
||||
name="Application.ReadWrite.All",
|
||||
target_app_id="00000003-0000-0000-c000-000000000000",
|
||||
target_app_name="Microsoft Graph",
|
||||
permission_type="Application",
|
||||
privilege_level="High",
|
||||
usage_status="NotInUse",
|
||||
),
|
||||
],
|
||||
service_principal_id=str(uuid4()),
|
||||
is_admin_consented=True,
|
||||
last_used_time="2024-01-15T10:30:00Z",
|
||||
app_origin="External",
|
||||
)
|
||||
}
|
||||
|
||||
check = entra_app_registration_no_unused_privileged_permissions()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"App registration {app_name} has 7 unused privileged permission(s): Mail.ReadWrite.All, Directory.ReadWrite.All, User.ReadWrite.All, Group.ReadWrite.All, Sites.ReadWrite.All (and 2 more)."
|
||||
)
|
||||
assert result[0].resource_name == app_name
|
||||
assert result[0].resource_id == app_id
|
||||
|
||||
def test_app_unused_with_not_in_use_status(self):
|
||||
"""App with unused permission using 'not_in_use' status variant: expected FAIL."""
|
||||
app_id = str(uuid4())
|
||||
app_name = "Test App NotInUse Variant"
|
||||
entra_client = mock.MagicMock
|
||||
entra_client.audited_tenant = "audited_tenant"
|
||||
entra_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.services.entra.entra_app_registration_no_unused_privileged_permissions.entra_app_registration_no_unused_privileged_permissions.entra_client",
|
||||
new=entra_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.m365.services.entra.entra_app_registration_no_unused_privileged_permissions.entra_app_registration_no_unused_privileged_permissions import (
|
||||
entra_app_registration_no_unused_privileged_permissions,
|
||||
)
|
||||
|
||||
entra_client.oauth_apps = {
|
||||
app_id: OAuthApp(
|
||||
id=app_id,
|
||||
name=app_name,
|
||||
status="Enabled",
|
||||
privilege_level="High",
|
||||
permissions=[
|
||||
OAuthAppPermission(
|
||||
name="Mail.ReadWrite.All",
|
||||
target_app_id="00000003-0000-0000-c000-000000000000",
|
||||
target_app_name="Microsoft Graph",
|
||||
permission_type="Application",
|
||||
privilege_level="High",
|
||||
usage_status="not_in_use",
|
||||
),
|
||||
],
|
||||
service_principal_id=str(uuid4()),
|
||||
is_admin_consented=True,
|
||||
last_used_time=None,
|
||||
app_origin="Internal",
|
||||
)
|
||||
}
|
||||
|
||||
check = entra_app_registration_no_unused_privileged_permissions()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"App registration {app_name} has 1 unused privileged permission(s): Mail.ReadWrite.All."
|
||||
)
|
||||
assert result[0].resource_name == app_name
|
||||
assert result[0].resource_id == app_id
|
||||
|
||||
def test_multiple_apps_mixed_results(self):
|
||||
"""Multiple apps with mixed results: one PASS and one FAIL."""
|
||||
app_id_pass = str(uuid4())
|
||||
app_name_pass = "Test App Pass"
|
||||
app_id_fail = str(uuid4())
|
||||
app_name_fail = "Test App Fail"
|
||||
entra_client = mock.MagicMock
|
||||
entra_client.audited_tenant = "audited_tenant"
|
||||
entra_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.services.entra.entra_app_registration_no_unused_privileged_permissions.entra_app_registration_no_unused_privileged_permissions.entra_client",
|
||||
new=entra_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.m365.services.entra.entra_app_registration_no_unused_privileged_permissions.entra_app_registration_no_unused_privileged_permissions import (
|
||||
entra_app_registration_no_unused_privileged_permissions,
|
||||
)
|
||||
|
||||
entra_client.oauth_apps = {
|
||||
app_id_pass: OAuthApp(
|
||||
id=app_id_pass,
|
||||
name=app_name_pass,
|
||||
status="Enabled",
|
||||
privilege_level="High",
|
||||
permissions=[
|
||||
OAuthAppPermission(
|
||||
name="Mail.ReadWrite.All",
|
||||
target_app_id="00000003-0000-0000-c000-000000000000",
|
||||
target_app_name="Microsoft Graph",
|
||||
permission_type="Application",
|
||||
privilege_level="High",
|
||||
usage_status="InUse",
|
||||
),
|
||||
],
|
||||
service_principal_id=str(uuid4()),
|
||||
is_admin_consented=True,
|
||||
last_used_time="2024-01-15T10:30:00Z",
|
||||
app_origin="Internal",
|
||||
),
|
||||
app_id_fail: OAuthApp(
|
||||
id=app_id_fail,
|
||||
name=app_name_fail,
|
||||
status="Enabled",
|
||||
privilege_level="High",
|
||||
permissions=[
|
||||
OAuthAppPermission(
|
||||
name="Directory.ReadWrite.All",
|
||||
target_app_id="00000003-0000-0000-c000-000000000000",
|
||||
target_app_name="Microsoft Graph",
|
||||
permission_type="Application",
|
||||
privilege_level="High",
|
||||
usage_status="NotInUse",
|
||||
),
|
||||
],
|
||||
service_principal_id=str(uuid4()),
|
||||
is_admin_consented=True,
|
||||
last_used_time="2024-01-15T10:30:00Z",
|
||||
app_origin="External",
|
||||
),
|
||||
}
|
||||
|
||||
check = entra_app_registration_no_unused_privileged_permissions()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 2
|
||||
|
||||
# Find results by app ID
|
||||
result_pass = next(r for r in result if r.resource_id == app_id_pass)
|
||||
result_fail = next(r for r in result if r.resource_id == app_id_fail)
|
||||
|
||||
assert result_pass.status == "PASS"
|
||||
assert (
|
||||
result_pass.status_extended
|
||||
== f"App registration {app_name_pass} has no unused privileged permissions."
|
||||
)
|
||||
assert result_pass.resource_name == app_name_pass
|
||||
|
||||
assert result_fail.status == "FAIL"
|
||||
assert (
|
||||
result_fail.status_extended
|
||||
== f"App registration {app_name_fail} has 1 unused privileged permission(s): Directory.ReadWrite.All."
|
||||
)
|
||||
assert result_fail.resource_name == app_name_fail
|
||||
|
||||
def test_app_mixed_privilege_levels_unused(self):
|
||||
"""App with mixed privilege levels (High and Low) unused: only High triggers FAIL."""
|
||||
app_id = str(uuid4())
|
||||
app_name = "Test App Mixed Privileges"
|
||||
entra_client = mock.MagicMock
|
||||
entra_client.audited_tenant = "audited_tenant"
|
||||
entra_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.services.entra.entra_app_registration_no_unused_privileged_permissions.entra_app_registration_no_unused_privileged_permissions.entra_client",
|
||||
new=entra_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.m365.services.entra.entra_app_registration_no_unused_privileged_permissions.entra_app_registration_no_unused_privileged_permissions import (
|
||||
entra_app_registration_no_unused_privileged_permissions,
|
||||
)
|
||||
|
||||
entra_client.oauth_apps = {
|
||||
app_id: OAuthApp(
|
||||
id=app_id,
|
||||
name=app_name,
|
||||
status="Enabled",
|
||||
privilege_level="High",
|
||||
permissions=[
|
||||
OAuthAppPermission(
|
||||
name="Mail.ReadWrite.All",
|
||||
target_app_id="00000003-0000-0000-c000-000000000000",
|
||||
target_app_name="Microsoft Graph",
|
||||
permission_type="Application",
|
||||
privilege_level="High",
|
||||
usage_status="NotInUse",
|
||||
),
|
||||
OAuthAppPermission(
|
||||
name="User.Read",
|
||||
target_app_id="00000003-0000-0000-c000-000000000000",
|
||||
target_app_name="Microsoft Graph",
|
||||
permission_type="Delegated",
|
||||
privilege_level="Low",
|
||||
usage_status="NotInUse",
|
||||
),
|
||||
OAuthAppPermission(
|
||||
name="Files.Read",
|
||||
target_app_id="00000003-0000-0000-c000-000000000000",
|
||||
target_app_name="Microsoft Graph",
|
||||
permission_type="Delegated",
|
||||
privilege_level="Medium",
|
||||
usage_status="NotInUse",
|
||||
),
|
||||
],
|
||||
service_principal_id=str(uuid4()),
|
||||
is_admin_consented=True,
|
||||
last_used_time="2024-01-15T10:30:00Z",
|
||||
app_origin="Internal",
|
||||
)
|
||||
}
|
||||
|
||||
check = entra_app_registration_no_unused_privileged_permissions()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
# Only the High privilege permission should be reported
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"App registration {app_name} has 1 unused privileged permission(s): Mail.ReadWrite.All."
|
||||
)
|
||||
assert result[0].resource_name == app_name
|
||||
assert result[0].resource_id == app_id
|
||||
|
||||
def test_app_high_privilege_in_use_and_unused(self):
|
||||
"""App with some high privilege permissions in use and some unused: expected FAIL."""
|
||||
app_id = str(uuid4())
|
||||
app_name = "Test App Partial Usage"
|
||||
entra_client = mock.MagicMock
|
||||
entra_client.audited_tenant = "audited_tenant"
|
||||
entra_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.services.entra.entra_app_registration_no_unused_privileged_permissions.entra_app_registration_no_unused_privileged_permissions.entra_client",
|
||||
new=entra_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.m365.services.entra.entra_app_registration_no_unused_privileged_permissions.entra_app_registration_no_unused_privileged_permissions import (
|
||||
entra_app_registration_no_unused_privileged_permissions,
|
||||
)
|
||||
|
||||
entra_client.oauth_apps = {
|
||||
app_id: OAuthApp(
|
||||
id=app_id,
|
||||
name=app_name,
|
||||
status="Enabled",
|
||||
privilege_level="High",
|
||||
permissions=[
|
||||
OAuthAppPermission(
|
||||
name="Mail.ReadWrite.All",
|
||||
target_app_id="00000003-0000-0000-c000-000000000000",
|
||||
target_app_name="Microsoft Graph",
|
||||
permission_type="Application",
|
||||
privilege_level="High",
|
||||
usage_status="InUse",
|
||||
),
|
||||
OAuthAppPermission(
|
||||
name="Directory.ReadWrite.All",
|
||||
target_app_id="00000003-0000-0000-c000-000000000000",
|
||||
target_app_name="Microsoft Graph",
|
||||
permission_type="Application",
|
||||
privilege_level="High",
|
||||
usage_status="NotInUse",
|
||||
),
|
||||
OAuthAppPermission(
|
||||
name="User.ReadWrite.All",
|
||||
target_app_id="00000003-0000-0000-c000-000000000000",
|
||||
target_app_name="Microsoft Graph",
|
||||
permission_type="Application",
|
||||
privilege_level="High",
|
||||
usage_status="InUse",
|
||||
),
|
||||
],
|
||||
service_principal_id=str(uuid4()),
|
||||
is_admin_consented=True,
|
||||
last_used_time="2024-01-15T10:30:00Z",
|
||||
app_origin="Internal",
|
||||
)
|
||||
}
|
||||
|
||||
check = entra_app_registration_no_unused_privileged_permissions()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== f"App registration {app_name} has 1 unused privileged permission(s): Directory.ReadWrite.All."
|
||||
)
|
||||
assert result[0].resource_name == app_name
|
||||
assert result[0].resource_id == app_id
|
||||
|
||||
def test_app_without_name_uses_id(self):
|
||||
"""App without a name should use app_id as resource_name."""
|
||||
app_id = str(uuid4())
|
||||
entra_client = mock.MagicMock
|
||||
entra_client.audited_tenant = "audited_tenant"
|
||||
entra_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.services.entra.entra_app_registration_no_unused_privileged_permissions.entra_app_registration_no_unused_privileged_permissions.entra_client",
|
||||
new=entra_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.m365.services.entra.entra_app_registration_no_unused_privileged_permissions.entra_app_registration_no_unused_privileged_permissions import (
|
||||
entra_app_registration_no_unused_privileged_permissions,
|
||||
)
|
||||
|
||||
entra_client.oauth_apps = {
|
||||
app_id: OAuthApp(
|
||||
id=app_id,
|
||||
name="",
|
||||
status="Enabled",
|
||||
privilege_level="Low",
|
||||
permissions=[],
|
||||
service_principal_id=str(uuid4()),
|
||||
is_admin_consented=False,
|
||||
last_used_time=None,
|
||||
app_origin="Internal",
|
||||
)
|
||||
}
|
||||
|
||||
check = entra_app_registration_no_unused_privileged_permissions()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
assert result[0].resource_name == ""
|
||||
assert result[0].resource_id == app_id
|
||||
Reference in New Issue
Block a user