Back to blog
AWSCloud SecurityMonitoring & LoggingOperations & ComplianceStorage

CloudTrail Bucket Has No Access Logging: Why It Matters and How to Fix It

Learn why your CloudTrail S3 bucket needs server access logging, the risk of leaving it off, and step-by-step CLI, console, and Terraform fixes.

TL;DR

Your CloudTrail logs are sitting in an S3 bucket that does not record who reads or modifies them. Enable S3 server access logging on that bucket so you have a second, independent trail of access events, and point it at a separate logging bucket.

CloudTrail tells you what happened across your AWS account: API calls, console logins, resource changes. It is one of the most important sources of truth during an incident. But CloudTrail logs land in an S3 bucket, and that bucket is itself a resource that can be read, copied, tampered with, or deleted. This check flags when that bucket has no S3 server access logging turned on, which means you have no record of who is touching your audit trail.


What this check detects

The account_cloudtrailbucketlogging check inspects the S3 bucket configured as the destination for your CloudTrail trail and verifies whether S3 server access logging is enabled on it. If logging is off, the check fails.

Two different logging mechanisms are in play here, and it is worth being precise about them:

  • CloudTrail records AWS API activity (the management and data events you configured) and writes those records as objects into the destination bucket.
  • S3 server access logging records HTTP-level access to the bucket itself: every GET, PUT, DELETE, and LIST request against the objects in that bucket, including the requester, the time, the operation, and the response status.

This check is about the second one. It confirms that access to the CloudTrail bucket is itself being logged.

Note: S3 server access logs are delivered on a best-effort basis and can lag by a few hours. For real-time, queryable access records you can also enable S3 data events in CloudTrail. The two are complementary, and many compliance frameworks expect server access logging specifically because it is cheap and independent of the CloudTrail pipeline.


Why it matters

The whole point of CloudTrail is to give you an evidentiary record you can trust after something goes wrong. If an attacker gains access to the bucket holding those logs, they can read them to learn what you can see, copy them to exfiltrate sensitive activity data, or tamper with the surrounding configuration to cover their tracks. Without server access logging on the bucket, none of that leaves a footprint.

Consider a realistic scenario. An attacker compromises an IAM role with overly broad S3 permissions. They list the CloudTrail bucket, download a few days of logs to understand your monitoring coverage, and notice exactly which event types you are and are not capturing. They now know what they can do without tripping an alert. If server access logging had been enabled, those LIST and GET requests would show up in a separate log stream, ideally in a different bucket and even a different account, giving your responders a thread to pull on.

Warning: Server access logging is not a substitute for hardening the bucket itself. It tells you that something happened, after the fact. Pair it with bucket policies, Block Public Access, and least-privilege IAM so access is prevented in the first place.

There is also a compliance dimension. CIS AWS Foundations Benchmark, PCI DSS, and SOC 2 all expect access to audit logs to be monitored. An empty access log story is a common audit finding, and it is one of the easier ones to fix.


How to fix it

You need a destination bucket for the access logs (the target bucket) and then you enable logging on the CloudTrail bucket (the source bucket) pointing at that target. Do not log a bucket to itself, since that creates a feedback loop of access events.

Step 1: Create or choose a target logging bucket

Use a dedicated bucket in the same region as the source bucket. If you already have a central logging bucket, reuse it with a per-source prefix.

aws s3api create-bucket \
  --bucket my-org-s3-access-logs \
  --region us-east-1

Step 2: Grant S3 log delivery permission to the target bucket

S3 delivers access logs using the logging service principal. Attach a bucket policy that allows it to write. This is the modern, recommended approach (the older log-delivery ACL grant is being phased out).

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "S3ServerAccessLogsPolicy",
      "Effect": "Allow",
      "Principal": { "Service": "logging.s3.amazonaws.com" },
      "Action": "s3:PutObject",
      "Resource": "arn:aws:s3:::my-org-s3-access-logs/cloudtrail-bucket/*",
      "Condition": {
        "StringEquals": {
          "aws:SourceAccount": "111122223333"
        }
      }
    }
  ]
}
aws s3api put-bucket-policy \
  --bucket my-org-s3-access-logs \
  --policy file://access-logs-policy.json

Step 3: Enable logging on the CloudTrail bucket

Create a logging configuration that points the CloudTrail bucket at the target bucket and prefix.

{
  "LoggingEnabled": {
    "TargetBucket": "my-org-s3-access-logs",
    "TargetPrefix": "cloudtrail-bucket/"
  }
}
aws s3api put-bucket-logging \
  --bucket my-cloudtrail-logs-bucket \
  --bucket-logging-status file://logging-config.json

Step 4: Verify

aws s3api get-bucket-logging --bucket my-cloudtrail-logs-bucket

A non-empty LoggingEnabled block in the response means the check will now pass. Access log objects should begin appearing under the prefix within a few hours.

Danger: Never set the CloudTrail bucket as its own logging target. Logging a bucket to itself causes each access log write to generate further access events, which can spiral into runaway object creation and storage cost. Always use a separate target bucket.

Console steps (if you prefer the UI)

  1. Open the S3 console and select your CloudTrail destination bucket.
  2. Go to the Properties tab.
  3. Find Server access logging and choose Edit.
  4. Toggle it to Enable.
  5. Set the target bucket and a target prefix such as cloudtrail-bucket/.
  6. Save changes.

How to prevent it from happening again

Manual fixes drift. The reliable way to keep this green is to define logging in your infrastructure code and enforce it in CI.

Terraform

resource "aws_s3_bucket" "cloudtrail" {
  bucket = "my-cloudtrail-logs-bucket"
}

resource "aws_s3_bucket_logging" "cloudtrail" {
  bucket        = aws_s3_bucket.cloudtrail.id
  target_bucket = aws_s3_bucket.access_logs.id
  target_prefix = "cloudtrail-bucket/"
}

CloudFormation

CloudTrailBucket:
  Type: AWS::S3::Bucket
  Properties:
    BucketName: my-cloudtrail-logs-bucket
    LoggingConfiguration:
      DestinationBucketName: !Ref AccessLogsBucket
      LogFilePrefix: cloudtrail-bucket/

Policy-as-code gate

Add a check in your pipeline so a CloudTrail bucket without a logging configuration fails the build before it ever reaches AWS. With Checkov this is covered by CKV_AWS_18:

checkov -d ./infra --check CKV_AWS_18

For OPA/Conftest, a minimal Rego rule against a Terraform plan looks like this:

package main

deny[msg] {
  resource := input.resource_changes[_]
  resource.type == "aws_s3_bucket"
  not has_logging(resource.address)
  msg := sprintf("S3 bucket %s has no access logging", [resource.address])
}

has_logging(addr) {
  log := input.resource_changes[_]
  log.type == "aws_s3_bucket_logging"
  contains(log.change.after.bucket, addr)
}

Tip: Rather than tracking this bucket by bucket, write the rule to require access logging on any bucket tagged as a log or audit destination. That way the control scales to new CloudTrail trails, config buckets, and ELB log buckets without anyone remembering to update a list.


Best practices

  • Centralize access logs. Send server access logs from all sensitive buckets to one logging bucket (or a logging account) so responders have a single place to query.
  • Separate the logging account. For the strongest tamper resistance, the target bucket should live in a dedicated log archive account that production roles cannot delete from.
  • Turn on Object Lock and versioning on the CloudTrail bucket itself so logs cannot be silently overwritten or deleted, even by someone with write access.
  • Add S3 data event logging in CloudTrail for the CloudTrail bucket. Combined with server access logging, you get both near real-time and best-effort coverage of bucket access.
  • Apply lifecycle rules to the access logs bucket. These logs accumulate quickly, so transition older objects to a cheaper storage class and expire them per your retention policy.
  • Alert on suspicious access. Query access logs (via Athena or a SIEM) for unexpected principals reading the CloudTrail bucket, and fire an alert when they appear.

Note: S3 server access logging is free to enable. You only pay normal S3 storage and request charges for the log objects themselves, which are small. Lifecycle expiration keeps that cost negligible.

Enabling access logging on your CloudTrail bucket is a small, cheap change that closes a real blind spot. When you are reconstructing an incident, the difference between knowing who read your audit logs and having no idea is the difference between a clean investigation and a guess.