Skip to content

Commit eae2468

Browse files
authored
Merge branch 'master' into sdk-config-compliance
2 parents daa7c5d + fb995a7 commit eae2468

31 files changed

Lines changed: 1022 additions & 105 deletions

api/CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
All notable changes to the **Prowler API** are documented in this file.
44

5-
## [1.32.0] (Prowler UNRELEASED)
5+
## [1.32.0] (Prowler v5.31.0)
66

77
### 🚀 Added
88

prowler/CHANGELOG.md

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,15 @@
22

33
All notable changes to the **Prowler SDK** are documented in this file.
44

5-
## [5.31.0] (Prowler UNRELEASED)
5+
## [5.32.0] (Prowler UNRELEASED)
6+
7+
### 🚀 Added
8+
9+
- Per-requirement configuration validation for compliance frameworks via `ConfigRequirements`, so a requirement is reported as FAIL when its configurable checks ran with a configuration too loose to satisfy it (applied across all compliance outputs: CSV, OCSF, and console tables) [(#11667)](https://github.com/prowler-cloud/prowler/pull/11667)
10+
11+
---
12+
13+
## [5.31.0] (Prowler v5.31.0)
614

715
### 🚀 Added
816

@@ -33,6 +41,7 @@ All notable changes to the **Prowler SDK** are documented in this file.
3341
- `network_vnet_ddos_protection_enabled` check for Azure provider, verifying virtual networks have Azure DDoS Network Protection enabled [(#11044)](https://github.com/prowler-cloud/prowler/pull/11044)
3442
- `entra_app_registration_credential_not_expired` check for Azure provider, verifying Entra ID app registration secrets and certificates are not expired, expiring within 30 days, or without an expiration date [(#11038)](https://github.com/prowler-cloud/prowler/pull/11038)
3543
- `entra_authentication_methods_policy_strong_auth_enforced` check for Azure provider, verifying the Entra ID authentication methods policy enforces MFA registration and enables at least one strong method (Microsoft Authenticator, FIDO2, or X.509 certificate) [(#11039)](https://github.com/prowler-cloud/prowler/pull/11039)
44+
- `entra_user_with_recent_sign_in` check for Azure provider, detecting stale enabled accounts that have not signed in within the last 90 days (requires Entra ID P1/P2 licensing for sign-in activity) [(#11040)](https://github.com/prowler-cloud/prowler/pull/11040)
3645
- `aks_cluster_auto_upgrade_enabled` check for Azure provider [(#11027)](https://github.com/prowler-cloud/prowler/pull/11027)
3746
- Public `Provider.get_class()` method that resolves a provider class by name for both built-in and external (entry-point) providers [(#11398)](https://github.com/prowler-cloud/prowler/pull/11398)
3847
- Jira timeout preventing the calls from hanging indefinitely when the Jira endpoint is unreachable or slow [(#11602)](https://github.com/prowler-cloud/prowler/pull/11602)
@@ -53,7 +62,7 @@ All notable changes to the **Prowler SDK** are documented in this file.
5362
- `acmpca_certificate_authority_pqc_key_algorithm` check and new `acmpca` service for AWS provider to verify AWS Private CA certificate authorities use a post-quantum (ML-DSA) key algorithm [(#11318)](https://github.com/prowler-cloud/prowler/pull/11318)
5463
- `rolesanywhere_trust_anchor_pqc_pki` check and new `rolesanywhere` service for AWS provider to verify IAM Roles Anywhere trust anchors are backed by a post-quantum (ML-DSA) PKI [(#11319)](https://github.com/prowler-cloud/prowler/pull/11319)
5564
- Kubernetes core checks for container CPU limits, CPU requests, memory limits, memory requests, fixed image tags, liveness probes, and readiness probes [(#11373)](https://github.com/prowler-cloud/prowler/pull/11373)
56-
- Per-requirement configuration validation for compliance frameworks via `ConfigRequirements`, so a requirement is reported as FAIL when its configurable checks ran with a configuration too loose to satisfy it (applied across all compliance outputs: CSV, OCSF, and console tables) [(#11667)](https://github.com/prowler-cloud/prowler/pull/11667)
65+
- `recovery_vault_backup_policy_retention_adequate` check for Azure provider, verifying Recovery Services backup policies retain daily backups for at least 30 days [(#11047)](https://github.com/prowler-cloud/prowler/pull/11047)
5766

5867
### 🔄 Changed
5968

prowler/compliance/azure/fedramp_20x_ksi_low_azure.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,7 @@
328328
"Checks": [
329329
"entra_non_privileged_user_has_mfa",
330330
"entra_privileged_user_has_mfa",
331+
"entra_user_with_recent_sign_in",
331332
"entra_user_with_vm_access_has_mfa",
332333
"iam_custom_role_has_permissions_to_administer_resource_locks",
333334
"iam_role_user_access_admin_restricted",

prowler/compliance/azure/hipaa_azure.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,7 @@
182182
}
183183
],
184184
"Checks": [
185+
"entra_user_with_recent_sign_in",
185186
"storage_key_rotation_90_days",
186187
"keyvault_key_rotation_enabled",
187188
"keyvault_rbac_key_expiration_set",
@@ -430,6 +431,7 @@
430431
}
431432
],
432433
"Checks": [
434+
"recovery_vault_backup_policy_retention_adequate",
433435
"vm_backup_enabled",
434436
"vm_sufficient_daily_backup_retention_period",
435437
"storage_blob_versioning_is_enabled",

prowler/compliance/azure/iso27001_2022_azure.json

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,7 @@
285285
"defender_ensure_notify_alerts_severity_is_high",
286286
"entra_policy_guest_users_access_restrictions",
287287
"entra_policy_restricts_user_consent_for_apps",
288+
"entra_user_with_recent_sign_in",
288289
"entra_users_cannot_create_microsoft_365_groups",
289290
"iam_custom_role_has_permissions_to_administer_resource_locks",
290291
"monitor_alert_create_update_security_solution",
@@ -333,7 +334,8 @@
333334
"entra_policy_guest_invite_only_for_admin_roles",
334335
"entra_policy_guest_users_access_restrictions",
335336
"entra_policy_restricts_user_consent_for_apps",
336-
"entra_policy_user_consent_for_verified_apps"
337+
"entra_policy_user_consent_for_verified_apps",
338+
"entra_user_with_recent_sign_in"
337339
]
338340
},
339341
{
@@ -1271,7 +1273,8 @@
12711273
"Checks": [
12721274
"cosmosdb_account_backup_policy_continuous",
12731275
"mysql_flexible_server_geo_redundant_backup_enabled",
1274-
"postgresql_flexible_server_geo_redundant_backup_enabled"
1276+
"postgresql_flexible_server_geo_redundant_backup_enabled",
1277+
"recovery_vault_backup_policy_retention_adequate"
12751278
]
12761279
},
12771280
{

prowler/providers/azure/services/entra/entra_service.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,12 @@ async def _get_users(self):
7474
try:
7575
request_configuration = RequestConfiguration(
7676
query_parameters=UsersRequestBuilder.UsersRequestBuilderGetQueryParameters(
77-
select=["id", "displayName", "accountEnabled"]
77+
select=[
78+
"id",
79+
"displayName",
80+
"accountEnabled",
81+
"signInActivity",
82+
]
7883
)
7984
)
8085
for tenant, client in self.clients.items():
@@ -87,6 +92,16 @@ async def _get_users(self):
8792
try:
8893
while users_response:
8994
for user in getattr(users_response, "value", []) or []:
95+
sign_in_activity = getattr(user, "sign_in_activity", None)
96+
last_sign_in = (
97+
getattr(
98+
sign_in_activity,
99+
"last_sign_in_date_time",
100+
None,
101+
)
102+
if sign_in_activity
103+
else None
104+
)
90105
users[tenant].update(
91106
{
92107
user.id: User(
@@ -98,6 +113,7 @@ async def _get_users(self):
98113
account_enabled=getattr(
99114
user, "account_enabled", True
100115
),
116+
last_sign_in=last_sign_in,
101117
)
102118
}
103119
)
@@ -556,6 +572,7 @@ class User(BaseModel):
556572
name: str
557573
is_mfa_capable: bool = False
558574
account_enabled: bool = True
575+
last_sign_in: Optional[datetime] = None
559576

560577

561578
class DefaultUserRolePermissions(BaseModel):

prowler/providers/azure/services/entra/entra_user_with_recent_sign_in/__init__.py

Whitespace-only changes.
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
{
2+
"Provider": "azure",
3+
"CheckID": "entra_user_with_recent_sign_in",
4+
"CheckTitle": "Enabled user has signed in within the last 90 days",
5+
"CheckType": [],
6+
"ServiceName": "entra",
7+
"SubServiceName": "",
8+
"ResourceIdTemplate": "",
9+
"Severity": "medium",
10+
"ResourceType": "NotDefined",
11+
"ResourceGroup": "IAM",
12+
"Description": "**Microsoft Entra ID** enabled user accounts are evaluated for **recent sign-in activity**. Accounts that have not signed in for more than 90 days are flagged as stale. Stale accounts indicate orphaned identities that may have been abandoned after personnel changes, project completions, or role transitions without proper deprovisioning.",
13+
"Risk": "Stale accounts retain **role assignments** and **group memberships**. Attackers target dormant accounts via **credential stuffing** and **password spraying** because owners are unlikely to notice anomalous activity. Compromise enables **lateral movement**, **data exfiltration**, and **persistence** while evading detection tuned to active users.",
14+
"RelatedUrl": "",
15+
"AdditionalURLs": [
16+
"https://learn.microsoft.com/en-us/graph/api/resources/signinactivity",
17+
"https://learn.microsoft.com/en-us/entra/identity/monitoring-health/concept-sign-ins"
18+
],
19+
"Remediation": {
20+
"Code": {
21+
"CLI": "az rest --method patch --url https://graph.microsoft.com/v1.0/users/<user_id> --body '{\"accountEnabled\":false}'",
22+
"NativeIaC": "",
23+
"Other": "1. Sign in to Microsoft Entra admin center\n2. Go to Identity > Users > All users\n3. Add filter: Sign-in activity > Last interactive sign-in date is before <90 days ago>\n4. Review each stale account with the account owner or manager\n5. Disable accounts confirmed as no longer needed\n6. After a grace period, delete disabled accounts\n7. Establish a recurring access review to automate this process",
24+
"Terraform": ""
25+
},
26+
"Recommendation": {
27+
"Text": "Implement **automated access reviews** in Entra ID to periodically review and remove stale accounts. Configure reviews to run quarterly, targeting all users or specific groups. Set auto-apply to disable accounts that are not confirmed by reviewers. For immediate remediation, filter users by last sign-in date and disable accounts inactive for more than 90 days after confirming with managers.",
28+
"Url": "https://hub.prowler.com/check/entra_user_with_recent_sign_in"
29+
}
30+
},
31+
"Categories": [
32+
"identity-access"
33+
],
34+
"DependsOn": [],
35+
"RelatedTo": [],
36+
"Notes": "The signInActivity resource requires Microsoft Entra ID P1 or P2 license. Tenants without this license will not have sign-in activity data available, and all users will be reported as never having signed in."
37+
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
from datetime import datetime, timezone
2+
3+
from prowler.lib.check.models import Check, Check_Report_Azure
4+
from prowler.providers.azure.services.entra.entra_client import entra_client
5+
6+
STALE_THRESHOLD_DAYS = 90
7+
8+
9+
class entra_user_with_recent_sign_in(Check):
10+
"""
11+
Ensure enabled Entra ID users have signed in within the last 90 days.
12+
13+
This check evaluates each enabled user's last interactive sign-in to detect stale or dormant accounts that should be reviewed or deprovisioned. Sign-in activity requires Entra ID P1/P2 licensing.
14+
15+
- PASS: The enabled user signed in within the last 90 days.
16+
- FAIL: The enabled user has not signed in for more than 90 days, or has never signed in.
17+
- FAIL (tenant-level): No sign-in activity data is available for any enabled user, indicating missing P1/P2 licensing or Graph permissions (reported once instead of flagging every user).
18+
"""
19+
20+
def execute(self) -> Check_Report_Azure:
21+
findings = []
22+
23+
for tenant_domain, users in entra_client.users.items():
24+
enabled_users = {k: v for k, v in users.items() if v.account_enabled}
25+
26+
if not enabled_users:
27+
continue
28+
29+
# If all enabled users are missing sign-in data, avoid claiming
30+
# they never signed in. This usually indicates missing telemetry,
31+
# often due to licensing or Graph permission limitations.
32+
all_null = all(u.last_sign_in is None for u in enabled_users.values())
33+
if all_null:
34+
first_user = next(iter(enabled_users.values()))
35+
report = Check_Report_Azure(
36+
metadata=self.metadata(), resource=first_user
37+
)
38+
report.subscription = f"Tenant: {tenant_domain}"
39+
report.resource_name = "Sign-in Activity Data"
40+
count = len(enabled_users)
41+
noun = "user" if count == 1 else "users"
42+
report.status = "FAIL"
43+
report.status_extended = (
44+
f"No sign-in activity data available for any of the "
45+
f"{count} enabled {noun}. This likely means the tenant "
46+
f"is missing Entra ID P1/P2 licensing or the required "
47+
f"Graph permissions to read sign-in activity."
48+
)
49+
findings.append(report)
50+
continue
51+
52+
for user_domain_name, user in enabled_users.items():
53+
report = Check_Report_Azure(metadata=self.metadata(), resource=user)
54+
report.subscription = f"Tenant: {tenant_domain}"
55+
56+
if user.last_sign_in is None:
57+
report.status = "FAIL"
58+
report.status_extended = f"User {user.name} has never signed in."
59+
else:
60+
last = user.last_sign_in
61+
if last.tzinfo is None:
62+
last = last.replace(tzinfo=timezone.utc)
63+
days_since = (datetime.now(timezone.utc) - last).days
64+
if days_since > STALE_THRESHOLD_DAYS:
65+
report.status = "FAIL"
66+
report.status_extended = (
67+
f"User {user.name} has not signed in for {days_since} days."
68+
)
69+
else:
70+
report.status = "PASS"
71+
report.status_extended = (
72+
f"User {user.name} signed in {days_since} days ago."
73+
)
74+
75+
findings.append(report)
76+
77+
return findings

0 commit comments

Comments
 (0)