Compare commits

...

1 Commits

Author SHA1 Message Date
Daniel Barranquero
c097346140 feat(aws): add bedrock_access_not_stale security check
Add new security check bedrock_access_not_stale for aws provider.
Includes check implementation, metadata, and unit tests.
2026-04-01 13:15:43 +02:00
42 changed files with 1107 additions and 0 deletions

View File

@@ -6,6 +6,7 @@ All notable changes to the **Prowler SDK** are documented in this file.
### 🚀 Added
- `bedrock_access_not_stale` check for aws provider [(#10536)](https://github.com/prowler-cloud/prowler/pull/10536)
- `apikeys_api_restricted_with_gemini_api` and `gemini_api_disabled` checks for GCP provider [(#10280)](https://github.com/prowler-cloud/prowler/pull/10280)
- `cloudfront_distributions_logging_enabled` detects Standard Logging v2 via CloudWatch Log Delivery [(#10090)](https://github.com/prowler-cloud/prowler/pull/10090)
- `glue_etl_jobs_no_secrets_in_arguments` check for plaintext secrets in AWS Glue ETL job arguments [(#10368)](https://github.com/prowler-cloud/prowler/pull/10368)

View File

@@ -2542,6 +2542,7 @@
"Name": "Unused IAM user credentials should be removed",
"Description": "This control checks whether your IAM users have passwords or active access keys that have not been used for 90 days.",
"Checks": [
"bedrock_access_not_stale",
"iam_user_accesskey_unused",
"iam_user_console_access_unused"
],
@@ -2630,6 +2631,7 @@
"Name": "IAM user credentials unused for 45 days should be removed",
"Description": "This control checks whether your IAM users have passwords or active access keys that have not been used for 45 days or more. To do so, it checks whether the maxCredentialUsageAge parameter of the AWS Config rule is equal to 45 or more.",
"Checks": [
"bedrock_access_not_stale",
"iam_user_accesskey_unused",
"iam_user_console_access_unused"
],
@@ -2648,6 +2650,7 @@
"Name": "Expired SSL/TLS certificates managed in IAM should be removed",
"Description": "This controls checks whether an active SSL/TLS server certificate that is managed in IAM has expired. The control fails if the expired SSL/TLS server certificate isn't removed.",
"Checks": [
"bedrock_access_not_stale",
"iam_user_accesskey_unused",
"iam_user_console_access_unused"
],

View File

@@ -171,6 +171,7 @@
}
],
"Checks": [
"bedrock_access_not_stale",
"iam_rotate_access_key_90_days",
"iam_user_accesskey_unused",
"iam_user_with_temporary_credentials",
@@ -317,6 +318,7 @@
}
],
"Checks": [
"bedrock_access_not_stale",
"iam_rotate_access_key_90_days",
"iam_user_accesskey_unused",
"iam_user_with_temporary_credentials",

View File

@@ -5282,6 +5282,7 @@
"iam_password_policy_reuse_24",
"cognito_user_pool_blocks_potential_malicious_sign_in_attempts",
"cognito_user_pool_blocks_compromised_credentials_sign_in_attempts",
"bedrock_access_not_stale",
"iam_user_accesskey_unused",
"iam_user_console_access_unused",
"secretsmanager_secret_unused"
@@ -5304,6 +5305,7 @@
"iam_no_root_access_key",
"cloudwatch_log_metric_filter_root_usage",
"iam_user_console_access_unused",
"bedrock_access_not_stale",
"iam_user_accesskey_unused",
"iam_user_with_temporary_credentials",
"iam_password_policy_reuse_24",
@@ -5324,6 +5326,7 @@
],
"Checks": [
"iam_user_console_access_unused",
"bedrock_access_not_stale",
"iam_user_accesskey_unused",
"iam_password_policy_reuse_24",
"iam_user_with_temporary_credentials",
@@ -5537,6 +5540,7 @@
}
],
"Checks": [
"bedrock_access_not_stale",
"iam_user_accesskey_unused",
"iam_user_console_access_unused"
]
@@ -6350,6 +6354,7 @@
"iam_role_administratoraccess_policy",
"iam_role_cross_account_readonlyaccess_policy",
"iam_rotate_access_key_90_days",
"bedrock_access_not_stale",
"iam_user_accesskey_unused",
"iam_user_administrator_access_policy",
"iam_user_console_access_unused",
@@ -6565,6 +6570,7 @@
"bedrock_api_key_no_long_term_credentials",
"iam_no_root_access_key",
"iam_rotate_access_key_90_days",
"bedrock_access_not_stale",
"iam_user_accesskey_unused",
"iam_user_no_setup_initial_access_key",
"iam_user_two_active_access_key",

View File

@@ -72,6 +72,7 @@
"Id": "1.12",
"Description": "Ensure credentials unused for 45 days or greater are disabled",
"Checks": [
"bedrock_access_not_stale",
"iam_user_accesskey_unused",
"iam_user_console_access_unused"
],

View File

@@ -72,6 +72,7 @@
"Id": "1.12",
"Description": "Ensure credentials unused for 45 days or greater are disabled",
"Checks": [
"bedrock_access_not_stale",
"iam_user_accesskey_unused",
"iam_user_console_access_unused"
],

View File

@@ -72,6 +72,7 @@
"Id": "1.12",
"Description": "Ensure credentials unused for 45 days or greater are disabled",
"Checks": [
"bedrock_access_not_stale",
"iam_user_accesskey_unused",
"iam_user_console_access_unused"
],

View File

@@ -72,6 +72,7 @@
"Id": "1.12",
"Description": "Ensure credentials unused for 45 days or greater are disabled",
"Checks": [
"bedrock_access_not_stale",
"iam_user_accesskey_unused",
"iam_user_console_access_unused"
],

View File

@@ -251,6 +251,7 @@
"Id": "1.12",
"Description": "Ensure credentials unused for 45 days or more are disabled",
"Checks": [
"bedrock_access_not_stale",
"iam_user_accesskey_unused",
"iam_user_console_access_unused"
],

View File

@@ -229,6 +229,7 @@
"Id": "1.11",
"Description": "Ensure credentials unused for 45 days or more are disabled",
"Checks": [
"bedrock_access_not_stale",
"iam_user_accesskey_unused",
"iam_user_console_access_unused"
],

View File

@@ -229,6 +229,7 @@
"Id": "2.11",
"Description": "Ensure credentials unused for 45 days or more are disabled",
"Checks": [
"bedrock_access_not_stale",
"iam_user_accesskey_unused",
"iam_user_console_access_unused"
],

View File

@@ -98,6 +98,7 @@
"iam_rotate_access_key_90_days",
"iam_user_mfa_enabled_console_access",
"iam_user_mfa_enabled_console_access",
"bedrock_access_not_stale",
"iam_user_accesskey_unused",
"iam_user_console_access_unused",
"kms_cmk_rotation_enabled",

View File

@@ -3098,6 +3098,7 @@
}
],
"Checks": [
"bedrock_access_not_stale",
"iam_user_accesskey_unused",
"iam_user_console_access_unused",
"iam_user_two_active_access_key"
@@ -3436,6 +3437,7 @@
}
],
"Checks": [
"bedrock_access_not_stale",
"iam_user_accesskey_unused",
"iam_user_console_access_unused",
"iam_user_no_setup_initial_access_key"
@@ -3543,6 +3545,7 @@
}
],
"Checks": [
"bedrock_access_not_stale",
"iam_user_accesskey_unused",
"iam_user_console_access_unused",
"iam_rotate_access_key_90_days",

View File

@@ -345,6 +345,7 @@
}
],
"Checks": [
"bedrock_access_not_stale",
"iam_user_accesskey_unused",
"iam_user_console_access_unused",
"iam_rotate_access_key_90_days"
@@ -373,6 +374,7 @@
],
"Checks": [
"iam_password_policy_expires_passwords_within_90_days_or_less",
"bedrock_access_not_stale",
"iam_user_accesskey_unused",
"iam_user_console_access_unused",
"iam_rotate_access_key_90_days"
@@ -542,6 +544,7 @@
}
],
"Checks": [
"bedrock_access_not_stale",
"iam_user_accesskey_unused",
"iam_user_console_access_unused"
]

View File

@@ -105,6 +105,7 @@
"iam_root_hardware_mfa_enabled",
"iam_root_mfa_enabled",
"iam_rotate_access_key_90_days",
"bedrock_access_not_stale",
"iam_user_accesskey_unused",
"iam_user_console_access_unused",
"iam_user_hardware_mfa_enabled",
@@ -319,6 +320,7 @@
"iam_no_root_access_key",
"iam_policy_attached_only_to_group_or_roles",
"iam_rotate_access_key_90_days",
"bedrock_access_not_stale",
"iam_user_accesskey_unused",
"iam_user_console_access_unused",
"organizations_delegated_administrators"

View File

@@ -37,6 +37,7 @@
"iam_rotate_access_key_90_days",
"iam_user_mfa_enabled_console_access",
"iam_user_hardware_mfa_enabled",
"bedrock_access_not_stale",
"iam_user_accesskey_unused",
"iam_user_console_access_unused",
"rds_instance_integration_cloudwatch_logs",
@@ -66,6 +67,7 @@
"iam_customer_attached_policy_no_administrative_privileges",
"iam_inline_policy_no_administrative_privileges",
"iam_no_root_access_key",
"bedrock_access_not_stale",
"iam_user_accesskey_unused",
"iam_user_console_access_unused",
"awslambda_function_not_publicly_accessible",

View File

@@ -30,6 +30,7 @@
"iam_rotate_access_key_90_days",
"iam_user_mfa_enabled_console_access",
"iam_user_mfa_enabled_console_access",
"bedrock_access_not_stale",
"iam_user_accesskey_unused",
"iam_user_console_access_unused",
"securityhub_enabled"
@@ -105,6 +106,7 @@
"iam_rotate_access_key_90_days",
"iam_user_mfa_enabled_console_access",
"iam_user_mfa_enabled_console_access",
"bedrock_access_not_stale",
"iam_user_accesskey_unused",
"iam_user_console_access_unused"
]
@@ -159,6 +161,7 @@
"iam_rotate_access_key_90_days",
"iam_user_mfa_enabled_console_access",
"iam_user_mfa_enabled_console_access",
"bedrock_access_not_stale",
"iam_user_accesskey_unused",
"iam_user_console_access_unused"
]
@@ -177,6 +180,7 @@
],
"Checks": [
"iam_password_policy_minimum_length_14",
"bedrock_access_not_stale",
"iam_user_accesskey_unused",
"iam_user_console_access_unused"
]
@@ -202,6 +206,7 @@
"iam_customer_attached_policy_no_administrative_privileges",
"iam_inline_policy_no_administrative_privileges",
"iam_no_root_access_key",
"bedrock_access_not_stale",
"iam_user_accesskey_unused",
"iam_user_console_access_unused",
"awslambda_function_not_publicly_accessible",
@@ -265,6 +270,7 @@
"iam_customer_attached_policy_no_administrative_privileges",
"iam_inline_policy_no_administrative_privileges",
"iam_no_root_access_key",
"bedrock_access_not_stale",
"iam_user_accesskey_unused",
"iam_user_console_access_unused"
]
@@ -308,6 +314,7 @@
"iam_customer_attached_policy_no_administrative_privileges",
"iam_inline_policy_no_administrative_privileges",
"iam_no_root_access_key",
"bedrock_access_not_stale",
"iam_user_accesskey_unused",
"iam_user_console_access_unused",
"awslambda_function_not_publicly_accessible",

View File

@@ -592,6 +592,7 @@
"iam_rotate_access_key_90_days",
"iam_user_mfa_enabled_console_access",
"iam_user_mfa_enabled_console_access",
"bedrock_access_not_stale",
"iam_user_accesskey_unused",
"iam_user_console_access_unused"
]

View File

@@ -44,6 +44,7 @@
"iam_no_root_access_key",
"iam_support_role_created",
"iam_rotate_access_key_90_days",
"bedrock_access_not_stale",
"iam_user_mfa_enabled_console_access",
"iam_user_accesskey_unused",
"iam_user_console_access_unused",

View File

@@ -93,6 +93,7 @@
"iam_rotate_access_key_90_days",
"iam_user_mfa_enabled_console_access",
"iam_user_mfa_enabled_console_access",
"bedrock_access_not_stale",
"iam_user_accesskey_unused",
"iam_user_console_access_unused",
"awslambda_function_not_publicly_accessible",
@@ -179,6 +180,7 @@
"iam_rotate_access_key_90_days",
"iam_user_mfa_enabled_console_access",
"iam_user_mfa_enabled_console_access",
"bedrock_access_not_stale",
"iam_user_accesskey_unused",
"iam_user_console_access_unused",
"awslambda_function_not_publicly_accessible",
@@ -326,6 +328,7 @@
"iam_password_policy_symbol",
"iam_password_policy_uppercase",
"iam_rotate_access_key_90_days",
"bedrock_access_not_stale",
"iam_user_accesskey_unused",
"iam_user_console_access_unused",
"secretsmanager_automatic_rotation_enabled"

View File

@@ -174,6 +174,7 @@
}
],
"Checks": [
"bedrock_access_not_stale",
"iam_aws_attached_policy_no_administrative_privileges",
"iam_customer_attached_policy_no_administrative_privileges",
"iam_inline_policy_no_administrative_privileges",
@@ -286,6 +287,7 @@
"iam_aws_attached_policy_no_administrative_privileges",
"iam_customer_attached_policy_no_administrative_privileges",
"iam_inline_policy_no_administrative_privileges",
"bedrock_access_not_stale",
"iam_no_root_access_key",
"iam_rotate_access_key_90_days",
"iam_user_accesskey_unused",
@@ -344,6 +346,7 @@
"iam_password_policy_number",
"iam_password_policy_symbol",
"iam_password_policy_uppercase",
"bedrock_access_not_stale",
"iam_password_policy_reuse_24",
"iam_rotate_access_key_90_days",
"iam_user_accesskey_unused",

View File

@@ -867,6 +867,7 @@
}
],
"Checks": [
"bedrock_access_not_stale",
"iam_user_accesskey_unused",
"iam_user_console_access_unused"
]
@@ -1044,6 +1045,7 @@
}
],
"Checks": [
"bedrock_access_not_stale",
"iam_user_accesskey_unused",
"iam_user_console_access_unused"
]
@@ -1253,6 +1255,7 @@
}
],
"Checks": [
"bedrock_access_not_stale",
"iam_user_accesskey_unused",
"iam_user_console_access_unused"
]

View File

@@ -245,6 +245,7 @@
"iam_policy_attached_only_to_group_or_roles",
"iam_user_mfa_enabled_console_access",
"iam_root_mfa_enabled",
"bedrock_access_not_stale",
"iam_rotate_access_key_90_days",
"iam_user_accesskey_unused",
"iam_user_console_access_unused",

View File

@@ -2117,6 +2117,7 @@
"eks_cluster_kms_cmk_encryption_in_secrets_enabled",
"iam_no_root_access_key",
"iam_rotate_access_key_90_days",
"bedrock_access_not_stale",
"iam_user_accesskey_unused",
"iam_user_no_setup_initial_access_key",
"iam_user_two_active_access_key",
@@ -3151,6 +3152,7 @@
"iam_rotate_access_key_90_days",
"iam_securityaudit_role_created",
"iam_support_role_created",
"bedrock_access_not_stale",
"iam_user_accesskey_unused",
"iam_user_administrator_access_policy",
"iam_user_console_access_unused",

View File

@@ -2119,6 +2119,7 @@
"eks_cluster_kms_cmk_encryption_in_secrets_enabled",
"iam_no_root_access_key",
"iam_rotate_access_key_90_days",
"bedrock_access_not_stale",
"iam_user_accesskey_unused",
"iam_user_no_setup_initial_access_key",
"iam_user_two_active_access_key",
@@ -3155,6 +3156,7 @@
"iam_rotate_access_key_90_days",
"iam_securityaudit_role_created",
"iam_support_role_created",
"bedrock_access_not_stale",
"iam_user_accesskey_unused",
"iam_user_administrator_access_policy",
"iam_user_console_access_unused",

View File

@@ -169,6 +169,7 @@
"iam_customer_unattached_policy_no_administrative_privileges",
"iam_inline_policy_no_administrative_privileges",
"iam_no_expired_server_certificates_stored",
"bedrock_access_not_stale",
"iam_user_accesskey_unused",
"iam_user_console_access_unused",
"iam_no_root_access_key",
@@ -752,6 +753,7 @@
"iam_policy_allows_privilege_escalation",
"iam_policy_no_full_access_to_cloudtrail",
"iam_policy_no_full_access_to_kms",
"bedrock_access_not_stale",
"iam_user_accesskey_unused",
"iam_user_console_access_unused",
"iam_no_root_access_key",
@@ -800,6 +802,7 @@
"Checks": [
"guardduty_is_enabled",
"config_recorder_all_regions_enabled",
"bedrock_access_not_stale",
"iam_user_accesskey_unused",
"iam_user_console_access_unused",
"iam_password_policy_expires_passwords_within_90_days_or_less",

View File

@@ -1910,6 +1910,7 @@
"Id": "11.5.4",
"Description": "The relevant entities shall regularly review the identities for network and information systems and their users and, if no longer needed, deactivate them without delay.",
"Checks": [
"bedrock_access_not_stale",
"iam_user_accesskey_unused",
"iam_user_console_access_unused"
],

View File

@@ -30,6 +30,7 @@
"iam_no_root_access_key",
"iam_user_mfa_enabled_console_access",
"iam_user_mfa_enabled_console_access",
"bedrock_access_not_stale",
"iam_user_accesskey_unused",
"iam_user_console_access_unused",
"awslambda_function_not_publicly_accessible",
@@ -72,6 +73,7 @@
"iam_no_root_access_key",
"iam_user_mfa_enabled_console_access",
"iam_user_mfa_enabled_console_access",
"bedrock_access_not_stale",
"iam_user_accesskey_unused",
"iam_user_console_access_unused",
"awslambda_function_not_publicly_accessible",
@@ -136,6 +138,7 @@
"iam_customer_attached_policy_no_administrative_privileges",
"iam_inline_policy_no_administrative_privileges",
"iam_no_root_access_key",
"bedrock_access_not_stale",
"iam_user_accesskey_unused",
"iam_user_console_access_unused"
]
@@ -156,6 +159,7 @@
"iam_customer_attached_policy_no_administrative_privileges",
"iam_inline_policy_no_administrative_privileges",
"iam_no_root_access_key",
"bedrock_access_not_stale",
"iam_user_accesskey_unused",
"iam_user_console_access_unused"
]
@@ -579,6 +583,7 @@
"Checks": [
"iam_password_policy_reuse_24",
"iam_password_policy_expires_passwords_within_90_days_or_less",
"bedrock_access_not_stale",
"iam_user_accesskey_unused",
"iam_user_console_access_unused"
]
@@ -602,6 +607,7 @@
"iam_password_policy_uppercase",
"iam_password_policy_reuse_24",
"iam_password_policy_expires_passwords_within_90_days_or_less",
"bedrock_access_not_stale",
"iam_user_accesskey_unused",
"iam_user_console_access_unused"
]
@@ -620,6 +626,7 @@
"Checks": [
"iam_password_policy_reuse_24",
"iam_password_policy_expires_passwords_within_90_days_or_less",
"bedrock_access_not_stale",
"iam_user_accesskey_unused",
"iam_user_console_access_unused"
]

View File

@@ -21,6 +21,7 @@
"guardduty_is_enabled",
"iam_password_policy_reuse_24",
"iam_rotate_access_key_90_days",
"bedrock_access_not_stale",
"iam_user_accesskey_unused",
"iam_user_console_access_unused",
"securityhub_enabled"
@@ -39,6 +40,7 @@
}
],
"Checks": [
"bedrock_access_not_stale",
"iam_user_accesskey_unused",
"iam_user_console_access_unused"
]
@@ -110,6 +112,7 @@
"iam_root_mfa_enabled",
"iam_no_root_access_key",
"iam_rotate_access_key_90_days",
"bedrock_access_not_stale",
"iam_user_accesskey_unused",
"iam_user_console_access_unused",
"rds_instance_integration_cloudwatch_logs",
@@ -135,6 +138,7 @@
"iam_customer_attached_policy_no_administrative_privileges",
"iam_inline_policy_no_administrative_privileges",
"iam_no_root_access_key",
"bedrock_access_not_stale",
"iam_user_accesskey_unused",
"iam_user_console_access_unused",
"awslambda_function_url_public",
@@ -230,6 +234,7 @@
"iam_customer_attached_policy_no_administrative_privileges",
"iam_inline_policy_no_administrative_privileges",
"iam_no_root_access_key",
"bedrock_access_not_stale",
"iam_user_accesskey_unused",
"iam_user_console_access_unused",
"awslambda_function_url_public",

View File

@@ -29,6 +29,7 @@
"iam_rotate_access_key_90_days",
"iam_user_mfa_enabled_console_access",
"iam_user_mfa_enabled_console_access",
"bedrock_access_not_stale",
"iam_user_accesskey_unused",
"iam_user_console_access_unused",
"secretsmanager_automatic_rotation_enabled"
@@ -49,6 +50,7 @@
],
"Checks": [
"iam_password_policy_minimum_length_14",
"bedrock_access_not_stale",
"iam_user_accesskey_unused",
"iam_user_console_access_unused"
]
@@ -68,6 +70,7 @@
],
"Checks": [
"iam_password_policy_minimum_length_14",
"bedrock_access_not_stale",
"iam_user_accesskey_unused",
"iam_user_console_access_unused"
]
@@ -87,6 +90,7 @@
],
"Checks": [
"iam_password_policy_minimum_length_14",
"bedrock_access_not_stale",
"iam_user_accesskey_unused",
"iam_user_console_access_unused"
]
@@ -106,6 +110,7 @@
],
"Checks": [
"iam_password_policy_minimum_length_14",
"bedrock_access_not_stale",
"iam_user_accesskey_unused",
"iam_user_console_access_unused"
]
@@ -124,6 +129,7 @@
],
"Checks": [
"iam_password_policy_minimum_length_14",
"bedrock_access_not_stale",
"iam_user_accesskey_unused",
"iam_user_console_access_unused"
]
@@ -173,6 +179,7 @@
"iam_customer_attached_policy_no_administrative_privileges",
"iam_inline_policy_no_administrative_privileges",
"iam_no_root_access_key",
"bedrock_access_not_stale",
"iam_user_accesskey_unused",
"iam_user_console_access_unused",
"awslambda_function_not_publicly_accessible",
@@ -233,6 +240,7 @@
}
],
"Checks": [
"bedrock_access_not_stale",
"iam_user_accesskey_unused",
"iam_user_console_access_unused"
]
@@ -269,6 +277,7 @@
}
],
"Checks": [
"bedrock_access_not_stale",
"iam_user_accesskey_unused",
"iam_user_console_access_unused"
]
@@ -294,6 +303,7 @@
"iam_customer_attached_policy_no_administrative_privileges",
"iam_inline_policy_no_administrative_privileges",
"iam_policy_attached_only_to_group_or_roles",
"bedrock_access_not_stale",
"iam_user_accesskey_unused",
"iam_user_console_access_unused",
"awslambda_function_not_publicly_accessible",
@@ -375,6 +385,7 @@
"iam_rotate_access_key_90_days",
"iam_user_mfa_enabled_console_access",
"iam_user_mfa_enabled_console_access",
"bedrock_access_not_stale",
"iam_user_accesskey_unused",
"iam_user_console_access_unused",
"secretsmanager_automatic_rotation_enabled"
@@ -406,6 +417,7 @@
"iam_rotate_access_key_90_days",
"iam_user_mfa_enabled_console_access",
"iam_user_mfa_enabled_console_access",
"bedrock_access_not_stale",
"iam_user_accesskey_unused",
"iam_user_console_access_unused",
"secretsmanager_automatic_rotation_enabled"
@@ -437,6 +449,7 @@
"iam_rotate_access_key_90_days",
"iam_user_mfa_enabled_console_access",
"iam_user_mfa_enabled_console_access",
"bedrock_access_not_stale",
"iam_user_accesskey_unused",
"iam_user_console_access_unused",
"secretsmanager_automatic_rotation_enabled"
@@ -468,6 +481,7 @@
"iam_rotate_access_key_90_days",
"iam_user_mfa_enabled_console_access",
"iam_user_mfa_enabled_console_access",
"bedrock_access_not_stale",
"iam_user_accesskey_unused",
"iam_user_console_access_unused",
"secretsmanager_automatic_rotation_enabled"
@@ -499,6 +513,7 @@
"iam_rotate_access_key_90_days",
"iam_user_mfa_enabled_console_access",
"iam_user_mfa_enabled_console_access",
"bedrock_access_not_stale",
"iam_user_accesskey_unused",
"iam_user_console_access_unused",
"secretsmanager_automatic_rotation_enabled"
@@ -530,6 +545,7 @@
"iam_rotate_access_key_90_days",
"iam_user_mfa_enabled_console_access",
"iam_user_mfa_enabled_console_access",
"bedrock_access_not_stale",
"iam_user_accesskey_unused",
"iam_user_console_access_unused",
"secretsmanager_automatic_rotation_enabled"
@@ -561,6 +577,7 @@
"iam_rotate_access_key_90_days",
"iam_user_mfa_enabled_console_access",
"iam_user_mfa_enabled_console_access",
"bedrock_access_not_stale",
"iam_user_accesskey_unused",
"iam_user_console_access_unused",
"secretsmanager_automatic_rotation_enabled"
@@ -592,6 +609,7 @@
"iam_rotate_access_key_90_days",
"iam_user_mfa_enabled_console_access",
"iam_user_mfa_enabled_console_access",
"bedrock_access_not_stale",
"iam_user_accesskey_unused",
"iam_user_console_access_unused",
"secretsmanager_automatic_rotation_enabled"
@@ -622,6 +640,7 @@
"iam_rotate_access_key_90_days",
"iam_user_mfa_enabled_console_access",
"iam_user_mfa_enabled_console_access",
"bedrock_access_not_stale",
"iam_user_accesskey_unused",
"iam_user_console_access_unused",
"secretsmanager_automatic_rotation_enabled"
@@ -653,6 +672,7 @@
"iam_rotate_access_key_90_days",
"iam_user_mfa_enabled_console_access",
"iam_user_mfa_enabled_console_access",
"bedrock_access_not_stale",
"iam_user_accesskey_unused",
"iam_user_console_access_unused",
"secretsmanager_automatic_rotation_enabled"
@@ -673,6 +693,7 @@
],
"Checks": [
"secretsmanager_automatic_rotation_enabled",
"bedrock_access_not_stale",
"iam_user_accesskey_unused",
"iam_user_console_access_unused",
"iam_user_mfa_enabled_console_access",
@@ -715,6 +736,7 @@
"iam_rotate_access_key_90_days",
"iam_user_mfa_enabled_console_access",
"iam_user_mfa_enabled_console_access",
"bedrock_access_not_stale",
"iam_user_accesskey_unused",
"iam_user_console_access_unused",
"secretsmanager_automatic_rotation_enabled"
@@ -746,6 +768,7 @@
"iam_rotate_access_key_90_days",
"iam_user_mfa_enabled_console_access",
"iam_user_mfa_enabled_console_access",
"bedrock_access_not_stale",
"iam_user_accesskey_unused",
"iam_user_console_access_unused",
"secretsmanager_automatic_rotation_enabled"
@@ -777,6 +800,7 @@
"iam_rotate_access_key_90_days",
"iam_user_mfa_enabled_console_access",
"iam_user_mfa_enabled_console_access",
"bedrock_access_not_stale",
"iam_user_accesskey_unused",
"iam_user_console_access_unused",
"secretsmanager_automatic_rotation_enabled"
@@ -805,6 +829,7 @@
"iam_customer_attached_policy_no_administrative_privileges",
"iam_inline_policy_no_administrative_privileges",
"iam_no_root_access_key",
"bedrock_access_not_stale",
"iam_user_accesskey_unused",
"iam_user_console_access_unused",
"awslambda_function_not_publicly_accessible",
@@ -843,6 +868,7 @@
"iam_rotate_access_key_90_days",
"iam_user_mfa_enabled_console_access",
"iam_user_mfa_enabled_console_access",
"bedrock_access_not_stale",
"iam_user_accesskey_unused",
"iam_user_console_access_unused",
"secretsmanager_automatic_rotation_enabled"
@@ -897,6 +923,7 @@
"iam_rotate_access_key_90_days",
"iam_user_mfa_enabled_console_access",
"iam_user_mfa_enabled_console_access",
"bedrock_access_not_stale",
"iam_user_accesskey_unused",
"iam_user_console_access_unused",
"secretsmanager_automatic_rotation_enabled"
@@ -944,6 +971,7 @@
"iam_rotate_access_key_90_days",
"iam_user_mfa_enabled_console_access",
"iam_user_mfa_enabled_console_access",
"bedrock_access_not_stale",
"iam_user_accesskey_unused",
"iam_user_console_access_unused",
"secretsmanager_automatic_rotation_enabled"
@@ -975,6 +1003,7 @@
"iam_rotate_access_key_90_days",
"iam_user_mfa_enabled_console_access",
"iam_user_mfa_enabled_console_access",
"bedrock_access_not_stale",
"iam_user_accesskey_unused",
"iam_user_console_access_unused",
"secretsmanager_automatic_rotation_enabled"
@@ -1006,6 +1035,7 @@
"iam_rotate_access_key_90_days",
"iam_user_mfa_enabled_console_access",
"iam_user_mfa_enabled_console_access",
"bedrock_access_not_stale",
"iam_user_accesskey_unused",
"iam_user_console_access_unused",
"secretsmanager_automatic_rotation_enabled"
@@ -1133,6 +1163,7 @@
"iam_rotate_access_key_90_days",
"iam_user_mfa_enabled_console_access",
"iam_user_mfa_enabled_console_access",
"bedrock_access_not_stale",
"iam_user_accesskey_unused",
"iam_user_console_access_unused",
"secretsmanager_automatic_rotation_enabled"
@@ -1177,6 +1208,7 @@
"iam_customer_attached_policy_no_administrative_privileges",
"iam_inline_policy_no_administrative_privileges",
"iam_no_root_access_key",
"bedrock_access_not_stale",
"iam_user_accesskey_unused",
"iam_user_console_access_unused",
"awslambda_function_not_publicly_accessible",
@@ -1530,6 +1562,7 @@
"iam_rotate_access_key_90_days",
"iam_user_mfa_enabled_console_access",
"iam_user_mfa_enabled_console_access",
"bedrock_access_not_stale",
"iam_user_accesskey_unused",
"iam_user_console_access_unused",
"secretsmanager_automatic_rotation_enabled"
@@ -2707,6 +2740,7 @@
"iam_rotate_access_key_90_days",
"iam_user_mfa_enabled_console_access",
"iam_user_mfa_enabled_console_access",
"bedrock_access_not_stale",
"iam_user_accesskey_unused",
"iam_user_console_access_unused",
"secretsmanager_automatic_rotation_enabled"
@@ -2788,6 +2822,7 @@
"iam_no_root_access_key",
"iam_rotate_access_key_90_days",
"iam_user_mfa_enabled_console_access",
"bedrock_access_not_stale",
"iam_user_accesskey_unused",
"iam_user_console_access_unused",
"kms_cmk_rotation_enabled",
@@ -3046,6 +3081,7 @@
"iam_no_root_access_key",
"iam_rotate_access_key_90_days",
"iam_user_mfa_enabled_console_access",
"bedrock_access_not_stale",
"iam_user_accesskey_unused",
"iam_user_console_access_unused",
"kms_cmk_rotation_enabled",
@@ -4016,6 +4052,7 @@
"iam_customer_attached_policy_no_administrative_privileges",
"iam_inline_policy_no_administrative_privileges",
"iam_no_root_access_key",
"bedrock_access_not_stale",
"iam_user_accesskey_unused",
"iam_user_console_access_unused",
"awslambda_function_not_publicly_accessible",
@@ -5495,6 +5532,7 @@
"iam_rotate_access_key_90_days",
"iam_user_mfa_enabled_console_access",
"iam_user_mfa_enabled_console_access",
"bedrock_access_not_stale",
"iam_user_accesskey_unused",
"iam_user_console_access_unused",
"secretsmanager_automatic_rotation_enabled"

View File

@@ -575,6 +575,7 @@
"iam_inline_policy_no_administrative_privileges",
"iam_no_root_access_key",
"iam_rotate_access_key_90_days",
"bedrock_access_not_stale",
"iam_user_accesskey_unused",
"iam_user_console_access_unused",
"secretsmanager_automatic_rotation_enabled"
@@ -632,6 +633,7 @@
"iam_customer_attached_policy_no_administrative_privileges",
"iam_inline_policy_no_administrative_privileges",
"iam_no_root_access_key",
"bedrock_access_not_stale",
"iam_user_accesskey_unused",
"iam_user_console_access_unused"
]

View File

@@ -705,6 +705,7 @@
"iam_root_mfa_enabled",
"iam_no_root_access_key",
"iam_user_console_access_unused",
"bedrock_access_not_stale",
"iam_user_accesskey_unused",
"iam_user_two_active_access_key",
"iam_root_credentials_management_enabled",

View File

@@ -1527,6 +1527,7 @@
"Name": "Define and implement policies and procedures to ensure proper user identification management for non-consumer users and administrators",
"Description": "By ensuring each user is uniquely identified— instead of using one ID for several employees—an organization can maintain individual responsibility for actions and an effective audit trail per employee. This will help speed issue resolution and containment when misuse or malicious intent occurs.",
"Checks": [
"bedrock_access_not_stale",
"cloudwatch_log_metric_filter_policy_changes",
"iam_password_policy_reuse_24",
"iam_user_accesskey_unused",
@@ -1560,6 +1561,7 @@
"Name": "Observe user accounts to verify that any inactive accounts over 90 days old are either removed or disabled",
"Description": "Accounts that are not used regularly are often targets of attack since it is less likely that any changes (such as a changed password) will be noticed. As such, these accounts may be more easily exploited and used to access cardholder data.",
"Checks": [
"bedrock_access_not_stale",
"iam_password_policy_reuse_24",
"iam_user_accesskey_unused",
"iam_user_console_access_unused"

View File

@@ -16878,6 +16878,7 @@
"Description": "Checks if your AWS Identity and Access Management (IAM) users have passwords or active access keys that have not been used within the specified number of days you provided",
"Name": "iam",
"Checks": [
"bedrock_access_not_stale",
"iam_user_accesskey_unused"
],
"Attributes": [
@@ -16918,6 +16919,7 @@
"Description": "Checks if your AWS Identity and Access Management (IAM) users have passwords or active access keys that have not been used within the specified number of days you provided",
"Name": "iam",
"Checks": [
"bedrock_access_not_stale",
"iam_user_accesskey_unused"
],
"Attributes": [
@@ -18574,6 +18576,7 @@
"Description": "Checks if your AWS Identity and Access Management (IAM) users have passwords or active access keys that have not been used within the specified number of days you provided",
"Name": "iam",
"Checks": [
"bedrock_access_not_stale",
"iam_user_accesskey_unused"
],
"Attributes": [
@@ -22658,6 +22661,7 @@
"Description": "Checks if your AWS Identity and Access Management (IAM) users have passwords or active access keys that have not been used within the specified number of days you provided",
"Name": "iam",
"Checks": [
"bedrock_access_not_stale",
"iam_user_accesskey_unused"
],
"Attributes": [

View File

@@ -171,6 +171,7 @@
"Id": "1.1.10",
"Description": "Ensure credentials unused for 45 days or more are disabled",
"Checks": [
"bedrock_access_not_stale",
"iam_user_accesskey_unused",
"iam_user_console_access_unused"
],

View File

@@ -293,6 +293,7 @@
}
],
"Checks": [
"bedrock_access_not_stale",
"iam_user_accesskey_unused",
"iam_user_console_access_unused",
"iam_no_expired_server_certificates_stored"
@@ -335,6 +336,7 @@
],
"Checks": [
"iam_rotate_access_key_90_days",
"bedrock_access_not_stale",
"iam_user_accesskey_unused",
"iam_user_console_access_unused",
"accessanalyzer_enabled_without_findings"

View File

@@ -18,6 +18,7 @@
}
],
"Checks": [
"bedrock_access_not_stale",
"iam_policy_attached_only_to_group_or_roles",
"iam_aws_attached_policy_no_administrative_privileges",
"iam_customer_attached_policy_no_administrative_privileges",

View File

@@ -0,0 +1,44 @@
{
"Provider": "aws",
"CheckID": "bedrock_access_not_stale",
"CheckTitle": "Regular Bedrock access ensures IAM identities retain only actively used permissions",
"CheckType": [
"Software and Configuration Checks/AWS Security Best Practices"
],
"ServiceName": "bedrock",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "medium",
"ResourceType": "AwsIamUser",
"ResourceGroup": "IAM",
"Description": "IAM identities (users and roles) granted **Bedrock** permissions are evaluated for recent service usage.\n\nIdentities whose last Bedrock access exceeds the configured threshold (default **60 days**) or that have **never** accessed Bedrock are flagged, indicating stale permissions that should be reviewed.",
"Risk": "Stale Bedrock permissions widen the **blast radius** of a credential compromise.\n\nAn attacker who gains access to an identity with unused Bedrock permissions can invoke foundation models, exfiltrate data through model responses, or incur significant costs — all without triggering expected usage patterns.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_access-advisor.html",
"https://docs.aws.amazon.com/bedrock/latest/userguide/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 or role\n2. Review the **Access Advisor** tab to confirm Bedrock has not been accessed recently\n3. Remove or detach any policies granting Bedrock permissions that are no longer needed\n4. If the identity still requires Bedrock 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 Bedrock 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/bedrock_access_not_stale"
}
},
"Categories": [
"identity-access",
"gen-ai"
],
"DependsOn": [],
"RelatedTo": [
"bedrock_api_key_no_administrative_privileges",
"bedrock_api_key_no_long_term_credentials"
],
"Notes": "The staleness threshold is configurable via the `max_unused_bedrock_access_days` audit config key (default: 60 days)."
}

View File

@@ -0,0 +1,158 @@
from datetime import datetime, timezone
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 bedrock_access_not_stale(Check):
"""Detect stale identities with unused Bedrock permissions.
This check evaluates whether IAM users and roles with Bedrock service
permissions have actively used those permissions within the configured
threshold (default 60 days).
- PASS: The identity has accessed Bedrock within the allowed period.
- FAIL: The identity has Bedrock permissions but has not used them
within the allowed period or has never used them.
"""
def execute(self) -> list[Check_Report_AWS]:
"""Execute the Bedrock access staleness check.
Iterates over IAM users and roles, inspecting service last accessed
data for the ``bedrock`` namespace. Identities whose last Bedrock
access exceeds the configured threshold are reported as non-compliant.
Returns:
A list of reports containing the result of the check.
"""
findings = []
max_unused_bedrock_days = iam_client.audit_config.get(
"max_unused_bedrock_access_days", 60
)
# Check IAM users
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 = 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.region = iam_client.region
# Retrieve tags from the user object
for iam_user in iam_client.users:
if iam_user.arn == user_arn:
report.resource_tags = iam_user.tags
break
self._evaluate_staleness(
report, bedrock_service, max_unused_bedrock_days, user_name, "User"
)
findings.append(report)
# Check IAM roles
for (
role_data,
last_accessed_services,
) in iam_client.role_last_accessed_services.items():
role_name = role_data[0]
role_arn = role_data[1]
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.region = iam_client.region
# Retrieve tags from the role object
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
self._evaluate_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],
) -> dict | None:
"""Return the Bedrock entry from a service last accessed list.
Args:
last_accessed_services: List of service last accessed records.
Returns:
The dictionary for the ``bedrock`` namespace, or ``None``.
"""
for service in last_accessed_services:
if service.get("ServiceNamespace") == "bedrock":
return service
return None
@staticmethod
def _evaluate_staleness(
report: Check_Report_AWS,
bedrock_service: dict,
max_days: int,
identity_name: str,
identity_type: str,
) -> None:
"""Populate the report based on Bedrock access recency.
Args:
report: The check report to populate.
bedrock_service: The Bedrock service last accessed record.
max_days: Maximum allowed days since last Bedrock access.
identity_name: Name of the IAM identity.
identity_type: Either ``User`` or ``Role``.
"""
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)."
)

View File

@@ -92,6 +92,8 @@ class IAM(AWSService):
self._get_access_keys_metadata()
self.last_accessed_services = {}
self._get_last_accessed_services()
self.role_last_accessed_services = {}
self._get_role_last_accessed_services()
self.user_temporary_credentials_usage = {}
self._get_user_temporary_credentials_usage()
self.organization_features = []
@@ -901,6 +903,47 @@ class IAM(AWSService):
f"{self.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
def _get_role_last_accessed_services(self):
"""Retrieve service last accessed details for all IAM roles."""
logger.info("IAM - Getting Role Last Accessed Services ...")
try:
if self.roles is None:
return
for role in self.roles:
try:
details = self.client.generate_service_last_accessed_details(
Arn=role.arn
)
response = self.client.get_service_last_accessed_details(
JobId=details["JobId"]
)
while response["JobStatus"] == "IN_PROGRESS":
response = self.client.get_service_last_accessed_details(
JobId=details["JobId"]
)
self.role_last_accessed_services[(role.name, role.arn)] = (
response.get("ServicesLastAccessed", {})
)
except ClientError as error:
if error.response["Error"]["Code"] == "NoSuchEntity":
logger.warning(
f"{self.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
else:
logger.error(
f"{self.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
except Exception as error:
logger.error(
f"{self.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
except Exception as error:
logger.error(
f"{self.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
def _get_access_keys_metadata(self):
logger.info("IAM - Getting Access Keys Metadata ...")
try:

View File

@@ -0,0 +1,742 @@
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)
IAM_ROLE_NAME = "test-role"
IAM_ROLE_ARN = f"arn:aws:iam::{AWS_ACCOUNT_NUMBER}:role/{IAM_ROLE_NAME}"
ROLE_DATA = (IAM_ROLE_NAME, IAM_ROLE_ARN)
class Test_bedrock_access_not_stale:
@mock_aws
def test_no_identities_with_bedrock_permissions(self):
"""No findings when no identities have Bedrock 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 = {}
iam.role_last_accessed_services = {}
with mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=aws_provider,
), mock.patch(
"prowler.providers.aws.services.bedrock.bedrock_access_not_stale.bedrock_access_not_stale.iam_client",
new=iam,
):
from prowler.providers.aws.services.bedrock.bedrock_access_not_stale.bedrock_access_not_stale import (
bedrock_access_not_stale,
)
check = bedrock_access_not_stale()
result = check.execute()
assert len(result) == 0
@mock_aws
def test_user_without_bedrock_permissions(self):
"""User with non-Bedrock 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"},
]
}
iam.role_last_accessed_services = {}
with mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=aws_provider,
), mock.patch(
"prowler.providers.aws.services.bedrock.bedrock_access_not_stale.bedrock_access_not_stale.iam_client",
new=iam,
):
from prowler.providers.aws.services.bedrock.bedrock_access_not_stale.bedrock_access_not_stale import (
bedrock_access_not_stale,
)
check = bedrock_access_not_stale()
result = check.execute()
assert len(result) == 0
@mock_aws
def test_user_bedrock_access_recent(self):
"""PASS when user accessed Bedrock 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": "bedrock",
"ServiceName": "Amazon Bedrock",
"LastAuthenticated": last_authenticated,
},
]
}
iam.role_last_accessed_services = {}
with mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=aws_provider,
), mock.patch(
"prowler.providers.aws.services.bedrock.bedrock_access_not_stale.bedrock_access_not_stale.iam_client",
new=iam,
):
from prowler.providers.aws.services.bedrock.bedrock_access_not_stale.bedrock_access_not_stale import (
bedrock_access_not_stale,
)
check = bedrock_access_not_stale()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert "accessed Bedrock" 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_bedrock_access_stale(self):
"""FAIL when user last accessed Bedrock more than 60 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=90)
iam.last_accessed_services = {
USER_DATA: [
{
"ServiceNamespace": "bedrock",
"ServiceName": "Amazon Bedrock",
"LastAuthenticated": last_authenticated,
},
]
}
iam.role_last_accessed_services = {}
with mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=aws_provider,
), mock.patch(
"prowler.providers.aws.services.bedrock.bedrock_access_not_stale.bedrock_access_not_stale.iam_client",
new=iam,
):
from prowler.providers.aws.services.bedrock.bedrock_access_not_stale.bedrock_access_not_stale import (
bedrock_access_not_stale,
)
check = bedrock_access_not_stale()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert "has not accessed Bedrock" in result[0].status_extended
assert "90 days" 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_bedrock_never_accessed(self):
"""FAIL when user has Bedrock 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": "bedrock",
"ServiceName": "Amazon Bedrock",
# No LastAuthenticated key — service was never accessed
},
]
}
iam.role_last_accessed_services = {}
with mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=aws_provider,
), mock.patch(
"prowler.providers.aws.services.bedrock.bedrock_access_not_stale.bedrock_access_not_stale.iam_client",
new=iam,
):
from prowler.providers.aws.services.bedrock.bedrock_access_not_stale.bedrock_access_not_stale import (
bedrock_access_not_stale,
)
check = bedrock_access_not_stale()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert "has never used them" 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
@mock_aws
def test_role_bedrock_access_stale(self):
"""FAIL when a role last accessed Bedrock more than 60 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=120)
iam.last_accessed_services = {}
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(
"prowler.providers.aws.services.bedrock.bedrock_access_not_stale.bedrock_access_not_stale.iam_client",
new=iam,
):
from prowler.providers.aws.services.bedrock.bedrock_access_not_stale.bedrock_access_not_stale import (
bedrock_access_not_stale,
)
check = bedrock_access_not_stale()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert "has not accessed Bedrock" in result[0].status_extended
assert "Role" in result[0].status_extended
assert IAM_ROLE_NAME in result[0].status_extended
assert result[0].resource_id == IAM_ROLE_NAME
assert result[0].resource_arn == IAM_ROLE_ARN
@mock_aws
def test_role_bedrock_access_recent(self):
"""PASS when a role accessed Bedrock recently."""
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=5)
iam.last_accessed_services = {}
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(
"prowler.providers.aws.services.bedrock.bedrock_access_not_stale.bedrock_access_not_stale.iam_client",
new=iam,
):
from prowler.providers.aws.services.bedrock.bedrock_access_not_stale.bedrock_access_not_stale import (
bedrock_access_not_stale,
)
check = bedrock_access_not_stale()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert "accessed Bedrock" in result[0].status_extended
assert "Role" in result[0].status_extended
assert IAM_ROLE_NAME in result[0].status_extended
assert result[0].resource_id == IAM_ROLE_NAME
assert result[0].resource_arn == IAM_ROLE_ARN
@mock_aws
def test_role_bedrock_never_accessed(self):
"""FAIL when a role has Bedrock permissions but never accessed them."""
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.last_accessed_services = {}
iam.role_last_accessed_services = {
ROLE_DATA: [
{
"ServiceNamespace": "bedrock",
"ServiceName": "Amazon Bedrock",
},
]
}
with mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=aws_provider,
), mock.patch(
"prowler.providers.aws.services.bedrock.bedrock_access_not_stale.bedrock_access_not_stale.iam_client",
new=iam,
):
from prowler.providers.aws.services.bedrock.bedrock_access_not_stale.bedrock_access_not_stale import (
bedrock_access_not_stale,
)
check = bedrock_access_not_stale()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert "has never used them" in result[0].status_extended
assert "Role" in result[0].status_extended
assert IAM_ROLE_NAME 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_bedrock_access_days": 30}
mock_user = User(
name=IAM_USER_NAME,
arn=IAM_USER_ARN,
attached_policies=[],
inline_policies=[],
)
iam.users = [mock_user]
# Accessed 45 days ago — within default 60, but beyond custom 30
last_authenticated = datetime.now(timezone.utc) - timedelta(days=45)
iam.last_accessed_services = {
USER_DATA: [
{
"ServiceNamespace": "bedrock",
"ServiceName": "Amazon Bedrock",
"LastAuthenticated": last_authenticated,
},
]
}
iam.role_last_accessed_services = {}
with mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=aws_provider,
), mock.patch(
"prowler.providers.aws.services.bedrock.bedrock_access_not_stale.bedrock_access_not_stale.iam_client",
new=iam,
):
from prowler.providers.aws.services.bedrock.bedrock_access_not_stale.bedrock_access_not_stale import (
bedrock_access_not_stale,
)
check = bedrock_access_not_stale()
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_mixed_users_and_roles(self):
"""Multiple identities with different staleness states produce correct findings."""
from prowler.providers.aws.services.iam.iam_service import IAM, Role, 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=[],
)
mock_role = Role(
name=IAM_ROLE_NAME,
arn=IAM_ROLE_ARN,
assume_role_policy={},
is_service_role=False,
attached_policies=[],
inline_policies=[],
)
iam.users = [mock_user]
iam.roles = [mock_role]
# User accessed recently — PASS
iam.last_accessed_services = {
USER_DATA: [
{
"ServiceNamespace": "bedrock",
"ServiceName": "Amazon Bedrock",
"LastAuthenticated": datetime.now(timezone.utc)
- timedelta(days=10),
},
]
}
# Role access is stale — FAIL
iam.role_last_accessed_services = {
ROLE_DATA: [
{
"ServiceNamespace": "bedrock",
"ServiceName": "Amazon Bedrock",
"LastAuthenticated": datetime.now(timezone.utc)
- timedelta(days=100),
},
]
}
with mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=aws_provider,
), mock.patch(
"prowler.providers.aws.services.bedrock.bedrock_access_not_stale.bedrock_access_not_stale.iam_client",
new=iam,
):
from prowler.providers.aws.services.bedrock.bedrock_access_not_stale.bedrock_access_not_stale import (
bedrock_access_not_stale,
)
check = bedrock_access_not_stale()
result = check.execute()
assert len(result) == 2
user_result = next(r for r in result if r.resource_id == IAM_USER_NAME)
assert user_result.status == "PASS"
role_result = next(r for r in result if r.resource_id == IAM_ROLE_NAME)
assert role_result.status == "FAIL"
@mock_aws
def test_user_bedrock_access_at_exact_threshold(self):
"""PASS when user accessed Bedrock exactly at the 60-day boundary (uses > not >=)."""
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=60)
iam.last_accessed_services = {
USER_DATA: [
{
"ServiceNamespace": "bedrock",
"ServiceName": "Amazon Bedrock",
"LastAuthenticated": last_authenticated,
},
]
}
iam.role_last_accessed_services = {}
with mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=aws_provider,
), mock.patch(
"prowler.providers.aws.services.bedrock.bedrock_access_not_stale.bedrock_access_not_stale.iam_client",
new=iam,
):
from prowler.providers.aws.services.bedrock.bedrock_access_not_stale.bedrock_access_not_stale import (
bedrock_access_not_stale,
)
check = bedrock_access_not_stale()
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_user_bedrock_access_one_day_over_threshold(self):
"""FAIL when user accessed Bedrock 61 days ago (just over 60-day 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=61)
iam.last_accessed_services = {
USER_DATA: [
{
"ServiceNamespace": "bedrock",
"ServiceName": "Amazon Bedrock",
"LastAuthenticated": last_authenticated,
},
]
}
iam.role_last_accessed_services = {}
with mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=aws_provider,
), mock.patch(
"prowler.providers.aws.services.bedrock.bedrock_access_not_stale.bedrock_access_not_stale.iam_client",
new=iam,
):
from prowler.providers.aws.services.bedrock.bedrock_access_not_stale.bedrock_access_not_stale import (
bedrock_access_not_stale,
)
check = bedrock_access_not_stale()
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_user_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, 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": "bedrock",
"ServiceName": "Amazon Bedrock",
"LastAuthenticated": last_access_date.isoformat(),
},
]
}
iam.role_last_accessed_services = {}
with mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=aws_provider,
), mock.patch(
"prowler.providers.aws.services.bedrock.bedrock_access_not_stale.bedrock_access_not_stale.iam_client",
new=iam,
):
from prowler.providers.aws.services.bedrock.bedrock_access_not_stale.bedrock_access_not_stale import (
bedrock_access_not_stale,
)
check = bedrock_access_not_stale()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert result[0].resource_id == IAM_USER_NAME
assert result[0].resource_arn == IAM_USER_ARN
@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": "bedrock",
"ServiceName": "Amazon Bedrock",
"LastAuthenticated": last_authenticated,
},
]
}
iam.role_last_accessed_services = {}
with mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=aws_provider,
), mock.patch(
"prowler.providers.aws.services.bedrock.bedrock_access_not_stale.bedrock_access_not_stale.iam_client",
new=iam,
):
from prowler.providers.aws.services.bedrock.bedrock_access_not_stale.bedrock_access_not_stale import (
bedrock_access_not_stale,
)
check = bedrock_access_not_stale()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert result[0].resource_tags == user_tags
@mock_aws
def test_role_tags_are_populated(self):
"""Verify resource_tags are populated from the role 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)
role_tags = [{"Key": "Team", "Value": "ml-platform"}]
mock_role = Role(
name=IAM_ROLE_NAME,
arn=IAM_ROLE_ARN,
assume_role_policy={},
is_service_role=False,
attached_policies=[],
inline_policies=[],
tags=role_tags,
)
iam.roles = [mock_role]
last_authenticated = datetime.now(timezone.utc) - timedelta(days=5)
iam.last_accessed_services = {}
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(
"prowler.providers.aws.services.bedrock.bedrock_access_not_stale.bedrock_access_not_stale.iam_client",
new=iam,
):
from prowler.providers.aws.services.bedrock.bedrock_access_not_stale.bedrock_access_not_stale import (
bedrock_access_not_stale,
)
check = bedrock_access_not_stale()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert result[0].resource_tags == role_tags