fix(bedrock): per-finding severity for long-term API key check (#11526)

This commit is contained in:
Daniel Barranquero
2026-06-11 08:31:08 +02:00
committed by GitHub
parent 75f95559d6
commit 989c3b174e
4 changed files with 220 additions and 474 deletions
+1
View File
@@ -30,6 +30,7 @@ All notable changes to the **Prowler SDK** are documented in this file.
- M365 Admin Center group enumeration now follows Microsoft Graph pagination so group-scoped checks include groups beyond the first page [(#11510)](https://github.com/prowler-cloud/prowler/pull/11510)
- GCP `kms_key_rotation_enabled` check now only verifies that automatic key rotation is enabled (any interval) instead of enforcing a 90-day period, resolving the mismatch between the check and its documentation; the CIS, Prowler ThreatScore, and CCC requirements that mandate a 90-day maximum were remapped to the new `kms_key_rotation_max_90_days` check [(#11516)](https://github.com/prowler-cloud/prowler/pull/11516)
- AWS CloudWatch log metric filter checks now validate `filterPattern` clauses regardless of order [(#11345)](https://github.com/prowler-cloud/prowler/pull/11345)
- AWS `bedrock_api_key_no_long_term_credentials` now applies severity per finding (never-expires keys correctly flag as critical, no leak across findings) and aligns title and wording with AWS guidance to prefer short-term Bedrock API keys [(#11526)](https://github.com/prowler-cloud/prowler/pull/11526)
---
@@ -1,7 +1,7 @@
{
"Provider": "aws",
"CheckID": "bedrock_api_key_no_long_term_credentials",
"CheckTitle": "Amazon Bedrock API key is expired",
"CheckTitle": "Amazon Bedrock long-term API key has expired",
"CheckType": [
"Software and Configuration Checks/AWS Security Best Practices",
"Software and Configuration Checks/Industry and Regulatory Standards/AWS Foundational Security Best Practices",
@@ -14,23 +14,24 @@
"Severity": "high",
"ResourceType": "AwsIamUser",
"ResourceGroup": "IAM",
"Description": "**Bedrock API keys** are evaluated for **lifetime** and **expiration**.\n\nThe finding identifies keys that are long-lived, set to expire far in the future, or configured to `never expire`, and distinguishes them from keys that have already expired.",
"Risk": "Long-lived or non-expiring keys enable persistent access if compromised.\n- Confidentiality: unauthorized inference and exposure of prompts/outputs\n- Availability/Cost: uncontrolled usage and spend spikes\n- Integrity: actions can continue without timely revocation or rotation",
"Description": "AWS recommends Amazon Bedrock **long-term API keys** only for **exploration**; production workloads should use **short-term API keys** (session-scoped, valid up to **12 hours**). This check fails for any active long-term Bedrock API key, escalating to `critical` severity when configured to **never expire**. Already-expired keys pass — they can no longer authenticate.",
"Risk": "Long-term Bedrock API keys persist beyond a session until their stored expiration, and keys set to **never expire** grant indefinite access until manually revoked, enabling unauthorized inference, uncontrolled usage and spend, and activity that continues past timely revocation.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://docs.aws.amazon.com/ja_jp/bedrock/latest/userguide/getting-started-api-keys.html",
"https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html#rotate-credentials",
"https://docs.aws.amazon.com/bedrock/latest/userguide/api-keys.html"
"https://docs.aws.amazon.com/bedrock/latest/userguide/api-keys.html",
"https://docs.aws.amazon.com/bedrock/latest/userguide/api-keys-generate.html",
"https://docs.aws.amazon.com/IAM/latest/UserGuide/security-creds-programmatic-access.html#security-creds-alternatives-to-long-term-access-keys",
"https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html#rotate-credentials"
],
"Remediation": {
"Code": {
"CLI": "aws iam delete-service-specific-credential --user-name <username> --service-specific-credential-id <credential-id>",
"NativeIaC": "",
"Other": "1. Sign in to the AWS Management Console and open IAM\n2. Go to Users > select <example_resource_name> > Security credentials\n3. In \"API keys for Amazon Bedrock\", find the non-expired key and click Delete\n4. Confirm deletion to remove the key (removes the long-term credential so the check passes)",
"Other": "1. Sign in to the AWS Management Console and open IAM\n2. Go to Users > select the IAM user backing the Bedrock API key > Security credentials\n3. In \"API keys for Amazon Bedrock\", select the active long-term key and click Delete\n4. For workloads that still need Bedrock access, generate a short-term API key from the Bedrock console (Short-term API keys tab), or call the Bedrock API with short-term credentials issued by AWS STS",
"Terraform": ""
},
"Recommendation": {
"Text": "Prefer **short-term credentials** and **IAM roles**; avoid `never expire`.\n\nEnforce **least privilege**, strict **rotation**, and automatic **expiration** for any long-term key. Store secrets securely, monitor with audit logs, and revoke unused or stale keys quickly.",
"Text": "Use short-term Amazon Bedrock API keys for any non-exploratory workload — they are bound to the IAM principal's session, valid for at most 12 hours, scoped to a single Region, and can be auto-refreshed by the SDK. For existing long-term keys, delete the underlying IAM service-specific credential. If a long-term key must be retained for an exploration scenario, set an explicit short expiration and never select `never expire`.",
"Url": "https://hub.prowler.com/check/bedrock_api_key_no_long_term_credentials"
}
},
@@ -40,5 +41,5 @@
],
"DependsOn": [],
"RelatedTo": [],
"Notes": "This check verifies that Amazon Bedrock API keys have expiration dates set. API keys without expiration dates are considered long-term credentials and pose a security risk. The check follows security best practices for credential management and the principle of least privilege."
"Notes": "AWS recommends against using long-term Amazon Bedrock API keys outside of exploration; production workloads should use short-term API keys (session-scoped, valid up to 12 hours). The IAM `ListServiceSpecificCredentials` API only enumerates long-term keys — short-term keys are session-scoped credentials that never appear here. The check therefore passes only when an existing long-term key has already expired and can no longer authenticate; any active long-term key fails, with critical severity when it is configured to never expire."
}
@@ -1,49 +1,62 @@
from datetime import datetime, timezone
from prowler.lib.check.models import Check, Check_Report_AWS
from prowler.lib.check.models import Check, Check_Report_AWS, Severity
from prowler.providers.aws.services.iam.iam_client import iam_client
# Days threshold above which a Bedrock long-term API key is considered effectively non-expiring.
NEVER_EXPIRES_THRESHOLD_DAYS = 10000
class bedrock_api_key_no_long_term_credentials(Check):
"""
Bedrock API keys should be short-lived to reduce the risk of unauthorized access.
This check verifies if there are any long-term Bedrock API keys.
If there are, it checks if they are expired or will be expired.
If they are expired, it will be marked as PASS.
If they are not expired, it will be marked as FAIL and the severity will be critical if the key will never expire.
"""Amazon Bedrock long-term API keys should not be used outside of exploration.
AWS recommends short-term Bedrock API keys (session-scoped, valid up to 12 hours)
for any non-exploratory workload. ``ListServiceSpecificCredentials`` only enumerates
long-term keys, so every key inspected here is by definition a long-term credential.
PASS when the long-term key has already expired (it can no longer authenticate).
FAIL (critical) when the key is configured to never expire.
FAIL (high) for any other active long-term key.
"""
def execute(self):
"""
Execute the Bedrock API key no long-term credentials check.
Iterate over all the Bedrock API keys and check if they are expired or will be expired.
Returns:
List[Check_Report_AWS]: A list of report objects with the results of the check.
"""
findings = []
for api_key in iam_client.service_specific_credentials:
if api_key.service_name != "bedrock.amazonaws.com":
continue
if api_key.expiration_date:
report = Check_Report_AWS(metadata=self.metadata(), resource=api_key)
# Check if the expiration date is in the future
if api_key.expiration_date > datetime.now(timezone.utc):
report.status = "FAIL"
# Get the days until the expiration date
days_until_expiration = (
api_key.expiration_date - datetime.now(timezone.utc)
).days
if days_until_expiration > 10000:
self.Severity = "critical"
report.status_extended = f"Long-term Bedrock API key {api_key.id} in user {api_key.user.name} exists and never expires."
else:
report.status_extended = f"Long-term Bedrock API key {api_key.id} in user {api_key.user.name} exists and will expire in {days_until_expiration} days."
else:
report.status = "PASS"
report.status_extended = f"Long-term Bedrock API key {api_key.id} in user {api_key.user.name} exists but has expired."
findings.append(report)
if not api_key.expiration_date:
continue
report = Check_Report_AWS(metadata=self.metadata(), resource=api_key)
now = datetime.now(timezone.utc)
if api_key.expiration_date <= now:
report.status = "PASS"
report.status_extended = (
f"Bedrock long-term API key {api_key.id} in user "
f"{api_key.user.name} has already expired and can no longer "
f"authenticate."
)
elif (api_key.expiration_date - now).days > NEVER_EXPIRES_THRESHOLD_DAYS:
report.status = "FAIL"
report.check_metadata.Severity = Severity.critical
report.status_extended = (
f"Bedrock long-term API key {api_key.id} in user "
f"{api_key.user.name} is configured to never expire. Use "
f"short-term Bedrock API keys (session-scoped, valid up to "
f"12 hours) for non-exploratory workloads instead."
)
else:
days_until_expiration = (api_key.expiration_date - now).days
report.status = "FAIL"
report.status_extended = (
f"Bedrock long-term API key {api_key.id} in user "
f"{api_key.user.name} is active and will expire in "
f"{days_until_expiration} days. Use short-term Bedrock API "
f"keys (session-scoped, valid up to 12 hours) for "
f"non-exploratory workloads instead."
)
findings.append(report)
return findings
@@ -3,466 +3,197 @@ from unittest import mock
from moto import mock_aws
from prowler.lib.check.models import Severity
from tests.providers.aws.utils import AWS_REGION_US_EAST_1, set_mocked_aws_provider
BEDROCK_SERVICE = "bedrock.amazonaws.com"
def _make_user(name="test_user"):
from prowler.providers.aws.services.iam.iam_service import User
return User(
name=name,
arn=f"arn:aws:iam:{AWS_REGION_US_EAST_1}:123456789012:user/{name}",
attached_policies=[],
inline_policies=[],
)
def _make_credential(
user,
credential_id="test-credential-id",
expiration_delta_days=None,
service_name=BEDROCK_SERVICE,
):
from prowler.providers.aws.services.iam.iam_service import ServiceSpecificCredential
expiration_date = (
datetime.now(timezone.utc) + timedelta(days=expiration_delta_days)
if expiration_delta_days is not None
else None
)
return ServiceSpecificCredential(
arn=(
f"arn:aws:iam:{AWS_REGION_US_EAST_1}:123456789012:user/{user.name}/"
f"credential/{credential_id}"
),
user=user,
status="Active",
create_date=datetime.now(timezone.utc),
service_user_name=None,
service_credential_alias=None,
expiration_date=expiration_date,
id=credential_id,
service_name=service_name,
region=AWS_REGION_US_EAST_1,
)
def _run_check(credentials):
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.service_specific_credentials = credentials
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=aws_provider,
),
mock.patch(
"prowler.providers.aws.services.bedrock.bedrock_api_key_no_long_term_credentials.bedrock_api_key_no_long_term_credentials.iam_client",
new=iam,
),
):
from prowler.providers.aws.services.bedrock.bedrock_api_key_no_long_term_credentials.bedrock_api_key_no_long_term_credentials import (
bedrock_api_key_no_long_term_credentials,
)
check = bedrock_api_key_no_long_term_credentials()
return check.execute()
class Test_bedrock_api_key_no_long_term_credentials:
@mock_aws
def test_no_bedrock_api_keys(self):
from prowler.providers.aws.services.iam.iam_service import IAM
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=aws_provider,
),
mock.patch(
"prowler.providers.aws.services.bedrock.bedrock_api_key_no_long_term_credentials.bedrock_api_key_no_long_term_credentials.iam_client",
new=IAM(aws_provider),
),
):
from prowler.providers.aws.services.bedrock.bedrock_api_key_no_long_term_credentials.bedrock_api_key_no_long_term_credentials import (
bedrock_api_key_no_long_term_credentials,
)
check = bedrock_api_key_no_long_term_credentials()
result = check.execute()
assert len(result) == 0
assert _run_check([]) == []
@mock_aws
def test_bedrock_api_key_with_future_expiration_date(self):
from prowler.providers.aws.services.iam.iam_service import IAM
def test_active_short_expiration_key_fails_high(self):
# Per AWS guidance, every active long-term key is a finding regardless of
# how soon it expires. Short remaining lifetime does not downgrade severity.
credential = _make_credential(_make_user(), expiration_delta_days=30)
result = _run_check([credential])
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
iam = IAM(aws_provider)
# Mock service-specific credentials
from prowler.providers.aws.services.iam.iam_service import (
ServiceSpecificCredential,
User,
)
# Create a mock user
mock_user = User(
name="test_user",
arn=f"arn:aws:iam:{AWS_REGION_US_EAST_1}:123456789012:user/test_user",
attached_policies=[],
inline_policies=[],
)
# Create a mock service-specific credential with future expiration date
expiration_date = datetime.now(timezone.utc) + timedelta(days=30)
mock_credential = ServiceSpecificCredential(
arn=f"arn:aws:iam:{AWS_REGION_US_EAST_1}:123456789012:user/test_user/credential/test-credential-id",
user=mock_user,
status="Active",
create_date=datetime.now(timezone.utc),
service_user_name=None,
service_credential_alias=None,
expiration_date=expiration_date,
id="test-credential-id",
service_name="bedrock.amazonaws.com",
region=AWS_REGION_US_EAST_1,
)
iam.service_specific_credentials = [mock_credential]
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=aws_provider,
),
mock.patch(
"prowler.providers.aws.services.bedrock.bedrock_api_key_no_long_term_credentials.bedrock_api_key_no_long_term_credentials.iam_client",
new=iam,
),
):
from prowler.providers.aws.services.bedrock.bedrock_api_key_no_long_term_credentials.bedrock_api_key_no_long_term_credentials import (
bedrock_api_key_no_long_term_credentials,
)
check = bedrock_api_key_no_long_term_credentials()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert "will expire in" in result[0].status_extended
assert "test-credential-id" in result[0].status_extended
assert "test_user" in result[0].status_extended
assert result[0].resource_id == "test-credential-id"
assert result[0].region == AWS_REGION_US_EAST_1
assert len(result) == 1
assert result[0].status == "FAIL"
assert result[0].check_metadata.Severity == Severity.high
assert "is active and will expire in" in result[0].status_extended
assert "short-term Bedrock API keys" in result[0].status_extended
@mock_aws
def test_bedrock_api_key_with_critical_expiration_date(self):
from prowler.providers.aws.services.iam.iam_service import IAM
def test_active_long_expiration_key_fails_high(self):
credential = _make_credential(_make_user(), expiration_delta_days=365)
result = _run_check([credential])
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
iam = IAM(aws_provider)
# Mock service-specific credentials
from prowler.providers.aws.services.iam.iam_service import (
ServiceSpecificCredential,
User,
)
# Create a mock user
mock_user = User(
name="test_user",
arn=f"arn:aws:iam:{AWS_REGION_US_EAST_1}:123456789012:user/test_user",
attached_policies=[],
inline_policies=[],
)
# Create a mock service-specific credential with very far future expiration date (>10000 days)
expiration_date = datetime.now(timezone.utc) + timedelta(days=15000)
mock_credential = ServiceSpecificCredential(
arn=f"arn:aws:iam:{AWS_REGION_US_EAST_1}:123456789012:user/test_user/credential/test-credential-id",
user=mock_user,
status="Active",
create_date=datetime.now(timezone.utc),
service_user_name=None,
service_credential_alias=None,
expiration_date=expiration_date,
id="test-credential-id",
service_name="bedrock.amazonaws.com",
region=AWS_REGION_US_EAST_1,
)
iam.service_specific_credentials = [mock_credential]
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=aws_provider,
),
mock.patch(
"prowler.providers.aws.services.bedrock.bedrock_api_key_no_long_term_credentials.bedrock_api_key_no_long_term_credentials.iam_client",
new=iam,
),
):
from prowler.providers.aws.services.bedrock.bedrock_api_key_no_long_term_credentials.bedrock_api_key_no_long_term_credentials import (
bedrock_api_key_no_long_term_credentials,
)
check = bedrock_api_key_no_long_term_credentials()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert "never expires" in result[0].status_extended
assert "test-credential-id" in result[0].status_extended
assert "test_user" in result[0].status_extended
assert result[0].resource_id == "test-credential-id"
assert result[0].region == AWS_REGION_US_EAST_1
assert check.Severity == "critical"
assert len(result) == 1
assert result[0].status == "FAIL"
assert result[0].check_metadata.Severity == Severity.high
assert "is active and will expire in" in result[0].status_extended
@mock_aws
def test_bedrock_api_key_with_expired_date(self):
from prowler.providers.aws.services.iam.iam_service import IAM
def test_never_expires_key_fails_critical(self):
# >10000 days approximates AWS's "no expiration" sentinel (~100 years).
credential = _make_credential(_make_user(), expiration_delta_days=15000)
result = _run_check([credential])
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
iam = IAM(aws_provider)
# Mock service-specific credentials
from prowler.providers.aws.services.iam.iam_service import (
ServiceSpecificCredential,
User,
)
# Create a mock user
mock_user = User(
name="test_user",
arn=f"arn:aws:iam:{AWS_REGION_US_EAST_1}:123456789012:user/test_user",
attached_policies=[],
inline_policies=[],
)
# Create a mock service-specific credential with past expiration date
expiration_date = datetime.now(timezone.utc) - timedelta(days=30)
mock_credential = ServiceSpecificCredential(
arn=f"arn:aws:iam:{AWS_REGION_US_EAST_1}:123456789012:user/test_user/credential/test-credential-id",
user=mock_user,
status="Active",
create_date=datetime.now(timezone.utc),
service_user_name=None,
service_credential_alias=None,
expiration_date=expiration_date,
id="test-credential-id",
service_name="bedrock.amazonaws.com",
region=AWS_REGION_US_EAST_1,
)
iam.service_specific_credentials = [mock_credential]
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=aws_provider,
),
mock.patch(
"prowler.providers.aws.services.bedrock.bedrock_api_key_no_long_term_credentials.bedrock_api_key_no_long_term_credentials.iam_client",
new=iam,
),
):
from prowler.providers.aws.services.bedrock.bedrock_api_key_no_long_term_credentials.bedrock_api_key_no_long_term_credentials import (
bedrock_api_key_no_long_term_credentials,
)
check = bedrock_api_key_no_long_term_credentials()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert "has expired" in result[0].status_extended
assert "test-credential-id" in result[0].status_extended
assert "test_user" in result[0].status_extended
assert result[0].resource_id == "test-credential-id"
assert result[0].region == AWS_REGION_US_EAST_1
assert len(result) == 1
assert result[0].status == "FAIL"
assert result[0].check_metadata.Severity == Severity.critical
assert "configured to never expire" in result[0].status_extended
assert "short-term Bedrock API keys" in result[0].status_extended
@mock_aws
def test_bedrock_api_key_without_expiration_date_ignored(self):
from prowler.providers.aws.services.iam.iam_service import IAM
def test_already_expired_key_passes(self):
credential = _make_credential(_make_user(), expiration_delta_days=-30)
result = _run_check([credential])
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
iam = IAM(aws_provider)
# Mock service-specific credentials
from prowler.providers.aws.services.iam.iam_service import (
ServiceSpecificCredential,
User,
)
# Create a mock user
mock_user = User(
name="test_user",
arn=f"arn:aws:iam:{AWS_REGION_US_EAST_1}:123456789012:user/test_user",
attached_policies=[],
inline_policies=[],
)
# Create a mock service-specific credential without expiration date (should be ignored)
mock_credential = ServiceSpecificCredential(
arn=f"arn:aws:iam:{AWS_REGION_US_EAST_1}:123456789012:user/test_user/credential/test-credential-id",
user=mock_user,
status="Active",
create_date=datetime.now(timezone.utc),
service_user_name=None,
service_credential_alias=None,
expiration_date=None, # No expiration date - should be ignored
id="test-credential-id",
service_name="bedrock.amazonaws.com",
region=AWS_REGION_US_EAST_1,
)
iam.service_specific_credentials = [mock_credential]
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=aws_provider,
),
mock.patch(
"prowler.providers.aws.services.bedrock.bedrock_api_key_no_long_term_credentials.bedrock_api_key_no_long_term_credentials.iam_client",
new=iam,
),
):
from prowler.providers.aws.services.bedrock.bedrock_api_key_no_long_term_credentials.bedrock_api_key_no_long_term_credentials import (
bedrock_api_key_no_long_term_credentials,
)
check = bedrock_api_key_no_long_term_credentials()
result = check.execute()
assert len(result) == 0
assert len(result) == 1
assert result[0].status == "PASS"
assert "has already expired" in result[0].status_extended
@mock_aws
def test_non_bedrock_api_key_ignored(self):
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)
# Mock service-specific credentials
from prowler.providers.aws.services.iam.iam_service import (
ServiceSpecificCredential,
User,
)
# Create a mock user
mock_user = User(
name="test_user",
arn=f"arn:aws:iam:{AWS_REGION_US_EAST_1}:123456789012:user/test_user",
attached_policies=[],
inline_policies=[],
)
# Create a mock service-specific credential for a different service
expiration_date = datetime.now(timezone.utc) + timedelta(days=30)
mock_credential = ServiceSpecificCredential(
arn=f"arn:aws:iam:{AWS_REGION_US_EAST_1}:123456789012:user/test_user/credential/test-credential-id",
user=mock_user,
status="Active",
create_date=datetime.now(timezone.utc),
service_user_name=None,
service_credential_alias=None,
expiration_date=expiration_date,
id="test-credential-id",
service_name="codecommit.amazonaws.com", # Different service
region=AWS_REGION_US_EAST_1,
)
iam.service_specific_credentials = [mock_credential]
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=aws_provider,
),
mock.patch(
"prowler.providers.aws.services.bedrock.bedrock_api_key_no_long_term_credentials.bedrock_api_key_no_long_term_credentials.iam_client",
new=iam,
),
):
from prowler.providers.aws.services.bedrock.bedrock_api_key_no_long_term_credentials.bedrock_api_key_no_long_term_credentials import (
bedrock_api_key_no_long_term_credentials,
)
check = bedrock_api_key_no_long_term_credentials()
result = check.execute()
assert len(result) == 0
def test_key_without_expiration_date_ignored(self):
credential = _make_credential(_make_user(), expiration_delta_days=None)
assert _run_check([credential]) == []
@mock_aws
def test_multiple_bedrock_api_keys_mixed_scenarios(self):
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)
# Mock service-specific credentials
from prowler.providers.aws.services.iam.iam_service import (
ServiceSpecificCredential,
User,
def test_non_bedrock_service_ignored(self):
credential = _make_credential(
_make_user(),
expiration_delta_days=30,
service_name="codecommit.amazonaws.com",
)
assert _run_check([credential]) == []
# Create mock users
mock_user1 = User(
name="test_user1",
arn=f"arn:aws:iam:{AWS_REGION_US_EAST_1}:123456789012:user/test_user1",
attached_policies=[],
inline_policies=[],
@mock_aws
def test_mixed_scenarios(self):
user1, user2, user3 = (
_make_user("u1"),
_make_user("u2"),
_make_user("u3"),
)
mock_user2 = User(
name="test_user2",
arn=f"arn:aws:iam:{AWS_REGION_US_EAST_1}:123456789012:user/test_user2",
attached_policies=[],
inline_policies=[],
)
mock_user3 = User(
name="test_user3",
arn=f"arn:aws:iam:{AWS_REGION_US_EAST_1}:123456789012:user/test_user3",
attached_policies=[],
inline_policies=[],
)
# Create a mock service-specific credential with future expiration date
expiration_date1 = datetime.now(timezone.utc) + timedelta(days=30)
mock_credential1 = ServiceSpecificCredential(
arn=f"arn:aws:iam:{AWS_REGION_US_EAST_1}:123456789012:user/test_user1/credential/test-credential-id-1",
user=mock_user1,
status="Active",
create_date=datetime.now(timezone.utc),
service_user_name=None,
service_credential_alias=None,
expiration_date=expiration_date1,
id="test-credential-id-1",
service_name="bedrock.amazonaws.com",
region=AWS_REGION_US_EAST_1,
)
# Create a mock service-specific credential with critical expiration date
expiration_date2 = datetime.now(timezone.utc) + timedelta(days=15000)
mock_credential2 = ServiceSpecificCredential(
arn=f"arn:aws:iam:{AWS_REGION_US_EAST_1}:123456789012:user/test_user2/credential/test-credential-id-2",
user=mock_user2,
status="Active",
create_date=datetime.now(timezone.utc),
service_user_name=None,
service_credential_alias=None,
expiration_date=expiration_date2,
id="test-credential-id-2",
service_name="bedrock.amazonaws.com",
region=AWS_REGION_US_EAST_1,
)
# Create a mock service-specific credential with expired date
expiration_date3 = datetime.now(timezone.utc) - timedelta(days=30)
mock_credential3 = ServiceSpecificCredential(
arn=f"arn:aws:iam:{AWS_REGION_US_EAST_1}:123456789012:user/test_user3/credential/test-credential-id-3",
user=mock_user3,
status="Active",
create_date=datetime.now(timezone.utc),
service_user_name=None,
service_credential_alias=None,
expiration_date=expiration_date3,
id="test-credential-id-3",
service_name="bedrock.amazonaws.com",
region=AWS_REGION_US_EAST_1,
)
iam.service_specific_credentials = [
mock_credential1,
mock_credential2,
mock_credential3,
credentials = [
_make_credential(user1, "active-key", expiration_delta_days=191),
_make_credential(user2, "never-key", expiration_delta_days=15000),
_make_credential(user3, "expired-key", expiration_delta_days=-30),
]
result = _run_check(credentials)
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=aws_provider,
assert len(result) == 3
by_id = {r.resource_id: r for r in result}
assert by_id["active-key"].status == "FAIL"
assert by_id["active-key"].check_metadata.Severity == Severity.high
assert by_id["never-key"].status == "FAIL"
assert by_id["never-key"].check_metadata.Severity == Severity.critical
assert by_id["expired-key"].status == "PASS"
@mock_aws
def test_severity_does_not_leak_never_then_active(self):
"""Regression: a never-expires key processed before an active key must
not bleed `critical` severity into the active finding."""
credentials = [
_make_credential(
_make_user("u-never"), "never-key", expiration_delta_days=15000
),
mock.patch(
"prowler.providers.aws.services.bedrock.bedrock_api_key_no_long_term_credentials.bedrock_api_key_no_long_term_credentials.iam_client",
new=iam,
_make_credential(
_make_user("u-active"), "active-key", expiration_delta_days=191
),
):
from prowler.providers.aws.services.bedrock.bedrock_api_key_no_long_term_credentials.bedrock_api_key_no_long_term_credentials import (
bedrock_api_key_no_long_term_credentials,
)
]
result = _run_check(credentials)
check = bedrock_api_key_no_long_term_credentials()
result = check.execute()
by_id = {r.resource_id: r for r in result}
assert by_id["never-key"].check_metadata.Severity == Severity.critical
assert by_id["active-key"].check_metadata.Severity == Severity.high
assert len(result) == 3
@mock_aws
def test_severity_does_not_leak_active_then_never(self):
"""Regression: same as above with the reverse iteration order."""
credentials = [
_make_credential(
_make_user("u-active"), "active-key", expiration_delta_days=191
),
_make_credential(
_make_user("u-never"), "never-key", expiration_delta_days=15000
),
]
result = _run_check(credentials)
# Check the credential with future expiration date (FAIL)
fail_result1 = next(
r for r in result if r.resource_id == "test-credential-id-1"
)
assert fail_result1.status == "FAIL"
assert "will expire in" in fail_result1.status_extended
assert "test-credential-id-1" in fail_result1.status_extended
assert "test_user1" in fail_result1.status_extended
# Check the credential with critical expiration date (FAIL)
fail_result2 = next(
r for r in result if r.resource_id == "test-credential-id-2"
)
assert fail_result2.status == "FAIL"
assert "never expires" in fail_result2.status_extended
assert "test-credential-id-2" in fail_result2.status_extended
assert "test_user2" in fail_result2.status_extended
# Check the credential with expired date (PASS)
pass_result = next(
r for r in result if r.resource_id == "test-credential-id-3"
)
assert pass_result.status == "PASS"
assert "has expired" in pass_result.status_extended
assert "test-credential-id-3" in pass_result.status_extended
assert "test_user3" in pass_result.status_extended
by_id = {r.resource_id: r for r in result}
assert by_id["active-key"].check_metadata.Severity == Severity.high
assert by_id["never-key"].check_metadata.Severity == Severity.critical