90-day
default CloudTrail retention in Event History (free) -- S3-backed trails or CloudTrail Lake extend this to 7 years and enable structured querying
aws cloudtrail lookup-events
is the free CLI entry point for CloudTrail investigation -- limited to 90 days and single attribute lookups but sufficient for most initial triage
userIdentity.arn
is the primary pivot field in CloudTrail events -- all events from a compromised role or user will share this value, making it the starting point for scope assessment

AWS CloudTrail records every API call made in your account, including who made it, from where, what they did, and what the response was. When a GuardDuty finding or suspicious activity triggers an investigation, CloudTrail is the forensic record that lets you answer: what did this principal do, when did it start, and what is the full scope of the activity? These queries build a complete timeline from a single suspicious event.

Investigation Setup -- Verify You Have Comprehensive Logging

Before querying, confirm your logging baseline:

Check if a trail exists:

aws cloudtrail describe-trails --include-shadow-trails

Verify the trail is actively logging:

aws cloudtrail get-trail-status --name [trail-name]

Key fields to check in the output:

  • IsLogging: true -- trail is active
  • IsMultiRegionTrail: true -- required to catch cross-region activity; a single-region trail misses activity in other regions
  • HasCustomEventSelectors or HasInsightSelectors -- confirms data events and/or Insights are enabled

If no trail exists and you need one immediately:

aws cloudtrail create-trail \
  --name incident-trail \
  --s3-bucket-name [your-bucket] \
  --is-multi-region-trail

aws cloudtrail start-logging --name incident-trail

Note: a new trail only captures events going forward. For historical events, you are limited to Event History (90 days) unless a prior trail was writing to S3.

Starting Point -- Triaging a GuardDuty Finding

Retrieve the full GuardDuty finding detail:

aws guardduty get-findings \
  --detector-id [id] \
  --finding-ids [finding-id] \
  | jq '.Findings[0]'

Extract the three primary pivot values from the finding:

  1. Principal ARN (who acted):
jq '.Findings[0].Resource.AccessKeyDetails.UserName'
# or
jq '.Findings[0].Resource.AccessKeyDetails.PrincipalId'
  1. Source IP (where it came from):
jq '.Findings[0].Service.Action.AwsApiCallAction.RemoteIpDetails.IpAddressV4'
  1. The triggering API call (what they did):
jq '.Findings[0].Service.Action.AwsApiCallAction.Api'

These three values -- principal ARN, source IP, and triggering API call -- are the starting pivot points for everything that follows.

Free daily briefing

Briefings like this, every morning before 9am.

Threat intel, active CVEs, and campaign alerts, distilled for practitioners. 50,000+ subscribers. No noise.

Pivoting by Principal -- All Actions from a Role or User

Quick lookup via CLI (90-day window, single attribute at a time):

aws cloudtrail lookup-events \
  --lookup-attributes AttributeKey=Username,AttributeValue=[role-session-name] \
  --start-time 2026-05-13T00:00:00Z \
  --max-results 50 \
  | jq '.Events[] | {time: .EventTime, event: .EventName, region: (.CloudTrailEvent | fromjson | .awsRegion)}'

Full history via S3 trail with Athena (covers all time, all regions, all event types):

SELECT eventtime, eventsource, eventname, sourceipaddress,
       json_extract_scalar(useridentity, '$.arn') AS principal_arn,
       requestparameters
FROM cloudtrail_logs
WHERE year='2026' AND month='05'
  AND json_extract_scalar(useridentity, '$.sessionContext.sessionIssuer.arn') = '[role-arn]'
ORDER BY eventtime DESC
LIMIT 500;

The sessionContext.sessionIssuer.arn filter catches all assumed-role sessions from the compromised role, even if the session names differ across invocations.

Pivoting by IP -- All Principals from a Source IP

Determine whether multiple principals were used from the same suspicious IP:

aws cloudtrail lookup-events \
  --lookup-attributes AttributeKey=SourceIPAddress,AttributeValue=[suspicious-ip] \
  | jq '.Events[] | {time: .EventTime, principal: (.CloudTrailEvent | fromjson | .userIdentity.arn), event: .EventName}'

Athena equivalent:

SELECT DISTINCT json_extract_scalar(useridentity, '$.arn') AS principal,
       COUNT(*) AS event_count
FROM cloudtrail_logs
WHERE sourceipaddress = '[suspicious-ip]'
  AND year='2026' AND month='05'
GROUP BY 1
ORDER BY 2 DESC;

If the suspicious IP is an internal address, cross-reference with VPC Flow Logs:

aws logs filter-log-events \
  --log-group-name [vpc-flow-log-group] \
  --filter-pattern "[version, account, eni, source=[suspicious-ip], ...]" \
  --start-time [epoch-ms]

High-Risk Event Patterns to Look For

Categorize the activity you find by attacker objective:

Credential persistence (attacker creating durable access):

  • CreateAccessKey, CreateLoginProfile, UpdateLoginProfile

Privilege escalation:

  • AttachUserPolicy, AttachRolePolicy, PutUserPolicy, CreateRole, PassRole

Reconnaissance:

  • ListBuckets, ListFunctions, DescribeInstances, GetAccountSummary, ListRoles

Data exfiltration:

  • GetObject (S3), GetSecretValue (Secrets Manager), GetParameter (SSM Parameter Store), Decrypt (KMS)

Covering tracks:

  • DeleteTrail, StopLogging, DeleteLogGroup, DeleteFlowLogs

jq filter to isolate high-risk events from a downloaded CloudTrail export:

cat cloudtrail-export.json | jq '[
  .Records[] |
  select(.eventName | test(
    "CreateAccessKey|AttachUserPolicy|PutUserPolicy|CreateRole|GetSecretValue|DeleteTrail|StopLogging"
  ))
] | sort_by(.eventTime)'

Building the Timeline

Sort all events chronologically and extract the key fields:

cat cloudtrail-export.json | jq '[
  .Records[]
] | sort_by(.eventTime) | .[] | {
  time: .eventTime,
  action: .eventName,
  principal: .userIdentity.arn,
  region: .awsRegion,
  sourceIP: .sourceIPAddress,
  params: .requestParameters
}'

Analysis objectives for the timeline:

  1. Initial access event: the first API call from the suspicious principal or IP. This is the breach point. Check what credential was used and whether there was a ConsoleLogin or AssumeRole event immediately before it.

  2. Persistence mechanisms: any CreateAccessKey, CreateUser, or role modification after initial access. These indicate the attacker planned to return.

  3. Blast radius: all resource types accessed, all regions where activity occurred, any data events (S3 GetObject at scale indicates exfiltration).

Timeline template columns for documentation and escalation:

| Timestamp (UTC) | EventName | Principal ARN | Source IP | Region | Resource Type | Resource ARN | Notes |

The bottom line

CloudTrail investigation is a pivot exercise. Start with the three values from the triggering alert (principal, IP, event name), then systematically expand: all actions by that principal, all principals from that IP, all high-risk event types in the timeframe. The goal in the first 60 minutes is not to understand everything -- it is to bound the scope so you know whether you are dealing with a single compromised key or a broader intrusion.

Frequently asked questions

What is the difference between CloudTrail Event History and a CloudTrail trail?

Event History is the free 90-day lookup UI in the console. A trail is a configuration that writes all events to an S3 bucket (and optionally CloudWatch Logs or CloudTrail Lake) for longer retention and structured querying. You need a trail for serious incident investigation.

How do I find all actions taken by a specific IAM role in CloudTrail?

Use `aws cloudtrail lookup-events --lookup-attributes AttributeKey=Username,AttributeValue=[role-name]` for quick lookup. For comprehensive results, query the S3 trail with Athena or use CloudTrail Lake.

What CloudTrail events indicate a credential compromise?

High-risk events: ConsoleLogin (especially from a new IP or region), CreateAccessKey (attacker creating persistence), AssumeRole (lateral movement), GetSecretValue (data exfiltration from Secrets Manager), DescribeInstances followed by RunInstances (reconnaissance then resource deployment).

How do I set up Athena to query CloudTrail logs in S3?

Create an Athena table using the CloudTrail Athena DDL (AWS provides a one-click option in the CloudTrail console). Then query with standard SQL against the table, using partition filters (year, month, day) to control costs.

What is CloudTrail Lake and when should I use it instead of Athena?

CloudTrail Lake is a managed event data store with a SQL query interface. It is simpler to set up than Athena with S3 but costs more per event stored. Use CloudTrail Lake if you want fast setup for incident response; use Athena if you already have S3 trails and want to minimize ongoing cost.

How do I tell if a CloudTrail event came from an assumed role vs. a direct IAM user?

Check `userIdentity.type` in the event. Values: `IAMUser` (direct user), `AssumedRole` (temporary credentials from STS), `Root` (root account), `AWSService` (AWS service action). For `AssumedRole`, `userIdentity.sessionContext.sessionIssuer.arn` shows the underlying role.

Sources & references

  1. AWS CloudTrail Documentation
  2. AWS Security Incident Response Guide
  3. MITRE ATT&CK Cloud Matrix
  4. Pacu AWS Exploitation Framework
  5. CloudTrail Lake Query Reference

Free resources

25
Free download

Critical CVE Reference Card 2025–2026

25 actively exploited vulnerabilities with CVSS scores, exploit status, and patch availability. Print it, pin it, share it with your SOC team.

No spam. Unsubscribe anytime.

Free download

Ransomware Incident Response Playbook

Step-by-step 24-hour IR checklist covering detection, containment, eradication, and recovery. Built for SOC teams, IR leads, and CISOs.

No spam. Unsubscribe anytime.

Free newsletter

Get threat intel before your inbox does.

50,000+ security professionals read Decryption Digest for early warnings on zero-days, ransomware, and nation-state campaigns. Free, weekly, no spam.

Unsubscribe anytime. We never sell your data.

Eric Bang
Author

Founder & Cybersecurity Evangelist, Decryption Digest

Cybersecurity professional with expertise in threat intelligence, vulnerability research, and enterprise security. Covers zero-days, ransomware, and nation-state operations for 50,000+ security professionals weekly.

Free Brief

The Mythos Brief is free.

AI that finds 27-year-old zero-days. What it means for your security program.

Joins Decryption Digest. Unsubscribe anytime.

Daily Briefing

Get briefings like this every morning

Actionable threat intelligence for working practitioners. Free. No spam. Trusted by 50,000+ SOC analysts, CISOs, and security engineers.

Unsubscribe anytime.

Mythos Brief

Anthropic's AI finds zero-days your scanners miss.