はじめに
IAM Identity Center で SSO ログインしたユーザーの S3 オブジェクト操作をモニタリングしたい場合は、CloudTrail のデータべイベントの useridentity.principalid
や useridentity.arn
に着目する必要があります。
SELECT
eventtime,
useridentity.principalid,
useridentity.arn,
sourceipaddress,
useragent,
requestparameters
FROM cloudtrail_logs_pp
WHERE eventtime >= '2025-04-28T06:00:00Z'
AND eventname in ('GetObject','PutObject', 'DeleteObject', 'DeleteObjects')
AND eventSource = 's3.amazonaws.com'
AND useridentity.type = 'AssumedRole'
AND useragent != 'athena.amazonaws.com';
CloudTrail イベントの useridentity エレメントの違い
以下のドキュメントに例が記載されています。
IAM ユーザーによる操作 ("type": "IAMUser")
userIdentity.userName
に IAM ユーザー名が記録されます。
"userIdentity": {
"type": "IAMUser",
"principalId": "AIDAJ45Q7YFFAREXAMPLE",
"arn": "arn:aws:iam::123456789012:user/Alice",
"accountId": "123456789012",
"accessKeyId": "",
"userName": "Alice"
}
SSO ユーザーによる操作 ("type": "AssumedRole")
SSO ログインの場合には、内部的には STS による Assume Role でのアクセスになるため、username には値が記録されません。そのため、ロールセッション名を確認できる useridentity.principalid
や useridentity.arn
を参照します。
userIdentity.sessionContext.serssionIssuer
で参照できる arn や userName はロール名のみが記録されるため、ユーザーまでは把握できません。
"userIdentity": {
"type": "AssumedRole",
"principalId": "AROAIDPPEZS35WEXAMPLE:AssumedRoleSessionName",
"arn": "arn:aws:sts::123456789012:assumed-role/RoleToBeAssumed/MySessionName",
"accountId": "123456789012",
"accessKeyId": "",
"sessionContext": {
"sessionIssuer": {
"type": "Role",
"principalId": "AROAIDPPEZS35WEXAMPLE",
"arn": "arn:aws:iam::123456789012:role/RoleToBeAssumed",
"accountId": "123456789012",
"userName": "RoleToBeAssumed"
},
"attributes": {
"mfaAuthenticated": "false",
"creationDate": "20131102T010628Z"
}
}
}
参考: IAM Identity Center ユーザーに代わって行われたリクエスト ("type": "IdentityCenterUser")
"userIdentity": {
"type": "IdentityCenterUser",
"accountId": "123456789012",
"onBehalfOf": {
"userId": "544894e8-80c1-707f-60e3-3ba6510dfac1",
"identityStoreArn": "arn:aws:identitystore::123456789012:identitystore/d-9067642ac7"
},
"credentialId": "EXAMPLEVHULjJdTUdPJfofVa1sufHDoj7aYcOYcxFVllWR_Whr1fEXAMPLE"
}
このタイプのリクエストは IAM Identity Center を使用してアプリケーションアクセスを行った場合などに記録されます。例えば Amazon Q Developer (CodeWhisperer) の IDE によるアクティビティをデータイベントとして取得できますが、"type": "IdentityCenterUser" で記録されます。
AWS アカウントにアクセスしたユーザーの操作記録は前述のとおり "type": "AssumedRole"
として記録されることにご注意ください。
また 2025/7/14 以降、アクセスポータルにおける操作など、これまで userIdentity が Unknown で記録されていたものも "type": "IdentityCenterUser"
として記録されるように変更される予定です。
変更の対象:
- UserAuthentication などの IAM Identity Center サインインイベント
- Authenticate, GetRoleCredentials などの AWS アクセスポータル API オペレーション
- CreateToken などの OpenID Connect (OIDC) イベント
このタイプのリクエストは userIdentity.onBehalfOf
で IAM Identity Center ユーザーの userId を確認できるほか、additionalEventData 要素で userName も確認できるようです。
事前に必要な設定
S3 データイベントの取得設定
CloudTrail 証跡で S3 のオブジェクトレベルの操作を記録するにはデータイベントの記録を有効化する必要があります。
CloudTrail ログ用テーブルの作成
パーティション射影を使用して Athena で CloudTrail ログ用テーブルを作成します。必要に応じて専用のデータベースを作成し、ドキュメントに記載の CREATE TABLE ステートメントを実行するだけで OK です。
パーティション射影を使用することで、必要なパーティションのみを効率的にスキャンできるため、クエリの実行時間が短縮され、スキャンするデータ量が減ってコストも削減されます。
参考: S3 サーバーアクセスログ用テーブルの作成
CloudTrail のデータイベントでは DeleteObjects API などバッチオペレーション操作はオブジェクトキーやアクセス拒否 (Access Denied) などの情報が記録されません。必要に応じて S3 サーバーアクセスログを組み合わせて分析することも検討できます。S3 のサーバーアクセスログの出力はベストエフォートである点にはご注意ください。
パーティション射影を使用して S3 のサーバーアクセスログを分析する方法は以下のナレッジに記載があります。
サーバーアクセスログでパーティション射影を使用する場合はログオブジェクトキーの形式を「分析およびクエリのアプリケーションを高速化するには、この形式を使用します。」の方で選択する必要があります。
実行例
これまでの情報を踏まえて以下のようなクエリを実行してみます。このクエリでは S3 バケットへのアクセスパターンを分析したり、不正アクセスの調査をしたりすることを前提に、SSO ユーザーによる特定の S3 オブジェクト操作(取得、追加、削除)を抽出します。またアクセス拒否が発生したかどうかを示す access_denied フィールドを追加しています。
SELECT
eventtime AS event_time,
eventname AS event_name,
awsRegion AS aws_region,
json_extract_scalar(requestparameters, '$.bucketName') AS bucket_name,
json_extract_scalar(requestparameters, '$.key') AS object_key,
useridentity.principalId AS user_id,
useridentity.arn AS user_arn,
sourceipaddress AS source_ip,
useragent AS user_agent,
CASE
WHEN errorcode = 'AccessDenied' THEN 'Yes'
ELSE 'No'
END AS access_denied,
errorcode AS error_code,
errormessage AS error_message
FROM cloudtrail_logs_pp
WHERE eventtime >= '2025-04-30T06:00:00Z'
AND eventname in ('GetObject','PutObject', 'DeleteObject', 'DeleteObjects')
AND eventSource = 's3.amazonaws.com'
AND useridentity.type = 'AssumedRole'
AND useragent NOT LIKE '%.amazonaws.com' -- AWS サービスのユーザーエージェントを除外
ORDER BY eventtime ASC
以下のような実行結果を得ることができます。
肝心な情報が黒塗りばかりで恐縮ですが、テキストでマスキングすると以下のような結果です。
"event_time","event_name","aws_region","bucket_name","object_key","user_id","user_arn","source_ip","user_agent","access_denied","error_code","error_message"
"2025-04-30T16:28:12Z","GetObject","ap-northeast-1","<bucket_name>","test.json","PrincipalID:example@example.com","arn:aws:sts::123456789012:assumed-role/AWSReservedSSO_ViewOnlyAccess_abcd1234efgh5678/example@example.com","xxx.xxx.xxx.xxx","[Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36]","Yes","AccessDenied","User: arn:aws:sts::123456789012:assumed-role/AWSReservedSSO_ViewOnlyAccess_abcd1234efgh5678/example@example.com is not authorized to perform: s3:GetObject on resource: ""arn:aws:s3:::<bucket_name>/test.json"" because no identity-based policy allows the s3:GetObject action"
"2025-04-30T16:28:32Z","DeleteObjects","ap-northeast-1","<bucket_name>",,"PrincipalID:example@example.com","arn:aws:sts::123456789012:assumed-role/AWSReservedSSO_ViewOnlyAccess_abcd1234efgh5678/example@example.com","xxx.xxx.xxx.xxx","[Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36]","No",,
"2025-04-30T17:31:33Z","DeleteObject","ap-northeast-1","<bucket_name>","test.json","PrincipalID:example@example.com","arn:aws:sts::123456789012:assumed-role/AWSReservedSSO_ViewOnlyAccess_abcd1234efgh5678/example@example.com","xxx.xxx.xxx.xxx","[aws-cli/2.25.8 md/awscrt#0.23.8 ua/2.1 os/windows#10 md/arch#amd64 lang/python#3.12.9 md/pyimpl#CPython m/G cfg/retry-mode#standard md/installer#exe md/prompt#off md/command#s3.rm]","Yes","AccessDenied","User: arn:aws:sts::123456789012:assumed-role/AWSReservedSSO_ViewOnlyAccess_abcd1234efgh5678/example@example.com is not authorized to perform: s3:DeleteObject on resource: ""arn:aws:s3:::<bucket_name>/test.json"" because no identity-based policy allows the s3:DeleteObject action"
"2025-05-01T01:26:03Z","PutObject","ap-northeast-1","<bucket_name>","test2.json","PrincipalID:example@example.com","arn:aws:sts::123456789012:assumed-role/AWSReservedSSO_AdministratorAccess_wxyz9876ijkl4321/example@example.com","xxx.xxx.xxx.xxx","[aws-cli/2.26.6 md/awscrt#0.25.4 ua/2.1 os/linux#6.1.132-147.221.amzn2023.x86_64 md/arch#x86_64 lang/python#3.13.2 md/pyimpl#CPython exec-env/CloudShell m/G cfg/retry-mode#standard md/installer#exe md/distrib#amzn.2023 md/prompt#off md/command#s3.cp]","No",,
前述の S3 サーバーアクセスログを併用することで、DeleteObjects API によって削除が試行されたオブジェクトキーを特定することもできます。
WITH cloudtrail_events AS (
SELECT
eventtime AS event_time,
eventname AS event_name,
awsRegion AS aws_region,
json_extract_scalar(requestparameters, '$.bucketName') AS bucket_name,
json_extract_scalar(requestparameters, '$.key') AS object_key,
useridentity.principalId AS user_id,
useridentity.arn AS user_arn,
sourceipaddress AS source_ip,
useragent AS user_agent,
CASE
WHEN errorcode = 'AccessDenied' THEN 'Yes'
ELSE 'No'
END AS access_denied,
errorcode AS error_code,
errormessage AS error_message,
requestid AS request_id
FROM cloudtrail_logs_pp
WHERE eventtime >= '2025-05-01T00:00:00Z'
AND eventname = 'DeleteObjects'
AND eventSource = 's3.amazonaws.com'
AND useridentity.type = 'AssumedRole'
AND useragent NOT LIKE '%.amazonaws.com'
),
s3_logs AS (
SELECT
parse_datetime(requestdatetime, 'dd/MMM/yyyy:HH:mm:ss Z') AS log_time,
bucket_name,
remoteip AS remote_ip,
requester,
operation,
key AS object_key,
httpstatus,
errorcode AS s3_error_code,
useragent AS s3_user_agent,
requestid AS request_id
FROM server_access_logs_pp
WHERE timestamp >= '2025/05/01'
AND operation = 'BATCH.DELETE.OBJECT'
)
-- CloudTrail データイベントと S3 サーバーアクセスログを結合
SELECT
CASE
WHEN c.event_time IS NOT NULL THEN CAST(c.event_time AS varchar)
ELSE CAST(s.log_time AS varchar)
END AS operation_time,
COALESCE(c.bucket_name, s.bucket_name) AS bucket_name,
s.operation,
COALESCE(c.object_key, s.object_key) AS object_key,
c.user_arn,
COALESCE(c.source_ip, s.remote_ip) AS source_ip,
COALESCE(c.user_agent, s.s3_user_agent) AS user_agent,
s.httpstatus,
CASE
WHEN s.httpstatus = '403' OR c.access_denied = 'Yes' THEN 'Yes'
ELSE 'No'
END AS access_denied,
COALESCE(c.error_code, s.s3_error_code) AS error_code,
c.error_message,
c.request_id
FROM cloudtrail_events c
FULL OUTER JOIN s3_logs s ON c.request_id = s.request_id
ORDER BY operation_time ASC
以上です。
参考になれば幸いです。