Back to Checks
aws

CloudTrail trail destination S3 bucket has access logging enabled

cloudtrail_logs_s3_bucket_access_logging_enabled
Service: cloudtrailSeverity: medium
OfficialBuilt-inVerified
Categories:loggingforensics-ready
Resource Type:AwsCloudTrailTrail

CloudTrail trails deliver logs to an S3 bucket; this evaluates whether that bucket has S3 server access logging enabled to record requests against it.

If the destination bucket is outside the account or audit scope, a manual review is indicated.

Risk

Without access logging on the CloudTrail logs bucket, access and changes to log files lack an independent audit trail. Attackers could read, delete, or replace logs without attribution, undermining log confidentiality and integrity, and slowing incident response.

Run this check with Prowler

Run in Prowler Cloud
Run
prowler aws --checks cloudtrail_logs_s3_bucket_access_logging_enabled

Recommendation

Enable S3 server access logging on the CloudTrail logs bucket and write logs to a separate, tightly controlled bucket. Apply least privilege, enable versioning, and consider Object Lock to deter tampering. Centralize monitoring to support defense-in-depth and rapid investigation.

Remediation

CLI

aws s3api put-bucket-logging --bucket <CLOUDTRAIL_BUCKET_NAME> --bucket-logging-status "{\"LoggingEnabled\":{\"TargetBucket\":\"<TARGET_BUCKET_NAME>\"}}"

Terraform

# Enable access logging on the CloudTrail S3 bucket
resource "aws_s3_bucket" "<example_log_bucket_name>" {
  bucket = "<example_log_bucket_name>"
}

resource "aws_s3_bucket" "<example_bucket_name>" {
  bucket = "<example_bucket_name>"
}

resource "aws_s3_bucket_logging" "<example_resource_name>" {
  bucket        = aws_s3_bucket.<example_bucket_name>.id
  target_bucket = aws_s3_bucket.<example_log_bucket_name>.id  # Critical: enables server access logging to the target bucket
}

NativeIaC

# CloudFormation: enable S3 access logging on the CloudTrail destination bucket
Resources:
  <example_log_bucket_name>:
    Type: AWS::S3::Bucket

  <example_cloudtrail_bucket>:
    Type: AWS::S3::Bucket
    Properties:
      LoggingConfiguration:
        DestinationBucketName: !Ref <example_log_bucket_name>  # Critical: turns on server access logging to this destination bucket
        # This enables access logging so the check passes

Other

1In the AWS Console, go to S3 and open the bucket used by your CloudTrail trail
2Select the Properties tab
3In Server access logging, click Edit
4Enable logging and choose a different destination S3 bucket for the logs
5Click Save changes

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_logs_s3_bucket_access_logging_enabled(Check):
    def execute(self):
        findings = []
        if cloudtrail_client.trails is not None:
            for trail in cloudtrail_client.trails.values():
                if trail.name:
                    trail_bucket_is_in_account = False
                    trail_bucket = trail.s3_bucket
                    report = Check_Report_AWS(metadata=self.metadata(), resource=trail)
                    report.region = trail.home_region
                    report.status = "FAIL"
                    if trail.is_multiregion:
                        report.status_extended = f"Multiregion Trail {trail.name} S3 bucket access logging is not enabled for bucket {trail_bucket}."
                    else:
                        report.status_extended = f"Single region Trail {trail.name} S3 bucket access logging is not enabled for bucket {trail_bucket}."
                    for bucket in s3_client.buckets.values():
                        if trail_bucket == bucket.name:
                            trail_bucket_is_in_account = True
                            if bucket.logging:
                                report.status = "PASS"
                                if trail.is_multiregion:
                                    report.status_extended = f"Multiregion Trail {trail.name} S3 bucket access logging is enabled for bucket {trail_bucket}."
                                else:
                                    report.status_extended = f"Single region Trail {trail.name} S3 bucket access logging is enabled for bucket {trail_bucket}."
                            break

                    # check if trail is delivering logs in a cross account bucket or another region out of Prowler's audit scope
                    if not trail_bucket_is_in_account:
                        report.status = "MANUAL"
                        report.status_extended = f"Trail {trail.name} is delivering logs to bucket {trail_bucket} which is a cross-account bucket or out of Prowler's audit scope, please check it manually."
                    findings.append(report)

        return findings