AWS CloudTrail evidence for SOC 2 CC7 controls
Which CloudTrail events an auditor will actually accept, how to scope the query so it proves something, and why retention is usually the thing that fails you.
Rony Shulkin · Software Engineer

On this page
The short version: CloudTrail can satisfy a meaningful slice of SOC 2's CC7 criteria, but only if you can show three things — that logging was enabled continuously across the audit period, that someone reviewed what it produced, and that the log could not have been altered. Most teams get the first one right and lose the audit on the other two.
This is a post about the mechanics. It assumes you already know why you want monitoring evidence and are trying to work out which API call produces it.
What CC7 actually asks for
CC7 is the Common Criteria series covering system operations. Four of them touch logging directly:
| Criterion | In plain terms | What CloudTrail can show |
|---|---|---|
| CC7.1 | You detect configuration changes that introduce vulnerabilities | ConfigurationItemChangeNotification, plus CloudTrail management events |
| CC7.2 | You monitor for anomalies and act on them | Trail existence, delivery continuity, alarm history |
| CC7.3 | You evaluate detected events to decide if they are incidents | The triage record — not in CloudTrail |
| CC7.4 | You respond to identified incidents | The response record — not in CloudTrail |
Note what falls out of scope. CC7.3 and CC7.4 are about human judgement, and no log export demonstrates judgement. Teams routinely hand an auditor a pile of CloudTrail exports for all four and get pushed back on two of them. The log is evidence that you could have detected something. The ticket, the Slack thread, the runbook execution — that is evidence that you did.
The three questions an auditor is really asking
Was logging on for the whole period?
This is the one people assume is trivially satisfied and it usually is not. A SOC 2 Type II covers a window — commonly three to twelve months — and the assertion is continuous. A trail created two weeks before fieldwork proves two weeks.
The API call that answers this is not DescribeTrails, which only tells you the trail exists now:
aws cloudtrail get-trail-status --name <trail-arn> \
--query '{Logging:IsLogging,LatestDelivery:LatestDeliveryTime,LatestFailure:LatestDeliveryError}'
IsLogging is a point-in-time boolean. To evidence continuity you need the history of that boolean, which means CloudTrail's own record of changes to itself:
aws cloudtrail lookup-events \
--lookup-attributes AttributeKey=EventName,AttributeValue=StopLogging \
--start-time 2026-01-01 --end-time 2026-12-31
An empty result set is the evidence. Run the same lookup for DeleteTrail, UpdateTrail and PutEventSelectors — the last one matters because narrowing event selectors mid-period silently reduces what you were capturing, and an auditor who knows the service will ask.
The awkward part: lookup-events only reaches back 90 days. For a twelve-month period you are querying the S3 bucket or Athena instead, which is why the retention section below is not an afterthought.
Did anyone look at it?
Logging without review is CC7.2 half-done. The evidence here is not the log — it is the alarm that fired and what happened next.
If you have CloudWatch metric filters over a CloudTrail log group, the artefact is the alarm history:
aws cloudwatch describe-alarm-history \
--alarm-name root-account-usage \
--history-item-type StateUpdate \
--start-date 2026-01-01T00:00:00Z --end-date 2026-12-31T23:59:59Z
Two failure modes here, and both are common enough to plan around.
The first is an alarm that has never fired. An auditor may read that as "the control was never exercised", and testing operating effectiveness on a control that never operated is genuinely difficult. Some auditors accept a documented test — deliberately trigger the condition in a controlled way, screenshot the alert, file it. Ask before fieldwork rather than during.
The second is an alarm that fires constantly and is ignored. That is worse than no alarm, because you have produced documentary evidence of an alert nobody acted on. If you have a noisy alarm, fix the noise before the audit window, not during it.
Could the log have been tampered with?
CC7.1 wants integrity. CloudTrail has a purpose-built answer that a surprising number of teams leave off:
aws cloudtrail update-trail --name <trail> --enable-log-file-validation
With validation on, CloudTrail writes a signed digest file every hour. Verification is a single command, and its output is the evidence:
aws cloudtrail validate-logs --trail-arn <arn> \
--start-time 2026-07-01T00:00:00Z --end-time 2026-09-30T23:59:59Z
Pair it with S3 Object Lock in compliance mode on the destination bucket, and MFA delete. The three together let you say something much stronger than "we keep logs": you can say the logs are provably the ones that were written.
Retention is where this usually fails
Here is the trap, and it catches teams who did everything else right.
CloudTrail's free event history is 90 days. A Type II period is commonly a year. If your evidence strategy is "we will pull the logs when the auditor asks", and the auditor asks in month eleven about month one, the data is gone. There is no recovery from this. You cannot retroactively log.
The fix is dull and has to happen before the period starts, not during it:
- A trail delivering to S3, with a lifecycle policy that does not transition to Glacier Deep Archive inside the audit window — retrieval times measured in hours are a bad surprise mid-fieldwork.
- CloudWatch Logs retention set explicitly. The default on a new log group is never expire, which is fine, but an inherited group set to 30 days will quietly discard exactly what you need.
- An Athena table over the S3 bucket, so a question about February can be answered in November without a restore.
Scoping the query so it proves something
An auditor asking for "evidence of monitoring" and receiving a 4GB JSON dump has been given nothing. Evidence is a narrow query with a stated scope, and the scope statement matters as much as the result.
A usable artefact looks like this:
Control: CC7.2 — anomaly detection on privileged access Population: all
ConsoleLoginevents for accounts in the production organizational unit, 1 Jan 2026 – 31 Dec 2026 Query: the Athena SQL below Result: 1,204 events; 12 from IPs outside the corporate ranges; all 12 correspond to tickets OPS-3341 through OPS-3352 Collected: 4 Sep 2026 by R. Shulkin
SELECT eventtime, useridentity.arn, sourceipaddress, awsregion
FROM cloudtrail_logs
WHERE eventname = 'ConsoleLogin'
AND useridentity.type = 'IAMUser'
AND year = '2026'
ORDER BY eventtime;
The population statement is the part people skip, and it is the part that makes the artefact defensible. Without it the auditor cannot tell whether twelve exceptions out of 1,204 events is the whole picture or the twelve you chose to show.
What we do about this
The reason I have opinions about the scope statement is that we built the collector that has to write one. In Cyberwiz, evidence pulled from a cloud account is stored with its query, its population definition, its collection timestamp and an expiry — because an artefact without those four things is a file, not evidence, and the difference only becomes visible during fieldwork when it is too late to fix.
The expiry is the piece that surprises people — see continuous control monitoring vs point-in-time audit for why. A CloudTrail export is not durable evidence; it describes a window that recedes. Treating freshness as a property of the artefact rather than a thing you remember to check is most of what separates a programme that holds up from one that gets rebuilt every year.
The full artefact list by criteria is in the SOC 2 evidence collection checklist.
A short checklist
- One organization trail, all regions, management and relevant data events
- Log file validation on, verified at least once per quarter with the output kept
- S3 Object Lock in compliance mode; MFA delete on the bucket
- No lifecycle transition to deep archive inside the audit window
- Explicit CloudWatch Logs retention, checked rather than assumed
-
StopLogging/DeleteTrail/UpdateTraillookups run and empty - Alarm history exported, and every alert traceable to a ticket
- Every export filed with its query, population and collection date
The first six are configuration and take an afternoon. The last two are a habit, and they are the ones that decide how fieldwork goes.