CloudTrail Logs S3 Bucket Is Not Publicly Accessible
Overview
This check verifies that the S3 bucket storing your CloudTrail logs is not publicly accessible. CloudTrail logs contain a detailed record of every API call made in your AWS account, including who made the call, when, and from where. This is extremely sensitive audit data that should never be exposed to the public.
Risk
If your CloudTrail S3 bucket is publicly accessible, attackers can:
- Reconnaissance: Read your logs to learn about your AWS infrastructure, IAM users, roles, and resources
- Attack planning: Study your API patterns and security configurations to plan targeted attacks
- Credential discovery: Find service accounts, role ARNs, and resource names to exploit
- Cover their tracks: If write access is granted, attackers could modify or delete logs to hide evidence of a breach
- Compliance violations: Most security frameworks (SOC 2, PCI-DSS, HIPAA, etc.) require audit logs to be protected
This is a critical security issue. CloudTrail logs are often the first thing incident responders analyze during a breach investigation. Public access to these logs gives attackers a significant advantage.
Remediation Steps
Prerequisites
You need:
- AWS Console access with permissions to modify S3 bucket settings
- Knowledge of which S3 bucket stores your CloudTrail logs
Required IAM permissions (for administrators)
Your IAM user or role needs these permissions:
s3:GetBucketAcls3:PutBucketAcls3:GetBucketPublicAccessBlocks3:PutBucketPublicAccessBlocks3:GetBucketPolicys3:PutBucketPolicys3:DeleteBucketPolicycloudtrail:DescribeTrails
AWS Console Method
Step 1: Find your CloudTrail S3 bucket
- Go to CloudTrail Console in us-east-1
- Click Trails in the left sidebar
- Click on your trail name
- Note the S3 bucket name shown in the trail details
Step 2: Enable S3 Block Public Access
This is the most important step. Block Public Access is an account-level and bucket-level setting that prevents any public access regardless of ACLs or policies.
- Go to S3 Console
- Click on your CloudTrail bucket name
- Go to the Permissions tab
- In the Block public access (bucket settings) section, click Edit
- Check Block all public access (this enables all four options)
- Click Save changes
- Type
confirmin the confirmation dialog and click Confirm
Step 3: Remove public ACLs (if any)
- Still on the Permissions tab, scroll to Access control list (ACL)
- Click Edit
- Under Grantee, look for entries with:
- Everyone (public access) - also known as
AllUsers - Authenticated Users group - also known as
AuthenticatedUsers
- Everyone (public access) - also known as
- If found, uncheck all permissions for these public groups
- Click Save changes
Step 4: Review and fix bucket policy (if needed)
- Still on the Permissions tab, scroll to Bucket policy
- Review the policy for any statements that grant public access:
- Look for
"Principal": "*"or"Principal": {"AWS": "*"} - Look for statements without restrictive conditions
- Look for
- If you find public access statements, click Edit and either:
- Remove the problematic statement entirely, or
- Add conditions to restrict access (see example below)
- Click Save changes
A secure CloudTrail bucket policy should only allow the CloudTrail service to write logs. Example:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AWSCloudTrailAclCheck",
"Effect": "Allow",
"Principal": {
"Service": "cloudtrail.amazonaws.com"
},
"Action": "s3:GetBucketAcl",
"Resource": "arn:aws:s3:::your-cloudtrail-bucket",
"Condition": {
"StringEquals": {
"aws:SourceArn": "arn:aws:cloudtrail:us-east-1:123456789012:trail/your-trail-name"
}
}
},
{
"Sid": "AWSCloudTrailWrite",
"Effect": "Allow",
"Principal": {
"Service": "cloudtrail.amazonaws.com"
},
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::your-cloudtrail-bucket/AWSLogs/*",
"Condition": {
"StringEquals": {
"s3:x-amz-acl": "bucket-owner-full-control",
"aws:SourceArn": "arn:aws:cloudtrail:us-east-1:123456789012:trail/your-trail-name"
}
}
}
]
}
AWS CLI (optional)
Step 1: Identify your CloudTrail S3 bucket
aws cloudtrail describe-trails \
--region us-east-1 \
--query 'trailList[*].{Name:Name,S3BucketName:S3BucketName}'
Note the S3BucketName value for your trail.
Step 2: Enable Block Public Access
aws s3api put-public-access-block \
--bucket <your-cloudtrail-bucket> \
--public-access-block-configuration '{
"BlockPublicAcls": true,
"IgnorePublicAcls": true,
"BlockPublicPolicy": true,
"RestrictPublicBuckets": true
}'
Step 3: Set ACL to private
Remove any public ACL grants by setting the bucket ACL to private:
aws s3api put-bucket-acl \
--bucket <your-cloudtrail-bucket> \
--acl private
Step 4: Check current bucket policy
aws s3api get-bucket-policy \
--bucket <your-cloudtrail-bucket> \
--query Policy \
--output text | jq .
Review the output for any statements with "Principal": "*" that lack restrictive conditions.
Step 5: Apply a secure bucket policy
Create a policy file with proper CloudTrail permissions:
cat > /tmp/cloudtrail-bucket-policy.json << 'EOF'
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AWSCloudTrailAclCheck",
"Effect": "Allow",
"Principal": {
"Service": "cloudtrail.amazonaws.com"
},
"Action": "s3:GetBucketAcl",
"Resource": "arn:aws:s3:::<your-cloudtrail-bucket>",
"Condition": {
"StringEquals": {
"aws:SourceArn": "arn:aws:cloudtrail:us-east-1:<your-account-id>:trail/<your-trail-name>"
}
}
},
{
"Sid": "AWSCloudTrailWrite",
"Effect": "Allow",
"Principal": {
"Service": "cloudtrail.amazonaws.com"
},
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::<your-cloudtrail-bucket>/AWSLogs/*",
"Condition": {
"StringEquals": {
"s3:x-amz-acl": "bucket-owner-full-control",
"aws:SourceArn": "arn:aws:cloudtrail:us-east-1:<your-account-id>:trail/<your-trail-name>"
}
}
}
]
}
EOF
Replace the placeholders:
<your-cloudtrail-bucket>with your bucket name<your-account-id>with your AWS account ID<your-trail-name>with your CloudTrail trail name
Apply the policy:
aws s3api put-bucket-policy \
--bucket <your-cloudtrail-bucket> \
--policy file:///tmp/cloudtrail-bucket-policy.json
CloudFormation (optional)
This template creates a new S3 bucket for CloudTrail logs with all public access blocked. If you have an existing bucket, you will need to update it manually using the console or CLI methods above.
AWSTemplateFormatVersion: '2010-09-09'
Description: Secure S3 bucket for CloudTrail logs with public access blocked
Parameters:
BucketName:
Type: String
Description: Name for the CloudTrail logs bucket
TrailName:
Type: String
Description: Name of the CloudTrail trail that will use this bucket
Resources:
CloudTrailBucket:
Type: AWS::S3::Bucket
Properties:
BucketName: !Ref BucketName
PublicAccessBlockConfiguration:
BlockPublicAcls: true
BlockPublicPolicy: true
IgnorePublicAcls: true
RestrictPublicBuckets: true
BucketEncryption:
ServerSideEncryptionConfiguration:
- ServerSideEncryptionByDefault:
SSEAlgorithm: AES256
VersioningConfiguration:
Status: Enabled
LifecycleConfiguration:
Rules:
- Id: DeleteOldLogs
Status: Enabled
ExpirationInDays: 365
CloudTrailBucketPolicy:
Type: AWS::S3::BucketPolicy
Properties:
Bucket: !Ref CloudTrailBucket
PolicyDocument:
Version: '2012-10-17'
Statement:
- Sid: AWSCloudTrailAclCheck
Effect: Allow
Principal:
Service: cloudtrail.amazonaws.com
Action: s3:GetBucketAcl
Resource: !GetAtt CloudTrailBucket.Arn
Condition:
StringEquals:
aws:SourceArn: !Sub 'arn:aws:cloudtrail:${AWS::Region}:${AWS::AccountId}:trail/${TrailName}'
- Sid: AWSCloudTrailWrite
Effect: Allow
Principal:
Service: cloudtrail.amazonaws.com
Action: s3:PutObject
Resource: !Sub '${CloudTrailBucket.Arn}/AWSLogs/${AWS::AccountId}/*'
Condition:
StringEquals:
s3:x-amz-acl: bucket-owner-full-control
aws:SourceArn: !Sub 'arn:aws:cloudtrail:${AWS::Region}:${AWS::AccountId}:trail/${TrailName}'
- Sid: DenyPublicAccess
Effect: Deny
Principal: '*'
Action: 's3:*'
Resource:
- !GetAtt CloudTrailBucket.Arn
- !Sub '${CloudTrailBucket.Arn}/*'
Condition:
Bool:
aws:SecureTransport: 'false'
Outputs:
BucketName:
Description: Name of the CloudTrail logs bucket
Value: !Ref CloudTrailBucket
BucketArn:
Description: ARN of the CloudTrail logs bucket
Value: !GetAtt CloudTrailBucket.Arn
Deploy with:
aws cloudformation deploy \
--template-file cloudtrail-secure-bucket.yaml \
--stack-name cloudtrail-secure-bucket \
--parameter-overrides \
BucketName=my-secure-cloudtrail-bucket \
TrailName=my-cloudtrail \
--region us-east-1
Terraform (optional)
# Variables
variable "bucket_name" {
description = "Name for the CloudTrail logs bucket"
type = string
}
variable "trail_name" {
description = "Name of the CloudTrail trail"
type = string
}
# Data source for current account
data "aws_caller_identity" "current" {}
data "aws_region" "current" {}
# S3 bucket for CloudTrail logs
resource "aws_s3_bucket" "cloudtrail" {
bucket = var.bucket_name
}
# Block all public access
resource "aws_s3_bucket_public_access_block" "cloudtrail" {
bucket = aws_s3_bucket.cloudtrail.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
# Enable versioning
resource "aws_s3_bucket_versioning" "cloudtrail" {
bucket = aws_s3_bucket.cloudtrail.id
versioning_configuration {
status = "Enabled"
}
}
# Enable encryption
resource "aws_s3_bucket_server_side_encryption_configuration" "cloudtrail" {
bucket = aws_s3_bucket.cloudtrail.id
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "AES256"
}
}
}
# Secure bucket policy - CloudTrail only, no public access
resource "aws_s3_bucket_policy" "cloudtrail" {
bucket = aws_s3_bucket.cloudtrail.id
# Ensure public access block is in place first
depends_on = [aws_s3_bucket_public_access_block.cloudtrail]
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Sid = "AWSCloudTrailAclCheck"
Effect = "Allow"
Principal = {
Service = "cloudtrail.amazonaws.com"
}
Action = "s3:GetBucketAcl"
Resource = aws_s3_bucket.cloudtrail.arn
Condition = {
StringEquals = {
"aws:SourceArn" = "arn:aws:cloudtrail:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:trail/${var.trail_name}"
}
}
},
{
Sid = "AWSCloudTrailWrite"
Effect = "Allow"
Principal = {
Service = "cloudtrail.amazonaws.com"
}
Action = "s3:PutObject"
Resource = "${aws_s3_bucket.cloudtrail.arn}/AWSLogs/${data.aws_caller_identity.current.account_id}/*"
Condition = {
StringEquals = {
"s3:x-amz-acl" = "bucket-owner-full-control"
"aws:SourceArn" = "arn:aws:cloudtrail:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:trail/${var.trail_name}"
}
}
},
{
Sid = "DenyInsecureTransport"
Effect = "Deny"
Principal = "*"
Action = "s3:*"
Resource = [
aws_s3_bucket.cloudtrail.arn,
"${aws_s3_bucket.cloudtrail.arn}/*"
]
Condition = {
Bool = {
"aws:SecureTransport" = "false"
}
}
}
]
})
}
# Outputs
output "bucket_name" {
description = "Name of the CloudTrail logs bucket"
value = aws_s3_bucket.cloudtrail.id
}
output "bucket_arn" {
description = "ARN of the CloudTrail logs bucket"
value = aws_s3_bucket.cloudtrail.arn
}
Deploy with:
terraform init
terraform plan \
-var="bucket_name=my-secure-cloudtrail-bucket" \
-var="trail_name=my-cloudtrail"
terraform apply \
-var="bucket_name=my-secure-cloudtrail-bucket" \
-var="trail_name=my-cloudtrail"
To remediate an existing bucket, you can import it and apply the public access block:
terraform import aws_s3_bucket.cloudtrail existing-bucket-name
terraform apply
Verification
After applying the remediation, verify that public access is blocked:
-
In the AWS Console:
- Go to S3 and click on your CloudTrail bucket
- Go to the Permissions tab
- Under Block public access, confirm all four options are On
- Under Access control list, confirm no public grants exist
- Under Bucket policy, confirm no
"Principal": "*"statements without proper conditions
-
Re-run the Prowler check:
- Run Prowler again to confirm the finding is resolved
CLI verification commands
Check Block Public Access settings:
aws s3api get-public-access-block \
--bucket <your-cloudtrail-bucket> \
--region us-east-1
Expected output (all values should be true):
{
"PublicAccessBlockConfiguration": {
"BlockPublicAcls": true,
"IgnorePublicAcls": true,
"BlockPublicPolicy": true,
"RestrictPublicBuckets": true
}
}
Check bucket ACL for public grants:
aws s3api get-bucket-acl \
--bucket <your-cloudtrail-bucket> \
--region us-east-1
Look for grants to:
http://acs.amazonaws.com/groups/global/AllUsers(public)http://acs.amazonaws.com/groups/global/AuthenticatedUsers(all AWS users)
If found, these indicate public access.
Check bucket policy for public access:
aws s3api get-bucket-policy \
--bucket <your-cloudtrail-bucket> \
--query Policy \
--output text | jq .
Review for any statements with "Principal": "*" that lack restrictive conditions.
Additional Resources
- AWS Documentation: Blocking public access to your Amazon S3 storage
- AWS Documentation: Amazon S3 bucket policy for CloudTrail
- AWS Documentation: Security best practices for CloudTrail
- AWS Security Hub: CloudTrail Controls
- CIS AWS Foundations Benchmark
Notes
- Block Public Access is the strongest protection: Even if a bucket policy or ACL inadvertently grants public access, Block Public Access will override it. Always enable this feature.
- Cross-account buckets: If your CloudTrail logs are stored in a bucket owned by another AWS account, you will need to coordinate with that account owner to verify and remediate public access settings.
- Organization-level settings: If you use AWS Organizations, consider enabling Block Public Access at the organization level to prevent any member account from creating public buckets.
- Check for S3 Access Points: If your bucket has S3 Access Points configured, review their policies as well. Each Access Point has its own Block Public Access settings.
- CloudTrail will continue to work: Removing public access does not affect CloudTrail's ability to write logs. CloudTrail uses its service principal with
aws:SourceArnconditions, not public access. - Monitor for policy changes: Consider setting up CloudWatch Events or EventBridge rules to alert on S3 bucket policy changes to detect if someone accidentally re-enables public access.