CloudTrail のイベント履歴は、過去90日分の管理イベントを表示・検索・ダウンロードできる機能です。これはアカウント作成時から自動で利用できますが、90日を超えた調査には向きません。
一方で、CloudTrail の証跡を作成しておくと、イベントを S3 バケットへ配信・保存できます。90日超の調査は、この S3 に保存されたログを Athena で検索する必要があります。
目次
手順の全体像
手順の全体像は以下になります。
CloudTrail Trail
↓
S3 バケットにログ保存
↓
Athena テーブル作成
↓
SQLで検索
重要なのは、事故が起きてからAthenaテーブルを作るのではなく、事前に作っておくことです。
手順1:CloudTrail が S3 に保存されているか確認する
CloudTrail の証跡が S3 にログを出している必要があります。
AWSコンソールの場合
CloudTrail
↓
証跡
↓
対象のTrail
↓
S3 バケット名を確認
確認するポイントは以下です。
・S3BucketName
・S3KeyPrefix
・IsOrganizationTrail
・IsMultiRegionTrail
手順2:S3上のCloudTrailログパスを確認する
S3 にログがあるか確認します。通常のアカウント証跡なら、以下のようなパスになります。
s3://<bucket>/AWSLogs/<account-id>/CloudTrail/<region>/<yyyy>/<mm>/<dd>/
【例】
s3://aws-cloudtrail-log-aggregation-xxxx/AWSLogs/123456789012/CloudTrail/ap-northeast-1/2026/05/16/
組織証跡の場合は、以下のように Organization ID が入ることがあります。
s3://<bucket>/AWSLogs/<organization-id>/<account-id>/CloudTrail/<region>/<yyyy>/<mm>/<dd>/
【例】
s3://aws-cloudtrail-log-aggregation-xxxx/AWSLogs/o-xxxxxxxxxx/123456789012/CloudTrail/ap-northeast-1/2026/05/16/
手順3:Athena のクエリ結果保存先を設定する
Athena はクエリ結果を S3 に保存します。未設定だとクエリ実行時にエラーになります。
AWS コンソールの場合
Athena
↓
Query editor
↓
Settings
↓
Manage
↓
Query result location
ログ用S3バケットと同じでも動きますが、分けて管理しても良いです。
手順4:Athena データベースを作成する
Athena のクエリエディタで実行します。
CREATE DATABASE IF NOT EXISTS cloudtrail_logs;
その後、利用DBを切り替えます。
USE cloudtrail_logs;
手順5:Athena テーブルを作成する
ここが一番重要です。CloudTrail ログは日付・リージョン・アカウントIDでパスが分かれているので、Athena では パーティション を使うのが基本です。
さらに、CloudTrail はパス構造が決まっているため、Partition Projection を使うと便利です。Partition Projection を使うと、新しい日付のパーティションを ALTER TABLE ADD PARTITION で毎回追加しなくて済みます。
パターンA:通常のアカウント証跡の場合
S3 パスが以下の場合です。
s3://<bucket>/AWSLogs/<account-id>/CloudTrail/<region>/<yyyy>/<mm>/<dd>/
Athena テーブル例です。
CREATE EXTERNAL TABLE IF NOT EXISTS cloudtrail_logs.cloudtrail_partitioned (
eventVersion string,
userIdentity struct<
type:string,
principalId:string,
arn:string,
accountId:string,
invokedBy:string,
accessKeyId:string,
userName:string,
sessionContext:struct<
attributes:struct<
mfaAuthenticated:string,
creationDate:string
>,
sessionIssuer:struct<
type:string,
principalId:string,
arn:string,
accountId:string,
userName:string
>
>
>,
eventTime string,
eventSource string,
eventName string,
awsRegion string,
sourceIPAddress string,
userAgent string,
errorCode string,
errorMessage string,
requestParameters string,
responseElements string,
additionalEventData string,
requestID string,
eventID string,
readOnly string,
resources array<struct<
arn:string,
accountId:string,
type:string
>>,
eventType string,
apiVersion string,
recipientAccountId string,
serviceEventDetails string,
sharedEventID string,
vpcEndpointId string
)
PARTITIONED BY (
account_id string,
region string,
year string,
month string,
day string
)
ROW FORMAT SERDE 'org.apache.hive.hcatalog.data.JsonSerDe'
STORED AS INPUTFORMAT 'com.amazon.emr.cloudtrail.CloudTrailInputFormat'
OUTPUTFORMAT 'org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat'
LOCATION 's3://<cloudtrail-log-bucket>/AWSLogs/'
TBLPROPERTIES (
'projection.enabled' = 'true',
'projection.account_id.type' = 'enum',
'projection.account_id.values' = '123456789012,234567890123',
'projection.region.type' = 'enum',
'projection.region.values' = 'ap-northeast-1,us-east-1,ap-southeast-1',
'projection.year.type' = 'integer',
'projection.year.range' = '2023,2030',
'projection.month.type' = 'integer',
'projection.month.range' = '1,12',
'projection.month.digits' = '2',
'projection.day.type' = 'integer',
'projection.day.range' = '1,31',
'projection.day.digits' = '2',
'storage.location.template' = 's3://<cloudtrail-log-bucket>/AWSLogs/${account_id}/CloudTrail/${region}/${year}/${month}/${day}'
);
置き換える場所はここです。
<cloudtrail-log-bucket>
123456789012,234567890123
ap-northeast-1,us-east-1,ap-southeast-1
パターンB:Organizations 組織証跡の場合
S3 パスがこれの場合です。
s3://<bucket>/AWSLogs/<organization-id>/<account-id>/CloudTrail/<region>/<yyyy>/<mm>/<dd>/
その場合、最後の storage.location.template が変わります。
'storage.location.template' = 's3://<cloudtrail-log-bucket>/AWSLogs/o-xxxxxxxxxx/${account_id}/CloudTrail/${region}/${year}/${month}/${day}'
組織証跡は、管理アカウントで作成すると組織内アカウントのイベントを同じS3バケットへ集約できます。メンバーアカウント側では組織証跡を表示できますが、変更・削除はできません。
手順6:まず1日分だけ検索テストする
テーブル作成後、いきなり広範囲検索しない方がいいです。まず1アカウント、1リージョン、1日で試します。
SELECT
eventTime,
eventSource,
eventName,
userIdentity.arn,
sourceIPAddress,
awsRegion
FROM cloudtrail_logs.cloudtrail_partitioned
WHERE account_id = '123456789012'
AND region = 'ap-northeast-1'
AND year = '2026'
AND month = '05'
AND day = '16'
ORDER BY eventTime DESC
LIMIT 50;
結果が返ればOKです。
手順7:よく使う調査SQLを保存する
DB を運用しているとよく使用する調査 SQL が定まってきます。その SQL を保存します。
1. 誰がログインしたか確認する
SELECT
eventTime,
userIdentity.type,
userIdentity.arn,
sourceIPAddress,
userAgent,
responseElements,
additionalEventData
FROM cloudtrail_logs.cloudtrail_partitioned
WHERE account_id = '123456789012'
AND region = 'us-east-1'
AND year = '2026'
AND month = '05'
AND eventName = 'ConsoleLogin'
ORDER BY eventTime DESC;
ConsoleLogin はグローバル系イベントとして us-east-1 側で見ることが多いです。
2. IAM変更を調べる
SELECT
eventTime,
eventName,
userIdentity.arn,
sourceIPAddress,
requestParameters,
errorCode,
errorMessage
FROM cloudtrail_logs.cloudtrail_partitioned
WHERE account_id = '123456789012'
AND region = 'us-east-1'
AND year = '2026'
AND month = '05'
AND eventSource = 'iam.amazonaws.com'
AND eventName IN (
'CreateUser',
'DeleteUser',
'CreateRole',
'DeleteRole',
'AttachUserPolicy',
'DetachUserPolicy',
'AttachRolePolicy',
'DetachRolePolicy',
'PutUserPolicy',
'PutRolePolicy',
'UpdateAssumeRolePolicy',
'CreateAccessKey',
'DeleteAccessKey'
)
ORDER BY eventTime DESC;
IAM はグローバルサービスなので、まず us-east-1 を見るのが実務上わかりやすいです。
3. セキュリティグループ変更を調べる
SELECT
eventTime,
eventName,
userIdentity.arn,
sourceIPAddress,
requestParameters,
errorCode
FROM cloudtrail_logs.cloudtrail_partitioned
WHERE account_id = '123456789012'
AND region = 'ap-northeast-1'
AND year = '2026'
AND month = '05'
AND eventSource = 'ec2.amazonaws.com'
AND eventName IN (
'AuthorizeSecurityGroupIngress',
'AuthorizeSecurityGroupEgress',
'RevokeSecurityGroupIngress',
'RevokeSecurityGroupEgress',
'ModifySecurityGroupRules',
'CreateSecurityGroup',
'DeleteSecurityGroup'
)
ORDER BY eventTime DESC;
4. ルートユーザー利用を調べる
SELECT
eventTime,
eventSource,
eventName,
userIdentity.type,
userIdentity.arn,
sourceIPAddress,
userAgent
FROM cloudtrail_logs.cloudtrail_partitioned
WHERE account_id = '123456789012'
AND year = '2026'
AND month = '05'
AND userIdentity.type = 'Root'
ORDER BY eventTime DESC;
5. エラーになったAPIを調べる
SELECT
eventTime,
eventSource,
eventName,
userIdentity.arn,
sourceIPAddress,
errorCode,
errorMessage
FROM cloudtrail_logs.cloudtrail_partitioned
WHERE account_id = '123456789012'
AND year = '2026'
AND month = '05'
AND errorCode IS NOT NULL
ORDER BY eventTime DESC
LIMIT 200;
6. 特定IPからの操作を調べる
SELECT
eventTime,
eventSource,
eventName,
userIdentity.arn,
sourceIPAddress,
awsRegion,
requestParameters
FROM cloudtrail_logs.cloudtrail_partitioned
WHERE year = '2026'
AND month = '05'
AND sourceIPAddress = 'xxx.xxx.xxx.xxx'
ORDER BY eventTime DESC
LIMIT 200;
この場合、アカウントやリージョンを絞らないと広範囲検索になるので、できれば以下も入れた方がいいです。
AND account_id = '123456789012'
AND region = 'ap-northeast-1'
7. 特定ユーザー・ロールの操作を調べる
SELECT
eventTime,
eventSource,
eventName,
userIdentity.arn,
sourceIPAddress,
awsRegion,
requestParameters
FROM cloudtrail_logs.cloudtrail_partitioned
WHERE account_id = '123456789012'
AND year = '2026'
AND month = '05'
AND userIdentity.arn LIKE '%AdminRole%'
ORDER BY eventTime DESC
LIMIT 300;
手順8:コスト・速度対策
Athena はスキャンしたデータ量に応じて課金されます。
なので、CloudTrail 調査では必ず以下を絞ります。
account_id
region
year
month
day
eventSource
eventName
以下は悪い例です。
SELECT *
FROM cloudtrail_logs.cloudtrail_partitioned
WHERE eventName = 'CreateUser';
これは全期間・全アカウント・全リージョンを見に行く可能性があり、遅く高くなります。
良い例です。
SELECT
eventTime,
eventName,
userIdentity.arn,
sourceIPAddress,
requestParameters
FROM cloudtrail_logs.cloudtrail_partitioned
WHERE account_id = '123456789012'
AND region = 'us-east-1'
AND year = '2026'
AND month = '05'
AND day BETWEEN '01' AND '16'
AND eventSource = 'iam.amazonaws.com'
AND eventName = 'CreateUser';
コメント