mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-05-15 00:33:27 +00:00
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a646c68308 | |||
| 62ad4e5b9f | |||
| ce8f6037c1 | |||
| b49e49f543 | |||
| 4cf2207d58 | |||
| 080bc174fa | |||
| 6fac51047c | |||
| 15a5527910 |
@@ -2,6 +2,14 @@
|
||||
|
||||
All notable changes to the **Prowler SDK** are documented in this file.
|
||||
|
||||
## [5.27.0] (Prowler UNRELEASED)
|
||||
|
||||
### 🚀 Added
|
||||
|
||||
- `entra_pim_stale_sign_in_alert` check for m365 provider [(#10798)](https://github.com/prowler-cloud/prowler/pull/10798)
|
||||
|
||||
---
|
||||
|
||||
## [5.26.0] (Prowler UNRELEASED)
|
||||
|
||||
### 🚀 Added
|
||||
|
||||
@@ -1502,7 +1502,9 @@
|
||||
{
|
||||
"Id": "5.3.1",
|
||||
"Description": "Microsoft Entra Privileged Identity Management can be used to audit roles, allow just in time activation of roles and allow for periodic role attestation. Organizations should remove permanent members from privileged Office 365 roles and instead make them eligible, through a JIT activation workflow.",
|
||||
"Checks": [],
|
||||
"Checks": [
|
||||
"entra_pim_stale_sign_in_alert"
|
||||
],
|
||||
"Attributes": [
|
||||
{
|
||||
"Section": "5 Microsoft Entra admin center",
|
||||
@@ -1544,7 +1546,9 @@
|
||||
{
|
||||
"Id": "5.3.3",
|
||||
"Description": "Access reviews enable administrators to establish an efficient automated process for reviewing group memberships, access to enterprise applications, and role assignments. These reviews can be scheduled to recur regularly, with flexible options for delegating the task of reviewing membership to different members of the organization.Ensure `Access reviews` for high privileged Entra ID roles are done `monthly` or more frequently. These reviews should include **at a minimum** the roles listed below:- Global Administrator- Exchange Administrator- SharePoint Administrator- Teams Administrator- Security Administrator**Note:** An access review is created for each role selected after completing the process.",
|
||||
"Checks": [],
|
||||
"Checks": [
|
||||
"entra_pim_stale_sign_in_alert"
|
||||
],
|
||||
"Attributes": [
|
||||
{
|
||||
"Section": "5 Microsoft Entra admin center",
|
||||
|
||||
@@ -1803,7 +1803,9 @@
|
||||
{
|
||||
"Id": "5.3.1",
|
||||
"Description": "Microsoft Entra Privileged Identity Management can be used to audit roles, allow just in time activation of roles and allow for periodic role attestation. Organizations should remove permanent members from privileged Office 365 roles and instead make them eligible, through a JIT activation workflow. Ensure 'Privileged Identity Management' is used to manage roles.",
|
||||
"Checks": [],
|
||||
"Checks": [
|
||||
"entra_pim_stale_sign_in_alert"
|
||||
],
|
||||
"Attributes": [
|
||||
{
|
||||
"Section": "5 Microsoft Entra admin center",
|
||||
@@ -1845,7 +1847,9 @@
|
||||
{
|
||||
"Id": "5.3.3",
|
||||
"Description": "Access reviews enable administrators to establish an efficient automated process for reviewing group memberships, access to enterprise applications, and role assignments. Ensure 'Access reviews' for privileged roles are configured to be done monthly or more frequently.",
|
||||
"Checks": [],
|
||||
"Checks": [
|
||||
"entra_pim_stale_sign_in_alert"
|
||||
],
|
||||
"Attributes": [
|
||||
{
|
||||
"Section": "5 Microsoft Entra admin center",
|
||||
|
||||
@@ -281,6 +281,7 @@
|
||||
"Checks": [
|
||||
"entra_admin_portals_access_restriction",
|
||||
"entra_app_registration_no_unused_privileged_permissions",
|
||||
"entra_pim_stale_sign_in_alert",
|
||||
"entra_policy_guest_users_access_restrictions",
|
||||
"sharepoint_external_sharing_managed",
|
||||
"sharepoint_external_sharing_restricted",
|
||||
@@ -672,6 +673,7 @@
|
||||
"entra_admin_users_sign_in_frequency_enabled",
|
||||
"entra_break_glass_account_fido2_security_key_registered",
|
||||
"entra_app_registration_no_unused_privileged_permissions",
|
||||
"entra_pim_stale_sign_in_alert",
|
||||
"entra_policy_ensure_default_user_cannot_create_tenants",
|
||||
"entra_policy_guest_invite_only_for_admin_roles",
|
||||
"entra_seamless_sso_disabled"
|
||||
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"Provider": "m365",
|
||||
"CheckID": "entra_pim_stale_sign_in_alert",
|
||||
"CheckTitle": "PIM stale sign-in alert detects unused privileged accounts that may be compromised",
|
||||
"CheckType": [],
|
||||
"ServiceName": "entra",
|
||||
"SubServiceName": "",
|
||||
"ResourceIdTemplate": "",
|
||||
"Severity": "high",
|
||||
"ResourceType": "NotDefined",
|
||||
"ResourceGroup": "governance",
|
||||
"Description": "Microsoft Entra PIM monitors privileged role assignments and raises a **stale sign-in alert** when accounts have not authenticated within a configured period (default 30 days).\n\n*Stale privileged accounts indicate roles that may be over-provisioned or abandoned.*",
|
||||
"Risk": "Stale accounts retaining **privileged roles** expand the attack surface by providing dormant credentials that attackers can exploit undetected. Compromised stale accounts enable **privilege escalation** and **lateral movement** without triggering normal user activity alerts, weakening confidentiality and integrity.",
|
||||
"RelatedUrl": "",
|
||||
"AdditionalURLs": [
|
||||
"https://learn.microsoft.com/en-us/entra/id-governance/privileged-identity-management/pim-how-to-configure-security-alerts",
|
||||
"https://learn.microsoft.com/en-us/entra/id-governance/privileged-identity-management/pim-security-alerts"
|
||||
],
|
||||
"Remediation": {
|
||||
"Code": {
|
||||
"CLI": "",
|
||||
"NativeIaC": "",
|
||||
"Other": "1. Sign in to the Microsoft Entra admin center\n2. Navigate to Identity governance > Privileged Identity Management > Alerts\n3. Review the 'Potential stale accounts in a privileged role' alert\n4. For each affected account, remove unnecessary role assignments or confirm the account is still required\n5. Investigate whether stale accounts show signs of compromise",
|
||||
"Terraform": ""
|
||||
},
|
||||
"Recommendation": {
|
||||
"Text": "Apply **least privilege** by removing privileged role assignments from accounts that no longer require them. Implement **just-in-time access** via PIM eligible assignments instead of permanent roles. Establish periodic **access reviews** to detect and remediate stale privileged accounts before they can be exploited.",
|
||||
"Url": "https://hub.prowler.com/check/entra_pim_stale_sign_in_alert"
|
||||
}
|
||||
},
|
||||
"Categories": [
|
||||
"identity-access",
|
||||
"e3"
|
||||
],
|
||||
"DependsOn": [],
|
||||
"RelatedTo": [],
|
||||
"Notes": "Requires Microsoft Entra ID P2 license and RoleManagement.Read.All permission. The stale sign-in threshold is configurable in PIM alert settings (default: 30 days)."
|
||||
}
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
from typing import List
|
||||
|
||||
from prowler.lib.check.models import Check, CheckReportM365
|
||||
from prowler.providers.m365.services.entra.entra_client import entra_client
|
||||
|
||||
STALE_SIGN_IN_ALERT_DEFINITION_ID = "DirectoryRole_StaleSignInAlert"
|
||||
|
||||
|
||||
class entra_pim_stale_sign_in_alert(Check):
|
||||
"""Check if there are stale accounts in privileged roles detected by PIM.
|
||||
|
||||
This check verifies that Privileged Identity Management (PIM) does not
|
||||
report any stale sign-in alerts for users with privileged role assignments.
|
||||
A stale account is one that has not signed in within a configured period
|
||||
(default 30 days) while still retaining a privileged directory role.
|
||||
|
||||
Stale privileged accounts represent a significant security risk because
|
||||
unused credentials in elevated roles can be exploited by attackers without
|
||||
detection.
|
||||
"""
|
||||
|
||||
def execute(self) -> List[CheckReportM365]:
|
||||
"""Execute the PIM stale sign-in alert check.
|
||||
|
||||
Retrieves the PIM stale sign-in alert from the Entra client and generates
|
||||
a report indicating whether stale accounts exist in privileged roles.
|
||||
|
||||
Returns:
|
||||
List[CheckReportM365]: A list containing the report object with the result of the check.
|
||||
"""
|
||||
findings = []
|
||||
|
||||
stale_alert = entra_client.pim_alerts.get(STALE_SIGN_IN_ALERT_DEFINITION_ID)
|
||||
|
||||
if stale_alert:
|
||||
report = CheckReportM365(
|
||||
self.metadata(),
|
||||
resource=stale_alert,
|
||||
resource_id=stale_alert.id,
|
||||
resource_name="PIM Stale Sign-In Alert",
|
||||
)
|
||||
|
||||
if stale_alert.is_active and stale_alert.number_of_affected_items > 0:
|
||||
affected_users = ", ".join(
|
||||
incident.assignee_display_name or incident.assignee_id
|
||||
for incident in stale_alert.affected_items[:5]
|
||||
)
|
||||
suffix = (
|
||||
f" and {stale_alert.number_of_affected_items - 5} more"
|
||||
if stale_alert.number_of_affected_items > 5
|
||||
else ""
|
||||
)
|
||||
report.status = "FAIL"
|
||||
report.status_extended = (
|
||||
f"PIM detected {stale_alert.number_of_affected_items} "
|
||||
f"stale account(s) in privileged roles: {affected_users}{suffix}."
|
||||
)
|
||||
else:
|
||||
report.status = "PASS"
|
||||
report.status_extended = "PIM stale sign-in alert reports no stale accounts in privileged roles."
|
||||
|
||||
findings.append(report)
|
||||
elif entra_client.organizations:
|
||||
organization = entra_client.organizations[0]
|
||||
report = CheckReportM365(
|
||||
self.metadata(),
|
||||
resource=organization,
|
||||
resource_id=organization.id,
|
||||
resource_name=organization.name,
|
||||
)
|
||||
report.status = "MANUAL"
|
||||
report.status_extended = (
|
||||
"PIM stale sign-in alert is not available. This can happen when "
|
||||
"the tenant lacks Microsoft Entra ID P2, the alert is disabled, "
|
||||
"or the running credentials cannot read PIM alerts. Review the "
|
||||
"alert configuration in the Entra admin center under Identity "
|
||||
"Governance > Privileged Identity Management > Alerts."
|
||||
)
|
||||
findings.append(report)
|
||||
|
||||
return findings
|
||||
@@ -5,6 +5,8 @@ from enum import Enum
|
||||
from typing import Dict, List, Optional
|
||||
from uuid import UUID
|
||||
|
||||
from kiota_abstractions.method import Method
|
||||
from kiota_abstractions.request_information import RequestInformation
|
||||
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,
|
||||
@@ -23,7 +25,7 @@ class Entra(M365Service):
|
||||
This class provides methods to retrieve and manage Microsoft Entra ID
|
||||
security policies and configurations, including authorization policies,
|
||||
conditional access policies, admin consent policies, groups, organizations,
|
||||
users, and OAuth application data from Defender XDR.
|
||||
users, OAuth application data from Defender XDR, and PIM alerts.
|
||||
|
||||
Attributes:
|
||||
tenant_domain (str): The tenant domain.
|
||||
@@ -36,6 +38,7 @@ class Entra(M365Service):
|
||||
user_accounts_status (dict): Dictionary of user account statuses.
|
||||
oauth_apps (dict): Dictionary of OAuth applications from Defender XDR.
|
||||
authentication_method_configurations (dict): Dictionary of authentication method configurations.
|
||||
pim_alerts (dict): Dictionary of PIM alerts keyed by alert definition ID.
|
||||
"""
|
||||
|
||||
def __init__(self, provider: M365Provider):
|
||||
@@ -83,6 +86,7 @@ class Entra(M365Service):
|
||||
self._get_oauth_apps(),
|
||||
self._get_directory_sync_settings(),
|
||||
self._get_authentication_method_configurations(),
|
||||
self._get_pim_alerts(),
|
||||
)
|
||||
)
|
||||
|
||||
@@ -98,6 +102,7 @@ class Entra(M365Service):
|
||||
self.authentication_method_configurations: Dict[
|
||||
str, AuthenticationMethodConfiguration
|
||||
] = attributes[9]
|
||||
self.pim_alerts: Dict[str, PimAlert] = attributes[10]
|
||||
self.user_accounts_status = {}
|
||||
|
||||
if created_loop:
|
||||
@@ -1055,6 +1060,72 @@ OAuthAppInfo
|
||||
)
|
||||
return authentication_method_configurations
|
||||
|
||||
async def _get_pim_alerts(self):
|
||||
"""Retrieve Privileged Identity Management (PIM) role management alerts.
|
||||
|
||||
Fetches unified role management alerts from the Microsoft Graph API to
|
||||
identify security issues such as stale accounts in privileged roles.
|
||||
Uses a raw HTTP request since the SDK does not expose this endpoint natively.
|
||||
|
||||
Returns:
|
||||
Dict[str, PimAlert]: Dictionary of PIM alerts keyed by alert definition ID.
|
||||
"""
|
||||
logger.info("Entra - Getting PIM alerts...")
|
||||
pim_alerts = {}
|
||||
try:
|
||||
request_info = RequestInformation()
|
||||
request_info.http_method = Method.GET
|
||||
request_info.url = "https://graph.microsoft.com/v1.0/identityGovernance/roleManagement/alerts/alerts?$expand=alertIncidents"
|
||||
response = await self.client.request_adapter.send_primitive_async(
|
||||
request_info, "bytes", {}
|
||||
)
|
||||
if response:
|
||||
data = json.loads(response)
|
||||
for alert in data.get("value", []):
|
||||
alert_definition_id = alert.get("alertDefinitionId", "")
|
||||
incidents = alert.get("alertIncidents", [])
|
||||
affected_items = []
|
||||
for incident in incidents:
|
||||
affected_items.append(
|
||||
PimAlertIncident(
|
||||
assignee_display_name=incident.get(
|
||||
"assigneeDisplayName", ""
|
||||
),
|
||||
assignee_id=incident.get("assigneeId", ""),
|
||||
role_display_name=incident.get("roleDisplayName", ""),
|
||||
last_sign_in_date_time=incident.get(
|
||||
"lastSignInDateTime", ""
|
||||
),
|
||||
)
|
||||
)
|
||||
pim_alerts[alert_definition_id] = PimAlert(
|
||||
id=alert.get("id", ""),
|
||||
alert_definition_id=alert_definition_id,
|
||||
scope_id=alert.get("scopeId", "/"),
|
||||
scope_type=alert.get("scopeType", ""),
|
||||
is_active=alert.get("isActive", False),
|
||||
number_of_affected_items=alert.get("numberOfAffectedItems", 0),
|
||||
incident_count=alert.get("incidentCount", 0),
|
||||
affected_items=affected_items,
|
||||
)
|
||||
except ODataError as error:
|
||||
error_code = getattr(error.error, "code", None) if error.error else None
|
||||
if error_code == "Authorization_RequestDenied":
|
||||
logger.error(
|
||||
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: "
|
||||
"Insufficient privileges to read PIM alerts. "
|
||||
"Required permission: RoleManagement.Read.All or RoleManagement.ReadWrite.Directory"
|
||||
)
|
||||
else:
|
||||
logger.error(
|
||||
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
|
||||
)
|
||||
except Exception as error:
|
||||
logger.error(
|
||||
f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
|
||||
)
|
||||
return pim_alerts
|
||||
|
||||
|
||||
class ConditionalAccessPolicyState(Enum):
|
||||
ENABLED = "enabled"
|
||||
@@ -1486,3 +1557,43 @@ class OAuthApp(BaseModel):
|
||||
is_admin_consented: bool = False
|
||||
last_used_time: Optional[str] = None
|
||||
app_origin: str = ""
|
||||
|
||||
|
||||
class PimAlertIncident(BaseModel):
|
||||
"""Model representing an incident (affected resource) within a PIM alert.
|
||||
|
||||
Attributes:
|
||||
assignee_display_name: The display name of the user with a stale sign-in.
|
||||
assignee_id: The unique identifier of the affected user.
|
||||
role_display_name: The privileged role assigned to the user.
|
||||
last_sign_in_date_time: The last sign-in date for the user (ISO 8601 format).
|
||||
"""
|
||||
|
||||
assignee_display_name: str = ""
|
||||
assignee_id: str = ""
|
||||
role_display_name: str = ""
|
||||
last_sign_in_date_time: str = ""
|
||||
|
||||
|
||||
class PimAlert(BaseModel):
|
||||
"""Model representing a Privileged Identity Management (PIM) alert.
|
||||
|
||||
Attributes:
|
||||
id: The unique identifier of the alert.
|
||||
alert_definition_id: The alert type identifier (e.g., 'DirectoryRole_StaleSignInAlert').
|
||||
scope_id: The scope of the alert (typically '/' for tenant-wide).
|
||||
scope_type: The scope type (e.g., 'DirectoryRole').
|
||||
is_active: Whether the alert is currently active.
|
||||
number_of_affected_items: Count of resources affected by the alert.
|
||||
incident_count: Number of incidents reported for the alert.
|
||||
affected_items: List of affected resources (incidents).
|
||||
"""
|
||||
|
||||
id: str
|
||||
alert_definition_id: str
|
||||
scope_id: str = "/"
|
||||
scope_type: str = ""
|
||||
is_active: bool = False
|
||||
number_of_affected_items: int = 0
|
||||
incident_count: int = 0
|
||||
affected_items: List[PimAlertIncident] = []
|
||||
|
||||
+275
@@ -0,0 +1,275 @@
|
||||
from unittest import mock
|
||||
|
||||
from prowler.providers.m365.services.entra.entra_service import (
|
||||
Organization,
|
||||
PimAlert,
|
||||
PimAlertIncident,
|
||||
)
|
||||
from tests.providers.m365.m365_fixtures import set_mocked_m365_provider
|
||||
|
||||
|
||||
class Test_entra_pim_stale_sign_in_alert:
|
||||
def test_no_stale_accounts(self):
|
||||
"""PASS: PIM stale sign-in alert exists with no affected items."""
|
||||
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_pim_stale_sign_in_alert.entra_pim_stale_sign_in_alert.entra_client",
|
||||
new=entra_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.m365.services.entra.entra_pim_stale_sign_in_alert.entra_pim_stale_sign_in_alert import (
|
||||
entra_pim_stale_sign_in_alert,
|
||||
)
|
||||
|
||||
entra_client.pim_alerts = {
|
||||
"DirectoryRole_StaleSignInAlert": PimAlert(
|
||||
id="alert-001",
|
||||
alert_definition_id="DirectoryRole_StaleSignInAlert",
|
||||
scope_id="/",
|
||||
scope_type="DirectoryRole",
|
||||
is_active=True,
|
||||
number_of_affected_items=0,
|
||||
incident_count=0,
|
||||
affected_items=[],
|
||||
)
|
||||
}
|
||||
|
||||
check = entra_pim_stale_sign_in_alert()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
assert (
|
||||
result[0].status_extended
|
||||
== "PIM stale sign-in alert reports no stale accounts in privileged roles."
|
||||
)
|
||||
assert result[0].resource_id == "alert-001"
|
||||
assert result[0].resource_name == "PIM Stale Sign-In Alert"
|
||||
assert result[0].location == "global"
|
||||
|
||||
def test_stale_accounts_detected(self):
|
||||
"""FAIL: PIM stale sign-in alert has affected items."""
|
||||
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_pim_stale_sign_in_alert.entra_pim_stale_sign_in_alert.entra_client",
|
||||
new=entra_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.m365.services.entra.entra_pim_stale_sign_in_alert.entra_pim_stale_sign_in_alert import (
|
||||
entra_pim_stale_sign_in_alert,
|
||||
)
|
||||
|
||||
entra_client.pim_alerts = {
|
||||
"DirectoryRole_StaleSignInAlert": PimAlert(
|
||||
id="alert-001",
|
||||
alert_definition_id="DirectoryRole_StaleSignInAlert",
|
||||
scope_id="/",
|
||||
scope_type="DirectoryRole",
|
||||
is_active=True,
|
||||
number_of_affected_items=2,
|
||||
incident_count=2,
|
||||
affected_items=[
|
||||
PimAlertIncident(
|
||||
assignee_display_name="John Doe",
|
||||
assignee_id="user-001",
|
||||
role_display_name="Global Administrator",
|
||||
last_sign_in_date_time="2025-01-01T00:00:00Z",
|
||||
),
|
||||
PimAlertIncident(
|
||||
assignee_display_name="Jane Smith",
|
||||
assignee_id="user-002",
|
||||
role_display_name="Security Administrator",
|
||||
last_sign_in_date_time="2025-02-01T00:00:00Z",
|
||||
),
|
||||
],
|
||||
)
|
||||
}
|
||||
|
||||
check = entra_pim_stale_sign_in_alert()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert "2 stale account(s)" in result[0].status_extended
|
||||
assert "John Doe" in result[0].status_extended
|
||||
assert "Jane Smith" in result[0].status_extended
|
||||
assert result[0].resource_id == "alert-001"
|
||||
assert result[0].resource_name == "PIM Stale Sign-In Alert"
|
||||
assert result[0].location == "global"
|
||||
|
||||
def test_stale_accounts_more_than_five(self):
|
||||
"""FAIL: PIM stale sign-in alert with more than 5 affected items truncates display."""
|
||||
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_pim_stale_sign_in_alert.entra_pim_stale_sign_in_alert.entra_client",
|
||||
new=entra_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.m365.services.entra.entra_pim_stale_sign_in_alert.entra_pim_stale_sign_in_alert import (
|
||||
entra_pim_stale_sign_in_alert,
|
||||
)
|
||||
|
||||
affected_items = [
|
||||
PimAlertIncident(
|
||||
assignee_display_name=f"User {i}",
|
||||
assignee_id=f"user-{i:03d}",
|
||||
role_display_name="Global Administrator",
|
||||
last_sign_in_date_time="2025-01-01T00:00:00Z",
|
||||
)
|
||||
for i in range(7)
|
||||
]
|
||||
|
||||
entra_client.pim_alerts = {
|
||||
"DirectoryRole_StaleSignInAlert": PimAlert(
|
||||
id="alert-001",
|
||||
alert_definition_id="DirectoryRole_StaleSignInAlert",
|
||||
scope_id="/",
|
||||
scope_type="DirectoryRole",
|
||||
is_active=True,
|
||||
number_of_affected_items=7,
|
||||
incident_count=7,
|
||||
affected_items=affected_items,
|
||||
)
|
||||
}
|
||||
|
||||
check = entra_pim_stale_sign_in_alert()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert "7 stale account(s)" in result[0].status_extended
|
||||
assert "and 2 more" in result[0].status_extended
|
||||
assert result[0].resource_id == "alert-001"
|
||||
|
||||
def test_alert_not_configured(self):
|
||||
"""MANUAL: PIM stale sign-in alert is not available."""
|
||||
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_pim_stale_sign_in_alert.entra_pim_stale_sign_in_alert.entra_client",
|
||||
new=entra_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.m365.services.entra.entra_pim_stale_sign_in_alert.entra_pim_stale_sign_in_alert import (
|
||||
entra_pim_stale_sign_in_alert,
|
||||
)
|
||||
|
||||
entra_client.pim_alerts = {}
|
||||
entra_client.organizations = [
|
||||
Organization(
|
||||
id="org-001",
|
||||
name="Contoso",
|
||||
on_premises_sync_enabled=False,
|
||||
),
|
||||
Organization(
|
||||
id="org-002",
|
||||
name="Contoso Two",
|
||||
on_premises_sync_enabled=False,
|
||||
),
|
||||
]
|
||||
|
||||
check = entra_pim_stale_sign_in_alert()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "MANUAL"
|
||||
assert "not available" in result[0].status_extended
|
||||
assert "P2" in result[0].status_extended
|
||||
assert result[0].resource_id == "org-001"
|
||||
assert result[0].resource_name == "Contoso"
|
||||
assert result[0].location == "global"
|
||||
|
||||
def test_inactive_alert_with_lingering_affected_items(self):
|
||||
"""PASS: alert reports affected items but is not active."""
|
||||
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_pim_stale_sign_in_alert.entra_pim_stale_sign_in_alert.entra_client",
|
||||
new=entra_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.m365.services.entra.entra_pim_stale_sign_in_alert.entra_pim_stale_sign_in_alert import (
|
||||
entra_pim_stale_sign_in_alert,
|
||||
)
|
||||
|
||||
entra_client.pim_alerts = {
|
||||
"DirectoryRole_StaleSignInAlert": PimAlert(
|
||||
id="alert-resolved",
|
||||
alert_definition_id="DirectoryRole_StaleSignInAlert",
|
||||
scope_id="/",
|
||||
scope_type="DirectoryRole",
|
||||
is_active=False,
|
||||
number_of_affected_items=3,
|
||||
incident_count=3,
|
||||
affected_items=[
|
||||
PimAlertIncident(
|
||||
assignee_display_name="Resolved User",
|
||||
assignee_id="user-resolved",
|
||||
role_display_name="Global Administrator",
|
||||
last_sign_in_date_time="2024-01-01T00:00:00Z",
|
||||
)
|
||||
],
|
||||
)
|
||||
}
|
||||
|
||||
check = entra_pim_stale_sign_in_alert()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
assert "no stale accounts" in result[0].status_extended
|
||||
assert result[0].resource_id == "alert-resolved"
|
||||
|
||||
def test_empty_pim_alerts_no_organizations(self):
|
||||
"""No findings when PIM alerts empty and no organizations."""
|
||||
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_pim_stale_sign_in_alert.entra_pim_stale_sign_in_alert.entra_client",
|
||||
new=entra_client,
|
||||
),
|
||||
):
|
||||
from prowler.providers.m365.services.entra.entra_pim_stale_sign_in_alert.entra_pim_stale_sign_in_alert import (
|
||||
entra_pim_stale_sign_in_alert,
|
||||
)
|
||||
|
||||
entra_client.pim_alerts = {}
|
||||
entra_client.organizations = []
|
||||
|
||||
check = entra_pim_stale_sign_in_alert()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 0
|
||||
Reference in New Issue
Block a user