← 記事 aws · 2026年8月10日 · 3 min 読む
追加インフラなしでS3アクセスログをAthenaで調べる
S3アクセスログを調べられる状態にする。date-based partitioning、partition projection、実際に使っているSQL。
S3アクセスログは、有効にしたまま忘れられがちだ。コンプライアンス上は保管が必要で、そのまま溜まり続けるのに、クエリは一度も実行されない。これを変えるためにSIEMもOpenSearch clusterも要らない。ログはすでにbucketにあり、Athenaから直接クエリできる。僕が実際に使っている最小構成がこれだ。
まず、ログを日付ベースのパスにする
S3 server access loggingはデフォルトだと、日付構造のない一つのフラットなprefixに全ログオブジェクトを置く。このままではAthenaのクエリが毎回フルスキャンになる。
最初にsource bucketのlogging設定を開き、date-based partitioningを有効にする。新しいログは次のようなパスに入る。
s3://my-log-bucket/logs/123456789012/ap-northeast-1/my-data-bucket/2026/08/10/
パーティションにはevent timeとdelivery timeのどちらも使える。セキュリティ調査ならevent timeを選ぶ。
ちなみに、この変更が効くのは変更後に書き込まれたログだけだ。古いログはフラットなまま残るので、別テーブルを作るか、新しいログだけをクエリする運用にする。
partition projectionでテーブルを作る
パスに日付が入れば、partition projectionでkey構造からAthenaがパーティションを直接導出できる。Glue crawlerもMSCK REPAIRも不要だ。
CREATE EXTERNAL TABLE s3_access_logs (
bucketowner STRING, bucket_name STRING, requestdatetime STRING,
remoteip STRING, requester STRING, requestid STRING, operation STRING,
key STRING, request_uri STRING, httpstatus STRING, errorcode STRING,
bytessent BIGINT, objectsize BIGINT, totaltime STRING,
turnaroundtime STRING, referrer STRING, useragent STRING,
versionid STRING, hostid STRING, sigv STRING, ciphersuite STRING,
authtype STRING, endpoint STRING, tlsversion STRING,
accesspointarn STRING, aclrequired STRING, sourceregion STRING
)
PARTITIONED BY (`dt` STRING)
ROW FORMAT SERDE 'org.apache.hadoop.hive.serde2.RegexSerDe'
WITH SERDEPROPERTIES (
'input.regex' = '([^ ]*) ([^ ]*) \\[(.*?)\\] ([^ ]*) ([^ ]*) ([^ ]*) ([^ ]*) ([^ ]*) ("[^"]*"|-) (-|[0-9]*) ([^ ]*) ([^ ]*) ([^ ]*) ([^ ]*) ([^ ]*) ([^ ]*) ("[^"]*"|-) ([^ ]*)(?: ([^ ]*) ([^ ]*) ([^ ]*) ([^ ]*) ([^ ]*) ([^ ]*) ([^ ]*) ([^ ]*) ([^ ]*))?.*$'
)
LOCATION 's3://my-log-bucket/logs/123456789012/ap-northeast-1/my-data-bucket/'
TBLPROPERTIES (
'projection.enabled' = 'true',
'projection.dt.type' = 'date',
'projection.dt.format' = 'yyyy/MM/dd',
'projection.dt.interval' = '1',
'projection.dt.interval.unit' = 'DAYS',
'projection.dt.range' = '2026/01/01,NOW',
'storage.location.template' = 's3://my-log-bucket/logs/123456789012/ap-northeast-1/my-data-bucket/${dt}'
);
カラムとregexは公式ドキュメントにある標準のものだ。そのままコピーする。編集するのはLOCATIONのパスとprojection.dt.rangeの開始日だけ。以降のクエリには必ずdt条件を付け、Athenaに対象日のみ読ませる。
最初に見る三つ
機密prefixに誰がアクセスしたか。セキュリティレビューではまずこれを見る。
SELECT requestdatetime, requester, remoteip, key
FROM s3_access_logs
WHERE dt BETWEEN '2026/08/01' AND '2026/08/10'
AND key LIKE 'confidential/%'
AND operation LIKE 'REST.GET%'
ORDER BY parse_datetime(requestdatetime, 'dd/MMM/yyyy:HH:mm:ss Z');
特定の期間に4xxレスポンスが急増していないか。スキャンや総当たりの兆候になるので、先に集計し、それからrequesterとremoteipを調べる。
SELECT dt, httpstatus, count(*) AS hits
FROM s3_access_logs
WHERE dt >= '2026/08/01' AND httpstatus LIKE '4%'
GROUP BY dt, httpstatus
ORDER BY hits DESC;
各オブジェクトが最後に読まれた時刻も見る。S3 Inventoryと突き合わせ、ここに一度も出てこないkeyはlifecycle policyの候補にする。
SELECT key, max(parse_datetime(requestdatetime, 'dd/MMM/yyyy:HH:mm:ss Z')) AS last_read
FROM s3_access_logs
WHERE dt >= '2026/01/01' AND operation LIKE 'REST.GET%'
GROUP BY key;
コスト
Athenaの課金はスキャン量ベースで、1TBあたり5 USD。パーティションと日付条件があれば、1か月分のアクセスログをクエリしても通常は数セントで済む。
高くつくのは、dt条件を忘れてbucket全体をスキャンするクエリだ。定型クエリは条件を埋め込んだsaved queryにしておく。
限界
- Server access logsはbest-effortで、たまに欠落し、たまに重複する。傾向の把握や調査には使えるが、監査の証拠にはならない。それはCloudTrail data eventsの仕事で、こちらは別課金だ。
- 配信には数時間の遅延がある。リアルタイムアラートの情報源にはならない。
- log bucketには専用のlifecycle policyを設定する。放置すると請求でいちばん目立つ項目に育つ。それから、log bucket自身のaccess loggingを同じbucketに向けない。ログがログを呼んで増え続ける。
Alex Chih
Security consultant & instructor · AWS / Azure