Claude CodeでCloudTrailのlookup-eventsから設定変更者を特定する
aws cloudtrail lookup-eventsだけを許可し、書き込みイベントを期間とユーザーで絞って変更者の時系列表を作る手順。フィルタが1つしか取れない制約と、90日の外をAthenaに回す線引きも示します。
「昨日の夕方、誰がセキュリティグループを開けたのか」という調べ物は、Claude Codeにaws cloudtrail lookup-eventsだけを許可して任せると、変更者・時刻・操作名を並べた時系列表まで一気に作れます。許可するのは参照コマンド1本で、結果はファイルに保存してjqで読ませます。この構成なら、同じ範囲を何度もAWSに問い合わせずに済みます。
押さえる制約は4つです。検索対象は直近90日、フィルタ条件は1つだけ、1回の応答は最大50件、リクエストは1秒に2回まで。90日より前はlookup-eventsの守備範囲外なので、Athenaに回します。以下、この線引きと権限の書き方を順に見ていきます。
最小権限のポリシーをCloudTrailの利用履歴から作る手順は、IAM最小権限をAccess Analyzerから作る記事にあります。ここで扱うのは逆向きの問いで、すでに起きた変更の犯人探しです。
lookup-eventsで引ける範囲を先に知る
lookup-eventsは、CloudTrailが記録した管理イベントとInsightsイベントを引くコマンドです。AWS CLIのリファレンスには、リージョン内で直近90日に起きたイベントを検索できると書かれています。
lookup-eventsの上限
検索できる期間
90日
リージョンごと
1回の取得件数
最大50件
既定も50件
リクエスト頻度
2回/秒
アカウント・リージョンごと
検索条件の数
1つ
lookup-attributesは1項目
見落としやすいのは4つ目です。--lookup-attributesには「指定できるのは1項目だけ」とあります。「EventNameがPutBucketPolicyで、かつユーザーがaliceのイベント」のように2条件を同時に渡す書き方はできません。片方をAWS側で絞り、もう片方はローカルでjqに絞らせます。
指定できる属性は、EventId、EventName、ReadOnly、Username、ResourceType、ResourceName、EventSource、AccessKeyIdです。変更者探しで使うのは主に次の3つになります。
| 知りたいこと | AttributeKey | 値の例 |
|---|---|---|
| 書き込み操作だけ見たい | AttributeKeyReadOnly | 値の例false |
| 特定の操作を誰が行ったか | AttributeKeyEventName | 値の例AuthorizeSecurityGroupIngress |
| 特定の人やロールが何をしたか | AttributeKeyUsername | 値の例ユーザー名またはロールセッション名 |
Usernameは、リクエスト元のユーザー名かロール名です。ロールを引き受けて操作した場合は、ロール名ではなくセッション名が入ることがあります。人を特定するときは、後述のとおりCloudTrailEventの中身まで読みます。
参照できるのは管理イベントです。S3オブジェクトの読み書きのようなデータイベントは、このコマンドの対象に含まれません。
許可するのはlookup-eventsだけにする
awsコマンドは、権限設定のページが挙げる組み込みの参照専用コマンド群(ls、cat、grep、findなど)に名前が出てきません。許可しなければ毎回確認が出ます。次のルールをプロジェクトの.claude/settings.jsonに置きます。
{
"permissions": {
"allow": [
"Bash(aws cloudtrail lookup-events *)",
"Bash(jq *)"
]
}
}書き方には、公式の規則から導ける落とし穴が3つあります。
落とし穴
awsを丸ごと拒否すると、狭い許可も効かなくなる
権限の評価順はdeny、ask、allowで、最初に一致したものが結果を決めます。Bash(aws *)のような広いdenyは、Bash(aws cloudtrail lookup-events *)に一致する呼び出しまで止めます。「awsは全部deny、lookup-eventsだけallow」という構成は成立しません。許可を1つずつ足す形にして、書き込み系を止めたいときはBash(aws s3 rm *)のように個別のdenyで補います。
1つ目は上のとおりです。2つ目は、ルールが書かれたコマンド文字列に対して前方一致する点です。*より前の部分がそのまま照合されるので、aws --profile audit cloudtrail lookup-eventsのようにサブコマンドの前へオプションを置くと、上のルールには一致せず確認が出ます。プロファイルやリージョンはaws cloudtrail lookup-events --profile audit --region ap-northeast-1とサブコマンドの後ろに並べます。
3つ目は環境変数の前置きです。許可ルールは、既知の安全な変数以外への代入をまたいでは一致しません。AWS_PROFILE=audit aws cloudtrail lookup-events ...と書くと確認が出るので、--profileで渡します。
パイプにも注意が要ります。aws ... | jq ...のような複合コマンドは、すべての部分が許可されて初めて確認なしで動きます。それ以前に、AWSの応答は一度ファイルへ保存するほうが調査には向いています。出力リダイレクト> events.jsonは、作業ディレクトリ内なら編集権限のルールで判定されます。
AWS側の権限も読み取り専用に寄せる
Claude Codeの許可ルールは、渡すコマンドの文字列を見る仕組みです。認証情報が持つ権限そのものは絞れません。調査用には、CloudTrailを引ける参照専用のロールやプロファイルを別に用意し、--profileで指定するのが堅い構成です。権限設定のページ自体も、コマンド文字列の照合は同じプログラムの別表記を止める境界ではないと書いています。
変更者を探す3ステップ
まず、書き込みイベントだけを期間で切って保存します。
aws cloudtrail lookup-events \
--lookup-attributes AttributeKey=ReadOnly,AttributeValue=false \
--start-time 2026-10-20T00:00:00Z \
--end-time 2026-10-21T00:00:00Z \
--region ap-northeast-1 --output json > events.json--start-timeと--end-timeは両端を含みます。開始が終了より後だとエラーになります。時刻はUTCで渡すので、日本時間の朝9時は00:00:00Zです。
次に、イベントごとの要点を1行にします。Events配列の各要素にはEventNameやUsernameのほか、イベント全体のJSONが文字列としてCloudTrailEventに入っています。文字列なので、jqのfromjsonで展開してから読みます。
jq -r '.Events[] | .CloudTrailEvent | fromjson
| [.eventTime, .userIdentity.arn, .eventName, .sourceIPAddress]
| @tsv' events.json | sortEventNameで絞りたいときは、AWS側のフィルタをReadOnlyからEventNameに替えるか、上のjqにselect(.eventName == "PutBucketPolicy")を足します。「あの操作を、あの人が」の2条件は、AWS側で片方、手元でもう片方、という分担です。
最後に、保存したファイルをClaudeに読ませて表にします。
events.json の書き込みイベントを、eventTime の昇順で表にしてください。
列は「時刻(UTCとJST)」「実行者のarn」「操作」「送信元IP」「変更内容」。
変更内容は requestParameters から読み取れる範囲だけを書き、
読み取れない項目は空欄にしてください。「読み取れる範囲だけ」と指定するのは、要約で推測が混ざるのを防ぐためです。返ってくる表の形は、たとえば次のようなものです。値は説明用の架空のものです。
| 時刻(JST) | 実行者 | 操作 | 送信元IP | 変更内容 |
|---|---|---|---|---|
| 10/20 14:03 | 実行者…/assumed-role/ops/alice | 操作AuthorizeSecurityGroupIngress | 送信元IP203.0.113.10 | 変更内容ポート22を0.0.0.0/0へ開放 |
| 10/20 14:21 | 実行者…/user/ci-deployer | 操作PutBucketPolicy | 送信元IP198.51.100.7 | 変更内容バケットポリシーを更新 |
| 10/20 17:48 | 実行者…/assumed-role/ops/bob | 操作ModifyDBInstance | 送信元IP203.0.113.22 | 変更内容保持期間を変更 |
変更内容はrequestParametersから読む
誰が・いつ・何を、の3つはイベントの標準項目で埋まります。「何がどう変わったか」は、リクエストの引数を記録するrequestParametersに出ます。AWS CLIリファレンスの出力例にも、userIdentity、eventTime、eventName、sourceIPAddress、requestParametersが並びます。
requestParametersは操作ごとに中身の形が違います。たとえばセキュリティグループの開放なら、対象のグループIDとポート範囲、許可したCIDRが入ります。中身が大きい操作は、1件ずつClaudeに読ませるのが確実です。
jq '.Events[] | select(.EventName == "AuthorizeSecurityGroupIngress")
| .CloudTrailEvent | fromjson | .requestParameters' events.jsonこのイベントは「変更を命じた記録」で、変更の前後の差ではありません。変更前の状態を知りたいときは、IaCのリポジトリ履歴や、別の時点のdescribe結果と突き合わせます。
人の特定にもCloudTrailEventを使います。ロール経由のイベントでは、userIdentity.arnの末尾にセッション名が入ります。このセッション名が本人のメールアドレスなどに紐づく運用なら、そこから担当者を引けます。紐づけていない運用では、ロールの引き受け(AssumeRole)イベントを同じ期間で引き、引き受けた側の送信元IPをたどります。
調査用のCLAUDE.mdを置く
同じ調査を繰り返すなら、作業ルールをCLAUDE.mdに書いておきます。許可ルールと違い、これはClaudeの動き方を方向づけるだけで、強制力はありません。
## CloudTrail調査
- 実行してよいAWSコマンドは aws cloudtrail lookup-events のみ
- --start-time と --end-time を必ず付け、範囲は直近90日以内にする
- 取得結果は events.json に保存し、以後は jq で読む(同じ範囲を再取得しない)
- 時刻はUTC。表にするときはJST併記再取得を避けるルールには、頻度制限という理由があります。リクエストは1秒に2回までで、超えるとスロットリングのエラーが返ります。AWS CLIは自動でページを順に取りに行くため、広い期間をまとめて引くと呼び出しが連続します。タイムアウトが出るときは--page-sizeを小さくすると、1回あたりの取得件数が減って回避できると説明されています。
90日より前はAthenaに回す
90日を過ぎたイベントは、lookup-eventsでは引けません。次の表で、どこまでをlookup-eventsに任せ、どこからを別の手段にするかを分けます。
| 調べたい範囲 | 手段 | 向いている場面 |
|---|---|---|
| 直近90日・1リージョン | 手段lookup-events | 向いている場面先週の設定変更の犯人探し |
| 90日より前、証跡がS3にある | 手段Athenaで証跡のログをSQL検索 | 向いている場面半年前の変更の経緯、複数日にまたがる集計 |
| 組織全体・複数リージョンを横断 | 手段CloudTrail Lake | 向いている場面アカウントをまたぐ調査 |
Athenaの公式ページは、CloudTrailのログがS3に保存されること、Athenaでそのログを直接検索できることを説明しています。また、複数のアカウント・リージョン・日付にまたがるSQL検索には、CloudTrail Lakeも選択肢だと案内しています。Lakeはイベントデータストアに保存する別の仕組みで、CloudTrailのコンソール内でクエリを実行します。
Athenaを使えるのは、証跡がS3へログを配信している場合です。配信していなければ、検索するログがそもそも手元にありません。90日を超える調査が起こりうる環境なら、起きる前に証跡の配信を確認します。
Athenaに回したあとの作業は、lookup-eventsと同じ形です。許可するコマンドをSQLの実行に替え、結果をファイルに落としてClaudeに表へ整形させます。Athenaを権限つきで任せる設計は、NAT Gatewayの料金調査の記事やALBログの集計記事にまとめられています。
変更者を探すときの切り分け
- Q調べたい変更は直近90日以内に起きたか?答えがいいえのとき答えがはい
Athena(証跡がS3にある場合)かCloudTrail Lakeで検索します。
lookup-eventsでは何も返りません。 - Q変更が起きたリージョンは分かっているか?答えがいいえのとき答えがはい
リージョンごとに同じコマンドを実行します。
--regionを替えて、結果を別ファイルに保存します。 - Q対象は管理イベント(設定の変更)か?答えがいいえのとき答えがはい
S3オブジェクトの読み書きのようなデータイベントは
lookup-eventsの対象外です。証跡のログ側を検索します。
ReadOnly=falseで期間を切って取得する
保存したevents.jsonをjqで時系列にし、requestParametersで変更内容を確かめます。
任せる範囲を絞った設計の効きどころ
この構成の要点は、Claude Codeに任せる範囲を「期間を切った参照」と「結果ファイルの整形」に絞ったことです。AWSに問い合わせるのは1回、以後は手元のJSONを読むだけなので、頻度制限にも触れません。複数条件の絞り込みを手元のjqで行うのは、lookup-eventsが検索条件を1つしか受けないことへの現実的な対処です。
対応しきれない部分もあります。検索はリージョン単位で、90日の壁があり、記録されるのは命令の内容であって変更前の状態ではありません。この3点は、調査を始める前に決めておくと手戻りが減ります。リージョンの当たりを付ける、90日を超えそうなら最初からAthenaを選ぶ、変更前の状態はIaCの履歴に当たる、の3つです。
権限の面では、狭いallowを積む形が前提になります。Bash(aws *)をdenyにして例外を許可する書き方は、評価順の都合で成立しません。--profileの位置と環境変数の前置きも、ルールに一致しなくなる原因として覚えておきます。
参照した一次ソース
この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。