Athena で CloudTrail ログ(S3 データイベント)を直接クエリして、IAM ユーザーやロールが操作したオブジェクトを特定する
はじめに
猫とアポロチョコが好きな m.hayakawa です。
今回は意外と準備が大変な Athena での CloudTrail 証跡ログのクエリについてやってみます。
管理イベントについてのクエリについてはいくつか記事があると思いますが、データイベントをどうクエリするかという内容の記事がなかったため、やってみようの精神で、本記事にてご紹介します。
前提知識: データイベントをクエリするケースについて
まず、どういったシーンでデータイベントをクエリしたくなるでしょうか。
アプリケーションから S3 へのアクセスに失敗した際の原因調査をするということがすぐに浮かびますが、今回はアプリケーションが使用している IAM エンティティの IAM ポリシーを最小化したい場合を想定とします。
IAM ユーザーの権限を最小化する際、IAM Access Analyzer のポリシー生成が便利ですが、この機能は管理イベントのみが対象となり、S3 の GetObject や PutObject などのデータイベントを分析対象に含みません。
データイベントを使用できません — IAM Access Analyzer は、生成されたポリシーで、Amazon S3 データイベントなどのデータイベントのアクションレベルのアクティビティを識別しません。
そのため、データイベントを主に使う IAM ユーザーの権限を、実際に使ったアクションやリソース単位まで詰めようとすると、ポリシー生成だけでは不十分です。
本記事で紹介すること
本記事では、CloudTrail の S3 データイベントを Athena で直接クエリし、次の 2 点を確認する方法を解説します。
- 対象の IAM ユーザーが、どの S3 データイベント(
GetObject、PutObjectなど)を実行したか - どのバケット・オブジェクト(リソース ARN)に対して実行したか
データイベントの対象サービスは代表的なものとして下記がありますが、本記事では対象外とします(いずれ記事化するかもしれません)。
- Amazon SNS
- Publish など
- Amazon CloudWatch
- メトリクスに対する API
- DynamoDB
- PutItem, DeleteItem, UpdateItem などのアイテムレベルの API
やってみた
本記事では、検証も兼ねて次の流れで進めます。
- CloudFormation で、データイベントの対象バケット・ログ保存先バケット・CloudTrail 証跡を作成する
- CloudTrail のマネジメントコンソールから Athena テーブルを作成する
- CloudFormation の Outputs(出力) で確認した対象バケットへ、CloudShell からオブジェクトをアップロードする
- Athena でクエリし、データイベントを確認する
ステップ1. CloudFormation で対象バケットと証跡を作成する
CloudTrail のデータイベントは、記録対象が多いほどコストがかかります。そこで本記事では、検証用の S3 バケットを 1 つ作成し、そのバケット配下のオブジェクトに対する操作のみをデータイベントとして記録します。
CloudFormation で、次のリソースを作成します。
- データイベントの記録対象となる検証用バケット
- このバケット内のオブジェクトのみをデータイベントとして記録する CloudTrail 証跡(管理イベントは無効)
- CloudTrail のログ保存先バケット
AWSTemplateFormatVersion: '2010-09-09'
Description: CloudTrail trail logging S3 data events for a single target bucket only
Resources:
# データイベントの記録対象となる検証用バケット
TargetBucket:
Type: AWS::S3::Bucket
Properties:
# スタック ID に含まれる UUID の先頭セグメント(8桁)をバケット名に付与
BucketName: !Sub
- 'dataevent-target-${AWS::AccountId}-${Suffix}'
- Suffix: !Select [0, !Split ['-', !Select [2, !Split ['/', !Ref 'AWS::StackId']]]]
PublicAccessBlockConfiguration:
BlockPublicAcls: true
BlockPublicPolicy: true
IgnorePublicAcls: true
RestrictPublicBuckets: true
# CloudTrail のログ保存先バケット
TrailBucket:
Type: AWS::S3::Bucket
Properties:
# スタック ID に含まれる UUID の先頭セグメント(8桁)をバケット名に付与
BucketName: !Sub
- 'cloudtrail-dataevent-${AWS::AccountId}-${Suffix}'
- Suffix: !Select [0, !Split ['-', !Select [2, !Split ['/', !Ref 'AWS::StackId']]]]
PublicAccessBlockConfiguration:
BlockPublicAcls: true
BlockPublicPolicy: true
IgnorePublicAcls: true
RestrictPublicBuckets: true
# CloudTrail がログを書き込むためのバケットポリシー
TrailBucketPolicy:
Type: AWS::S3::BucketPolicy
Properties:
Bucket: !Ref TrailBucket
PolicyDocument:
Version: '2012-10-17'
Statement:
- Sid: AWSCloudTrailAclCheck
Effect: Allow
Principal:
Service: cloudtrail.amazonaws.com
Action: s3:GetBucketAcl
Resource: !GetAtt TrailBucket.Arn
Condition:
StringEquals:
aws:SourceArn: !Sub 'arn:aws:cloudtrail:${AWS::Region}:${AWS::AccountId}:trail/dataevent-trail'
- Sid: AWSCloudTrailWrite
Effect: Allow
Principal:
Service: cloudtrail.amazonaws.com
Action: s3:PutObject
Resource: !Sub '${TrailBucket.Arn}/AWSLogs/${AWS::AccountId}/*'
Condition:
StringEquals:
s3:x-amz-acl: bucket-owner-full-control
aws:SourceArn: !Sub 'arn:aws:cloudtrail:${AWS::Region}:${AWS::AccountId}:trail/dataevent-trail'
# 検証用バケットのオブジェクトのみをデータイベントとして記録する証跡
DataEventTrail:
Type: AWS::CloudTrail::Trail
DependsOn: TrailBucketPolicy
Properties:
TrailName: dataevent-trail
S3BucketName: !Ref TrailBucket
IsLogging: true
IsMultiRegionTrail: false
IncludeGlobalServiceEvents: false
AdvancedEventSelectors:
- Name: S3 data events for target bucket only
FieldSelectors:
- Field: eventCategory
Equals:
- Data
- Field: resources.type
Equals:
- AWS::S3::Object
# 検証用バケット配下のオブジェクトに限定してコストを抑える
- Field: resources.ARN
StartsWith:
- !Sub '${TargetBucket.Arn}/'
Outputs:
TargetBucketName:
Description: S3 bucket for data event logging
Value: !Ref TargetBucket
TrailBucketName:
Description: S3 bucket for CloudTrail logs
Value: !Ref TrailBucket
※バケット名の重複を防ぐため、CloudFormation のスタック ID に含まれる UUID の先頭セグメントを付与しています。
上記のテンプレートを、CloudFormation のマネジメントコンソールからそのままデプロイします。今回の検証リージョンは ap-northeast-1 としました。
- CloudFormation コンソールを開く
- 「スタックの作成」→「新しいリソースを使用(標準)」を選択する
- 下記のテンプレートをファイルとして用意し、「テンプレートファイルのアップロード」で指定する
- スタック名を
cloudtrail-dataeventとする(この後の AWS CLI コマンドで、CloudFormation スタックから各リソース名を取得するため、スタック名は固定としてください。) - 内容を確認してスタックを作成する
作成後、検証用バケット名とログ保存先バケット名は、スタックの Output(出力)タブにある TargetBucketName と TrailBucketName で確認できます。以降の手順では、これらの値を使用します。
ステップ2. Athena テーブルを作成する
CloudTrail のマネジメントコンソールから Athena テーブルを作成します。
- CloudTrail コンソールの「イベント履歴」を開く
- Athena テーブルの作成画面で、CloudFormation の Outputs(出力) に表示された
TrailBucketNameの値をログ保存先として指定する - 「テーブルを作成」を押下する

上記手順で作成されるテーブルは非パーティションです。パーティションを使いたい場合は、Athena で手動作成してください。
ステップ3. AWS CLI で S3 を操作してデータイベントを発生させる
下記の手順を実施します。
- S3 へのアクセス権限がある IAM ユーザー/ロールでマネジメントコンソールへログイン
- CloudShell から、データイベント取得対象バケットへオブジェクトをアップロード
# CloudFormation スタックのOutputsからTargetBucketNameを取得
TARGET_BUCKET_NAME=$(aws cloudformation describe-stacks \
--stack-name cloudtrail-dataevent \
--region ap-northeast-1 \
--query "Stacks[0].Outputs[?OutputKey=='TargetBucketName'].OutputValue" \
--output text)
echo "データイベント対象バケット: ${TARGET_BUCKET_NAME}"
# テスト用ファイルを作成
echo "hello" > testobject.txt
# オブジェクトをアップロード(PutObject: データイベント)
aws s3api put-object \
--region ap-northeast-1 \
--bucket "${TARGET_BUCKET_NAME}" \
--key testobject.txt \
--body testobject.txt

ステップ4. Athena でクエリする
データイベントは反映まで数分かかることがあります。
データイベント保存先バケットにオブジェクトが生成されたら、以下のクエリで確認します。
対象 IAM ロールが実行した S3 イベントを抽出する
IAM ロールで操作した場合、useridentity.type は AssumedRole になります。useridentity.arn はロール本体の ARN ではなく、セッション名を含む STS の ARN です。そのため、ロール本体の ARN を保持する useridentity.sessioncontext.sessionissuer.arn で絞り込みます。
SELECT
eventtime,
eventsource,
eventname,
errorcode,
resources
FROM <ここはAthenaのテーブル名に置き換え>
WHERE useridentity.type = 'AssumedRole'
AND useridentity.sessioncontext.sessionissuer.arn = 'arn:aws:iam::123456789012:role/example-role'
AND eventsource = 's3.amazonaws.com'
ORDER BY eventtime DESC;
なお、クエリ結果は下記のようになりました。
# eventtime eventsource eventname errorcode resources
1 2026-09-04T02:11:32Z s3.amazonaws.com PutObject [{arn=arn:aws:s3:::dataevent-target-123456789012-385d36f0, accountid=123456789012, type=AWS::S3::Bucket}, {arn=arn:aws:s3:::dataevent-target-123456789012-385d36f0/testobject.txt, accountid=null, type=AWS::S3::Object}]
2 2026-09-04T02:10:10Z s3.amazonaws.com HeadBucket [{arn=arn:aws:s3:::dataevent-target-123456789012-385d36f0, accountid=123456789012, type=AWS::S3::Bucket}, {arn=null, accountid=null, type=AWS::S3::Object}]
3 2026-09-04T01:56:25Z s3.amazonaws.com HeadBucket [{arn=arn:aws:s3:::dataevent-target-123456789012-385d36f0, accountid=123456789012, type=AWS::S3::Bucket}, {arn=null, accountid=null, type=AWS::S3::Object}]
対象 IAM ユーザーが実行した S3 イベントを抽出する
IAM ユーザーを対象にする場合は、useridentity.type = 'IAMUser' と useridentity.arn = 'arn:aws:iam::<アカウントID>:user/<ユーザー名>' の組み合わせに変更します。
SELECT
eventtime,
eventsource,
eventname,
errorcode,
resources
FROM <ここはAthenaのテーブル名に置き換え>
WHERE useridentity.type = 'IAMUser'
AND useridentity.arn = 'arn:aws:iam::123456789012:user/example-user'
AND eventsource = 's3.amazonaws.com'
ORDER BY eventtime DESC;
123456789012 は対象 AWS アカウントの ID、example-user は対象 IAM ユーザー名に置き換えてください。
なお、クエリ結果は下記のようになりました。
# eventtime eventsource eventname errorcode resources
1 2026-09-04T03:08:41Z s3.amazonaws.com PutObject [{arn=arn:aws:s3:::dataevent-target-123456789012-385d36f0, accountid=123456789012, type=AWS::S3::Bucket}, {arn=arn:aws:s3:::dataevent-target-123456789012-385d36f0/testobject2.txt, accountid=null, type=AWS::S3::Object}]
リソース(ARN)単位で確認する
どのリソースに対して実行したかは、resources フィールドから確認します。resources は ARRAY<STRUCT> 型のため、CROSS JOIN UNNEST で展開します。
SELECT
ct.eventname,
resource.arn AS resource_arn,
resource.type AS resource_type,
count(*) AS event_count
FROM <ここはAthenaのテーブル名に置き換え> AS ct
CROSS JOIN UNNEST(ct.resources) AS t(resource)
WHERE ct.useridentity.type = 'AssumedRole'
AND ct.useridentity.sessioncontext.sessionissuer.arn = 'arn:aws:iam::123456789012:role/example-role'
AND ct.eventsource = 's3.amazonaws.com'
AND ct.errorcode IS NULL
GROUP BY ct.eventname, resource.arn, resource.type
ORDER BY event_count DESC;
今回の場合は、resource.arn を元にアクセス先を絞り込めます。なお、resources は任意フィールドであり、記録される内容はサービスや API によって異なります。すべてのイベントでリソースの ARN が得られるとは限らない点には注意してください。
なお、クエリ結果は下記のようになりました。
# eventname resource_arn resource_type event_count
1 HeadBucket arn:aws:s3:::dataevent-target-123456789012-385d36f0 AWS::S3::Bucket 2
2 HeadBucket AWS::S3::Object 2
3 ListObjects AWS::S3::Object 1
4 ListObjects arn:aws:s3:::dataevent-target-123456789012-385d36f0 AWS::S3::Bucket 1
5 PutObject arn:aws:s3:::dataevent-target-123456789012-385d36f0 AWS::S3::Bucket 1
6 PutObject arn:aws:s3:::dataevent-target-123456789012-385d36f0/testobject.txt AWS::S3::Object 1
上記のように、データイベントを元にクエリを行うことができます。最小権限を絞るためのクエリとしては、まず上記を抑えておいて、環境に合わせて応用してくことが考えられます。
後片付け
検証後は、課金とデータイベント記録の停止のためにリソースを削除します。CloudFormation スタック cloudtrail-dataevent の Outputs から、対象バケット名とログ保存先バケット名を取得します。
# CloudFormation スタックのOutputsからバケット名を取得
TARGET_BUCKET_NAME=$(aws cloudformation describe-stacks \
--stack-name cloudtrail-dataevent \
--region ap-northeast-1 \
--query "Stacks[0].Outputs[?OutputKey=='TargetBucketName'].OutputValue" \
--output text)
TRAIL_BUCKET_NAME=$(aws cloudformation describe-stacks \
--stack-name cloudtrail-dataevent \
--region ap-northeast-1 \
--query "Stacks[0].Outputs[?OutputKey=='TrailBucketName'].OutputValue" \
--output text)
# 検証用バケットを空にする
aws s3 rm "s3://${TARGET_BUCKET_NAME}" --recursive
# CloudTrail の保存先バケットを空にする
aws s3 rm "s3://${TRAIL_BUCKET_NAME}" --recursive
その後、CloudFormation コンソールで cloudtrail-dataevent スタックを削除します。スタック削除時にバケットが空でないというエラーになった場合は、ログが追加されていないか確認して、両方のバケットを再度空にしてからスタックを削除します。
まとめ
CloudTrail の S3 データイベントを対象バケットに限定して記録し、Athena で直接クエリすることで、IAM Access Analyzer のポリシー生成では拾えないデータイベントと、アクセス対象のリソース ARN を確認できました。
データイベントまで踏み込んで最小権限を検討する場合は、成功した操作だけでなく、拒否された操作や実際のリソース ARN も確認したうえで IAM ポリシーを判断しましょう。
本記事がどこかの誰かに役立ちますように。
参考資料
- Query AWS CloudTrail logs - Amazon Athena ユーザーガイド
- AthenaによるCloudTrailログのクエリ例 - Amazon Athena ユーザーガイド
- ポリシーの生成 - IAM ユーザーガイド
クラスメソッドオペレーションズ株式会社について
クラスメソッドグループのオペレーション企業です。
運用・保守開発・サポート・情シス・バックオフィスの専門チームが、IT・AI をフル活用した「しくみ」を通じて、お客様の業務代行から課題解決や高付加価値サービスまでを提供するエキスパート集団です。
当社は様々な職種でメンバーを募集しています。
「オペレーション・エクセレンス」と「らしく働く、らしく生きる」を共に実現するカルチャー・しくみ・働き方にご興味がある方は、クラスメソッドオペレーションズ株式会社 コーポレートサイト をぜひご覧ください。※2026 年 1 月 アノテーション㈱から社名変更しました










