S3 にエクスポートされた AWS WAF v2 ログを Athena で検索する

2020年6月11日

概要

やりたいこと

S3 にエクスポートした AWS WAF ログを Athena で検索する。

AWS WAF ログサンプル

S3 に保存されたログを確認すると、以下のようログがきていた。

{
    "timestamp": 1589519024297,
    "formatVersion": 1,
    "webaclId": "arn:aws:wafv2:ap-northeast-1:111111111111:regional/webacl/ExampleWebACL/8f31247c-27a2-471d-8388-7fa4d7ab424f",
    "terminatingRuleId": "Default_Action",
    "terminatingRuleType": "REGULAR",
    "action": "ALLOW",
    "terminatingRuleMatchDetails": [],
    "httpSourceName": "ALB",
    "httpSourceId": "657885203613-app/webserver-alb/39ba02e250f5a76a",
    "ruleGroupList": [
        {
            "ruleGroupId": "AWS#AWSManagedRulesCommonRuleSet",
            "terminatingRule": null,
            "nonTerminatingMatchingRules": [],
            "excludedRules": [
                {
                    "exclusionType": "EXCLUDED_AS_COUNT",
                    "ruleId": "NoUserAgent_HEADER"
                }
            ]
        },
        {
            "ruleGroupId": "AWS#AWSManagedRulesKnownBadInputsRuleSet",
            "terminatingRule": null,
            "nonTerminatingMatchingRules": [],
            "excludedRules": null
        },
        {
            "ruleGroupId": "AWS#AWSManagedRulesAmazonIpReputationList",
            "terminatingRule": null,
            "nonTerminatingMatchingRules": [],
            "excludedRules": null
        },
        {
            "ruleGroupId": "AWS#AWSManagedRulesAnonymousIpList",
            "terminatingRule": null,
            "nonTerminatingMatchingRules": [],
            "excludedRules": null
        },
        {
            "ruleGroupId": "AWS#AWSManagedRulesSQLiRuleSet",
            "terminatingRule": null,
            "nonTerminatingMatchingRules": [],
            "excludedRules": null
        },
        {
            "ruleGroupId": "AWS#AWSManagedRulesLinuxRuleSet",
            "terminatingRule": null,
            "nonTerminatingMatchingRules": [],
            "excludedRules": null
        },
        {
            "ruleGroupId": "AWS#AWSManagedRulesUnixRuleSet",
            "terminatingRule": null,
            "nonTerminatingMatchingRules": [],
            "excludedRules": null
        }
    ],
    "rateBasedRuleList": [],
    "nonTerminatingMatchingRules": [],
    "httpRequest": {
        "clientIp": "184.105.247.252",
        "country": "US",
        "headers": [
            {
                "name": "Host",
                "value": "54.250.130.19"
            }
        ],
        "uri": "REDACTED",
        "args": "REDACTED",
        "httpVersion": "HTTP/1.1",
        "httpMethod": "REDACTED",
        "requestId": null
    }
}

AWS WAF ログのテーブル

AWS WAF ログのテーブル定義のカラムの説明。

No項目説明
1timestampタイムスタンプ (ミリ秒)
2formatversionログの形式バージョン
3webaclidウェブ ACL の GUID
4terminatingruleidリクエストを終了したルールの ID。リクエストを終了したものがない場合、この値は Default_Action となる
5terminatingruletypeリクエストを終了したルールのタイプ。可能な値: RATE_BASED, REGULAR、GROUP, MANAGED_RULE_GROUP
6actionアクション。可能な値 : ALLOW, BLOCK
7terminatingrulematchdetailsリクエストのどの場所(ヘッダ/クエリストリング/Body等)でどの値が検知に影響したか
8httpsourcenameリクエストの送信元。有効な値: CF(CloudFormation), APIGW(API Gateway), ALB (Application Load Balancer)
9httpsourceidソース ID。 Amazon CloudFront ディストリビューションの ID、API Gateway の REST API、または Application Load Balancer の名前
10rulegrouplistこのリクエストで動作したルールグループのリスト
11ratebasedrulelistルールグループの IDルールがリクエストをブロックした場合、ruleGroupID の ID は、terminatingRuleId の ID と同じ
12nonterminatingmatchingrulesリクエストに一致するルールグループ内の終了しないルールのリスト。これは常に COUNT ルール (一致する終了しないルール) となる
13httprequestリクエストに関するメタデータ

コンソールから設定

Athena でテーブルを作成

公式 : Querying AWS WAF Logs

S3 バケット waf-test-cross-account に対してテーブルを作成するクエリ。結果 waf_logs というテーブルが作成される。

CREATE EXTERNAL TABLE `waf_logs`(
  `timestamp` bigint,
  `formatversion` int,
  `webaclid` string,
  `terminatingruleid` string,
  `terminatingruletype` string,
  `action` string,
  `terminatingrulematchdetails` array<
                                  struct<
                                    conditiontype:string,
                                    location:string,
                                    matcheddata:array<string>
                                        >
                                     >,
  `httpsourcename` string,
  `httpsourceid` string,
  `rulegrouplist` array<string>,
  `ratebasedrulelist` array<
                        struct<
                          ratebasedruleid:string,
                          limitkey:string,
                          maxrateallowed:int
                              >
                           >,
  `nonterminatingmatchingrules` array<
                                  struct<
                                    ruleid:string,
                                    action:string
                                        >
                                     >,
  `httprequest` struct<
                      clientip:string,
                      country:string,
                      headers:array<
                                struct<
                                  name:string,
                                  value:string
                                      >
                                   >,
                      uri:string,
                      args:string,
                      httpversion:string,
                      httpmethod:string,
                      requestid:string
                      > 
)
ROW FORMAT SERDE 'org.openx.data.jsonserde.JsonSerDe'
WITH SERDEPROPERTIES (
 'paths'='action,formatVersion,httpRequest,httpSourceId,httpSourceName,nonTerminatingMatchingRules,rateBasedRuleList,ruleGroupList,terminatingRuleId,terminatingRuleMatchDetails,terminatingRuleType,timestamp,webaclId')
STORED AS INPUTFORMAT 'org.apache.hadoop.mapred.TextInputFormat'
OUTPUTFORMAT 'org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat'
LOCATION 's3://waf-test-cross-account/'

Athena より検索クエリを実行し、結果が返ることを確認。

パーティションを区切ったテーブルを作成

S3 に保存されている形式。2020/05/15/12 というパーティション分割できる。

今回は日にちまでの 2020/05/15 でパーティションを区切る。

パーティションを区切ったテーブルを作成。

CREATE EXTERNAL TABLE `waf_logs`(
  `timestamp` bigint,
  `formatversion` int,
  `webaclid` string,
  `terminatingruleid` string,
  `terminatingruletype` string,
  `action` string,
  `terminatingrulematchdetails` array<
                                  struct<
                                    conditiontype:string,
                                    location:string,
                                    matcheddata:array<string>
                                        >
                                     >,
  `httpsourcename` string,
  `httpsourceid` string,
  `rulegrouplist` array<string>,
  `ratebasedrulelist` array<
                        struct<
                          ratebasedruleid:string,
                          limitkey:string,
                          maxrateallowed:int
                              >
                           >,
  `nonterminatingmatchingrules` array<
                                  struct<
                                    ruleid:string,
                                    action:string
                                        >
                                     >,
  `httprequest` struct<
                      clientip:string,
                      country:string,
                      headers:array<
                                struct<
                                  name:string,
                                  value:string
                                      >
                                   >,
                      uri:string,
                      args:string,
                      httpversion:string,
                      httpmethod:string,
                      requestid:string
                      > 
)
PARTITIONED BY (year int, month int, day int)
ROW FORMAT SERDE 'org.openx.data.jsonserde.JsonSerDe'
WITH SERDEPROPERTIES (
 'paths'='action,formatVersion,httpRequest,httpSourceId,httpSourceName,nonTerminatingMatchingRules,rateBasedRuleList,ruleGroupList,terminatingRuleId,terminatingRuleMatchDetails,terminatingRuleType,timestamp,webaclId')
STORED AS INPUTFORMAT 'org.apache.hadoop.mapred.TextInputFormat'
OUTPUTFORMAT 'org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat'
LOCATION 's3://waf-test-cross-account/'

パーティションで区切れるテーブルが作成されたので、実際にパーティションを区切ってみる。2020/05/15 と 2020/05/16 の2つ。

ALTER TABLE waf_logs ADD IF NOT EXISTS PARTITION (year=2020, month=5, day=15) location 's3://waf-test-cross-account/2020/05/15/';
ALTER TABLE waf_logs ADD IF NOT EXISTS PARTITION (year=2020, month=5, day=16) location 's3://waf-test-cross-account/2020/05/16/';

パーティション状況を確認。「year=2020/month=5/day=15」という結果が返ってき、パーティションが区切られたことを確認できる。

SHOW PARTITIONS waf_logs;

パーティションで絞り込んでスキャンを行う。スキャン量が違うことを確認。

SELECT * FROM waf_logs WHERE year=2020 AND month=5 AND day=15;
SELECT * FROM waf_logs WHERE year=2020 AND month=5 AND day=16;

パーティションを削除する場合。

ALTER TABLE waf_logs DROP IF EXISTS PARTITION (year=2020, month=5, day=16);

参考

Querying AWS WAF Logs

ウェブ ACL トラフィック情報のログ記録

Athena の利用料金が気になったので AWS WAF ログ分析にパーティションを使ってみた