Home / AWS

AWSCheat sheetBeginner

Who changed this? Finding AWS changes with CloudTrail, Athena and Config

Commands and queries to find who changed a security group, IAM role, bucket or instance, from CloudTrail event history to Athena and AWS Config history.

Deepak Bhardwaj6 min read

Applies toAWS CloudTrail (event history and trails), Amazon Athena, AWS Config, AWS CLI v2

When to use this

Something changed and broke production. A security group opened up, an instance stopped, or a bucket policy disappeared. You need to know who did it, when, from where, and what the value was before. Here's where to look:

Question Tool Covers
Who made this API call in the last 90 days? CloudTrail event history (lookup-events) Management events, one region, last 90 days, no setup
Who did it more than 90 days ago, or across accounts? Athena over the trail's S3 bucket Whatever your trail logs, kept as long as the bucket keeps it
Who read or wrote an S3 object or invoked a Lambda function? Trail with data events enabled Only if data events were turned on before it happened
What did the resource look like before? AWS Config resource history Resources Config was recording

Tip

CloudTrail usually delivers events within about five minutes, but that isn't guaranteed. If a change from a minute ago isn't there yet, wait and search again before deciding it wasn't logged.

CloudTrail event history (CLI)

Event history is per region. Global services such as IAM, STS and CloudFront are logged in us-east-1, so search there for IAM changes.

You can pass only one lookup attribute per call. Valid keys are EventId, EventName, ReadOnly, Username, ResourceType, ResourceName, EventSource and AccessKeyId.

# Who touched this security group?
aws cloudtrail lookup-events --region eu-west-1 \
  --lookup-attributes AttributeKey=ResourceName,AttributeValue=sg-0abc1234 \
  --start-time 2026-10-20T00:00:00Z \
  --query "Events[].{time:EventTime,event:EventName,user:Username}" --output table

# Who stopped or terminated instances?
aws cloudtrail lookup-events \
  --lookup-attributes AttributeKey=EventName,AttributeValue=TerminateInstances \
  --max-results 50 \
  --query "Events[].{time:EventTime,user:Username,res:Resources[0].ResourceName}" --output table

# Everything a user or role session did (write calls only: filter afterwards)
aws cloudtrail lookup-events \
  --lookup-attributes AttributeKey=Username,AttributeValue=jane.doe@example.com \
  --start-time 2026-10-22T08:00:00Z --end-time 2026-10-22T12:00:00Z \
  --output json | jq -r '.Events[] | select((.CloudTrailEvent|fromjson).readOnly==false) | [.EventTime,.EventName] | @tsv'

# What did a leaked access key do?
aws cloudtrail lookup-events \
  --lookup-attributes AttributeKey=AccessKeyId,AttributeValue=AKIAEXAMPLE123 \
  --query "Events[].{time:EventTime,event:EventName,src:EventSource}" --output table

The full record is a JSON string in CloudTrailEvent. To see the source IP, the full identity ARN and the request parameters, parse it:

aws cloudtrail lookup-events \
  --lookup-attributes AttributeKey=EventName,AttributeValue=AuthorizeSecurityGroupIngress \
  --max-results 10 --output json \
  | jq '.Events[].CloudTrailEvent | fromjson | {eventTime, arn: .userIdentity.arn, sourceIPAddress, userAgent, requestParameters}'

lookup-events is limited to about two requests per second per account per region. Expect throttling if you loop over many attributes, and narrow the time window instead.

Common event names to search for

What happened EventName values
Security group rule added or removed AuthorizeSecurityGroupIngress, AuthorizeSecurityGroupEgress, RevokeSecurityGroupIngress, ModifySecurityGroupRules
Instance stopped, rebooted or deleted StopInstances, RebootInstances, TerminateInstances, ModifyInstanceAttribute
Routing changed CreateRoute, ReplaceRoute, DeleteRoute, ReplaceRouteTableAssociation
S3 bucket exposed or locked out PutBucketPolicy, DeleteBucketPolicy, PutBucketAcl, PutBucketPublicAccessBlock, DeletePublicAccessBlock
IAM permissions changed AttachRolePolicy, PutRolePolicy, DetachRolePolicy, UpdateAssumeRolePolicy, CreateAccessKey
Database changed ModifyDBInstance, RebootDBInstance, DeleteDBInstance, ModifyDBParameterGroup
Encryption key disabled DisableKey, ScheduleKeyDeletion, PutKeyPolicy
Logging tampered with StopLogging, DeleteTrail, UpdateTrail, PutEventSelectors
Console sign-in ConsoleLogin (check us-east-1 and the region the user signed in to)

Reading the identity

userIdentity.type What arn looks like How to trace it to a person
IAMUser arn:aws:iam::111122223333:user/jane That's the user
AssumedRole arn:aws:sts::111122223333:assumed-role/AWSReservedSSO_Admin_abc/jane.doe@example.com For IAM Identity Center, the session name is usually the user's username or email
AssumedRole (automation) .../assumed-role/deploy-role/GitHubActions or an instance ID Find the matching AssumeRole or AssumeRoleWithWebIdentity event to see who assumed it
AWSService invokedBy: autoscaling.amazonaws.com A service acted for you, so look at that service's config (for example, ASG scale-in)
Root arn:aws:iam::111122223333:root Treat this as an incident unless it was a planned break-glass action

Athena: older events and many accounts

If you have an organization trail, its bucket holds every account and region under AWSLogs/<org-id>/<account-id>/CloudTrail/<region>/. Create the Athena table from the CloudTrail console (Event history → Create Athena table), or use a DDL with partition projection so queries only scan the days you ask for. Table and partition column names depend on how the table was created, so check yours with SHOW CREATE TABLE.

-- Who changed a specific security group in October
SELECT eventtime, eventname, useridentity.arn, sourceipaddress, requestparameters
FROM cloudtrail_logs
WHERE eventsource = 'ec2.amazonaws.com'
  AND eventname IN ('AuthorizeSecurityGroupIngress','RevokeSecurityGroupIngress','ModifySecurityGroupRules')
  AND requestparameters LIKE '%sg-0abc1234%'
  AND eventtime BETWEEN '2026-10-01T00:00:00Z' AND '2026-10-23T23:59:59Z'
ORDER BY eventtime DESC
LIMIT 100;

-- Who is getting access denied, and on what
SELECT useridentity.arn, eventsource, eventname, errorcode, count(*) AS hits
FROM cloudtrail_logs
WHERE (errorcode LIKE '%AccessDenied%' OR errorcode LIKE '%UnauthorizedOperation%')
  AND eventtime > '2026-10-22T00:00:00Z'
GROUP BY 1,2,3,4
ORDER BY hits DESC
LIMIT 50;

-- Root activity in any account
SELECT eventtime, recipientaccountid, eventname, sourceipaddress
FROM cloudtrail_logs
WHERE useridentity.type = 'Root'
  AND eventtime > '2026-09-01T00:00:00Z'
ORDER BY eventtime DESC;

Warning

Athena charges by data scanned. A query with no partition or date filter scans the whole trail bucket, which can be many terabytes in a large organization. Always filter on the partition columns.

AWS Config: what the resource looked like before

CloudTrail tells you who made the call. Config shows the before and after state, which you need to roll back:

aws configservice get-resource-config-history \
  --resource-type AWS::EC2::SecurityGroup --resource-id sg-0abc1234 \
  --limit 5 \
  --query "configurationItems[].{time:configurationItemCaptureTime,status:configurationItemStatus,config:configuration}" \
  --output json

Each configuration item lists related CloudTrail event IDs in relatedEvents, which takes you back to the exact API call.

Prevent it next time

  • Run an organization trail for all regions, writing to a locked-down log archive account. Event history on its own only goes back 90 days.
  • Turn on data events for sensitive S3 buckets and Lambda functions now. They can't be added for the past.
  • Turn on log file validation (--enable-log-file-validation) so you can show logs weren't tampered with.
  • Alert on high-risk changes with EventBridge. This pattern matches CloudTrail API calls:
{
  "source": ["aws.ec2"],
  "detail-type": ["AWS API Call via CloudTrail"],
  "detail": { "eventName": ["AuthorizeSecurityGroupIngress", "DeleteRoute", "StopInstances"] }
}
  • Make people use named sessions (Identity Center, or sts:SourceIdentity for shared roles), so assumed-role entries lead back to a person.
  • Record the resource types you care about in AWS Config, so you always have a "before" to roll back to.