HackBlue

Tools · AWS detection and investigation · 07 / 24

CloudTrail Lake: query status, pages, and the note

Read QueryStatus before any rows, follow NextToken until it is absent, and file the statement, query id, store id, and result count with the note.

12 min read · practice only with authorization

Status before rows

start-query returns a query id and does not wait. get-query-results then reports QueryStatus. FINISHED means the engine completed and the statistics block is meaningful. FAILED, CANCELLED, and TIMED_OUT are outcomes with a message, not an empty story. QUEUED and RUNNING mean you do not have the answer yet.

Impact of reading too early: an empty row list while the status is still RUNNING looks like “nothing happened.” Control: write the status on the note, and only then write the rows. A failure message is copied too, so the next person does not rerun a broken statement and think the account was quiet.

Snippet
aws cloudtrail get-query-results --query-id "$QID" \
  --query '{Status:QueryStatus,Error:ErrorMessage,Stats:QueryStatistics,Token:NextToken}'

The first page is not the set

QueryStatistics.ResultsCount is how many rows the finished query produced. The body holds one page. When NextToken is present, pass it back as --next-token and repeat until the token is absent. Counting the lines on the first screen undercounts whenever the token was set.

What not to trust: “only a handful of calls” when ResultsCount is larger than the page you pasted. The note records ResultsCount and how many pages you actually saved.

What to file with the rows

Save the statement text, the event data store id in the FROM clause, the query id, the final status, ResultsCount, and TotalBytesScanned when the statistics include it. Those fields are how someone else reruns the same question and notices if the store or the window changed. A screenshot of a table without the statement cannot be rerun.

Snippet
store: <event data store id>
queryId: <id>
status: FINISHED
resultsCount: <from QueryStatistics>
pagesSaved: <how many NextToken steps you followed>
bytesScanned: <from QueryStatistics>
statement: |
  <the SQL, including the time bound>

Lab: force a second page in the sandbox

Point a query at a few hours of your own sandbox management events, with a time bound, and pass a small page size so a busy hour returns a token. Record status on the first call. If it is not FINISHED, wait and call again with the same query id. Then walk NextToken until it disappears, and check that the rows you saved add up to ResultsCount.

If the status is FAILED, the lab note is the error message and the statement, not a claim that the account had no events. Fix the statement in the sandbox and run a new query id. Do not reuse the failed id as if it were a finished result.

Diagram

From a query id to a complete note

  1. Status

    Read QueryStatus before counting rows.

    Field
    FINISHED, not RUNNING
  2. Count

    Copy ResultsCount from the statistics.

    Field
    not the length of one page
  3. Pages

    Follow NextToken until it is absent.

    Command
    get-query-results --next-token
  4. File

    Keep the statement, store id, and query id.

    Note
    so the question can be rerun

Check yourself

1. Rows are empty and QueryStatus is RUNNING. What is true?

2. ResultsCount is 40 and the first response has a NextToken. What do you have?

3. Which item lets someone else rerun the question?