Back to Checks
aws

CloudTrail trail has delivered logs to CloudWatch Logs in the last 24 hours

cloudtrail_cloudwatch_logging_enabled
Service: cloudtrailSeverity: low
OfficialBuilt-inVerified
Categories:loggingforensics-ready
Resource Type:AwsCloudTrailTrail

CloudTrail trails are configured to send events to CloudWatch Logs, and show recent delivery within the last 24h. Trails without integration or without recent CloudWatch delivery are identified, across single-Region and multi-Region trails.

Risk

Missing or stale CloudWatch delivery weakens visibility and delays detection, impacting confidentiality and integrity. Adversaries can:

  • Hide privilege escalation
  • Perform unauthorized resource changes
  • Exfiltrate data via API misuse

Run this check with Prowler

Run in Prowler Cloud
Run
prowler aws --checks cloudtrail_cloudwatch_logging_enabled

Recommendation

Integrate every trail with CloudWatch Logs and maintain continuous, near-real-time delivery. Enforce least privilege on the delivery role, prefer multi-Region coverage, and implement metric filters and alerts for sensitive actions. Centralize retention to support defense in depth.

Remediation

CLI

aws cloudtrail update-trail --name <trail_name> --cloud-watch-logs-log-group-arn <cloudwatch_log_group_arn> --cloud-watch-logs-role-arn <cloudwatch_logs_role_arn>

Terraform

# Terraform: enable CloudTrail delivery to CloudWatch Logs
resource "aws_cloudtrail" "<example_resource_name>" {
  name                       = "<example_resource_name>"
  s3_bucket_name             = "<example_resource_name>"
  cloud_watch_logs_group_arn = "<cloudwatch_log_group_arn>"  # CRITICAL: sends CloudTrail events to CloudWatch Logs
  cloud_watch_logs_role_arn  = "<cloudwatch_logs_role_arn>"   # CRITICAL: role CloudTrail assumes to deliver events
}

NativeIaC

# CloudFormation: enable CloudTrail delivery to CloudWatch Logs
Resources:
  <example_resource_name>:
    Type: AWS::CloudTrail::Trail
    Properties:
      S3BucketName: "<example_resource_name>"
      CloudWatchLogsLogGroupArn: "<cloudwatch_log_group_arn>"  # CRITICAL: sends CloudTrail events to CloudWatch Logs
      CloudWatchLogsRoleArn: "<cloudwatch_logs_role_arn>"      # CRITICAL: role CloudTrail assumes to deliver events

Other

1In AWS Console, go to CloudTrail > Trails and select the trail
2In the CloudWatch Logs section, click Edit
3Set CloudWatch Logs to Enabled
4Choose an existing Log group (or create new) and select an IAM role with permissions for CreateLogStream/PutLogEvents
5Click Save changes
6After a few minutes, verify events appear in the chosen CloudWatch Logs log group

Check Code

from datetime import datetime, timedelta, timezone

from prowler.lib.check.models import Check, Check_Report_AWS
from prowler.providers.aws.services.cloudtrail.cloudtrail_client import (
    cloudtrail_client,
)

maximum_time_without_logging = 1


class cloudtrail_cloudwatch_logging_enabled(Check):
    def execute(self):
        findings = []
        if cloudtrail_client.trails is not None:
            for trail in cloudtrail_client.trails.values():
                if trail.name:
                    report = Check_Report_AWS(metadata=self.metadata(), resource=trail)
                    report.region = trail.home_region
                    report.status = "PASS"
                    if trail.is_multiregion:
                        report.status_extended = f"Multiregion trail {trail.name} has been logging in the last 24h."
                    else:
                        report.status_extended = f"Single region trail {trail.name} has been logging in the last 24h."
                    if trail.latest_cloudwatch_delivery_time:
                        last_log_delivery = (
                            datetime.now().replace(tzinfo=timezone.utc)
                            - trail.latest_cloudwatch_delivery_time
                        )
                        if last_log_delivery > timedelta(
                            days=maximum_time_without_logging
                        ):
                            report.status = "FAIL"
                            if trail.is_multiregion:
                                report.status_extended = f"Multiregion trail {trail.name} has not been logging in the last 24h."
                            else:
                                report.status_extended = f"Single region trail {trail.name} has not been logging in the last 24h."
                    else:
                        report.status = "FAIL"
                        if trail.is_multiregion:
                            report.status_extended = f"Multiregion trail {trail.name} has not been logging in the last 24h or is not configured to deliver logs."
                        else:
                            report.status_extended = f"Single region trail {trail.name} has not been logging in the last 24h or is not configured to deliver logs."
                    findings.append(report)

        return findings