Filter concepts by levelShowing all levels.

AWS · Section 34

CloudTrail and Auditability

Level
intermediate
Read
25 min
Concepts
3

CloudTrail records API activity as management events (control-plane actions, logged by default) and data events (high-volume resource-level actions like S3 object reads, opt-in and billed). Event history gives a free 90-day management-events view with no setup; a trail is what you create for durable retention, data-event coverage, and delivery to an S3 bucket. That bucket needs to be centralized into a dedicated log archive account and locked down with a least-privilege policy and log file integrity validation, so a compromised workload role can never reach — let alone alter — the evidence of its own compromise. Every event record then answers the same five audit questions directly: who (userIdentity), what (eventSource + eventName), when (eventTime), from where (sourceIPAddress), and under which assumed identity (sessionIssuer / sourceIdentity).

What is true here

  1. Management events are logged by default; data events (S3 object-level, Lambda Invoke) are off by default and billed separately.
  2. Event history is a free 90-day, management-events-only view; a trail is required for durable retention and data-event coverage.
  3. Centralize logs into a dedicated log archive account via an organization trail — ordinary workload IAM roles should have no path to the bucket.
  4. Log file integrity validation and S3 MFA Delete turn "the bucket policy should have prevented tampering" into a provable fact.
  5. Every event record answers who/what/when/where/as-whom in one place: userIdentity, eventSource+eventName, eventTime, sourceIPAddress, sessionIssuer/sourceIdentity.

What you will be able to do

  • Explain the difference between a management event and a data event, and why one is billed and the other is not
  • Set up a trail (ideally an organization trail) that centralizes logs into a dedicated, access-restricted log archive account
  • Enable log file integrity validation and explain what it proves that a bucket policy alone cannot
  • Read a CloudTrail event record and extract who did what, when, from where, and under which identity
From event types to reading CloudTrail as evidence
delivered totrustedenough to

Management vs data events, trails, retention

Centralize + protect logs

Who / what / when / where / as-whom

  • Management vs data events, trails, retention
    • leads to Centralize + protect logs (delivered to)
  • Centralize + protect logs
    • leads to Who / what / when / where / as-whom (trusted enough to)
  • Who / what / when / where / as-whom

CloudTrail and Auditability

Event types and trails, why audit logs must be centralized and protected from the workloads they audit, and reading a CloudTrail record as evidence.

CloudTrail Event Types and Trails

coreintermediate

CloudTrail records every API call made in your account. Management events cover control-plane actions like creating a role or a subnet; data events cover high-volume resource-level actions like reading an S3 object. Event history keeps 90 days of management events automatically, no setup needed. A trail is what you create to keep events longer, choose which event types to log, and deliver them to an S3 bucket you control.

Think of it as

Event history is a free 90-day rear-view mirror that AWS keeps running by default — useful, but it forgets. A trail is a dashcam you set up yourself: you choose what it records and where the footage goes, and it keeps recording as long as you want.

What we're doing: See why a security investigation into an S3 object deleted 4 months ago finds nothing without a trail.

no-trail-no-data-events.txttext
Account has never created a trail.
An object in a sensitive S3 bucket is deleted.
Investigator opens Event history 4 months later, searches "DeleteObject":
→ No results — Event history only holds 90 days, AND it never held this
  event in the first place: DeleteObject is a data event, off by default
1
Nothing about a fresh AWS account logs S3 object-level activity automatically — that requires a trail with data events explicitly enabled.
4
Two separate gaps stack here: the 90-day window (even if it were a management event) and data events being opt-in and billed separately from management events.

Why this works: Event history's 90-day window and management-events-only scope make it useful for a quick "what changed this week" check, but it is not a substitute for a trail — an investigation that needs S3 object-level history or anything older than 90 days finds nothing without one.

Assuming CloudTrail logs everything by default, so no trail is needed

Wrong

text
# "CloudTrail is already on for every AWS account — we don't need to
# configure anything."

Better

text
# Create a multi-Region trail on day one, delivering to a dedicated S3
# bucket, and explicitly enable data events for the resources that matter
# (e.g. S3 buckets holding sensitive data)

What you see: A "what happened" question about an event more than 90 days old, or about S3/Lambda-level activity, returns nothing — the record needed for the investigation was never retained anywhere.

Why: Event history is a convenience view CloudTrail keeps running regardless of configuration, but it is bounded to 90 days and management events only — a trail is the only way to get durable retention and data-event coverage, and it does not exist until someone creates one.

Event history vs a trail

Event history

  • +On by default, no setup
  • +Management events only, 90 days per Region
  • +Viewed in the console or via lookup-events

Trail

  • You create it and choose an S3 bucket
  • Any event type, retained as long as you keep the logs
  • Can also stream to CloudWatch Logs / EventBridge
  • Event history
    • On by default, no setup
    • Management events only, 90 days per Region
    • Viewed in the console or via lookup-events
  • Trail
    • You create it and choose an S3 bucket
    • Any event type, retained as long as you keep the logs
    • Can also stream to CloudWatch Logs / EventBridge

Remember: Management events (control plane) are logged by default; data events (S3 object-level, Lambda Invoke) are off by default and billed extra. Event history is a free 90-day management-events view with no setup. A trail is what you create for durable retention, data-event coverage, and delivery to S3/CloudWatch Logs/EventBridge.

See also: centralizing and protecting audit logs · answering who changed what with cloudtrail

Centralizing and Protecting Audit Logs

coreintermediate

Audit logs prove what happened, including during an incident where the attacker has some level of access. If a compromised workload role can edit or delete the very logs meant to catch it, the logs prove nothing. The fix is two-fold: send every account's logs to one dedicated logging account nobody develops in, and lock the destination S3 bucket down so ordinary workload roles cannot touch it, only trusted administrators can.

Think of it as

A building's security camera footage is not stored in the room it films, and the people filmed cannot access the recorder. Centralized, protected audit logging is the same separation: the account being audited is not the account holding the record of what happened in it.

What we're doing: See what happens when a workload IAM role that can delete objects also happens to have access to the trail's own log bucket.

same-account-log-tampering.txttext
Trail's S3 bucket lives in the same account as the application it audits.
The application's IAM role has a broad s3:* policy (common over-grant).
Attacker compromises the application via a dependency vulnerability.
Attacker uses the app's own role to call s3:DeleteObject on the trail's
bucket, removing the log files that would show the compromise.
1
Nothing about CloudTrail itself requires this — it is a configuration choice to put the log bucket where the workload can reach it.
2
A broad s3:* grant is common when a team scopes IAM by "the app needs S3 access" rather than by bucket — and it silently includes the log bucket too.
5
The attacker never needed CloudTrail-specific permissions — ordinary S3 delete permission on the wrong bucket was enough to destroy the evidence.

Why this works: This is exactly the failure a dedicated log archive account with a least-privilege bucket policy prevents — the application's role, however broad its S3 permissions, simply has no network or IAM path to a bucket that lives in a different account it was never granted access to.

Relying on IAM permissions boundaries within one account instead of a separate account

Wrong

text
# "We'll just write a bucket policy that denies delete to everyone except
# the security team's role — same account is fine."

Better

text
# Move the trail's destination bucket to a dedicated log archive account
# that no application or developer role exists in at all

What you see: An account compromise that reaches administrative-level privilege (e.g. via a misconfigured trust policy or a privilege-escalation chain) can still reach and alter the log bucket, because it is a resource within the same account boundary being attacked.

Why: A bucket policy is enforced by IAM evaluation logic running inside the same account — if that account's trust boundary is the thing that got compromised, every policy inside it is now something the attacker can potentially read or reason about circumventing. A separate account is a much harder boundary to cross than a policy statement.

Workload account vs dedicated log archive account

Logs stay in the workload account

  • +Same IAM roles that run the workload can reach the log bucket
  • +A compromised or over-permissioned role can edit or delete evidence
  • +No structural separation between "doing the thing" and "recording the thing"

Centralized log archive account

  • Organization trail ships every account's events to one bucket
  • Bucket policy grants access to CloudTrail and a few trusted admins only
  • A compromised workload role has no path to the audit record at all
  • Logs stay in the workload account
    • Same IAM roles that run the workload can reach the log bucket
    • A compromised or over-permissioned role can edit or delete evidence
    • No structural separation between "doing the thing" and "recording the thing"
  • Centralized log archive account
    • Organization trail ships every account's events to one bucket
    • Bucket policy grants access to CloudTrail and a few trusted admins only
    • A compromised workload role has no path to the audit record at all

Remember: A compromised workload role should never be able to reach the bucket holding the evidence of the compromise. Centralize logs into a dedicated log archive account (an organization trail does this automatically), lock the bucket to CloudTrail plus a few trusted admins, and enable log file integrity validation so tampering is provable, not just assumed prevented.

See also: cloudtrail event types and trails · answering who changed what with cloudtrail

Answering "Who Changed What" with CloudTrail

coreintermediate

Every CloudTrail event record carries the same five answers in fixed fields: who (userIdentity), what (eventName + eventSource), when (eventTime), from where (sourceIPAddress), and under which identity a role was assumed through (userIdentity.sessionContext.sessionIssuer, or sourceIdentity if configured). Reading these five fields together is how you turn a raw log entry into an answer to "who did this."

Think of it as

Think of each CloudTrail event as a security-camera timestamp stamped with a badge photo: it does not just say "a door opened" — it says which badge opened it, at what time, from which entrance, and if that badge itself was borrowed from someone else's coat, who lent it.

What we're doing: Read a real CloudTrail event structure and pull out the five audit answers.

delete-bucket-event.jsonjson
{
  "eventVersion": "1.08",
  "userIdentity": {
    "type": "AssumedRole",
    "arn": "arn:aws:sts::123456789012:assumed-role/DevRole/jordan.reyes",
    "accountId": "123456789012",
    "sessionContext": {
      "sessionIssuer": {
        "type": "Role",
        "arn": "arn:aws:iam::123456789012:role/DevRole"
      }
    }
  },
  "eventTime": "2026-08-24T14:02:11Z",
  "eventSource": "s3.amazonaws.com",
  "eventName": "DeleteBucket",
  "sourceIPAddress": "203.0.113.42",
  "requestParameters": { "bucketName": "quarterly-reports-archive" }
}
3
who: an AssumedRole session named "jordan.reyes" — the ARN alone tells you the role, and the session name (set by the caller) usually maps to a specific person or automation job.
9
under which identity: sessionIssuer shows this session came from assuming the DevRole IAM role — the identity that was actually granted the DeleteBucket permission in the first place.
15
when: 2026-08-24 at 14:02:11 UTC, to the second.
16
what: eventSource + eventName together say precisely which API call happened — s3.amazonaws.com's DeleteBucket, not just "something in S3."
17
from where: sourceIPAddress narrows the search to a specific network origin, which is often the fastest way to tell a legitimate CI job from an unexpected human login.

Why this works: None of these five answers require correlating multiple systems — a single CloudTrail event record already carries all of them, which is exactly why CloudTrail (not application logs, not S3 access logs alone) is the first place an incident investigation starts.

Treating the IAM role name alone as "who did it"

Wrong

text
# See "arn:aws:iam::123456789012:role/DevRole" in the log and conclude
# "the DevRole did it" — stop the investigation there

Better

text
# Read userIdentity.arn for the specific assumed-role SESSION (which
# includes the session name), not just the role it was issued from, and
# check sourceIdentity if role chaining is in play

What you see: Multiple engineers share the same IAM role (common with CI/CD or a broadly-assigned developer role), so "DevRole did it" implicates an entire team instead of the one session, and the actual person or job responsible is never identified.

Why: A role is a permission set, not a person — many different sessions can assume the same role over time. The session name inside the assumed-role ARN (and sourceIdentity, if AWS STS is configured to require it) is what actually narrows "who" down to one caller instead of one shared credential.

The five fields, one record

who

userIdentity.arn

what

eventName + eventSource

when

eventTime

from where

sourceIPAddress

under which identity

sessionIssuer / sourceIdentity

  1. who — userIdentity.arn
  2. what — eventName + eventSource
  3. when — eventTime
  4. from where — sourceIPAddress
  5. under which identity — sessionIssuer / sourceIdentity

Remember: Every CloudTrail event carries who (userIdentity.arn), what (eventSource + eventName), when (eventTime), from where (sourceIPAddress), and under which identity (sessionIssuer / sourceIdentity) in one record — no cross-system correlation needed to get the first five answers of an investigation.

See also: cloudtrail event types and trails · centralizing and protecting audit logs

Advertisement