mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-10-09 21:14:22 +00:00
fix(bedrock): per-finding severity for long-term API key check (#11526)
This commit is contained in:
@@ -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)
|
||||
|
||||
---
|
||||
|
||||
|
||||
+10
-9
@@ -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."
|
||||
}
|
||||
|
||||
+47
-34
@@ -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
|
||||
|
||||
+162
-431
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user