ENGINEER BLOG ENGINEER BLOG
  • 公開日
  • 最終更新日

【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と犬が好きです。

橋本 歩

この記事を共有する

クラウドのご相談

CONTACT

クラウド導入や運用でお悩みの方は、お気軽にご相談ください。
専門家がサポートします。

サービス資料ダウンロード

DOWNLOAD

ビジネスをクラウドで加速させる準備はできていますか?
今すぐサービス資料をダウンロードして、詳細をご確認ください。