CloudTrail trail records all S3 object-level API operations for all buckets
CloudTrail trails include S3 object-level data events for write (or all) operations across all current and future buckets, via classic or advanced selectors. This records actions like PutObject, DeleteObject, and multipart uploads at the object level.
Risk
Without object-level write logging, unauthorized or accidental changes and deletions can go unobserved, undermining data integrity and availability. Forensics lose visibility into who modified or removed objects, hindering detection of ransomware, rogue automation, or insider tampering.
Run this check with Prowler
Run in Prowler Cloudprowler aws --checks cloudtrail_s3_dataevents_write_enabledRecommendation
Enable CloudTrail S3 data events for object-level write (and optionally read) across all buckets on a multi-Region trail. Apply least privilege to log storage, set lifecycle retention, and integrate alerts. Use advanced selectors to target sensitive buckets/operations for cost control and defense in depth.
Remediation
CLI
aws cloudtrail put-event-selectors --trail-name <example_resource_name> --event-selectors '[{"ReadWriteType":"WriteOnly","DataResources":[{"Type":"AWS::S3::Object","Values":["arn:aws:s3"]}]}]'Terraform
resource "aws_cloudtrail" "<example_resource_name>" {
name = "<example_resource_name>"
s3_bucket_name = "<example_resource_name>"
event_selector {
read_write_type = "WriteOnly"
data_resource {
type = "AWS::S3::Object"
values = ["arn:aws:s3"] # Critical: logs S3 object-level write events for all buckets to pass the check
}
}
}NativeIaC
Resources:
<example_resource_name>:
Type: AWS::CloudTrail::Trail
Properties:
TrailName: <example_resource_name>
S3BucketName: <example_resource_name>
EventSelectors:
- ReadWriteType: WriteOnly
DataResources:
- Type: AWS::S3::Object
Values:
- arn:aws:s3 # Critical: enables S3 object-level write data events for all buckets, fixing the checkOther
Check Code
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.s3.s3_client import s3_client
class cloudtrail_s3_dataevents_write_enabled(Check):
def execute(self):
findings = []
if cloudtrail_client.trails is not None:
for trail in cloudtrail_client.trails.values():
for data_event in trail.data_events:
# Classic event selectors
if not data_event.is_advanced:
# Check if trail has a data event for all S3 Buckets for write
if (
data_event.event_selector["ReadWriteType"] == "All"
or data_event.event_selector["ReadWriteType"] == "WriteOnly"
):
for resource in data_event.event_selector["DataResources"]:
if "AWS::S3::Object" == resource["Type"] and (
f"arn:{cloudtrail_client.audited_partition}:s3"
in resource["Values"]
or f"arn:{cloudtrail_client.audited_partition}:s3:::"
in resource["Values"]
or f"arn:{cloudtrail_client.audited_partition}:s3:::*/*"
in resource["Values"]
):
report = Check_Report_AWS(
metadata=self.metadata(),
resource=trail,
)
report.region = trail.home_region
report.status = "PASS"
report.status_extended = f"Trail {trail.name} from home region {trail.home_region} has a classic data event selector to record all S3 object-level API operations."
findings.append(report)
# Advanced event selectors
elif data_event.is_advanced:
for field_selector in data_event.event_selector[
"FieldSelectors"
]:
if (
field_selector["Field"] == "resources.type"
and field_selector["Equals"][0] == "AWS::S3::Object"
):
report = Check_Report_AWS(
metadata=self.metadata(), resource=trail
)
report.region = trail.home_region
report.status = "PASS"
report.status_extended = f"Trail {trail.name} from home region {trail.home_region} has an advanced data event selector to record all S3 object-level API operations."
findings.append(report)
if not findings and (
s3_client.buckets or cloudtrail_client.provider.scan_unused_services
):
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 have a data event to record all S3 object-level API operations."
findings.append(report)
return findingsReferences
- https://docs.aws.amazon.com/awscloudtrail/latest/userguide/logging-data-events-with-cloudtrail.html
- https://docs.aws.amazon.com/AmazonS3/latest/userguide/enable-cloudtrail-logging-for-s3.html
- https://www.go2share.net/article/s3-bucket-logging
- https://docs.amazonaws.cn/en_us/AmazonS3/latest/userguide/cloudtrail-logging-s3-info.html
- https://docs.aws.amazon.com/securityhub/latest/userguide/s3-controls.html#s3-22