mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-07-04 19:21:51 +00:00
feat(aws): add iam_user_access_not_stale_to_sagemaker security check (#11000)
Co-authored-by: Hugo P.Brito <hugopbrito@Mac.home>
This commit is contained in:
committed by
GitHub
parent
fc7fbddfe7
commit
0b26c1a39c
@@ -56,6 +56,7 @@ The following list includes all the AWS checks with configurable variables that
|
||||
| `elb_is_in_multiple_az` | `elb_min_azs` | Integer |
|
||||
| `elbv2_is_in_multiple_az` | `elbv2_min_azs` | Integer |
|
||||
| `guardduty_is_enabled` | `mute_non_default_regions` | Boolean |
|
||||
| `iam_user_access_not_stale_to_sagemaker` | `max_unused_sagemaker_access_days` | Integer |
|
||||
| `iam_user_accesskey_unused` | `max_unused_access_keys_days` | Integer |
|
||||
| `iam_user_console_access_unused` | `max_console_access_days` | Integer |
|
||||
| `organizations_delegated_administrators` | `organizations_trusted_delegated_administrators` | List of Strings |
|
||||
@@ -186,6 +187,8 @@ aws:
|
||||
max_unused_access_keys_days: 45
|
||||
# aws.iam_user_console_access_unused --> CIS recommends 45 days
|
||||
max_console_access_days: 45
|
||||
# aws.iam_user_access_not_stale_to_sagemaker --> default 90 days
|
||||
max_unused_sagemaker_access_days: 90
|
||||
|
||||
# AWS EC2 Configuration
|
||||
# aws.ec2_elastic_ip_shodan
|
||||
|
||||
@@ -2,6 +2,14 @@
|
||||
|
||||
All notable changes to the **Prowler SDK** are documented in this file.
|
||||
|
||||
## [5.27.0] (Prowler UNRELEASED)
|
||||
|
||||
### 🚀 Added
|
||||
|
||||
- `iam_user_access_not_stale_to_sagemaker` check for aws provider with configurable `max_unused_sagemaker_access_days` (default 90) [(#11000)](https://github.com/prowler-cloud/prowler/pull/11000)
|
||||
|
||||
---
|
||||
|
||||
## [5.26.0] (Prowler v5.26.0)
|
||||
|
||||
### 🚀 Added
|
||||
|
||||
@@ -5288,6 +5288,7 @@
|
||||
"cognito_user_pool_blocks_compromised_credentials_sign_in_attempts",
|
||||
"iam_role_access_not_stale_to_bedrock",
|
||||
"iam_user_access_not_stale_to_bedrock",
|
||||
"iam_user_access_not_stale_to_sagemaker",
|
||||
"iam_user_accesskey_unused",
|
||||
"iam_user_console_access_unused",
|
||||
"secretsmanager_secret_unused"
|
||||
@@ -6359,6 +6360,7 @@
|
||||
"iam_rotate_access_key_90_days",
|
||||
"iam_role_access_not_stale_to_bedrock",
|
||||
"iam_user_access_not_stale_to_bedrock",
|
||||
"iam_user_access_not_stale_to_sagemaker",
|
||||
"iam_user_accesskey_unused",
|
||||
"iam_user_administrator_access_policy",
|
||||
"iam_user_console_access_unused",
|
||||
|
||||
@@ -3101,6 +3101,7 @@
|
||||
"Checks": [
|
||||
"iam_role_access_not_stale_to_bedrock",
|
||||
"iam_user_access_not_stale_to_bedrock",
|
||||
"iam_user_access_not_stale_to_sagemaker",
|
||||
"iam_user_accesskey_unused",
|
||||
"iam_user_console_access_unused",
|
||||
"iam_user_two_active_access_key"
|
||||
@@ -3443,6 +3444,7 @@
|
||||
"Checks": [
|
||||
"iam_role_access_not_stale_to_bedrock",
|
||||
"iam_user_access_not_stale_to_bedrock",
|
||||
"iam_user_access_not_stale_to_sagemaker",
|
||||
"iam_user_accesskey_unused",
|
||||
"iam_user_console_access_unused",
|
||||
"iam_user_no_setup_initial_access_key"
|
||||
@@ -3552,6 +3554,7 @@
|
||||
"Checks": [
|
||||
"iam_role_access_not_stale_to_bedrock",
|
||||
"iam_user_access_not_stale_to_bedrock",
|
||||
"iam_user_access_not_stale_to_sagemaker",
|
||||
"iam_user_accesskey_unused",
|
||||
"iam_user_console_access_unused",
|
||||
"iam_rotate_access_key_90_days",
|
||||
|
||||
@@ -544,6 +544,7 @@
|
||||
"Checks": [
|
||||
"iam_role_access_not_stale_to_bedrock",
|
||||
"iam_user_access_not_stale_to_bedrock",
|
||||
"iam_user_access_not_stale_to_sagemaker",
|
||||
"iam_user_accesskey_unused",
|
||||
"iam_user_console_access_unused"
|
||||
]
|
||||
|
||||
@@ -109,6 +109,7 @@
|
||||
"iam_rotate_access_key_90_days",
|
||||
"iam_role_access_not_stale_to_bedrock",
|
||||
"iam_user_access_not_stale_to_bedrock",
|
||||
"iam_user_access_not_stale_to_sagemaker",
|
||||
"iam_user_accesskey_unused",
|
||||
"iam_user_console_access_unused",
|
||||
"iam_user_hardware_mfa_enabled",
|
||||
@@ -325,6 +326,7 @@
|
||||
"iam_rotate_access_key_90_days",
|
||||
"iam_role_access_not_stale_to_bedrock",
|
||||
"iam_user_access_not_stale_to_bedrock",
|
||||
"iam_user_access_not_stale_to_sagemaker",
|
||||
"iam_user_accesskey_unused",
|
||||
"iam_user_console_access_unused",
|
||||
"organizations_delegated_administrators"
|
||||
|
||||
@@ -39,6 +39,7 @@
|
||||
"iam_user_hardware_mfa_enabled",
|
||||
"iam_role_access_not_stale_to_bedrock",
|
||||
"iam_user_access_not_stale_to_bedrock",
|
||||
"iam_user_access_not_stale_to_sagemaker",
|
||||
"iam_user_accesskey_unused",
|
||||
"iam_user_console_access_unused",
|
||||
"rds_instance_integration_cloudwatch_logs",
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
"iam_user_mfa_enabled_console_access",
|
||||
"iam_role_access_not_stale_to_bedrock",
|
||||
"iam_user_access_not_stale_to_bedrock",
|
||||
"iam_user_access_not_stale_to_sagemaker",
|
||||
"iam_user_accesskey_unused",
|
||||
"iam_user_console_access_unused",
|
||||
"securityhub_enabled"
|
||||
@@ -109,6 +110,7 @@
|
||||
"iam_user_mfa_enabled_console_access",
|
||||
"iam_role_access_not_stale_to_bedrock",
|
||||
"iam_user_access_not_stale_to_bedrock",
|
||||
"iam_user_access_not_stale_to_sagemaker",
|
||||
"iam_user_accesskey_unused",
|
||||
"iam_user_console_access_unused"
|
||||
]
|
||||
@@ -165,6 +167,7 @@
|
||||
"iam_user_mfa_enabled_console_access",
|
||||
"iam_role_access_not_stale_to_bedrock",
|
||||
"iam_user_access_not_stale_to_bedrock",
|
||||
"iam_user_access_not_stale_to_sagemaker",
|
||||
"iam_user_accesskey_unused",
|
||||
"iam_user_console_access_unused"
|
||||
]
|
||||
@@ -185,6 +188,7 @@
|
||||
"iam_password_policy_minimum_length_14",
|
||||
"iam_role_access_not_stale_to_bedrock",
|
||||
"iam_user_access_not_stale_to_bedrock",
|
||||
"iam_user_access_not_stale_to_sagemaker",
|
||||
"iam_user_accesskey_unused",
|
||||
"iam_user_console_access_unused"
|
||||
]
|
||||
@@ -320,6 +324,7 @@
|
||||
"iam_no_root_access_key",
|
||||
"iam_role_access_not_stale_to_bedrock",
|
||||
"iam_user_access_not_stale_to_bedrock",
|
||||
"iam_user_access_not_stale_to_sagemaker",
|
||||
"iam_user_accesskey_unused",
|
||||
"iam_user_console_access_unused",
|
||||
"awslambda_function_not_publicly_accessible",
|
||||
|
||||
@@ -869,6 +869,7 @@
|
||||
"Checks": [
|
||||
"iam_role_access_not_stale_to_bedrock",
|
||||
"iam_user_access_not_stale_to_bedrock",
|
||||
"iam_user_access_not_stale_to_sagemaker",
|
||||
"iam_user_accesskey_unused",
|
||||
"iam_user_console_access_unused"
|
||||
]
|
||||
|
||||
@@ -247,6 +247,7 @@
|
||||
"iam_root_mfa_enabled",
|
||||
"iam_role_access_not_stale_to_bedrock",
|
||||
"iam_user_access_not_stale_to_bedrock",
|
||||
"iam_user_access_not_stale_to_sagemaker",
|
||||
"iam_rotate_access_key_90_days",
|
||||
"iam_user_accesskey_unused",
|
||||
"iam_user_console_access_unused",
|
||||
|
||||
@@ -171,6 +171,7 @@
|
||||
"iam_no_expired_server_certificates_stored",
|
||||
"iam_role_access_not_stale_to_bedrock",
|
||||
"iam_user_access_not_stale_to_bedrock",
|
||||
"iam_user_access_not_stale_to_sagemaker",
|
||||
"iam_user_accesskey_unused",
|
||||
"iam_user_console_access_unused",
|
||||
"iam_no_root_access_key",
|
||||
|
||||
@@ -1913,6 +1913,7 @@
|
||||
"Checks": [
|
||||
"iam_role_access_not_stale_to_bedrock",
|
||||
"iam_user_access_not_stale_to_bedrock",
|
||||
"iam_user_access_not_stale_to_sagemaker",
|
||||
"iam_user_accesskey_unused",
|
||||
"iam_user_console_access_unused"
|
||||
],
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
"iam_user_mfa_enabled_console_access",
|
||||
"iam_role_access_not_stale_to_bedrock",
|
||||
"iam_user_access_not_stale_to_bedrock",
|
||||
"iam_user_access_not_stale_to_sagemaker",
|
||||
"iam_user_accesskey_unused",
|
||||
"iam_user_console_access_unused",
|
||||
"awslambda_function_not_publicly_accessible",
|
||||
@@ -76,6 +77,7 @@
|
||||
"iam_user_mfa_enabled_console_access",
|
||||
"iam_role_access_not_stale_to_bedrock",
|
||||
"iam_user_access_not_stale_to_bedrock",
|
||||
"iam_user_access_not_stale_to_sagemaker",
|
||||
"iam_user_accesskey_unused",
|
||||
"iam_user_console_access_unused",
|
||||
"awslambda_function_not_publicly_accessible",
|
||||
@@ -164,6 +166,7 @@
|
||||
"iam_no_root_access_key",
|
||||
"iam_role_access_not_stale_to_bedrock",
|
||||
"iam_user_access_not_stale_to_bedrock",
|
||||
"iam_user_access_not_stale_to_sagemaker",
|
||||
"iam_user_accesskey_unused",
|
||||
"iam_user_console_access_unused"
|
||||
]
|
||||
@@ -589,6 +592,7 @@
|
||||
"iam_password_policy_expires_passwords_within_90_days_or_less",
|
||||
"iam_role_access_not_stale_to_bedrock",
|
||||
"iam_user_access_not_stale_to_bedrock",
|
||||
"iam_user_access_not_stale_to_sagemaker",
|
||||
"iam_user_accesskey_unused",
|
||||
"iam_user_console_access_unused"
|
||||
]
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
"iam_rotate_access_key_90_days",
|
||||
"iam_role_access_not_stale_to_bedrock",
|
||||
"iam_user_access_not_stale_to_bedrock",
|
||||
"iam_user_access_not_stale_to_sagemaker",
|
||||
"iam_user_accesskey_unused",
|
||||
"iam_user_console_access_unused",
|
||||
"securityhub_enabled"
|
||||
@@ -43,6 +44,7 @@
|
||||
"Checks": [
|
||||
"iam_role_access_not_stale_to_bedrock",
|
||||
"iam_user_access_not_stale_to_bedrock",
|
||||
"iam_user_access_not_stale_to_sagemaker",
|
||||
"iam_user_accesskey_unused",
|
||||
"iam_user_console_access_unused"
|
||||
]
|
||||
@@ -116,6 +118,7 @@
|
||||
"iam_rotate_access_key_90_days",
|
||||
"iam_role_access_not_stale_to_bedrock",
|
||||
"iam_user_access_not_stale_to_bedrock",
|
||||
"iam_user_access_not_stale_to_sagemaker",
|
||||
"iam_user_accesskey_unused",
|
||||
"iam_user_console_access_unused",
|
||||
"rds_instance_integration_cloudwatch_logs",
|
||||
@@ -240,6 +243,7 @@
|
||||
"iam_no_root_access_key",
|
||||
"iam_role_access_not_stale_to_bedrock",
|
||||
"iam_user_access_not_stale_to_bedrock",
|
||||
"iam_user_access_not_stale_to_sagemaker",
|
||||
"iam_user_accesskey_unused",
|
||||
"iam_user_console_access_unused",
|
||||
"awslambda_function_url_public",
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
"iam_user_mfa_enabled_console_access",
|
||||
"iam_role_access_not_stale_to_bedrock",
|
||||
"iam_user_access_not_stale_to_bedrock",
|
||||
"iam_user_access_not_stale_to_sagemaker",
|
||||
"iam_user_accesskey_unused",
|
||||
"iam_user_console_access_unused",
|
||||
"secretsmanager_automatic_rotation_enabled"
|
||||
@@ -53,6 +54,7 @@
|
||||
"iam_password_policy_minimum_length_14",
|
||||
"iam_role_access_not_stale_to_bedrock",
|
||||
"iam_user_access_not_stale_to_bedrock",
|
||||
"iam_user_access_not_stale_to_sagemaker",
|
||||
"iam_user_accesskey_unused",
|
||||
"iam_user_console_access_unused"
|
||||
]
|
||||
@@ -74,6 +76,7 @@
|
||||
"iam_password_policy_minimum_length_14",
|
||||
"iam_role_access_not_stale_to_bedrock",
|
||||
"iam_user_access_not_stale_to_bedrock",
|
||||
"iam_user_access_not_stale_to_sagemaker",
|
||||
"iam_user_accesskey_unused",
|
||||
"iam_user_console_access_unused"
|
||||
]
|
||||
@@ -95,6 +98,7 @@
|
||||
"iam_password_policy_minimum_length_14",
|
||||
"iam_role_access_not_stale_to_bedrock",
|
||||
"iam_user_access_not_stale_to_bedrock",
|
||||
"iam_user_access_not_stale_to_sagemaker",
|
||||
"iam_user_accesskey_unused",
|
||||
"iam_user_console_access_unused"
|
||||
]
|
||||
@@ -116,6 +120,7 @@
|
||||
"iam_password_policy_minimum_length_14",
|
||||
"iam_role_access_not_stale_to_bedrock",
|
||||
"iam_user_access_not_stale_to_bedrock",
|
||||
"iam_user_access_not_stale_to_sagemaker",
|
||||
"iam_user_accesskey_unused",
|
||||
"iam_user_console_access_unused"
|
||||
]
|
||||
@@ -136,6 +141,7 @@
|
||||
"iam_password_policy_minimum_length_14",
|
||||
"iam_role_access_not_stale_to_bedrock",
|
||||
"iam_user_access_not_stale_to_bedrock",
|
||||
"iam_user_access_not_stale_to_sagemaker",
|
||||
"iam_user_accesskey_unused",
|
||||
"iam_user_console_access_unused"
|
||||
]
|
||||
@@ -247,6 +253,7 @@
|
||||
"Checks": [
|
||||
"iam_role_access_not_stale_to_bedrock",
|
||||
"iam_user_access_not_stale_to_bedrock",
|
||||
"iam_user_access_not_stale_to_sagemaker",
|
||||
"iam_user_accesskey_unused",
|
||||
"iam_user_console_access_unused"
|
||||
]
|
||||
@@ -285,6 +292,7 @@
|
||||
"Checks": [
|
||||
"iam_role_access_not_stale_to_bedrock",
|
||||
"iam_user_access_not_stale_to_bedrock",
|
||||
"iam_user_access_not_stale_to_sagemaker",
|
||||
"iam_user_accesskey_unused",
|
||||
"iam_user_console_access_unused"
|
||||
]
|
||||
@@ -861,6 +869,7 @@
|
||||
"iam_user_mfa_enabled_console_access",
|
||||
"iam_role_access_not_stale_to_bedrock",
|
||||
"iam_user_access_not_stale_to_bedrock",
|
||||
"iam_user_access_not_stale_to_sagemaker",
|
||||
"iam_user_accesskey_unused",
|
||||
"iam_user_console_access_unused",
|
||||
"secretsmanager_automatic_rotation_enabled"
|
||||
@@ -1199,6 +1208,7 @@
|
||||
"iam_no_root_access_key",
|
||||
"iam_role_access_not_stale_to_bedrock",
|
||||
"iam_user_access_not_stale_to_bedrock",
|
||||
"iam_user_access_not_stale_to_sagemaker",
|
||||
"iam_user_accesskey_unused",
|
||||
"iam_user_console_access_unused",
|
||||
"awslambda_function_not_publicly_accessible",
|
||||
|
||||
@@ -577,6 +577,7 @@
|
||||
"iam_rotate_access_key_90_days",
|
||||
"iam_role_access_not_stale_to_bedrock",
|
||||
"iam_user_access_not_stale_to_bedrock",
|
||||
"iam_user_access_not_stale_to_sagemaker",
|
||||
"iam_user_accesskey_unused",
|
||||
"iam_user_console_access_unused",
|
||||
"secretsmanager_automatic_rotation_enabled"
|
||||
@@ -638,6 +639,7 @@
|
||||
"iam_no_root_access_key",
|
||||
"iam_role_access_not_stale_to_bedrock",
|
||||
"iam_user_access_not_stale_to_bedrock",
|
||||
"iam_user_access_not_stale_to_sagemaker",
|
||||
"iam_user_accesskey_unused",
|
||||
"iam_user_console_access_unused"
|
||||
]
|
||||
|
||||
@@ -707,6 +707,7 @@
|
||||
"iam_user_console_access_unused",
|
||||
"iam_role_access_not_stale_to_bedrock",
|
||||
"iam_user_access_not_stale_to_bedrock",
|
||||
"iam_user_access_not_stale_to_sagemaker",
|
||||
"iam_user_accesskey_unused",
|
||||
"iam_user_two_active_access_key",
|
||||
"iam_root_credentials_management_enabled",
|
||||
|
||||
@@ -1563,6 +1563,7 @@
|
||||
"Checks": [
|
||||
"iam_role_access_not_stale_to_bedrock",
|
||||
"iam_user_access_not_stale_to_bedrock",
|
||||
"iam_user_access_not_stale_to_sagemaker",
|
||||
"iam_password_policy_reuse_24",
|
||||
"iam_user_accesskey_unused",
|
||||
"iam_user_console_access_unused"
|
||||
|
||||
@@ -295,6 +295,7 @@
|
||||
"Checks": [
|
||||
"iam_role_access_not_stale_to_bedrock",
|
||||
"iam_user_access_not_stale_to_bedrock",
|
||||
"iam_user_access_not_stale_to_sagemaker",
|
||||
"iam_user_accesskey_unused",
|
||||
"iam_user_console_access_unused",
|
||||
"iam_no_expired_server_certificates_stored"
|
||||
@@ -340,6 +341,7 @@
|
||||
"iam_rotate_access_key_90_days",
|
||||
"iam_role_access_not_stale_to_bedrock",
|
||||
"iam_user_access_not_stale_to_bedrock",
|
||||
"iam_user_access_not_stale_to_sagemaker",
|
||||
"iam_user_accesskey_unused",
|
||||
"iam_user_console_access_unused",
|
||||
"accessanalyzer_enabled_without_findings"
|
||||
|
||||
@@ -26,6 +26,8 @@ aws:
|
||||
max_unused_access_keys_days: 45
|
||||
# aws.iam_user_console_access_unused --> CIS recommends 45 days
|
||||
max_console_access_days: 45
|
||||
# aws.iam_user_access_not_stale_to_sagemaker --> default 90 days
|
||||
max_unused_sagemaker_access_days: 90
|
||||
|
||||
# AWS EC2 Configuration
|
||||
# aws.ec2_elastic_ip_shodan
|
||||
|
||||
+65
-24
@@ -1,9 +1,10 @@
|
||||
from datetime import datetime, timezone
|
||||
from typing import Optional
|
||||
|
||||
from dateutil.parser import parse
|
||||
|
||||
from prowler.lib.check.models import Check, Check_Report_AWS
|
||||
from prowler.providers.aws.services.iam.iam_client import iam_client
|
||||
from prowler.providers.aws.services.iam.lib.policy import (
|
||||
evaluate_bedrock_staleness,
|
||||
find_bedrock_service,
|
||||
)
|
||||
|
||||
|
||||
class iam_role_access_not_stale_to_bedrock(Check):
|
||||
@@ -33,33 +34,73 @@ class iam_role_access_not_stale_to_bedrock(Check):
|
||||
"max_unused_bedrock_access_days", 60
|
||||
)
|
||||
|
||||
for (
|
||||
role_data,
|
||||
last_accessed_services,
|
||||
) in iam_client.role_last_accessed_services.items():
|
||||
role_name = role_data[0]
|
||||
role_arn = role_data[1]
|
||||
if iam_client.roles is None:
|
||||
return findings
|
||||
|
||||
bedrock_service = find_bedrock_service(last_accessed_services)
|
||||
for role in iam_client.roles:
|
||||
last_accessed_services = iam_client.role_last_accessed_services.get(
|
||||
(role.name, role.arn), []
|
||||
)
|
||||
bedrock_service = self._find_bedrock_service(last_accessed_services)
|
||||
if bedrock_service is None:
|
||||
continue
|
||||
|
||||
report = Check_Report_AWS(
|
||||
metadata=self.metadata(),
|
||||
resource={"name": role_name, "arn": role_arn},
|
||||
)
|
||||
report.resource_id = role_name
|
||||
report.resource_arn = role_arn
|
||||
report = Check_Report_AWS(metadata=self.metadata(), resource=role)
|
||||
report.region = iam_client.region
|
||||
if iam_client.roles is not None:
|
||||
for iam_role in iam_client.roles:
|
||||
if iam_role.arn == role_arn:
|
||||
report.resource_tags = iam_role.tags
|
||||
break
|
||||
|
||||
evaluate_bedrock_staleness(
|
||||
report, bedrock_service, max_unused_bedrock_days, role_name, "Role"
|
||||
self._evaluate_bedrock_staleness(
|
||||
report,
|
||||
bedrock_service,
|
||||
max_unused_bedrock_days,
|
||||
role.name,
|
||||
"Role",
|
||||
)
|
||||
findings.append(report)
|
||||
|
||||
return findings
|
||||
|
||||
@staticmethod
|
||||
def _find_bedrock_service(
|
||||
last_accessed_services: list[dict],
|
||||
) -> Optional[dict]:
|
||||
"""Return the Bedrock entry from a service last accessed list."""
|
||||
for service in last_accessed_services:
|
||||
if service.get("ServiceNamespace") == "bedrock":
|
||||
return service
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def _evaluate_bedrock_staleness(
|
||||
report: Check_Report_AWS,
|
||||
bedrock_service: dict,
|
||||
max_days: int,
|
||||
identity_name: str,
|
||||
identity_type: str,
|
||||
) -> None:
|
||||
"""Populate a check report based on Bedrock access recency."""
|
||||
last_authenticated = bedrock_service.get("LastAuthenticated")
|
||||
if last_authenticated is None:
|
||||
report.status = "FAIL"
|
||||
report.status_extended = (
|
||||
f"IAM {identity_type} {identity_name} has Bedrock permissions "
|
||||
f"but has never used them."
|
||||
)
|
||||
return
|
||||
|
||||
if isinstance(last_authenticated, str):
|
||||
last_authenticated = parse(last_authenticated)
|
||||
|
||||
days_since_access = (datetime.now(timezone.utc) - last_authenticated).days
|
||||
|
||||
if days_since_access > max_days:
|
||||
report.status = "FAIL"
|
||||
report.status_extended = (
|
||||
f"IAM {identity_type} {identity_name} has not accessed Bedrock "
|
||||
f"in {days_since_access} days (threshold: {max_days} days)."
|
||||
)
|
||||
else:
|
||||
report.status = "PASS"
|
||||
report.status_extended = (
|
||||
f"IAM {identity_type} {identity_name} accessed Bedrock "
|
||||
f"{days_since_access} days ago (threshold: {max_days} days)."
|
||||
)
|
||||
|
||||
+63
-24
@@ -1,9 +1,10 @@
|
||||
from datetime import datetime, timezone
|
||||
from typing import Optional
|
||||
|
||||
from dateutil.parser import parse
|
||||
|
||||
from prowler.lib.check.models import Check, Check_Report_AWS
|
||||
from prowler.providers.aws.services.iam.iam_client import iam_client
|
||||
from prowler.providers.aws.services.iam.lib.policy import (
|
||||
evaluate_bedrock_staleness,
|
||||
find_bedrock_service,
|
||||
)
|
||||
|
||||
|
||||
class iam_user_access_not_stale_to_bedrock(Check):
|
||||
@@ -33,32 +34,70 @@ class iam_user_access_not_stale_to_bedrock(Check):
|
||||
"max_unused_bedrock_access_days", 60
|
||||
)
|
||||
|
||||
for (
|
||||
user_data,
|
||||
last_accessed_services,
|
||||
) in iam_client.last_accessed_services.items():
|
||||
user_name = user_data[0]
|
||||
user_arn = user_data[1]
|
||||
|
||||
bedrock_service = find_bedrock_service(last_accessed_services)
|
||||
for user in iam_client.users:
|
||||
last_accessed_services = iam_client.last_accessed_services.get(
|
||||
(user.name, user.arn), []
|
||||
)
|
||||
bedrock_service = self._find_bedrock_service(last_accessed_services)
|
||||
if bedrock_service is None:
|
||||
continue
|
||||
|
||||
report = Check_Report_AWS(
|
||||
metadata=self.metadata(),
|
||||
resource={"name": user_name, "arn": user_arn},
|
||||
)
|
||||
report.resource_id = user_name
|
||||
report.resource_arn = user_arn
|
||||
report = Check_Report_AWS(metadata=self.metadata(), resource=user)
|
||||
report.region = iam_client.region
|
||||
for iam_user in iam_client.users:
|
||||
if iam_user.arn == user_arn:
|
||||
report.resource_tags = iam_user.tags
|
||||
break
|
||||
|
||||
evaluate_bedrock_staleness(
|
||||
report, bedrock_service, max_unused_bedrock_days, user_name, "User"
|
||||
self._evaluate_bedrock_staleness(
|
||||
report,
|
||||
bedrock_service,
|
||||
max_unused_bedrock_days,
|
||||
user.name,
|
||||
"User",
|
||||
)
|
||||
findings.append(report)
|
||||
|
||||
return findings
|
||||
|
||||
@staticmethod
|
||||
def _find_bedrock_service(
|
||||
last_accessed_services: list[dict],
|
||||
) -> Optional[dict]:
|
||||
"""Return the Bedrock entry from a service last accessed list."""
|
||||
for service in last_accessed_services:
|
||||
if service.get("ServiceNamespace") == "bedrock":
|
||||
return service
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def _evaluate_bedrock_staleness(
|
||||
report: Check_Report_AWS,
|
||||
bedrock_service: dict,
|
||||
max_days: int,
|
||||
identity_name: str,
|
||||
identity_type: str,
|
||||
) -> None:
|
||||
"""Populate a check report based on Bedrock access recency."""
|
||||
last_authenticated = bedrock_service.get("LastAuthenticated")
|
||||
if last_authenticated is None:
|
||||
report.status = "FAIL"
|
||||
report.status_extended = (
|
||||
f"IAM {identity_type} {identity_name} has Bedrock permissions "
|
||||
f"but has never used them."
|
||||
)
|
||||
return
|
||||
|
||||
if isinstance(last_authenticated, str):
|
||||
last_authenticated = parse(last_authenticated)
|
||||
|
||||
days_since_access = (datetime.now(timezone.utc) - last_authenticated).days
|
||||
|
||||
if days_since_access > max_days:
|
||||
report.status = "FAIL"
|
||||
report.status_extended = (
|
||||
f"IAM {identity_type} {identity_name} has not accessed Bedrock "
|
||||
f"in {days_since_access} days (threshold: {max_days} days)."
|
||||
)
|
||||
else:
|
||||
report.status = "PASS"
|
||||
report.status_extended = (
|
||||
f"IAM {identity_type} {identity_name} accessed Bedrock "
|
||||
f"{days_since_access} days ago (threshold: {max_days} days)."
|
||||
)
|
||||
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"Provider": "aws",
|
||||
"CheckID": "iam_user_access_not_stale_to_sagemaker",
|
||||
"CheckTitle": "Regular SageMaker access ensures IAM users retain only actively used permissions",
|
||||
"CheckType": [
|
||||
"Software and Configuration Checks/AWS Security Best Practices"
|
||||
],
|
||||
"ServiceName": "iam",
|
||||
"SubServiceName": "",
|
||||
"ResourceIdTemplate": "",
|
||||
"Severity": "medium",
|
||||
"ResourceType": "AwsIamUser",
|
||||
"ResourceGroup": "IAM",
|
||||
"Description": "IAM users granted **SageMaker** permissions are evaluated for recent service usage.\n\nUsers whose last SageMaker access exceeds the configured threshold (default **90 days**) or that have **never** accessed SageMaker are flagged, indicating stale permissions that should be reviewed.",
|
||||
"Risk": "Stale SageMaker permissions widen the **blast radius** of a credential compromise.\n\nAn attacker who gains access to a user with unused SageMaker permissions can access ML training data, models, endpoints, and notebooks — all without triggering expected usage patterns. Removing or scoping down stale permissions enforces least privilege and limits blast radius.",
|
||||
"RelatedUrl": "",
|
||||
"AdditionalURLs": [
|
||||
"https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_access-advisor.html",
|
||||
"https://docs.aws.amazon.com/sagemaker/latest/dg/security-iam.html",
|
||||
"https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html#remove-credentials"
|
||||
],
|
||||
"Remediation": {
|
||||
"Code": {
|
||||
"CLI": "",
|
||||
"NativeIaC": "",
|
||||
"Other": "1. Open the IAM console and select the user\n2. Review the **Access Advisor** tab to confirm SageMaker has not been accessed recently\n3. Remove or detach any policies granting SageMaker permissions that are no longer needed\n4. If the user still requires SageMaker access, verify usage and reduce scope to least privilege",
|
||||
"Terraform": ""
|
||||
},
|
||||
"Recommendation": {
|
||||
"Text": "Apply the **principle of least privilege** by regularly reviewing IAM Access Advisor data and revoking SageMaker permissions that are no longer actively used.\n\nEstablish a periodic access review process and automate alerts for stale permissions to maintain a minimal attack surface.",
|
||||
"Url": "https://hub.prowler.com/check/iam_user_access_not_stale_to_sagemaker"
|
||||
}
|
||||
},
|
||||
"Categories": [
|
||||
"identity-access"
|
||||
],
|
||||
"DependsOn": [],
|
||||
"RelatedTo": [
|
||||
"iam_user_access_not_stale_to_bedrock"
|
||||
],
|
||||
"Notes": "The staleness threshold is configurable via the `max_unused_sagemaker_access_days` audit config key (default: 90 days)."
|
||||
}
|
||||
+103
@@ -0,0 +1,103 @@
|
||||
from datetime import datetime, timezone
|
||||
from typing import Optional
|
||||
|
||||
from dateutil.parser import parse
|
||||
|
||||
from prowler.lib.check.models import Check, Check_Report_AWS
|
||||
from prowler.providers.aws.services.iam.iam_client import iam_client
|
||||
|
||||
|
||||
class iam_user_access_not_stale_to_sagemaker(Check):
|
||||
"""Detect IAM users with stale SageMaker permissions.
|
||||
|
||||
This check evaluates whether IAM users with SageMaker service permissions
|
||||
have actively used those permissions within the configured threshold
|
||||
(default 90 days).
|
||||
|
||||
- PASS: The user has accessed SageMaker within the allowed period.
|
||||
- FAIL: The user has SageMaker permissions but has not used them within
|
||||
the allowed period or has never used them.
|
||||
"""
|
||||
|
||||
def execute(self) -> list[Check_Report_AWS]:
|
||||
"""Execute the SageMaker access staleness check for IAM users.
|
||||
|
||||
Iterates over IAM users, inspecting service last accessed data for
|
||||
the ``sagemaker`` namespace. Users whose last SageMaker access exceeds
|
||||
the configured threshold are reported as non-compliant.
|
||||
|
||||
Returns:
|
||||
A list of reports containing the result of the check.
|
||||
"""
|
||||
findings = []
|
||||
max_unused_sagemaker_days = iam_client.audit_config.get(
|
||||
"max_unused_sagemaker_access_days", 90
|
||||
)
|
||||
|
||||
for user in iam_client.users:
|
||||
last_accessed_services = iam_client.last_accessed_services.get(
|
||||
(user.name, user.arn), []
|
||||
)
|
||||
sagemaker_service = self._find_sagemaker_service(last_accessed_services)
|
||||
if sagemaker_service is None:
|
||||
continue
|
||||
|
||||
report = Check_Report_AWS(metadata=self.metadata(), resource=user)
|
||||
report.region = iam_client.region
|
||||
|
||||
self._evaluate_sagemaker_staleness(
|
||||
report,
|
||||
sagemaker_service,
|
||||
max_unused_sagemaker_days,
|
||||
user.name,
|
||||
"User",
|
||||
)
|
||||
findings.append(report)
|
||||
|
||||
return findings
|
||||
|
||||
@staticmethod
|
||||
def _find_sagemaker_service(
|
||||
last_accessed_services: list[dict],
|
||||
) -> Optional[dict]:
|
||||
"""Return the SageMaker entry from a service last accessed list."""
|
||||
for service in last_accessed_services:
|
||||
if service.get("ServiceNamespace") == "sagemaker":
|
||||
return service
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def _evaluate_sagemaker_staleness(
|
||||
report: Check_Report_AWS,
|
||||
sagemaker_service: dict,
|
||||
max_days: int,
|
||||
identity_name: str,
|
||||
identity_type: str,
|
||||
) -> None:
|
||||
"""Populate a check report based on SageMaker access recency."""
|
||||
last_authenticated = sagemaker_service.get("LastAuthenticated")
|
||||
if last_authenticated is None:
|
||||
report.status = "FAIL"
|
||||
report.status_extended = (
|
||||
f"IAM {identity_type} {identity_name} has SageMaker permissions "
|
||||
f"but has never used them."
|
||||
)
|
||||
return
|
||||
|
||||
if isinstance(last_authenticated, str):
|
||||
last_authenticated = parse(last_authenticated)
|
||||
|
||||
days_since_access = (datetime.now(timezone.utc) - last_authenticated).days
|
||||
|
||||
if days_since_access > max_days:
|
||||
report.status = "FAIL"
|
||||
report.status_extended = (
|
||||
f"IAM {identity_type} {identity_name} has not accessed SageMaker "
|
||||
f"in {days_since_access} days (threshold: {max_days} days)."
|
||||
)
|
||||
else:
|
||||
report.status = "PASS"
|
||||
report.status_extended = (
|
||||
f"IAM {identity_type} {identity_name} accessed SageMaker "
|
||||
f"{days_since_access} days ago (threshold: {max_days} days)."
|
||||
)
|
||||
@@ -1,12 +1,9 @@
|
||||
import re
|
||||
from datetime import datetime, timezone
|
||||
from ipaddress import ip_address, ip_network
|
||||
from typing import Optional, Tuple
|
||||
|
||||
from dateutil.parser import parse
|
||||
from py_iam_expand.actions import InvalidActionHandling, expand_actions
|
||||
|
||||
from prowler.lib.check.models import Check_Report_AWS
|
||||
from prowler.lib.logger import logger
|
||||
from prowler.providers.aws.aws_provider import read_aws_regions_file
|
||||
|
||||
@@ -1121,47 +1118,3 @@ def has_codebuild_trusted_principal(trust_policy: dict) -> bool:
|
||||
)
|
||||
for s in statements
|
||||
)
|
||||
|
||||
|
||||
def find_bedrock_service(last_accessed_services: list[dict]) -> Optional[dict]:
|
||||
"""Return the Bedrock entry from a service last accessed list."""
|
||||
for service in last_accessed_services:
|
||||
if service.get("ServiceNamespace") == "bedrock":
|
||||
return service
|
||||
return None
|
||||
|
||||
|
||||
def evaluate_bedrock_staleness(
|
||||
report: Check_Report_AWS,
|
||||
bedrock_service: dict,
|
||||
max_days: int,
|
||||
identity_name: str,
|
||||
identity_type: str,
|
||||
) -> None:
|
||||
"""Populate a check report based on Bedrock access recency."""
|
||||
last_authenticated = bedrock_service.get("LastAuthenticated")
|
||||
if last_authenticated is None:
|
||||
report.status = "FAIL"
|
||||
report.status_extended = (
|
||||
f"IAM {identity_type} {identity_name} has Bedrock permissions "
|
||||
f"but has never used them."
|
||||
)
|
||||
return
|
||||
|
||||
if isinstance(last_authenticated, str):
|
||||
last_authenticated = parse(last_authenticated)
|
||||
|
||||
days_since_access = (datetime.now(timezone.utc) - last_authenticated).days
|
||||
|
||||
if days_since_access > max_days:
|
||||
report.status = "FAIL"
|
||||
report.status_extended = (
|
||||
f"IAM {identity_type} {identity_name} has not accessed Bedrock "
|
||||
f"in {days_since_access} days (threshold: {max_days} days)."
|
||||
)
|
||||
else:
|
||||
report.status = "PASS"
|
||||
report.status_extended = (
|
||||
f"IAM {identity_type} {identity_name} accessed Bedrock "
|
||||
f"{days_since_access} days ago (threshold: {max_days} days)."
|
||||
)
|
||||
|
||||
@@ -17,7 +17,7 @@ MOCK_OLD_PROWLER_VERSION = "0.0.0"
|
||||
MOCK_PROWLER_MASTER_VERSION = "3.4.0"
|
||||
|
||||
|
||||
def mock_prowler_get_latest_release(_, **kwargs):
|
||||
def mock_prowler_get_latest_release(_, **_kwargs):
|
||||
"""Mock requests.get() to get the Prowler latest release"""
|
||||
response = Response()
|
||||
response._content = b'[{"name":"3.3.0"}]'
|
||||
@@ -75,6 +75,7 @@ config_aws = {
|
||||
"mute_non_default_regions": False,
|
||||
"max_unused_access_keys_days": 45,
|
||||
"max_console_access_days": 45,
|
||||
"max_unused_sagemaker_access_days": 90,
|
||||
"shodan_api_key": None,
|
||||
"max_security_group_rules": 50,
|
||||
"max_ec2_instance_age_in_days": 180,
|
||||
|
||||
@@ -20,6 +20,8 @@ aws:
|
||||
max_unused_access_keys_days: 45
|
||||
# aws.iam_user_console_access_unused --> CIS recommends 45 days
|
||||
max_console_access_days: 45
|
||||
# aws.iam_user_access_not_stale_to_sagemaker --> default 90 days
|
||||
max_unused_sagemaker_access_days: 90
|
||||
|
||||
# AWS EC2 Configuration
|
||||
# aws.ec2_elastic_ip_shodan
|
||||
|
||||
+252
@@ -190,6 +190,258 @@ class Test_iam_role_access_not_stale_to_bedrock:
|
||||
assert "has never used them" in result[0].status_extended
|
||||
assert "Role" in result[0].status_extended
|
||||
|
||||
@mock_aws
|
||||
def test_no_roles_listed(self):
|
||||
"""No findings when iam.roles is None (short-circuit)."""
|
||||
from prowler.providers.aws.services.iam.iam_service import IAM
|
||||
|
||||
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
|
||||
iam = IAM(aws_provider)
|
||||
iam.roles = None
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=aws_provider,
|
||||
),
|
||||
mock.patch(f"{CHECK_MODULE}.iam_client", new=iam),
|
||||
):
|
||||
from prowler.providers.aws.services.iam.iam_role_access_not_stale_to_bedrock.iam_role_access_not_stale_to_bedrock import (
|
||||
iam_role_access_not_stale_to_bedrock,
|
||||
)
|
||||
|
||||
check = iam_role_access_not_stale_to_bedrock()
|
||||
assert check.execute() == []
|
||||
|
||||
@mock_aws
|
||||
def test_role_without_bedrock_permissions(self):
|
||||
"""Role with non-Bedrock services is skipped."""
|
||||
from prowler.providers.aws.services.iam.iam_service import IAM, Role
|
||||
|
||||
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
|
||||
iam = IAM(aws_provider)
|
||||
|
||||
mock_role = Role(
|
||||
name=IAM_ROLE_NAME,
|
||||
arn=IAM_ROLE_ARN,
|
||||
assume_role_policy={},
|
||||
is_service_role=False,
|
||||
attached_policies=[],
|
||||
inline_policies=[],
|
||||
)
|
||||
iam.roles = [mock_role]
|
||||
iam.role_last_accessed_services = {
|
||||
ROLE_DATA: [
|
||||
{"ServiceNamespace": "iam", "ServiceName": "IAM"},
|
||||
{"ServiceNamespace": "s3", "ServiceName": "S3"},
|
||||
]
|
||||
}
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=aws_provider,
|
||||
),
|
||||
mock.patch(f"{CHECK_MODULE}.iam_client", new=iam),
|
||||
):
|
||||
from prowler.providers.aws.services.iam.iam_role_access_not_stale_to_bedrock.iam_role_access_not_stale_to_bedrock import (
|
||||
iam_role_access_not_stale_to_bedrock,
|
||||
)
|
||||
|
||||
check = iam_role_access_not_stale_to_bedrock()
|
||||
assert len(check.execute()) == 0
|
||||
|
||||
@mock_aws
|
||||
def test_role_bedrock_access_with_string_date(self):
|
||||
"""PASS when LastAuthenticated is an ISO string instead of a datetime object."""
|
||||
from prowler.providers.aws.services.iam.iam_service import IAM, Role
|
||||
|
||||
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
|
||||
iam = IAM(aws_provider)
|
||||
|
||||
mock_role = Role(
|
||||
name=IAM_ROLE_NAME,
|
||||
arn=IAM_ROLE_ARN,
|
||||
assume_role_policy={},
|
||||
is_service_role=False,
|
||||
attached_policies=[],
|
||||
inline_policies=[],
|
||||
)
|
||||
iam.roles = [mock_role]
|
||||
|
||||
last_access_date = datetime.now(timezone.utc) - timedelta(days=5)
|
||||
iam.role_last_accessed_services = {
|
||||
ROLE_DATA: [
|
||||
{
|
||||
"ServiceNamespace": "bedrock",
|
||||
"ServiceName": "Amazon Bedrock",
|
||||
"LastAuthenticated": last_access_date.isoformat(),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=aws_provider,
|
||||
),
|
||||
mock.patch(f"{CHECK_MODULE}.iam_client", new=iam),
|
||||
):
|
||||
from prowler.providers.aws.services.iam.iam_role_access_not_stale_to_bedrock.iam_role_access_not_stale_to_bedrock import (
|
||||
iam_role_access_not_stale_to_bedrock,
|
||||
)
|
||||
|
||||
check = iam_role_access_not_stale_to_bedrock()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
|
||||
@mock_aws
|
||||
def test_role_bedrock_access_at_exact_threshold(self):
|
||||
"""PASS when role accessed Bedrock exactly at the 60-day boundary."""
|
||||
from prowler.providers.aws.services.iam.iam_service import IAM, Role
|
||||
|
||||
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
|
||||
iam = IAM(aws_provider)
|
||||
|
||||
mock_role = Role(
|
||||
name=IAM_ROLE_NAME,
|
||||
arn=IAM_ROLE_ARN,
|
||||
assume_role_policy={},
|
||||
is_service_role=False,
|
||||
attached_policies=[],
|
||||
inline_policies=[],
|
||||
)
|
||||
iam.roles = [mock_role]
|
||||
|
||||
last_authenticated = datetime.now(timezone.utc) - timedelta(days=60)
|
||||
iam.role_last_accessed_services = {
|
||||
ROLE_DATA: [
|
||||
{
|
||||
"ServiceNamespace": "bedrock",
|
||||
"ServiceName": "Amazon Bedrock",
|
||||
"LastAuthenticated": last_authenticated,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=aws_provider,
|
||||
),
|
||||
mock.patch(f"{CHECK_MODULE}.iam_client", new=iam),
|
||||
):
|
||||
from prowler.providers.aws.services.iam.iam_role_access_not_stale_to_bedrock.iam_role_access_not_stale_to_bedrock import (
|
||||
iam_role_access_not_stale_to_bedrock,
|
||||
)
|
||||
|
||||
check = iam_role_access_not_stale_to_bedrock()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
assert "60 days ago" in result[0].status_extended
|
||||
assert "threshold: 60 days" in result[0].status_extended
|
||||
|
||||
@mock_aws
|
||||
def test_role_bedrock_access_one_day_over_threshold(self):
|
||||
"""FAIL when role accessed Bedrock 61 days ago."""
|
||||
from prowler.providers.aws.services.iam.iam_service import IAM, Role
|
||||
|
||||
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
|
||||
iam = IAM(aws_provider)
|
||||
|
||||
mock_role = Role(
|
||||
name=IAM_ROLE_NAME,
|
||||
arn=IAM_ROLE_ARN,
|
||||
assume_role_policy={},
|
||||
is_service_role=False,
|
||||
attached_policies=[],
|
||||
inline_policies=[],
|
||||
)
|
||||
iam.roles = [mock_role]
|
||||
|
||||
last_authenticated = datetime.now(timezone.utc) - timedelta(days=61)
|
||||
iam.role_last_accessed_services = {
|
||||
ROLE_DATA: [
|
||||
{
|
||||
"ServiceNamespace": "bedrock",
|
||||
"ServiceName": "Amazon Bedrock",
|
||||
"LastAuthenticated": last_authenticated,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=aws_provider,
|
||||
),
|
||||
mock.patch(f"{CHECK_MODULE}.iam_client", new=iam),
|
||||
):
|
||||
from prowler.providers.aws.services.iam.iam_role_access_not_stale_to_bedrock.iam_role_access_not_stale_to_bedrock import (
|
||||
iam_role_access_not_stale_to_bedrock,
|
||||
)
|
||||
|
||||
check = iam_role_access_not_stale_to_bedrock()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert "61 days" in result[0].status_extended
|
||||
assert "threshold: 60 days" in result[0].status_extended
|
||||
|
||||
@mock_aws
|
||||
def test_custom_threshold_via_audit_config(self):
|
||||
"""Custom threshold from audit_config is respected for roles."""
|
||||
from prowler.providers.aws.services.iam.iam_service import IAM, Role
|
||||
|
||||
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
|
||||
iam = IAM(aws_provider)
|
||||
iam.audit_config = {"max_unused_bedrock_access_days": 30}
|
||||
|
||||
mock_role = Role(
|
||||
name=IAM_ROLE_NAME,
|
||||
arn=IAM_ROLE_ARN,
|
||||
assume_role_policy={},
|
||||
is_service_role=False,
|
||||
attached_policies=[],
|
||||
inline_policies=[],
|
||||
)
|
||||
iam.roles = [mock_role]
|
||||
|
||||
last_authenticated = datetime.now(timezone.utc) - timedelta(days=45)
|
||||
iam.role_last_accessed_services = {
|
||||
ROLE_DATA: [
|
||||
{
|
||||
"ServiceNamespace": "bedrock",
|
||||
"ServiceName": "Amazon Bedrock",
|
||||
"LastAuthenticated": last_authenticated,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=aws_provider,
|
||||
),
|
||||
mock.patch(f"{CHECK_MODULE}.iam_client", new=iam),
|
||||
):
|
||||
from prowler.providers.aws.services.iam.iam_role_access_not_stale_to_bedrock.iam_role_access_not_stale_to_bedrock import (
|
||||
iam_role_access_not_stale_to_bedrock,
|
||||
)
|
||||
|
||||
check = iam_role_access_not_stale_to_bedrock()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert "45 days" in result[0].status_extended
|
||||
assert "threshold: 30 days" in result[0].status_extended
|
||||
|
||||
@mock_aws
|
||||
def test_role_tags_are_populated(self):
|
||||
"""Verify resource_tags are populated from the role object."""
|
||||
|
||||
+623
@@ -0,0 +1,623 @@
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from unittest import mock
|
||||
|
||||
from moto import mock_aws
|
||||
|
||||
from tests.providers.aws.utils import (
|
||||
AWS_ACCOUNT_NUMBER,
|
||||
AWS_REGION_US_EAST_1,
|
||||
set_mocked_aws_provider,
|
||||
)
|
||||
|
||||
IAM_USER_NAME = "test-user"
|
||||
IAM_USER_ARN = f"arn:aws:iam::{AWS_ACCOUNT_NUMBER}:user/{IAM_USER_NAME}"
|
||||
USER_DATA = (IAM_USER_NAME, IAM_USER_ARN)
|
||||
|
||||
CHECK_MODULE = (
|
||||
"prowler.providers.aws.services.iam."
|
||||
"iam_user_access_not_stale_to_sagemaker.iam_user_access_not_stale_to_sagemaker"
|
||||
)
|
||||
|
||||
|
||||
class Test_iam_user_access_not_stale_to_sagemaker:
|
||||
@mock_aws
|
||||
def test_no_users_with_sagemaker_permissions(self):
|
||||
"""No findings when no users have SageMaker in last accessed services."""
|
||||
from prowler.providers.aws.services.iam.iam_service import IAM
|
||||
|
||||
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
|
||||
iam = IAM(aws_provider)
|
||||
iam.last_accessed_services = {}
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=aws_provider,
|
||||
),
|
||||
mock.patch(f"{CHECK_MODULE}.iam_client", new=iam),
|
||||
):
|
||||
from prowler.providers.aws.services.iam.iam_user_access_not_stale_to_sagemaker.iam_user_access_not_stale_to_sagemaker import (
|
||||
iam_user_access_not_stale_to_sagemaker,
|
||||
)
|
||||
|
||||
check = iam_user_access_not_stale_to_sagemaker()
|
||||
assert len(check.execute()) == 0
|
||||
|
||||
@mock_aws
|
||||
def test_user_without_sagemaker_permissions(self):
|
||||
"""User with non-SageMaker services is skipped."""
|
||||
from prowler.providers.aws.services.iam.iam_service import IAM, User
|
||||
|
||||
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
|
||||
iam = IAM(aws_provider)
|
||||
|
||||
mock_user = User(
|
||||
name=IAM_USER_NAME,
|
||||
arn=IAM_USER_ARN,
|
||||
attached_policies=[],
|
||||
inline_policies=[],
|
||||
)
|
||||
iam.users = [mock_user]
|
||||
iam.last_accessed_services = {
|
||||
USER_DATA: [
|
||||
{"ServiceNamespace": "iam", "ServiceName": "IAM"},
|
||||
{"ServiceNamespace": "s3", "ServiceName": "S3"},
|
||||
]
|
||||
}
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=aws_provider,
|
||||
),
|
||||
mock.patch(f"{CHECK_MODULE}.iam_client", new=iam),
|
||||
):
|
||||
from prowler.providers.aws.services.iam.iam_user_access_not_stale_to_sagemaker.iam_user_access_not_stale_to_sagemaker import (
|
||||
iam_user_access_not_stale_to_sagemaker,
|
||||
)
|
||||
|
||||
check = iam_user_access_not_stale_to_sagemaker()
|
||||
assert len(check.execute()) == 0
|
||||
|
||||
@mock_aws
|
||||
def test_user_sagemaker_access_recent(self):
|
||||
"""PASS when user accessed SageMaker within the threshold."""
|
||||
from prowler.providers.aws.services.iam.iam_service import IAM, User
|
||||
|
||||
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
|
||||
iam = IAM(aws_provider)
|
||||
|
||||
mock_user = User(
|
||||
name=IAM_USER_NAME,
|
||||
arn=IAM_USER_ARN,
|
||||
attached_policies=[],
|
||||
inline_policies=[],
|
||||
)
|
||||
iam.users = [mock_user]
|
||||
|
||||
last_authenticated = datetime.now(timezone.utc) - timedelta(days=10)
|
||||
iam.last_accessed_services = {
|
||||
USER_DATA: [
|
||||
{
|
||||
"ServiceNamespace": "sagemaker",
|
||||
"ServiceName": "Amazon SageMaker",
|
||||
"LastAuthenticated": last_authenticated,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=aws_provider,
|
||||
),
|
||||
mock.patch(f"{CHECK_MODULE}.iam_client", new=iam),
|
||||
):
|
||||
from prowler.providers.aws.services.iam.iam_user_access_not_stale_to_sagemaker.iam_user_access_not_stale_to_sagemaker import (
|
||||
iam_user_access_not_stale_to_sagemaker,
|
||||
)
|
||||
|
||||
check = iam_user_access_not_stale_to_sagemaker()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
assert "accessed SageMaker" in result[0].status_extended
|
||||
assert IAM_USER_NAME in result[0].status_extended
|
||||
assert result[0].resource_id == IAM_USER_NAME
|
||||
assert result[0].resource_arn == IAM_USER_ARN
|
||||
assert result[0].region == AWS_REGION_US_EAST_1
|
||||
|
||||
@mock_aws
|
||||
def test_user_sagemaker_access_stale(self):
|
||||
"""FAIL when user last accessed SageMaker more than 90 days ago."""
|
||||
from prowler.providers.aws.services.iam.iam_service import IAM, User
|
||||
|
||||
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
|
||||
iam = IAM(aws_provider)
|
||||
|
||||
mock_user = User(
|
||||
name=IAM_USER_NAME,
|
||||
arn=IAM_USER_ARN,
|
||||
attached_policies=[],
|
||||
inline_policies=[],
|
||||
)
|
||||
iam.users = [mock_user]
|
||||
|
||||
last_authenticated = datetime.now(timezone.utc) - timedelta(days=120)
|
||||
iam.last_accessed_services = {
|
||||
USER_DATA: [
|
||||
{
|
||||
"ServiceNamespace": "sagemaker",
|
||||
"ServiceName": "Amazon SageMaker",
|
||||
"LastAuthenticated": last_authenticated,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=aws_provider,
|
||||
),
|
||||
mock.patch(f"{CHECK_MODULE}.iam_client", new=iam),
|
||||
):
|
||||
from prowler.providers.aws.services.iam.iam_user_access_not_stale_to_sagemaker.iam_user_access_not_stale_to_sagemaker import (
|
||||
iam_user_access_not_stale_to_sagemaker,
|
||||
)
|
||||
|
||||
check = iam_user_access_not_stale_to_sagemaker()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert "has not accessed SageMaker" in result[0].status_extended
|
||||
assert "120 days" in result[0].status_extended
|
||||
assert IAM_USER_NAME in result[0].status_extended
|
||||
|
||||
@mock_aws
|
||||
def test_user_sagemaker_never_accessed(self):
|
||||
"""FAIL when user has SageMaker permissions but has never used them."""
|
||||
from prowler.providers.aws.services.iam.iam_service import IAM, User
|
||||
|
||||
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
|
||||
iam = IAM(aws_provider)
|
||||
|
||||
mock_user = User(
|
||||
name=IAM_USER_NAME,
|
||||
arn=IAM_USER_ARN,
|
||||
attached_policies=[],
|
||||
inline_policies=[],
|
||||
)
|
||||
iam.users = [mock_user]
|
||||
|
||||
iam.last_accessed_services = {
|
||||
USER_DATA: [
|
||||
{
|
||||
"ServiceNamespace": "sagemaker",
|
||||
"ServiceName": "Amazon SageMaker",
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=aws_provider,
|
||||
),
|
||||
mock.patch(f"{CHECK_MODULE}.iam_client", new=iam),
|
||||
):
|
||||
from prowler.providers.aws.services.iam.iam_user_access_not_stale_to_sagemaker.iam_user_access_not_stale_to_sagemaker import (
|
||||
iam_user_access_not_stale_to_sagemaker,
|
||||
)
|
||||
|
||||
check = iam_user_access_not_stale_to_sagemaker()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert "has never used them" in result[0].status_extended
|
||||
|
||||
@mock_aws
|
||||
def test_custom_threshold_via_audit_config(self):
|
||||
"""Custom threshold from audit_config is respected."""
|
||||
from prowler.providers.aws.services.iam.iam_service import IAM, User
|
||||
|
||||
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
|
||||
iam = IAM(aws_provider)
|
||||
iam.audit_config = {"max_unused_sagemaker_access_days": 30}
|
||||
|
||||
mock_user = User(
|
||||
name=IAM_USER_NAME,
|
||||
arn=IAM_USER_ARN,
|
||||
attached_policies=[],
|
||||
inline_policies=[],
|
||||
)
|
||||
iam.users = [mock_user]
|
||||
|
||||
last_authenticated = datetime.now(timezone.utc) - timedelta(days=45)
|
||||
iam.last_accessed_services = {
|
||||
USER_DATA: [
|
||||
{
|
||||
"ServiceNamespace": "sagemaker",
|
||||
"ServiceName": "Amazon SageMaker",
|
||||
"LastAuthenticated": last_authenticated,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=aws_provider,
|
||||
),
|
||||
mock.patch(f"{CHECK_MODULE}.iam_client", new=iam),
|
||||
):
|
||||
from prowler.providers.aws.services.iam.iam_user_access_not_stale_to_sagemaker.iam_user_access_not_stale_to_sagemaker import (
|
||||
iam_user_access_not_stale_to_sagemaker,
|
||||
)
|
||||
|
||||
check = iam_user_access_not_stale_to_sagemaker()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert "45 days" in result[0].status_extended
|
||||
assert "threshold: 30 days" in result[0].status_extended
|
||||
|
||||
@mock_aws
|
||||
def test_user_sagemaker_access_at_exact_threshold(self):
|
||||
"""PASS when user accessed SageMaker exactly at the 90-day boundary."""
|
||||
from prowler.providers.aws.services.iam.iam_service import IAM, User
|
||||
|
||||
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
|
||||
iam = IAM(aws_provider)
|
||||
|
||||
mock_user = User(
|
||||
name=IAM_USER_NAME,
|
||||
arn=IAM_USER_ARN,
|
||||
attached_policies=[],
|
||||
inline_policies=[],
|
||||
)
|
||||
iam.users = [mock_user]
|
||||
|
||||
last_authenticated = datetime.now(timezone.utc) - timedelta(days=90)
|
||||
iam.last_accessed_services = {
|
||||
USER_DATA: [
|
||||
{
|
||||
"ServiceNamespace": "sagemaker",
|
||||
"ServiceName": "Amazon SageMaker",
|
||||
"LastAuthenticated": last_authenticated,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=aws_provider,
|
||||
),
|
||||
mock.patch(f"{CHECK_MODULE}.iam_client", new=iam),
|
||||
):
|
||||
from prowler.providers.aws.services.iam.iam_user_access_not_stale_to_sagemaker.iam_user_access_not_stale_to_sagemaker import (
|
||||
iam_user_access_not_stale_to_sagemaker,
|
||||
)
|
||||
|
||||
check = iam_user_access_not_stale_to_sagemaker()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
assert "90 days ago" in result[0].status_extended
|
||||
assert "threshold: 90 days" in result[0].status_extended
|
||||
|
||||
@mock_aws
|
||||
def test_user_sagemaker_access_one_day_over_threshold(self):
|
||||
"""FAIL when user accessed SageMaker 91 days ago."""
|
||||
from prowler.providers.aws.services.iam.iam_service import IAM, User
|
||||
|
||||
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
|
||||
iam = IAM(aws_provider)
|
||||
|
||||
mock_user = User(
|
||||
name=IAM_USER_NAME,
|
||||
arn=IAM_USER_ARN,
|
||||
attached_policies=[],
|
||||
inline_policies=[],
|
||||
)
|
||||
iam.users = [mock_user]
|
||||
|
||||
last_authenticated = datetime.now(timezone.utc) - timedelta(days=91)
|
||||
iam.last_accessed_services = {
|
||||
USER_DATA: [
|
||||
{
|
||||
"ServiceNamespace": "sagemaker",
|
||||
"ServiceName": "Amazon SageMaker",
|
||||
"LastAuthenticated": last_authenticated,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=aws_provider,
|
||||
),
|
||||
mock.patch(f"{CHECK_MODULE}.iam_client", new=iam),
|
||||
):
|
||||
from prowler.providers.aws.services.iam.iam_user_access_not_stale_to_sagemaker.iam_user_access_not_stale_to_sagemaker import (
|
||||
iam_user_access_not_stale_to_sagemaker,
|
||||
)
|
||||
|
||||
check = iam_user_access_not_stale_to_sagemaker()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "FAIL"
|
||||
assert "91 days" in result[0].status_extended
|
||||
assert "threshold: 90 days" in result[0].status_extended
|
||||
|
||||
@mock_aws
|
||||
def test_user_sagemaker_access_with_string_date(self):
|
||||
"""PASS when LastAuthenticated is an ISO string instead of a datetime object."""
|
||||
from prowler.providers.aws.services.iam.iam_service import IAM, User
|
||||
|
||||
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
|
||||
iam = IAM(aws_provider)
|
||||
|
||||
mock_user = User(
|
||||
name=IAM_USER_NAME,
|
||||
arn=IAM_USER_ARN,
|
||||
attached_policies=[],
|
||||
inline_policies=[],
|
||||
)
|
||||
iam.users = [mock_user]
|
||||
|
||||
last_access_date = datetime.now(timezone.utc) - timedelta(days=5)
|
||||
iam.last_accessed_services = {
|
||||
USER_DATA: [
|
||||
{
|
||||
"ServiceNamespace": "sagemaker",
|
||||
"ServiceName": "Amazon SageMaker",
|
||||
"LastAuthenticated": last_access_date.isoformat(),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=aws_provider,
|
||||
),
|
||||
mock.patch(f"{CHECK_MODULE}.iam_client", new=iam),
|
||||
):
|
||||
from prowler.providers.aws.services.iam.iam_user_access_not_stale_to_sagemaker.iam_user_access_not_stale_to_sagemaker import (
|
||||
iam_user_access_not_stale_to_sagemaker,
|
||||
)
|
||||
|
||||
check = iam_user_access_not_stale_to_sagemaker()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
|
||||
@mock_aws
|
||||
def test_user_tags_are_populated(self):
|
||||
"""Verify resource_tags are populated from the user object."""
|
||||
from prowler.providers.aws.services.iam.iam_service import IAM, User
|
||||
|
||||
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
|
||||
iam = IAM(aws_provider)
|
||||
|
||||
user_tags = [{"Key": "Environment", "Value": "production"}]
|
||||
mock_user = User(
|
||||
name=IAM_USER_NAME,
|
||||
arn=IAM_USER_ARN,
|
||||
attached_policies=[],
|
||||
inline_policies=[],
|
||||
tags=user_tags,
|
||||
)
|
||||
iam.users = [mock_user]
|
||||
|
||||
last_authenticated = datetime.now(timezone.utc) - timedelta(days=10)
|
||||
iam.last_accessed_services = {
|
||||
USER_DATA: [
|
||||
{
|
||||
"ServiceNamespace": "sagemaker",
|
||||
"ServiceName": "Amazon SageMaker",
|
||||
"LastAuthenticated": last_authenticated,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=aws_provider,
|
||||
),
|
||||
mock.patch(f"{CHECK_MODULE}.iam_client", new=iam),
|
||||
):
|
||||
from prowler.providers.aws.services.iam.iam_user_access_not_stale_to_sagemaker.iam_user_access_not_stale_to_sagemaker import (
|
||||
iam_user_access_not_stale_to_sagemaker,
|
||||
)
|
||||
|
||||
check = iam_user_access_not_stale_to_sagemaker()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
assert result[0].resource_tags == user_tags
|
||||
|
||||
@mock_aws
|
||||
def test_multiple_users_mixed_results(self):
|
||||
"""Multiple users: one recent (PASS), one stale (FAIL), one without SageMaker (skipped)."""
|
||||
from prowler.providers.aws.services.iam.iam_service import IAM, User
|
||||
|
||||
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
|
||||
iam = IAM(aws_provider)
|
||||
|
||||
recent_user_name = "recent-user"
|
||||
recent_user_arn = f"arn:aws:iam::{AWS_ACCOUNT_NUMBER}:user/{recent_user_name}"
|
||||
stale_user_name = "stale-user"
|
||||
stale_user_arn = f"arn:aws:iam::{AWS_ACCOUNT_NUMBER}:user/{stale_user_name}"
|
||||
no_sagemaker_user_name = "no-sagemaker-user"
|
||||
no_sagemaker_user_arn = (
|
||||
f"arn:aws:iam::{AWS_ACCOUNT_NUMBER}:user/{no_sagemaker_user_name}"
|
||||
)
|
||||
|
||||
iam.users = [
|
||||
User(
|
||||
name=recent_user_name,
|
||||
arn=recent_user_arn,
|
||||
attached_policies=[],
|
||||
inline_policies=[],
|
||||
),
|
||||
User(
|
||||
name=stale_user_name,
|
||||
arn=stale_user_arn,
|
||||
attached_policies=[],
|
||||
inline_policies=[],
|
||||
),
|
||||
User(
|
||||
name=no_sagemaker_user_name,
|
||||
arn=no_sagemaker_user_arn,
|
||||
attached_policies=[],
|
||||
inline_policies=[],
|
||||
),
|
||||
]
|
||||
|
||||
recent_access = datetime.now(timezone.utc) - timedelta(days=10)
|
||||
stale_access = datetime.now(timezone.utc) - timedelta(days=120)
|
||||
iam.last_accessed_services = {
|
||||
(recent_user_name, recent_user_arn): [
|
||||
{
|
||||
"ServiceNamespace": "sagemaker",
|
||||
"ServiceName": "Amazon SageMaker",
|
||||
"LastAuthenticated": recent_access,
|
||||
},
|
||||
],
|
||||
(stale_user_name, stale_user_arn): [
|
||||
{
|
||||
"ServiceNamespace": "sagemaker",
|
||||
"ServiceName": "Amazon SageMaker",
|
||||
"LastAuthenticated": stale_access,
|
||||
},
|
||||
],
|
||||
(no_sagemaker_user_name, no_sagemaker_user_arn): [
|
||||
{"ServiceNamespace": "s3", "ServiceName": "S3"},
|
||||
],
|
||||
}
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=aws_provider,
|
||||
),
|
||||
mock.patch(f"{CHECK_MODULE}.iam_client", new=iam),
|
||||
):
|
||||
from prowler.providers.aws.services.iam.iam_user_access_not_stale_to_sagemaker.iam_user_access_not_stale_to_sagemaker import (
|
||||
iam_user_access_not_stale_to_sagemaker,
|
||||
)
|
||||
|
||||
check = iam_user_access_not_stale_to_sagemaker()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 2
|
||||
results_by_id = {r.resource_id: r for r in result}
|
||||
|
||||
assert results_by_id[recent_user_name].status == "PASS"
|
||||
assert (
|
||||
"accessed SageMaker" in results_by_id[recent_user_name].status_extended
|
||||
)
|
||||
|
||||
assert results_by_id[stale_user_name].status == "FAIL"
|
||||
assert (
|
||||
"has not accessed SageMaker"
|
||||
in results_by_id[stale_user_name].status_extended
|
||||
)
|
||||
assert "120 days" in results_by_id[stale_user_name].status_extended
|
||||
|
||||
assert no_sagemaker_user_name not in results_by_id
|
||||
|
||||
@mock_aws
|
||||
def test_user_arn_not_in_users_list(self):
|
||||
"""No findings when last_accessed_services entries do not match any iam.users."""
|
||||
from prowler.providers.aws.services.iam.iam_service import IAM
|
||||
|
||||
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
|
||||
iam = IAM(aws_provider)
|
||||
iam.users = []
|
||||
|
||||
last_authenticated = datetime.now(timezone.utc) - timedelta(days=10)
|
||||
iam.last_accessed_services = {
|
||||
USER_DATA: [
|
||||
{
|
||||
"ServiceNamespace": "sagemaker",
|
||||
"ServiceName": "Amazon SageMaker",
|
||||
"LastAuthenticated": last_authenticated,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=aws_provider,
|
||||
),
|
||||
mock.patch(f"{CHECK_MODULE}.iam_client", new=iam),
|
||||
):
|
||||
from prowler.providers.aws.services.iam.iam_user_access_not_stale_to_sagemaker.iam_user_access_not_stale_to_sagemaker import (
|
||||
iam_user_access_not_stale_to_sagemaker,
|
||||
)
|
||||
|
||||
check = iam_user_access_not_stale_to_sagemaker()
|
||||
assert check.execute() == []
|
||||
|
||||
@mock_aws
|
||||
def test_sagemaker_among_multiple_services(self):
|
||||
"""SageMaker entry is correctly found when mixed with other services."""
|
||||
from prowler.providers.aws.services.iam.iam_service import IAM, User
|
||||
|
||||
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
|
||||
iam = IAM(aws_provider)
|
||||
|
||||
mock_user = User(
|
||||
name=IAM_USER_NAME,
|
||||
arn=IAM_USER_ARN,
|
||||
attached_policies=[],
|
||||
inline_policies=[],
|
||||
)
|
||||
iam.users = [mock_user]
|
||||
|
||||
last_authenticated = datetime.now(timezone.utc) - timedelta(days=15)
|
||||
iam.last_accessed_services = {
|
||||
USER_DATA: [
|
||||
{"ServiceNamespace": "iam", "ServiceName": "IAM"},
|
||||
{"ServiceNamespace": "s3", "ServiceName": "S3"},
|
||||
{
|
||||
"ServiceNamespace": "sagemaker",
|
||||
"ServiceName": "Amazon SageMaker",
|
||||
"LastAuthenticated": last_authenticated,
|
||||
},
|
||||
{"ServiceNamespace": "ec2", "ServiceName": "EC2"},
|
||||
]
|
||||
}
|
||||
|
||||
with (
|
||||
mock.patch(
|
||||
"prowler.providers.common.provider.Provider.get_global_provider",
|
||||
return_value=aws_provider,
|
||||
),
|
||||
mock.patch(f"{CHECK_MODULE}.iam_client", new=iam),
|
||||
):
|
||||
from prowler.providers.aws.services.iam.iam_user_access_not_stale_to_sagemaker.iam_user_access_not_stale_to_sagemaker import (
|
||||
iam_user_access_not_stale_to_sagemaker,
|
||||
)
|
||||
|
||||
check = iam_user_access_not_stale_to_sagemaker()
|
||||
result = check.execute()
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].status == "PASS"
|
||||
assert "accessed SageMaker" in result[0].status_extended
|
||||
assert "15 days ago" in result[0].status_extended
|
||||
Reference in New Issue
Block a user