AWS WAFのログをAthenaのパーティション射影機能を用いて高速でクエリしてみた

AWS WAFのログをAthenaのパーティション射影機能を用いて高速でクエリしてみた

Clock Icon2025.04.16

はじめに

こんにちは。クラウド事業本部の戸川です。

皆さんAWS WAF使ってますか?(n回目)
Webアプリケーションに対するHTTPリクエストを監視してくれるWAFですが、ブロックされたリクエストの詳細などを確認するにはログの解析が必要です。
ログ解析の方法としては、S3に保管しているログに対してAmazon Athenaでクエリをかけるという形が一般的かと思いますが、やり方を工夫してあげないと、特定日付のログを抽出するだけでもそれなりの時間がかかります。

なので今回は、Athenaのパーティション射影機能というものを用いて高速でWAFログを確認する方法をご紹介します。

パーティション射影機能とは

AthenaでS3のデータを絞り込む際に、日付やIDなど特定のパーティションを使用して必要な部分だけを取り出す機能です。
データ全体を検索するのではなく必要な部分のみを使用するので、クエリのパフォーマンスが向上します。
WAFログのような日毎にプレフィックスが増えていくデータでもAthena側が自動で構造を認識してくれます。
パーティション射影機能については以下ブログが詳しいので是非ご参照ください。

WAFログの日付をパーティションキーとしてテーブルを作成する

パーティション射影機能を使用しテーブルを作成するクエリがこちらになります。

CREATE EXTERNAL TABLE `waflogs`(
  `timestamp` bigint, 
  `formatversion` int, 
  `webaclid` string, 
  `terminatingruleid` string, 
  `terminatingruletype` string, 
  `action` string, 
  `terminatingrulematchdetails` array<struct<conditiontype:string,sensitivitylevel:string,location:string,matcheddata:array<string>>>, 
  `httpsourcename` string, 
  `httpsourceid` string, 
  `rulegrouplist` array<struct<rulegroupid:string,terminatingrule:struct<ruleid:string,action:string,rulematchdetails:array<struct<conditiontype:string,sensitivitylevel:string,location:string,matcheddata:array<string>>>>,nonterminatingmatchingrules:array<struct<ruleid:string,action:string,overriddenaction:string,rulematchdetails:array<struct<conditiontype:string,sensitivitylevel:string,location:string,matcheddata:array<string>>>,challengeresponse:struct<responsecode:string,solvetimestamp:string>,captcharesponse:struct<responsecode:string,solvetimestamp:string>>>,excludedrules:string>>, 
  `ratebasedrulelist` array<struct<ratebasedruleid:string,limitkey:string,maxrateallowed:int>>, 
  `nonterminatingmatchingrules` array<struct<ruleid:string,action:string,rulematchdetails:array<struct<conditiontype:string,sensitivitylevel:string,location:string,matcheddata:array<string>>>,challengeresponse:struct<responsecode:string,solvetimestamp:string>,captcharesponse:struct<responsecode:string,solvetimestamp:string>>>, 
  `requestheadersinserted` array<struct<name:string,value:string>>, 
  `responsecodesent` string, 
  `httprequest` struct<clientip:string,country:string,headers:array<struct<name:string,value:string>>,uri:string,args:string,httpversion:string,httpmethod:string,requestid:string,fragment:string,scheme:string,host:string>,
  `labels` array<struct<name:string>>, 
  `captcharesponse` struct<responsecode:string,solvetimestamp:string,failurereason:string>, 
  `challengeresponse` struct<responsecode:string,solvetimestamp:string,failurereason:string>, 
  `ja3fingerprint` string, 
  `ja4fingerprint` string, 
  `oversizefields` string, 
  `requestbodysize` int, 
  `requestbodysizeinspectedbywaf` int)
  PARTITIONED BY ( 
   `log_time` string)
ROW FORMAT SERDE 
  'org.openx.data.jsonserde.JsonSerDe' 
STORED AS INPUTFORMAT 
  'org.apache.hadoop.mapred.TextInputFormat' 
OUTPUTFORMAT 
  'org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat'
LOCATION
  's3://<WAFログ格納用バケット名>/AWSLogs/<アカウントID>/WAFLogs/<リージョン名>/<WAF ACL名>/'
  TBLPROPERTIES(
    'projection.enabled' = 'true',
    'projection.log_time.type' = 'date',
    'projection.log_time.range' = '2025/04/01,NOW',
    'projection.log_time.format' = 'yyyy/MM/dd',
    'projection.log_time.interval' = '1',
    'projection.log_time.interval.unit' = 'DAYS',
    'storage.location.template' = 's3://<WAFログ格納用バケット名>/AWSLogs/<アカウントID>/WAFLogs/<リージョン名>/<WAF ACL名>/${log_time}/')

実際に流してみる

実際にテーブルを作成してみましょう。
今回はこちらのブログで作成した環境を使用します。
まずはAthenaコンソールから以下のクエリ文でデータベースを作成してください。

CREATE DATABASE <データベース名>;

作成したデータベースを選択し、先ほどのテーブル作成用クエリを実行します。クエリ結果が完了済みになればテーブル作成は成功です。
作成されたテーブルの中を見ると、log_timeという項目でパーティション化されていることがわかります。
athena_1

サンプルクエリ

今回作成したテーブルにクエリを投げる場合、以下のような形になります。

単日指定でクエリする場合

SELECT from_unixtime(timestamp/1000, 'Asia/Tokyo') AS JST,
       httpsourcename,
       httpsourceid,
       httprequest.clientip,
       httprequest.country,
       httprequest.uri,
       httprequest.args,
       httprequest.httpmethod,
       CASE 
           WHEN cardinality(filter(httprequest.headers, x -> x.name = 'Host')) > 0 
           THEN filter(httprequest.headers, x -> x.name = 'Host')[1].value 
           ELSE NULL 
       END AS host,
       terminatingruleid
FROM
       "waflogs"
WHERE
    action = 'ALLOW'
    AND log_time = '2025/04/16';

日付範囲指定でクエリする場合

SELECT from_unixtime(timestamp/1000, 'Asia/Tokyo') AS JST,
       httpsourcename,
       httpsourceid,
       httprequest.clientip,
       httprequest.country,
       httprequest.uri,
       httprequest.args,
       httprequest.httpmethod,
       CASE 
           WHEN cardinality(filter(httprequest.headers, x -> x.name = 'Host')) > 0 
           THEN filter(httprequest.headers, x -> x.name = 'Host')[1].value 
           ELSE NULL 
       END AS host,
       terminatingruleid
FROM
       "waflogs"
WHERE
    action = 'ALLOW'
    AND log_time >= '2025/04/14'
    AND log_time <= '2025/04/16';

最後に

今回はAthenaのパーティション射影機能を使用してAWS WAFログを解析してみました。
この記事が少しでもどなたかのお役に立てば幸いです。

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.