feat(aws): add cloudtrail_bedrock_logging_enabled security check (#10858)

This commit is contained in:
Daniel Barranquero
2026-05-11 17:11:49 +02:00
committed by GitHub
parent 0b26c1a39c
commit 759f7b84d6
18 changed files with 1298 additions and 0 deletions
+1
View File
@@ -7,6 +7,7 @@ All notable changes to the **Prowler SDK** are documented in this file.
### 🚀 Added
- `iam_user_access_not_stale_to_sagemaker` check for aws provider with configurable `max_unused_sagemaker_access_days` (default 90) [(#11000)](https://github.com/prowler-cloud/prowler/pull/11000)
- `cloudtrail_bedrock_logging_enabled` check for AWS provider [(#10858)](https://github.com/prowler-cloud/prowler/pull/10858)
---
@@ -550,6 +550,7 @@
"apigatewayv2_api_access_logging_enabled",
"awslambda_function_invoke_api_operations_cloudtrail_logging_enabled",
"cloudfront_distributions_logging_enabled",
"cloudtrail_bedrock_logging_enabled",
"cloudtrail_cloudwatch_logging_enabled",
"cloudtrail_logs_s3_bucket_access_logging_enabled",
"directoryservice_directory_log_forwarding_enabled",
+2
View File
@@ -3461,6 +3461,7 @@
],
"Checks": [
"kinesis_stream_data_retention_period",
"cloudtrail_bedrock_logging_enabled",
"cloudtrail_multi_region_enabled_logging_management_events"
]
},
@@ -3669,6 +3670,7 @@
"awslambda_function_invoke_api_operations_cloudtrail_logging_enabled",
"bedrock_model_invocation_logging_enabled",
"cloudfront_distributions_logging_enabled",
"cloudtrail_bedrock_logging_enabled",
"cloudtrail_cloudwatch_logging_enabled",
"cloudtrail_logs_s3_bucket_access_logging_enabled",
"cloudtrail_multi_region_enabled_logging_management_events",
+1
View File
@@ -1958,6 +1958,7 @@
}
],
"Checks": [
"cloudtrail_bedrock_logging_enabled",
"cloudtrail_multi_region_enabled",
"cloudtrail_multi_region_enabled_logging_management_events",
"cloudtrail_cloudwatch_logging_enabled",
@@ -5857,6 +5857,7 @@
}
],
"Checks": [
"cloudtrail_bedrock_logging_enabled",
"cloudtrail_multi_region_enabled",
"cloudtrail_multi_region_enabled_logging_management_events",
"cloudtrail_s3_dataevents_read_enabled",
@@ -439,6 +439,7 @@
"cloudtrail_s3_dataevents_read_enabled",
"cloudtrail_s3_dataevents_write_enabled",
"cloudtrail_multi_region_enabled",
"cloudtrail_bedrock_logging_enabled",
"cloudtrail_cloudwatch_logging_enabled",
"elbv2_logging_enabled",
"elb_logging_enabled",
@@ -594,6 +595,7 @@
"cloudtrail_s3_dataevents_read_enabled",
"cloudtrail_s3_dataevents_write_enabled",
"cloudtrail_multi_region_enabled",
"cloudtrail_bedrock_logging_enabled",
"cloudtrail_cloudwatch_logging_enabled",
"elbv2_logging_enabled",
"elb_logging_enabled",
+1
View File
@@ -119,6 +119,7 @@
],
"Checks": [
"apigateway_restapi_logging_enabled",
"cloudtrail_bedrock_logging_enabled",
"cloudtrail_multi_region_enabled",
"cloudtrail_s3_dataevents_read_enabled",
"cloudtrail_s3_dataevents_write_enabled",
+2
View File
@@ -87,6 +87,7 @@
],
"Checks": [
"apigateway_restapi_logging_enabled",
"cloudtrail_bedrock_logging_enabled",
"cloudtrail_multi_region_enabled",
"cloudtrail_s3_dataevents_read_enabled",
"cloudtrail_s3_dataevents_write_enabled",
@@ -632,6 +633,7 @@
],
"Checks": [
"apigateway_restapi_logging_enabled",
"cloudtrail_bedrock_logging_enabled",
"cloudtrail_multi_region_enabled",
"cloudtrail_s3_dataevents_read_enabled",
"cloudtrail_s3_dataevents_write_enabled",
@@ -1294,6 +1294,7 @@
"bedrock_model_invocation_logging_enabled",
"bedrock_model_invocation_logs_encryption_enabled",
"cloudfront_distributions_logging_enabled",
"cloudtrail_bedrock_logging_enabled",
"cloudtrail_cloudwatch_logging_enabled",
"cloudtrail_kms_encryption_enabled",
"cloudtrail_log_file_validation_enabled",
@@ -2540,6 +2540,7 @@
"bedrock_model_invocation_logging_enabled",
"bedrock_model_invocation_logs_encryption_enabled",
"cloudfront_distributions_logging_enabled",
"cloudtrail_bedrock_logging_enabled",
"cloudtrail_bucket_requires_mfa_delete",
"cloudtrail_cloudwatch_logging_enabled",
"cloudtrail_insights_exist",
@@ -1604,6 +1604,7 @@
"cloudtrail_s3_dataevents_read_enabled",
"cloudtrail_s3_dataevents_write_enabled",
"cloudtrail_multi_region_enabled",
"cloudtrail_bedrock_logging_enabled",
"cloudtrail_cloudwatch_logging_enabled",
"elbv2_logging_enabled",
"elb_logging_enabled",
@@ -2162,6 +2163,7 @@
"cloudtrail_s3_dataevents_read_enabled",
"cloudtrail_s3_dataevents_write_enabled",
"cloudtrail_multi_region_enabled",
"cloudtrail_bedrock_logging_enabled",
"cloudtrail_cloudwatch_logging_enabled",
"elbv2_logging_enabled",
"elb_logging_enabled",
@@ -2189,6 +2191,7 @@
"cloudtrail_s3_dataevents_read_enabled",
"cloudtrail_s3_dataevents_write_enabled",
"cloudtrail_multi_region_enabled",
"cloudtrail_bedrock_logging_enabled",
"cloudtrail_cloudwatch_logging_enabled",
"elbv2_logging_enabled",
"elb_logging_enabled",
@@ -1312,6 +1312,7 @@
}
],
"Checks": [
"cloudtrail_bedrock_logging_enabled",
"cloudtrail_kms_encryption_enabled",
"cloudtrail_log_file_validation_enabled",
"cloudtrail_logs_s3_bucket_access_logging_enabled",
@@ -1475,6 +1476,7 @@
"cloudtrail_threat_detection_enumeration",
"cloudtrail_threat_detection_privilege_escalation",
"cloudtrail_threat_detection_llm_jacking",
"cloudtrail_bedrock_logging_enabled",
"cloudtrail_cloudwatch_logging_enabled",
"cloudtrail_multi_region_enabled_logging_management_events"
]
@@ -1571,6 +1573,7 @@
"cloudtrail_threat_detection_llm_jacking",
"cloudtrail_threat_detection_enumeration",
"cloudtrail_multi_region_enabled_logging_management_events",
"cloudtrail_bedrock_logging_enabled",
"cloudtrail_cloudwatch_logging_enabled",
"cloudwatch_log_metric_filter_unauthorized_api_calls",
"cloudwatch_log_metric_filter_authentication_failures",
@@ -818,6 +818,7 @@
}
],
"Checks": [
"cloudtrail_bedrock_logging_enabled",
"cloudtrail_multi_region_enabled",
"cloudtrail_multi_region_enabled_logging_management_events",
"cloudtrail_s3_dataevents_read_enabled",
+1
View File
@@ -346,6 +346,7 @@
}
],
"Checks": [
"cloudtrail_bedrock_logging_enabled",
"cloudtrail_cloudwatch_logging_enabled",
"cloudwatch_changes_to_network_acls_alarm_configured",
"cloudwatch_changes_to_network_gateways_alarm_configured",
@@ -0,0 +1,44 @@
{
"Provider": "aws",
"CheckID": "cloudtrail_bedrock_logging_enabled",
"CheckTitle": "CloudTrail logs Amazon Bedrock API calls for security auditing",
"CheckType": [
"Software and Configuration Checks/AWS Security Best Practices"
],
"ServiceName": "cloudtrail",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "medium",
"ResourceType": "AwsCloudTrailTrail",
"ResourceGroup": "monitoring",
"Description": "**At least one actively logging CloudTrail trail** records **Amazon Bedrock API activity** through management events or advanced event selectors targeting Bedrock resources.\n\nThis check covers **control-plane** operations such as configuration changes through CloudTrail management events and can also cover **data-plane** Bedrock events when advanced event selectors target Bedrock resource types.",
"Risk": "Without CloudTrail logging for Bedrock control-plane operations, changes to prompts, guardrails, agents, flows, or knowledge bases can become invisible, weakening forensics and incident response. Management events do not capture `InvokeModel`; pair this control with `bedrock_model_invocation_logging_enabled` or Bedrock data event selectors for invocation visibility.",
"RelatedUrl": "",
"AdditionalURLs": [
"https://docs.aws.amazon.com/bedrock/latest/userguide/logging-using-cloudtrail.html",
"https://docs.aws.amazon.com/awscloudtrail/latest/userguide/logging-data-events-with-cloudtrail.html"
],
"Remediation": {
"Code": {
"CLI": "aws cloudtrail put-event-selectors --trail-name <example_resource_name> --advanced-event-selectors '[{\"Name\":\"Bedrock data events\",\"FieldSelectors\":[{\"Field\":\"eventCategory\",\"Equals\":[\"Data\"]},{\"Field\":\"resources.type\",\"Equals\":[\"AWS::Bedrock::Model\",\"AWS::Bedrock::Guardrail\",\"AWS::Bedrock::AgentAlias\",\"AWS::Bedrock::FlowAlias\",\"AWS::Bedrock::InlineAgent\",\"AWS::Bedrock::KnowledgeBase\",\"AWS::Bedrock::Prompt\"]}]}]'",
"NativeIaC": "```yaml\n# CloudFormation: enable Bedrock data event logging on an actively logging trail\nResources:\n ExampleTrail:\n Type: AWS::CloudTrail::Trail\n Properties:\n TrailName: <example_resource_name>\n S3BucketName: <example_resource_name>\n IsLogging: true\n AdvancedEventSelectors:\n - Name: Bedrock data events\n FieldSelectors:\n - Field: eventCategory\n Equals:\n - Data\n - Field: resources.type # CRITICAL: target Bedrock resources\n Equals:\n - AWS::Bedrock::Model\n - AWS::Bedrock::Guardrail\n - AWS::Bedrock::AgentAlias\n - AWS::Bedrock::FlowAlias\n - AWS::Bedrock::InlineAgent\n - AWS::Bedrock::KnowledgeBase\n - AWS::Bedrock::Prompt\n```",
"Other": "1. In the AWS Console, open CloudTrail and select a trail that is actively logging\n2. Edit the trail and enable Management events to capture Bedrock control-plane operations, or add Bedrock advanced data event selectors for data-plane visibility\n3. If using data events, select the Bedrock resource types you want to log\n4. Save changes and confirm the trail remains in logging state",
"Terraform": "```hcl\n# Terraform: enable Bedrock data event logging on an actively logging trail\nresource \"aws_cloudtrail\" \"example_resource\" {\n name = \"example_resource\"\n s3_bucket_name = \"example_resource\"\n\n advanced_event_selector {\n name = \"Bedrock data events\"\n field_selector {\n field = \"eventCategory\"\n equals = [\"Data\"]\n }\n field_selector {\n field = \"resources.type\" # CRITICAL: target Bedrock resources\n equals = [\"AWS::Bedrock::Model\", \"AWS::Bedrock::Guardrail\", \"AWS::Bedrock::AgentAlias\", \"AWS::Bedrock::FlowAlias\", \"AWS::Bedrock::InlineAgent\", \"AWS::Bedrock::KnowledgeBase\", \"AWS::Bedrock::Prompt\"]\n }\n }\n}\n```"
},
"Recommendation": {
"Text": "Enable CloudTrail logging for Amazon Bedrock on **at least one actively logging trail**. At minimum, enable **management events** to capture Bedrock control-plane operations. For invocation-level and other data-plane visibility, add **advanced event selectors** targeting Bedrock resource types or pair this control with `bedrock_model_invocation_logging_enabled`.\n\nFor broader region coverage, pair this control with a separate multi-region CloudTrail check. Centralize logs in an encrypted bucket or CloudWatch Logs to support **defense in depth** and forensic readiness for AI workloads.",
"Url": "https://hub.prowler.com/check/cloudtrail_bedrock_logging_enabled"
}
},
"Categories": [
"logging",
"forensics-ready",
"gen-ai"
],
"DependsOn": [],
"RelatedTo": [
"cloudtrail_multi_region_enabled_logging_management_events",
"bedrock_model_invocation_logging_enabled"
],
"Notes": "This check passes when CloudTrail captures Bedrock control-plane activity via management events or Bedrock data events via advanced selectors. It does not require multi-region coverage, and it does not by itself guarantee `InvokeModel` visibility unless Bedrock data events are selected; use `bedrock_model_invocation_logging_enabled` for model invocation logs. Additional advanced selector filters such as `eventName` or `resources.ARN` can further narrow effective coverage and should be reviewed explicitly."
}
@@ -0,0 +1,213 @@
from prowler.lib.check.models import Check, Check_Report_AWS
from prowler.providers.aws.services.cloudtrail.cloudtrail_client import (
cloudtrail_client,
)
from prowler.providers.aws.services.cloudtrail.cloudtrail_service import (
Event_Selector,
)
class cloudtrail_bedrock_logging_enabled(Check):
"""Ensure CloudTrail is configured to log Amazon Bedrock API calls.
This check verifies whether at least one CloudTrail trail is configured to
capture Amazon Bedrock control-plane API calls through management events or
Bedrock data events through advanced event selectors.
- PASS: A trail logs Bedrock control-plane API calls via management events
or Bedrock data events via Bedrock-specific advanced event selectors.
- FAIL: No CloudTrail trail is configured to log Bedrock API calls.
"""
# Bedrock resource types supported by CloudTrail advanced event selectors.
BEDROCK_RESOURCE_TYPES = frozenset(
{
"AWS::Bedrock::AgentAlias",
"AWS::Bedrock::FlowAlias",
"AWS::Bedrock::Guardrail",
"AWS::Bedrock::InlineAgent",
"AWS::Bedrock::KnowledgeBase",
"AWS::Bedrock::Model",
"AWS::Bedrock::Prompt",
}
)
# Bedrock control-plane event sources, including Bedrock Data Automation.
BEDROCK_EVENT_SOURCES = frozenset(
{
"bedrock.amazonaws.com",
"bedrock-agent.amazonaws.com",
"bedrock-runtime.amazonaws.com",
"bedrock-agent-runtime.amazonaws.com",
"bedrock-data-automation.amazonaws.com",
"bedrock-data-automation-runtime.amazonaws.com",
}
)
def execute(self) -> list[Check_Report_AWS]:
"""Execute the check logic.
Returns:
A list of reports containing the result of the check.
"""
findings = []
if cloudtrail_client.trails is not None:
for trail in cloudtrail_client.trails.values():
if trail.is_logging:
for data_event in trail.data_events:
match_type = self._get_bedrock_match_type(data_event)
if match_type:
report = Check_Report_AWS(
metadata=self.metadata(), resource=trail
)
report.region = trail.home_region
report.status = "PASS"
if match_type == "classic_management":
report.status_extended = (
f"Trail {trail.name} from home region "
f"{trail.home_region} has management events "
"enabled to log Amazon Bedrock control-plane "
"API calls."
)
elif match_type == "advanced_management":
report.status_extended = (
f"Trail {trail.name} from home region "
f"{trail.home_region} has an advanced "
"management event selector to log Amazon "
"Bedrock control-plane API calls."
)
else:
report.status_extended = (
f"Trail {trail.name} from home region "
f"{trail.home_region} has an advanced data "
"event selector to log Amazon Bedrock API "
"calls."
)
findings.append(report)
break
if not findings:
report = Check_Report_AWS(
metadata=self.metadata(), resource=cloudtrail_client.trails
)
report.region = cloudtrail_client.region
report.resource_arn = cloudtrail_client.trail_arn_template
report.resource_id = cloudtrail_client.audited_account
report.status = "FAIL"
report.status_extended = "No CloudTrail trails are configured to log Amazon Bedrock API calls."
findings.append(report)
return findings
def _get_bedrock_match_type(self, data_event: Event_Selector) -> str | None:
"""Return the Bedrock logging match type for an event selector.
Args:
data_event: An Event_Selector object from the trail.
Returns:
The matching selector type, or None if the selector does not log
the Bedrock events covered by this check.
"""
if not data_event.is_advanced:
if self._logs_classic_management_events(data_event.event_selector):
return "classic_management"
return None
field_selectors = data_event.event_selector.get("FieldSelectors", [])
if self._logs_advanced_management_events(field_selectors):
return "advanced_management"
if self._logs_advanced_bedrock_data_events(field_selectors):
return "advanced_data"
return None
@staticmethod
def _logs_classic_management_events(event_selector: dict) -> bool:
"""Check whether a classic selector logs Bedrock control-plane events."""
return event_selector.get(
"IncludeManagementEvents", True
) and event_selector.get("ReadWriteType", "All") in ("All", "WriteOnly")
def _logs_advanced_management_events(self, field_selectors: list[dict]) -> bool:
"""Check whether advanced selectors log Bedrock control-plane events."""
event_category_selectors = [
field for field in field_selectors if field.get("Field") == "eventCategory"
]
if not self._selectors_match_value("Management", event_category_selectors):
return False
read_only_selectors = [
field for field in field_selectors if field.get("Field") == "readOnly"
]
has_read_only_restriction = bool(read_only_selectors) and not any(
self._field_selector_matches_value("false", selector)
for selector in read_only_selectors
)
return not has_read_only_restriction and self._logs_bedrock_management_events(
field_selectors
)
def _logs_advanced_bedrock_data_events(self, field_selectors: list[dict]) -> bool:
"""Check whether advanced selectors log Bedrock data events."""
event_category_selectors = [
field for field in field_selectors if field.get("Field") == "eventCategory"
]
if not self._selectors_match_value("Data", event_category_selectors):
return False
resource_type_selectors = [
field for field in field_selectors if field.get("Field") == "resources.type"
]
return any(
self._selectors_match_value(resource_type, resource_type_selectors)
for resource_type in self.BEDROCK_RESOURCE_TYPES
)
def _logs_bedrock_management_events(self, field_selectors: list[dict]) -> bool:
"""Check whether advanced management selectors include Bedrock sources."""
event_source_selectors = [
field for field in field_selectors if field.get("Field") == "eventSource"
]
if not event_source_selectors:
return True
return any(
self._selectors_match_value(event_source, event_source_selectors)
for event_source in self.BEDROCK_EVENT_SOURCES
)
def _selectors_match_value(self, value: str, selectors: list[dict]) -> bool:
"""Check whether a candidate value satisfies all selectors for a field."""
return bool(selectors) and all(
self._field_selector_matches_value(value, selector)
for selector in selectors
)
@staticmethod
def _field_selector_matches_value(value: str, selector: dict) -> bool:
"""Evaluate a CloudTrail advanced field selector against a candidate value."""
conditions = []
if "Equals" in selector:
conditions.append(value in selector["Equals"])
if "NotEquals" in selector:
conditions.append(value not in selector["NotEquals"])
if "StartsWith" in selector:
conditions.append(
any(value.startswith(prefix) for prefix in selector["StartsWith"])
)
if "NotStartsWith" in selector:
conditions.append(
all(
not value.startswith(prefix) for prefix in selector["NotStartsWith"]
)
)
if "EndsWith" in selector:
conditions.append(
any(value.endswith(suffix) for suffix in selector["EndsWith"])
)
if "NotEndsWith" in selector:
conditions.append(
all(not value.endswith(suffix) for suffix in selector["NotEndsWith"])
)
return all(conditions) if conditions else True