Trails, S3 Delivery & Log File Validation
A trail is what turns CloudTrail from a 90-day rolling window into durable, exportable, integrity-checked audit history.
Search across all documentation pages
A trail is what turns CloudTrail from a 90-day rolling window into durable, exportable, integrity-checked audit history.
This page covers creating an organization-wide, multi-region trail, delivering it to S3, and enabling log file validation, plus what that validation actually checks.
Quick-reference recipe card - copy-paste ready.
# --- Python (boto3) ---
import boto3
client = boto3.client("cloudtrail")
trail = client.create_trail(
Name="org-baseline-trail",
S3BucketName="acme-org-cloudtrail-logs",
IsMultiRegionTrail=True,
IsOrganizationTrail=True,
EnableLogFileValidation=True,
)
client.start_logging(Name="org-baseline-trail")When to reach for this:
Create the S3 destination, attach the required bucket policy, then create and start an org-wide trail.
# --- Python (boto3) ---
import json, boto3
s3 = boto3.client("s3")
cloudtrail = boto3.client("cloudtrail")
bucket_name = "acme-org-cloudtrail-logs"
account_id = "111122223333"
s3.create_bucket(Bucket=bucket_name)
bucket_policy = {
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AWSCloudTrailAclCheck",
"Effect": "Allow",
"Principal": {"Service": "cloudtrail.amazonaws.com"},
"Action": "s3:GetBucketAcl",
"Resource": f"arn:aws:s3:::{bucket_name}",
},
{
"Sid": "AWSCloudTrailWrite",
"Effect": "Allow",
"Principal": {"Service": "cloudtrail.amazonaws.com"},
"Action": "s3:PutObject",
"Resource": f"arn:aws:s3:::{bucket_name}/AWSLogs/{account_id}/*",
"Condition": {"StringEquals": {"s3:x-amz-acl": "bucket-owner-full-control"}},
},
],
}
s3.put_bucket_policy(Bucket=bucket_name, Policy=json.dumps(bucket_policy))
trail = cloudtrail.create_trail(
Name="org-baseline-trail",
S3BucketName=bucket_name,
IsMultiRegionTrail=True,
IsOrganizationTrail=True,
EnableLogFileValidation=True,
)
cloudtrail.start_logging(Name="org-baseline-trail")
print(trail["TrailARN"])// --- TypeScript (AWS SDK v3) ---
import { S3Client, CreateBucketCommand, PutBucketPolicyCommand } from "@aws-sdk/client-s3";
import { CloudTrailClient, CreateTrailCommand, StartLoggingCommand } from "@aws-sdk/client-cloudtrail";
const s3 = new S3Client({});
const cloudtrail = new CloudTrailClient({});
const bucketName = "acme-org-cloudtrail-logs";
const accountId = "111122223333";
await s3.send(new CreateBucketCommand({ Bucket: bucketName }));
const bucketPolicy = {
Version: "2012-10-17",
Statement: [
{
Sid: "AWSCloudTrailAclCheck",
Effect: "Allow",
Principal: { Service: "cloudtrail.amazonaws.com" },
Action: "s3:GetBucketAcl",
Resource: `arn:aws:s3:::${bucketName}`,
},
{
Sid: "AWSCloudTrailWrite",
Effect: "Allow",
Principal: { Service: "cloudtrail.amazonaws.com" },
Action: "s3:PutObject",
Resource: `arn:aws:s3:::${bucketName}/AWSLogs/${accountId}/*`,
Condition: { StringEquals: { "s3:x-amz-acl": "bucket-owner-full-control" } },
},
],
};
await s3.send(new PutBucketPolicyCommand({ Bucket: bucketName, Policy: JSON.stringify(bucketPolicy) }));
const trail = await cloudtrail.send(new CreateTrailCommand({
Name: "org-baseline-trail",
S3BucketName: bucketName,
IsMultiRegionTrail: true,
IsOrganizationTrail: true,
EnableLogFileValidation: true,
}));
await cloudtrail.send(new StartLoggingCommand({ Name: "org-baseline-trail" }));
console.log(trail.TrailARN);What this demonstrates:
CreateTrail will succeed - CloudTrail needs explicit permission to write into the bucket.IsOrganizationTrail: true only works when called from the Organization's management account (or a delegated admin account).CreateTrail defines the trail's configuration; StartLogging is what actually begins delivery.EnableLogFileValidation doesn't change what's logged - it adds periodic digest files alongside the log files.IsMultiRegionTrail makes one trail capture management events from every AWS region into the same S3 destination, instead of requiring one trail per region.IsOrganizationTrail extends that further: one trail in the management account captures events from every member account in the Organization, each delivered under its own account-ID prefix in the shared bucket.AWSLogs/<account-id>/CloudTrail/<region>/<year>/<month>/<day>/, which is what makes Athena and manual S3 queries practical.EnableLogFileValidation, CloudTrail periodically writes a digest file referencing the log files delivered since the last digest, along with a hash of each. Digest files are themselves signed, forming a chain back to the trail's creation.validate-logs helper or a custom script, not a single SDK call.There's no ValidateLogFile operation in @aws-sdk/client-cloudtrail that returns a simple pass/fail. What CloudTrail gives you is the raw material - the digest files - and verification is a client-side process:
# Conceptual shape of validation - not a single boto3 call.
# 1. Locate the digest file covering the log files in question.
# 2. Verify the digest file's own signature against its published public key.
# 3. Recompute a hash of each referenced log file and compare to the digest's record.
# 4. Confirm digest files chain back-to-back with no gaps.The AWS CLI's aws cloudtrail validate-logs --trail-arn ... --start-time ... command implements this for you and is the practical entry point most teams use, even in an otherwise SDK-driven pipeline.
| Option | Effect | Typical Setting |
|---|---|---|
IsMultiRegionTrail | Captures all regions, not just the creation region | True for any production account |
IsOrganizationTrail | Captures all member accounts into one bucket | True from the management/delegated admin account |
EnableLogFileValidation | Adds tamper-evident digest files | True always - low cost, high audit value |
KmsKeyId | Encrypts log files with a customer-managed KMS key | Set for compliance regimes requiring CMK encryption |
CloudWatchLogsLogGroupArn | Also streams events to CloudWatch Logs | Set when you want near-real-time metric filters/alarms |
StartLogging - CreateTrail alone configures but does not activate delivery; a trail that's never started logs nothing. Fix: always follow CreateTrail with StartLogging and confirm via GetTrailStatus.CreateTrail fails outright, or silently stops delivering, if CloudTrail lacks s3:GetBucketAcl/s3:PutObject on the destination. Fix: attach the CloudTrail service-principal bucket policy shown above before creating the trail.aws cloudtrail validate-logs or a script that walks the digest chain, not an SDK method search.IsOrganizationTrail works from any account - it only succeeds from the Organization's management account or a delegated CloudTrail admin account. Fix: run trail creation from the correct account, or delegate admin first.| Alternative | Use When | Don't Use When |
|---|---|---|
| Single-account, single-region trail | A small standalone account with no Organization | You manage more than one AWS account |
| Organization trail (this page) | Centralizing audit logs across all member accounts | Each account needs a fully independent audit boundary |
| CloudTrail Lake event data store | You need SQL queries over long retention, not just S3 files | You only need raw log files for external tooling (Athena, SIEM) |
| CloudWatch Logs + metric filters | Near-real-time alerting on specific event patterns | Long-term bulk storage and forensic-grade retention |
No. CreateTrail only configures the trail; you must call StartLogging separately to begin delivery.
Multi-region captures every region within one account. Organization extends that across every member account in an AWS Organization, delivered to one central bucket.
No. It must be created from the management account or an account delegated as the CloudTrail administrator for the Organization.
No. It's a file-integrity mechanism built on digest files and hash chains. You verify it with the CLI's validate-logs command or custom logic that walks the digest chain, not a simple boolean-returning call.
The call typically fails, or the trail is created but never successfully delivers logs - check GetTrailStatus for delivery errors either way.
It's strongly recommended. Without one, log files accumulate forever and storage costs grow with no bound, even though CloudTrail itself has no storage cost.
Yes, via the KmsKeyId parameter on CreateTrail (or UpdateTrail), which encrypts delivered log files with a customer-managed key instead of the default SSE-S3 encryption.
Stack versions: This page was written for boto3 1.43.x (Python 3.10+) and the AWS SDK for JavaScript v3 (Node.js 18+).
Reviewed by Chris St. John·Last updated Jul 25, 2026