feat(aws): add bedrock_guardrails_configured security check (#10844)

This commit is contained in:
Daniel Barranquero
2026-04-28 14:16:19 +02:00
committed by GitHub
parent c76a9baa20
commit 8b368e1343
9 changed files with 428 additions and 8 deletions
+8
View File
@@ -2,6 +2,14 @@
All notable changes to the **Prowler SDK** are documented in this file.
## [5.26.0] (Prowler UNRELEASED)
### 🚀 Added
- `bedrock_guardrails_configured` check for AWS provider [(#10844)](https://github.com/prowler-cloud/prowler/pull/10844)
---
## [5.25.0] (Prowler v5.25.0)
### 🚀 Added
+8 -8
View File
@@ -6426,9 +6426,9 @@
}
],
"Checks": [
"bedrock_agent_guardrail_enabled",
"bedrock_guardrail_prompt_attack_filter_enabled",
"bedrock_guardrail_sensitive_information_filter_enabled",
"bedrock_agent_guardrail_enabled"
"bedrock_guardrail_sensitive_information_filter_enabled"
]
},
{
@@ -6485,9 +6485,9 @@
}
],
"Checks": [
"bedrock_agent_guardrail_enabled",
"bedrock_guardrail_prompt_attack_filter_enabled",
"bedrock_guardrail_sensitive_information_filter_enabled",
"bedrock_agent_guardrail_enabled"
"bedrock_guardrail_sensitive_information_filter_enabled"
]
},
{
@@ -6546,8 +6546,8 @@
}
],
"Checks": [
"bedrock_guardrail_sensitive_information_filter_enabled",
"bedrock_agent_guardrail_enabled"
"bedrock_agent_guardrail_enabled",
"bedrock_guardrail_sensitive_information_filter_enabled"
]
},
{
@@ -6606,8 +6606,8 @@
}
],
"Checks": [
"bedrock_guardrail_sensitive_information_filter_enabled",
"bedrock_agent_guardrail_enabled"
"bedrock_agent_guardrail_enabled",
"bedrock_guardrail_sensitive_information_filter_enabled"
]
},
{
@@ -2894,6 +2894,7 @@
"bedrock_agent_guardrail_enabled",
"bedrock_guardrail_prompt_attack_filter_enabled",
"bedrock_guardrail_sensitive_information_filter_enabled",
"bedrock_guardrails_configured",
"bedrock_model_invocation_logging_enabled",
"bedrock_model_invocation_logs_encryption_enabled",
"cloudformation_stack_outputs_find_secrets",
@@ -2898,6 +2898,7 @@
"bedrock_agent_guardrail_enabled",
"bedrock_guardrail_prompt_attack_filter_enabled",
"bedrock_guardrail_sensitive_information_filter_enabled",
"bedrock_guardrails_configured",
"bedrock_model_invocation_logging_enabled",
"bedrock_model_invocation_logs_encryption_enabled",
"cloudformation_stack_outputs_find_secrets",
@@ -0,0 +1,44 @@
{
"Provider": "aws",
"CheckID": "bedrock_guardrails_configured",
"CheckTitle": "Bedrock has at least one guardrail configured in the audited region",
"CheckType": [
"Software and Configuration Checks/AWS Security Best Practices",
"Software and Configuration Checks/AWS Security Best Practices/Runtime Behavior Analysis"
],
"ServiceName": "bedrock",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "medium",
"ResourceType": "Other",
"ResourceGroup": "ai_ml",
"Description": "**Amazon Bedrock guardrails** provide reusable safety policies for filtering harmful or unwanted content in model inputs and outputs.\n\nThis evaluation checks whether at least one guardrail exists in each successfully scanned region. It does **not** verify that guardrails are attached to agents or passed on individual model invocation API calls.",
"Risk": "Without any configured **Bedrock guardrails** in a region, teams lack a native reusable policy object for **content filtering** and **safety controls**. Applications may invoke models without standardized protections against **harmful content**, **prompt injection**, or **sensitive-data exposure** unless equivalent controls are enforced elsewhere.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://docs.aws.amazon.com/bedrock/latest/userguide/guardrails.html",
"https://docs.aws.amazon.com/bedrock/latest/userguide/guardrails-create.html"
],
"Remediation": {
"Code": {
"CLI": "aws bedrock create-guardrail --name example_resource --blocked-input-messaging 'Blocked' --blocked-outputs-messaging 'Blocked' --content-policy-config 'filtersConfig=[{type=HATE,inputStrength=HIGH,outputStrength=HIGH}]'",
"NativeIaC": "```yaml\nResources:\n example_resource:\n Type: AWS::Bedrock::Guardrail\n Properties:\n Name: example_resource\n BlockedInputMessaging: \"Blocked\"\n BlockedOutputsMessaging: \"Blocked\"\n ContentPolicyConfig:\n FiltersConfig:\n - Type: HATE\n InputStrength: HIGH # Critical: configures content filtering\n OutputStrength: HIGH\n```",
"Other": "1. Open the AWS Console and go to Amazon Bedrock\n2. Select **Guardrails** from the navigation pane\n3. Click **Create guardrail**\n4. Configure content filters for harmful categories\n5. Set input and output messaging for blocked content\n6. Click **Create guardrail**",
"Terraform": "```hcl\nresource \"aws_bedrock_guardrail\" \"example_resource\" {\n name = \"example_resource\"\n blocked_input_messaging = \"Blocked\"\n blocked_outputs_messaging = \"Blocked\"\n\n content_policy_config {\n filters_config {\n type = \"HATE\" # Critical: configures content filtering\n input_strength = \"HIGH\"\n output_strength = \"HIGH\"\n }\n }\n}\n```"
},
"Recommendation": {
"Text": "Create at least one **Bedrock guardrail** in each region where Bedrock is used, then separately ensure those guardrails are attached to relevant agents and invocation paths.\n- Configure **content filters** for harmful categories (hate, violence, sexual, misconduct)\n- Add **sensitive information filters** and **denied topic policies**\n- Apply guardrails at the API call level using `guardrailIdentifier` where supported",
"Url": "https://hub.prowler.com/check/bedrock_guardrails_configured"
}
},
"Categories": [
"gen-ai"
],
"DependsOn": [],
"RelatedTo": [
"bedrock_guardrail_prompt_attack_filter_enabled",
"bedrock_guardrail_sensitive_information_filter_enabled",
"bedrock_agent_guardrail_enabled"
],
"Notes": "This check validates guardrail existence per successfully scanned region. It does not verify attachment to agents or the use of guardrails on model invocations. Regions where Bedrock guardrails cannot be enumerated are skipped to avoid false failures."
}
@@ -0,0 +1,50 @@
from prowler.lib.check.models import Check, Check_Report_AWS
from prowler.providers.aws.services.bedrock.bedrock_client import bedrock_client
class bedrock_guardrails_configured(Check):
"""Ensure Bedrock guardrails are configured in successfully scanned regions.
This check verifies that at least one Amazon Bedrock guardrail is configured
in each successfully scanned region.
- PASS: At least one Bedrock guardrail is configured in the region.
- FAIL: No Bedrock guardrails are configured in the region.
"""
def execute(self) -> list[Check_Report_AWS]:
"""Execute the check logic.
Returns:
A list of reports containing the result of the check.
"""
findings = []
for region in sorted(bedrock_client.guardrails_scanned_regions):
regional_guardrails = sorted(
(
guardrail
for guardrail in bedrock_client.guardrails.values()
if guardrail.region == region
),
key=lambda guardrail: guardrail.name,
)
if regional_guardrails:
for guardrail in regional_guardrails:
report = Check_Report_AWS(
metadata=self.metadata(), resource=guardrail
)
report.status = "PASS"
report.status_extended = f"Bedrock guardrail {guardrail.name} is available in region {region}. This does not confirm that the guardrail is attached to agents or used on model invocations."
findings.append(report)
else:
report = Check_Report_AWS(metadata=self.metadata(), resource={})
report.region = region
report.resource_id = "bedrock-guardrails"
report.resource_arn = f"arn:{bedrock_client.audited_partition}:bedrock:{region}:{bedrock_client.audited_account}:guardrails"
report.status = "FAIL"
report.status_extended = (
f"Bedrock has no guardrails configured in region {region}."
)
findings.append(report)
return findings
@@ -1,5 +1,6 @@
from typing import Optional
from botocore.exceptions import ClientError
from pydantic.v1 import BaseModel
from prowler.lib.logger import logger
@@ -13,6 +14,8 @@ class Bedrock(AWSService):
super().__init__(__class__.__name__, provider)
self.logging_configurations = {}
self.guardrails = {}
self.guardrails_scanned_regions = set()
self.guardrails_scan_errors = {}
self.__threading_call__(self._get_model_invocation_logging_configuration)
self.__threading_call__(self._list_guardrails)
self.__threading_call__(self._get_guardrail, self.guardrails.values())
@@ -67,7 +70,18 @@ class Bedrock(AWSService):
arn=guardrail["arn"],
region=regional_client.region,
)
self.guardrails_scanned_regions.add(regional_client.region)
except ClientError as error:
self.guardrails_scan_errors[regional_client.region] = error.response[
"Error"
].get("Code", error.__class__.__name__)
logger.error(
f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
except Exception as error:
self.guardrails_scan_errors[regional_client.region] = (
error.__class__.__name__
)
logger.error(
f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
@@ -0,0 +1,302 @@
from unittest import mock
import botocore
from botocore.exceptions import ClientError
from moto import mock_aws
from tests.providers.aws.utils import (
AWS_ACCOUNT_NUMBER,
AWS_REGION_EU_WEST_1,
AWS_REGION_US_EAST_1,
set_mocked_aws_provider,
)
make_api_call = botocore.client.BaseClient._make_api_call
GUARDRAIL_ARN = (
f"arn:aws:bedrock:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:guardrail/test-id"
)
def mock_make_api_call_with_guardrail(self, operation_name, kwarg):
"""Mock API call returning one guardrail in us-east-1."""
if operation_name == "ListGuardrails":
return {
"guardrails": [
{
"id": "test-id",
"arn": GUARDRAIL_ARN,
"status": "READY",
"name": "test-guardrail",
}
]
}
elif operation_name == "GetGuardrail":
return {
"name": "test-guardrail",
"guardrailId": "test-id",
"guardrailArn": GUARDRAIL_ARN,
"status": "READY",
"blockedInputMessaging": "Blocked",
"blockedOutputsMessaging": "Blocked",
"contentPolicy": {"filters": []},
}
return make_api_call(self, operation_name, kwarg)
def mock_make_api_call_without_guardrails(self, operation_name, kwarg):
"""Mock API call returning no guardrails."""
if operation_name == "ListGuardrails":
return {"guardrails": []}
return make_api_call(self, operation_name, kwarg)
def mock_make_api_call_guardrails_only_in_us_east_1(self, operation_name, kwarg):
"""Mock API call returning a guardrail only in us-east-1 and none elsewhere."""
if operation_name == "ListGuardrails":
if self.meta.region_name == AWS_REGION_US_EAST_1:
return {
"guardrails": [
{
"id": "test-id",
"arn": GUARDRAIL_ARN,
"status": "READY",
"name": "test-guardrail",
}
]
}
return {"guardrails": []}
elif operation_name == "GetGuardrail":
return {
"name": "test-guardrail",
"guardrailId": "test-id",
"guardrailArn": GUARDRAIL_ARN,
"status": "READY",
"blockedInputMessaging": "Blocked",
"blockedOutputsMessaging": "Blocked",
"contentPolicy": {"filters": []},
}
return make_api_call(self, operation_name, kwarg)
def mock_make_api_call_guardrail_validation_exception(self, operation_name, kwarg):
"""Mock API call raising ValidationException for ListGuardrails."""
if operation_name == "ListGuardrails":
raise ClientError(
{
"Error": {
"Code": "ValidationException",
"Message": "Guardrails are not supported in this region.",
}
},
operation_name,
)
return make_api_call(self, operation_name, kwarg)
class Test_bedrock_guardrails_configured:
@mock.patch(
"botocore.client.BaseClient._make_api_call",
new=mock_make_api_call_without_guardrails,
)
@mock_aws
def test_no_guardrails_single_region(self):
"""Test FAIL when no guardrails are configured in a single region."""
from prowler.providers.aws.services.bedrock.bedrock_service import Bedrock
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_guardrails_configured.bedrock_guardrails_configured.bedrock_client",
new=Bedrock(aws_provider),
),
):
from prowler.providers.aws.services.bedrock.bedrock_guardrails_configured.bedrock_guardrails_configured import (
bedrock_guardrails_configured,
)
check = bedrock_guardrails_configured()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== f"Bedrock has no guardrails configured in region {AWS_REGION_US_EAST_1}."
)
assert result[0].resource_id == "bedrock-guardrails"
assert (
result[0].resource_arn
== f"arn:aws:bedrock:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:guardrails"
)
assert result[0].region == AWS_REGION_US_EAST_1
assert result[0].resource_tags == []
@mock_aws
@mock.patch(
"botocore.client.BaseClient._make_api_call",
new=mock_make_api_call_without_guardrails,
)
def test_no_guardrails_multi_region(self):
"""Test FAIL in both regions when no guardrails are configured."""
from prowler.providers.aws.services.bedrock.bedrock_service import Bedrock
aws_provider = set_mocked_aws_provider(
[AWS_REGION_EU_WEST_1, 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_guardrails_configured.bedrock_guardrails_configured.bedrock_client",
new=Bedrock(aws_provider),
),
):
from prowler.providers.aws.services.bedrock.bedrock_guardrails_configured.bedrock_guardrails_configured import (
bedrock_guardrails_configured,
)
check = bedrock_guardrails_configured()
result = check.execute()
assert len(result) == 2
assert result[0].status == "FAIL"
assert result[0].resource_id == "bedrock-guardrails"
assert result[0].resource_tags == []
assert result[1].status == "FAIL"
assert result[1].resource_id == "bedrock-guardrails"
assert result[1].resource_tags == []
@mock.patch(
"botocore.client.BaseClient._make_api_call",
new=mock_make_api_call_with_guardrail,
)
@mock_aws
def test_guardrail_configured(self):
"""Test PASS when at least one guardrail is configured in the region."""
from prowler.providers.aws.services.bedrock.bedrock_service import Bedrock
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_guardrails_configured.bedrock_guardrails_configured.bedrock_client",
new=Bedrock(aws_provider),
),
):
from prowler.providers.aws.services.bedrock.bedrock_guardrails_configured.bedrock_guardrails_configured import (
bedrock_guardrails_configured,
)
check = bedrock_guardrails_configured()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert (
result[0].status_extended
== f"Bedrock guardrail test-guardrail is available in region {AWS_REGION_US_EAST_1}. This does not confirm that the guardrail is attached to agents or used on model invocations."
)
assert result[0].resource_id == "test-id"
assert result[0].resource_arn == GUARDRAIL_ARN
assert result[0].region == AWS_REGION_US_EAST_1
assert result[0].resource_tags == []
@mock.patch(
"botocore.client.BaseClient._make_api_call",
new=mock_make_api_call_guardrails_only_in_us_east_1,
)
@mock_aws
def test_guardrails_in_one_region_only(self):
"""Test PASS in the region with a guardrail and FAIL in the region without one."""
from prowler.providers.aws.services.bedrock.bedrock_service import Bedrock
aws_provider = set_mocked_aws_provider(
[AWS_REGION_EU_WEST_1, 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_guardrails_configured.bedrock_guardrails_configured.bedrock_client",
new=Bedrock(aws_provider),
),
):
from prowler.providers.aws.services.bedrock.bedrock_guardrails_configured.bedrock_guardrails_configured import (
bedrock_guardrails_configured,
)
check = bedrock_guardrails_configured()
result = check.execute()
assert len(result) == 2
results_by_region = {r.region: r for r in result}
eu_result = results_by_region[AWS_REGION_EU_WEST_1]
assert eu_result.status == "FAIL"
assert (
eu_result.status_extended
== f"Bedrock has no guardrails configured in region {AWS_REGION_EU_WEST_1}."
)
assert eu_result.resource_id == "bedrock-guardrails"
assert (
eu_result.resource_arn
== f"arn:aws:bedrock:{AWS_REGION_EU_WEST_1}:{AWS_ACCOUNT_NUMBER}:guardrails"
)
assert eu_result.resource_tags == []
us_result = results_by_region[AWS_REGION_US_EAST_1]
assert us_result.status == "PASS"
assert (
us_result.status_extended
== f"Bedrock guardrail test-guardrail is available in region {AWS_REGION_US_EAST_1}. This does not confirm that the guardrail is attached to agents or used on model invocations."
)
assert us_result.resource_id == "test-id"
assert us_result.resource_arn == GUARDRAIL_ARN
assert us_result.resource_tags == []
@mock.patch(
"botocore.client.BaseClient._make_api_call",
new=mock_make_api_call_guardrail_validation_exception,
)
@mock_aws
def test_guardrails_unsupported_region_is_skipped(self):
"""Test unsupported regions are skipped instead of failing."""
from prowler.providers.aws.services.bedrock.bedrock_service import Bedrock
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_guardrails_configured.bedrock_guardrails_configured.bedrock_client",
new=Bedrock(aws_provider),
),
):
from prowler.providers.aws.services.bedrock.bedrock_guardrails_configured.bedrock_guardrails_configured import (
bedrock_guardrails_configured,
)
check = bedrock_guardrails_configured()
result = check.execute()
assert len(result) == 0