- 公開日
- 最終更新日
【Amazon Athena】特定期間を指定したAWS WAFログ分析
この記事を共有する
目次
はじめに
AWS WAFのログを分析する際、Amazon Athenaを利用することで柔軟にクエリを実行できます。
本記事では、Amazon S3に格納されたAWS WAFログに対して、
- Amazon Athenaテーブル作成
- パーティション設定
- 期間指定クエリ
- その他分析クエリ
- クエリ結果の確認
までの一連の流れを解説します。
また、パーティションが読み込まれないトラブルとその対処方法についても、実体験をもとに紹介します。
背景・課題
AWS WAFログを分析する際、以下のような課題がありました。
- 特定期間(例:2026/01/01〜01/03)のログだけを抽出したい
- IPごとのアクセスランキングを出したい
- Amazon Athenaでパーティションを設定したが、データが取得できない
特に、Amazon S3のフォルダ構成とパーティション定義が一致していないとクエリが失敗する点に注意が必要です。
前提条件・構成
今回の作業環境と前提条件は以下の通りです。
システム構成
- ログ種別: AWS WAFログ
- 保存先: Amazon S3
- クエリエンジン: Amazon Athena
- リージョン: ap-northeast-1(東京)
Amazon S3構成イメージ
s3://waf-log/waflog/
├ year=2026/
│ ├ month=01/
│ │ ├ day=01/
│ │ │ ├ hour=00/
│ │ │ ├ hour=01/
│ │ │ ├ ・・・
│ │ ├ day=02/
│ │ ├ day=03/
前提
- Amazon Athenaが利用可能であること
- Amazon S3にAWS WAFログが出力されていること
- クエリ結果出力用のAmazon S3バケットが作成済みであること
手順1:テーブル作成
まずはAmazon Athenaで外部テーブルを作成します。
CREATE EXTERNAL TABLE waflog_query (
timestamp bigint,
formatversion int,
webaclid string,
action string,
terminatingruleid string,
httprequest struct<
clientip:string,
country:string,
uri:string,
args:string,
httpmethod:string
>
)
PARTITIONED BY (
year string,
month string,
day string,
hour string
)
ROW FORMAT SERDE 'org.openx.data.jsonserde.JsonSerDe'
LOCATION 's3://waf-log/waflog/';
カラム一覧
| 項目 | 内容 | 備考 |
|---|---|---|
| dt | 日付 | 変換後の日付 |
| timestamp | UNIX時間(ms) | 元ログ値 |
| JST | 日本時間 | タイムゾーン変換 |
| clientip | アクセス元IP | - |
| country | 国 | - |
| uri | アクセスURI | - |
| action | ALLOW / BLOCK / COUNT | WAF判定結果 |
手順2:パーティションの読み込み
① 一括読み込み
MSCK REPAIR TABLE waflog_query;
② パーティションが読み込まれない場合
S3構造とのズレにより、MSCK(Metastore Checkの略)で読み込まれない場合があります。 その場合は手動で追加します。
ALTER TABLE waflog_query ADD
PARTITION (year='2026', month='01', day='01', hour='00') LOCATION 's3://waf-log/waflog/2026/01/01/00/'
PARTITION (year='2026', month='01', day='01', hour='01') LOCATION 's3://waf-log/waflog/2026/01/01/01/'
PARTITION (year='2026', month='01', day='01', hour='02') LOCATION 's3://waf-log/waflog/2026/01/01/02/'
PARTITION (year='2026', month='01', day='01', hour='03') LOCATION 's3://waf-log/waflog/2026/01/01/03/'
PARTITION (year='2026', month='01', day='01', hour='04') LOCATION 's3://waf-log/waflog/2026/01/01/04/'
PARTITION (year='2026', month='01', day='01', hour='05') LOCATION 's3://waf-log/waflog/2026/01/01/05/'
PARTITION (year='2026', month='01', day='01', hour='06') LOCATION 's3://waf-log/waflog/2026/01/01/06/'
PARTITION (year='2026', month='01', day='01', hour='07') LOCATION 's3://waf-log/waflog/2026/01/01/07/'
PARTITION (year='2026', month='01', day='01', hour='08') LOCATION 's3://waf-log/waflog/2026/01/01/08/'
PARTITION (year='2026', month='01', day='01', hour='09') LOCATION 's3://waf-log/waflog/2026/01/01/09/'
PARTITION (year='2026', month='01', day='01', hour='10') LOCATION 's3://waf-log/waflog/2026/01/01/10/'
PARTITION (year='2026', month='01', day='01', hour='11') LOCATION 's3://waf-log/waflog/2026/01/01/11/'
PARTITION (year='2026', month='01', day='01', hour='12') LOCATION 's3://waf-log/waflog/2026/01/01/12/'
PARTITION (year='2026', month='01', day='01', hour='13') LOCATION 's3://waf-log/waflog/2026/01/01/13/'
PARTITION (year='2026', month='01', day='01', hour='14') LOCATION 's3://waf-log/waflog/2026/01/01/14/'
PARTITION (year='2026', month='01', day='01', hour='15') LOCATION 's3://waf-log/waflog/2026/01/01/15/'
PARTITION (year='2026', month='01', day='01', hour='16') LOCATION 's3://waf-log/waflog/2026/01/01/16/'
PARTITION (year='2026', month='01', day='01', hour='17') LOCATION 's3://waf-log/waflog/2026/01/01/17/'
PARTITION (year='2026', month='01', day='01', hour='18') LOCATION 's3://waf-log/waflog/2026/01/01/18/'
PARTITION (year='2026', month='01', day='01', hour='19') LOCATION 's3://waf-log/waflog/2026/01/01/19/'
PARTITION (year='2026', month='01', day='01', hour='20') LOCATION 's3://waf-log/waflog/2026/01/01/20/'
PARTITION (year='2026', month='01', day='01', hour='21') LOCATION 's3://waf-log/waflog/2026/01/01/21/'
PARTITION (year='2026', month='01', day='01', hour='22') LOCATION 's3://waf-log/waflog/2026/01/01/22/'
PARTITION (year='2026', month='01', day='01', hour='23') LOCATION 's3://waf-log/waflog/2026/01/01/23/'
PARTITION (year='2026', month='01', day='02', hour='00') LOCATION 's3://waf-log/waflog/2026/01/02/00/'
PARTITION (year='2026', month='01', day='02', hour='01') LOCATION 's3://waf-log/waflog/2026/01/02/01/'
PARTITION (year='2026', month='01', day='02', hour='02') LOCATION 's3://waf-log/waflog/2026/01/02/02/'
PARTITION (year='2026', month='01', day='02', hour='03') LOCATION 's3://waf-log/waflog/2026/01/02/03/'
PARTITION (year='2026', month='01', day='02', hour='04') LOCATION 's3://waf-log/waflog/2026/01/02/04/'
PARTITION (year='2026', month='01', day='02', hour='05') LOCATION 's3://waf-log/waflog/2026/01/02/05/'
PARTITION (year='2026', month='01', day='02', hour='06') LOCATION 's3://waf-log/waflog/2026/01/02/06/'
PARTITION (year='2026', month='01', day='02', hour='07') LOCATION 's3://waf-log/waflog/2026/01/02/07/'
PARTITION (year='2026', month='01', day='02', hour='08') LOCATION 's3://waf-log/waflog/2026/01/02/08/'
PARTITION (year='2026', month='01', day='02', hour='09') LOCATION 's3://waf-log/waflog/2026/01/02/09/'
PARTITION (year='2026', month='01', day='02', hour='10') LOCATION 's3://waf-log/waflog/2026/01/02/10/'
PARTITION (year='2026', month='01', day='02', hour='11') LOCATION 's3://waf-log/waflog/2026/01/02/11/'
PARTITION (year='2026', month='01', day='02', hour='12') LOCATION 's3://waf-log/waflog/2026/01/02/12/'
PARTITION (year='2026', month='01', day='02', hour='13') LOCATION 's3://waf-log/waflog/2026/01/02/13/'
PARTITION (year='2026', month='01', day='02', hour='14') LOCATION 's3://waf-log/waflog/2026/01/02/14/'
PARTITION (year='2026', month='01', day='02', hour='15') LOCATION 's3://waf-log/waflog/2026/01/02/15/'
PARTITION (year='2026', month='01', day='02', hour='16') LOCATION 's3://waf-log/waflog/2026/01/02/16/'
PARTITION (year='2026', month='01', day='02', hour='17') LOCATION 's3://waf-log/waflog/2026/01/02/17/'
PARTITION (year='2026', month='01', day='02', hour='18') LOCATION 's3://waf-log/waflog/2026/01/02/18/'
PARTITION (year='2026', month='01', day='02', hour='19') LOCATION 's3://waf-log/waflog/2026/01/02/19/'
PARTITION (year='2026', month='01', day='02', hour='20') LOCATION 's3://waf-log/waflog/2026/01/02/20/'
PARTITION (year='2026', month='01', day='02', hour='21') LOCATION 's3://waf-log/waflog/2026/01/02/21/'
PARTITION (year='2026', month='01', day='02', hour='22') LOCATION 's3://waf-log/waflog/2026/01/02/22/'
PARTITION (year='2026', month='01', day='02', hour='23') LOCATION 's3://waf-log/waflog/2026/01/02/23/'
PARTITION (year='2026', month='01', day='03', hour='00') LOCATION 's3://waf-log/waflog/2026/01/03/00/'
PARTITION (year='2026', month='01', day='03', hour='01') LOCATION 's3://waf-log/waflog/2026/01/03/01/'
PARTITION (year='2026', month='01', day='03', hour='02') LOCATION 's3://waf-log/waflog/2026/01/03/02/'
PARTITION (year='2026', month='01', day='03', hour='03') LOCATION 's3://waf-log/waflog/2026/01/03/03/'
PARTITION (year='2026', month='01', day='03', hour='04') LOCATION 's3://waf-log/waflog/2026/01/03/04/'
PARTITION (year='2026', month='01', day='03', hour='05') LOCATION 's3://waf-log/waflog/2026/01/03/05/'
PARTITION (year='2026', month='01', day='03', hour='06') LOCATION 's3://waf-log/waflog/2026/01/03/06/'
PARTITION (year='2026', month='01', day='03', hour='07') LOCATION 's3://waf-log/waflog/2026/01/03/07/'
PARTITION (year='2026', month='01', day='03', hour='08') LOCATION 's3://waf-log/waflog/2026/01/03/08/'
PARTITION (year='2026', month='01', day='03', hour='09') LOCATION 's3://waf-log/waflog/2026/01/03/09/'
PARTITION (year='2026', month='01', day='03', hour='10') LOCATION 's3://waf-log/waflog/2026/01/03/10/'
PARTITION (year='2026', month='01', day='03', hour='11') LOCATION 's3://waf-log/waflog/2026/01/03/11/'
PARTITION (year='2026', month='01', day='03', hour='12') LOCATION 's3://waf-log/waflog/2026/01/03/12/'
PARTITION (year='2026', month='01', day='03', hour='13') LOCATION 's3://waf-log/waflog/2026/01/03/13/'
PARTITION (year='2026', month='01', day='03', hour='14') LOCATION 's3://waf-log/waflog/2026/01/03/14/'
PARTITION (year='2026', month='01', day='03', hour='15') LOCATION 's3://waf-log/waflog/2026/01/03/15/'
PARTITION (year='2026', month='01', day='03', hour='16') LOCATION 's3://waf-log/waflog/2026/01/03/16/'
PARTITION (year='2026', month='01', day='03', hour='17') LOCATION 's3://waf-log/waflog/2026/01/03/17/'
PARTITION (year='2026', month='01', day='03', hour='18') LOCATION 's3://waf-log/waflog/2026/01/03/18/'
PARTITION (year='2026', month='01', day='03', hour='19') LOCATION 's3://waf-log/waflog/2026/01/03/19/'
PARTITION (year='2026', month='01', day='03', hour='20') LOCATION 's3://waf-log/waflog/2026/01/03/20/'
PARTITION (year='2026', month='01', day='03', hour='21') LOCATION 's3://waf-log/waflog/2026/01/03/21/'
PARTITION (year='2026', month='01', day='03', hour='22') LOCATION 's3://waf-log/waflog/2026/01/03/22/'
PARTITION (year='2026', month='01', day='03', hour='23') LOCATION 's3://waf-log/waflog/2026/01/03/23/';
手順3:期間指定クエリ
2026/01/01〜01/03のログを抽出するクエリです。
SELECT
date(from_unixtime(timestamp/1000)) AS dt,
timestamp,
from_unixtime(timestamp/1000) AT TIME ZONE 'Asia/Tokyo' AS JST,
httprequest.clientip AS clientip,
httprequest.country AS country,
httprequest.httpmethod AS httpmethod,
httprequest.uri AS uri,
httprequest.args AS args,
action,
terminatingruleid AS rule
FROM waflog_query
WHERE year='2026'
AND month='01'
AND day BETWEEN '01' AND '03'
ORDER BY timestamp;
手順4:その他分析クエリ
本手順ではその他分析用のクエリをご紹介します。
① IPランキング
ブロックされていたアクセス元IPアドレスについて、アクセス数が多い順で確認できます。
SELECT
httprequest.clientip AS clientip,
COUNT(*) AS access_count
FROM waflog_query
WHERE action='BLOCK'
AND date(from_unixtime(timestamp/1000))
BETWEEN DATE '2026-01-01' AND DATE '2026-01-03'
GROUP BY httprequest.clientip
ORDER BY access_count DESC
LIMIT 20;
②ルールごとの検知件数
どのルールがどれくらい検知しているか確認できます。
SELECT
terminatingruleid,
count(*) AS detect_count
FROM waflog_query
WHERE year='2026'
AND month='01'
AND day BETWEEN '01' AND '03'
GROUP BY terminatingruleid
ORDER BY detect_count DESC;
手順5:クエリ結果の確認
クエリ結果はAWSマネージメントコンソール上でも確認ができますが、
CSVファイルをダウンロードすることも可能なため、ローカルPC上でもクエリ結果を確認することができます。
失敗事例
実際に失敗した事例を以下に記載します。
■ 発生した問題
Amazon Athenaでクエリを実行してもデータが取得できない
■ 原因
- Amazon S3のフォルダ構成に「hour」が存在していた
- テーブル定義にhourパーティションがなかった
- MSCK REPAIR TABLEでパーティションが認識されなかった
■ 対応
- hourパーティションを追加
- ALTER TABLEで手動登録
まとめ
今回は、AWS WAFログをAmazon Athenaで分析する手順を整理しました。
■ ポイント
- パーティション設計はAmazon S3構造と完全一致させることが重要
- MSCKで読み込まれない場合は手動追加が必要
- 期間指定・ランキング分析で攻撃傾向の把握が可能
AWS WAFログ分析は、セキュリティ運用において非常に重要な要素です。 定期的な分析と可視化を行うことで、異常検知の精度向上につながります。 本ブログが参考となれば幸いです。
この記事は私が書きました
橋本 歩
記事一覧AWSと犬が好きです。