CloudTrail S3 Data Events Write Enabled
Overview
This check verifies that your AWS CloudTrail trails are recording S3 object-level write operations (such as PutObject, DeleteObject, and multipart uploads) across all current and future S3 buckets. By default, CloudTrail only logs management events (like creating or deleting buckets), not data events (like uploading or deleting objects).
Risk
Without S3 write event logging, unauthorized modifications and deletions to your data can go undetected. This creates serious security blind spots:
- Ransomware attacks that encrypt or delete your files may not trigger any alerts
- Data tampering by malicious insiders or compromised credentials goes unnoticed
- Accidental deletions cannot be traced back to the source
- Forensic investigations are hampered because there is no record of who changed what
- Compliance violations occur for frameworks requiring object-level audit trails (HIPAA, PCI-DSS, FedRAMP)
Remediation Steps
Prerequisites
You need:
- AWS Console access with permissions to modify CloudTrail
- An existing CloudTrail trail (or permission to create one)
Required IAM permissions (for administrators)
Your IAM user or role needs these permissions:
cloudtrail:PutEventSelectorscloudtrail:GetEventSelectorscloudtrail:DescribeTrails
AWS Console Method
-
Open CloudTrail in the AWS Console
- Go to CloudTrail Console in us-east-1
-
Select your trail
- Click Trails in the left sidebar
- Click on the trail name you want to configure
-
Edit Data events
- Scroll to the Data events section
- Click Edit
-
Add S3 data event logging
- Under Data event type, select S3
- For Log selector template, choose Log all events or Log only write events
- Under Selector scope, choose All current and future S3 buckets to cover every bucket automatically
-
Save your changes
- Click Save changes
- Events will start being recorded within a few minutes
Important: Do not use the same S3 bucket for both storing CloudTrail logs and monitoring data events. This creates a circular logging loop that generates excessive events and costs.
AWS CLI (optional)
Option 1: Basic Event Selectors (simpler)
Enable write-only data events for all S3 buckets:
aws cloudtrail put-event-selectors \
--trail-name <your-trail-name> \
--event-selectors '[{
"ReadWriteType": "WriteOnly",
"IncludeManagementEvents": true,
"DataResources": [{
"Type": "AWS::S3::Object",
"Values": ["arn:aws:s3"]
}]
}]' \
--region us-east-1
Replace <your-trail-name> with your actual trail name.
Option 2: Advanced Event Selectors (more control)
Use advanced selectors to log specific write operations:
aws cloudtrail put-event-selectors \
--trail-name <your-trail-name> \
--advanced-event-selectors '[
{
"Name": "Log S3 Write Events",
"FieldSelectors": [
{ "Field": "eventCategory", "Equals": ["Data"] },
{ "Field": "resources.type", "Equals": ["AWS::S3::Object"] },
{ "Field": "readOnly", "Equals": ["false"] }
]
}
]' \
--region us-east-1
Exclude a specific bucket (e.g., your log bucket)
To avoid circular logging, exclude the bucket where CloudTrail delivers logs:
aws cloudtrail put-event-selectors \
--trail-name <your-trail-name> \
--advanced-event-selectors '[
{
"Name": "Log S3 Write Events Excluding Log Bucket",
"FieldSelectors": [
{ "Field": "eventCategory", "Equals": ["Data"] },
{ "Field": "resources.type", "Equals": ["AWS::S3::Object"] },
{ "Field": "readOnly", "Equals": ["false"] },
{ "Field": "resources.ARN", "NotStartsWith": ["arn:aws:s3:::<your-log-bucket-name>/"] }
]
}
]' \
--region us-east-1
Replace <your-log-bucket-name> with the bucket where CloudTrail delivers logs.
CloudFormation (optional)
This template creates a CloudTrail trail with S3 write data event logging enabled:
AWSTemplateFormatVersion: '2010-09-09'
Description: CloudTrail with S3 write data events logging
Parameters:
TrailName:
Type: String
Description: Name of the CloudTrail trail
Default: my-trail
S3BucketName:
Type: String
Description: S3 bucket where CloudTrail logs will be stored
Resources:
CloudTrailWithS3DataEvents:
Type: AWS::CloudTrail::Trail
Properties:
TrailName: !Ref TrailName
S3BucketName: !Ref S3BucketName
IsLogging: true
IsMultiRegionTrail: true
IncludeGlobalServiceEvents: true
EnableLogFileValidation: true
EventSelectors:
- ReadWriteType: WriteOnly
IncludeManagementEvents: true
DataResources:
- Type: AWS::S3::Object
Values:
- 'arn:aws:s3'
Outputs:
TrailArn:
Description: ARN of the CloudTrail trail
Value: !GetAtt CloudTrailWithS3DataEvents.Arn
Deploy with:
aws cloudformation deploy \
--template-file cloudtrail-s3-dataevents.yaml \
--stack-name cloudtrail-s3-dataevents \
--parameter-overrides TrailName=my-trail S3BucketName=<your-cloudtrail-bucket> \
--region us-east-1
Using Advanced Event Selectors (for more granular control):
AWSTemplateFormatVersion: '2010-09-09'
Description: CloudTrail with advanced S3 write data events logging
Parameters:
TrailName:
Type: String
Description: Name of the CloudTrail trail
Default: my-trail
S3BucketName:
Type: String
Description: S3 bucket where CloudTrail logs will be stored
ExcludedLogBucket:
Type: String
Description: S3 bucket to exclude from logging (to avoid circular logging)
Default: ''
Conditions:
HasExcludedBucket: !Not [!Equals [!Ref ExcludedLogBucket, '']]
Resources:
CloudTrailWithAdvancedSelectors:
Type: AWS::CloudTrail::Trail
Properties:
TrailName: !Ref TrailName
S3BucketName: !Ref S3BucketName
IsLogging: true
IsMultiRegionTrail: true
IncludeGlobalServiceEvents: true
EnableLogFileValidation: true
AdvancedEventSelectors:
- Name: S3WriteDataEvents
FieldSelectors:
- Field: eventCategory
Equals:
- Data
- Field: resources.type
Equals:
- AWS::S3::Object
- Field: readOnly
Equals:
- 'false'
Outputs:
TrailArn:
Description: ARN of the CloudTrail trail
Value: !GetAtt CloudTrailWithAdvancedSelectors.Arn
Terraform (optional)
Basic Configuration
variable "trail_name" {
description = "Name of the CloudTrail trail"
type = string
default = "my-trail"
}
variable "s3_bucket_name" {
description = "S3 bucket for CloudTrail logs"
type = string
}
resource "aws_cloudtrail" "s3_data_events" {
name = var.trail_name
s3_bucket_name = var.s3_bucket_name
is_multi_region_trail = true
include_global_service_events = true
enable_log_file_validation = true
event_selector {
read_write_type = "WriteOnly"
include_management_events = true
data_resource {
type = "AWS::S3::Object"
values = ["arn:aws:s3"]
}
}
}
output "trail_arn" {
description = "ARN of the CloudTrail trail"
value = aws_cloudtrail.s3_data_events.arn
}
Advanced Configuration (with bucket exclusion)
variable "trail_name" {
description = "Name of the CloudTrail trail"
type = string
default = "my-trail"
}
variable "s3_bucket_name" {
description = "S3 bucket for CloudTrail logs"
type = string
}
variable "excluded_bucket" {
description = "S3 bucket to exclude from logging (your log bucket)"
type = string
default = ""
}
resource "aws_cloudtrail" "s3_data_events_advanced" {
name = var.trail_name
s3_bucket_name = var.s3_bucket_name
is_multi_region_trail = true
include_global_service_events = true
enable_log_file_validation = true
advanced_event_selector {
name = "Log S3 Write Events"
field_selector {
field = "eventCategory"
equals = ["Data"]
}
field_selector {
field = "resources.type"
equals = ["AWS::S3::Object"]
}
field_selector {
field = "readOnly"
equals = ["false"]
}
}
}
output "trail_arn" {
description = "ARN of the CloudTrail trail"
value = aws_cloudtrail.s3_data_events_advanced.arn
}
Deploy with:
terraform init
terraform plan -var="trail_name=my-trail" -var="s3_bucket_name=my-cloudtrail-bucket"
terraform apply -var="trail_name=my-trail" -var="s3_bucket_name=my-cloudtrail-bucket"
Verification
After making changes, verify S3 data event logging is working:
-
In the AWS Console:
- Go to CloudTrail > Trails and select your trail
- Scroll to Data events and confirm S3 is listed with write events enabled
- Verify the selector scope shows "All current and future S3 buckets"
-
Generate a test event:
- Upload a test file to any S3 bucket in your account
- Wait 5-15 minutes for the event to be logged
- Check CloudTrail Event history and filter by Event name =
PutObject
CLI verification commands
Check the event selectors configured on your trail:
aws cloudtrail get-event-selectors \
--trail-name <your-trail-name> \
--region us-east-1
Look for output showing ReadWriteType: WriteOnly with AWS::S3::Object in the data resources.
For advanced event selectors:
aws cloudtrail get-event-selectors \
--trail-name <your-trail-name> \
--region us-east-1 \
--query 'AdvancedEventSelectors'
Verify by looking up recent S3 write events:
aws cloudtrail lookup-events \
--lookup-attributes AttributeKey=EventName,AttributeValue=PutObject \
--max-results 5 \
--region us-east-1
Additional Resources
- AWS Documentation: Logging Data Events with CloudTrail
- AWS Documentation: S3 CloudTrail Logging Guide
- AWS Security Hub: S3 Controls
- AWS CloudTrail Pricing (data events incur additional charges)
Notes
-
Cost considerations: Data events generate significantly more log volume than management events. AWS charges per 100,000 data events logged. Consider using advanced event selectors to filter only the events you need.
-
Circular logging warning: Never log data events for the same S3 bucket that receives your CloudTrail logs. Each log delivery creates a
PutObjectevent, which triggers another log, creating an infinite loop. Use the bucket exclusion pattern shown above. -
Event delay: Data events typically appear in CloudTrail within 5-15 minutes of occurring. For real-time monitoring, integrate CloudTrail with CloudWatch Logs.
-
Multi-region trails: If your trail is multi-region, data event logging applies to S3 buckets in all regions. Single-region trails only log events for buckets in that region.
-
Existing trails: You can add data event logging to an existing trail without disrupting management event logging.
-
S3 Express One Zone: If you use S3 Express One Zone directory buckets, you need to configure a separate data event type (
AWS::S3Express::Object) to capture those operations.