CloudWatch Logs Insights でWAFログ抽出

【追記 2022-09-12】
AWS公式ドキュメントに「Amazon CloudWatch Logs による AWS WAF ログの分析」という記事を見つけました。
https://aws.amazon.com/jp/blogs/news/analyzing-aws-waf-logs-in-amazon-cloudwatch-logs/

まずは公式ドキュメントの方をご参照いただき、こちらの記事は補足程度に見てただければと思います。

## この記事について

CloudFrontやELBの前に WAF を設定した場合に、正規のHTTPリクエストを意図せずブロック（オーバーブロッキング, 偽陽性誤判定）してしまうことがあります。
こういったケースで「どのWAFルールに引っかかったのか」を特定し、ブロック解除の可否の判断材料とするためのログ抽出手法を紹介します。

なお、今回は2021年12月に追加された「AWS WAF のログを CloudWatch Logs のロググループに直接送信する機能」※ を使って出力したログを、 [CloudWatch Logs Insights](https://docs.aws.amazon.com/ja_jp/AmazonCloudWatch/latest/logs/AnalyzingLogData.html) を使ってフィルタリング＆整形しました。

※ https://aws.amazon.com/jp/about-aws/whats-new/2021/12/awf-waf-cloudwatch-log-s3-bucket/

### Why CloudWatch Logs Insights

個人の感想レベルですが、他手法との比較です。

#### 比較1: CloudWatch Logs filter

- pros: 単純な文字列一致条件でのフィルタリングはこちらの方は早い、かも
- cons: 複雑な条件でのフィルタリング（WHERE句相当）が難しい、射影（SELECT句相当）や集計ができない

#### 比較2: AWS S3 + Athena

- prod: Presto SQLが書けるのでより柔軟＆汎用的な抽出が可能
- cons: CloudWatch Insights に比べてログ抽出までのステップ数が多い

## Setup

クラスメソッドさんの記事 ( https://dev.classmethod.jp/articles/aws-waf-log-support-s3-and-cloudwatch-logs/ ) を参考にセットアップしました。

### 当初やりたかったことと着地点

当初、CloudWatch Logs Insights での Scan サイズ削減のため、「 Blockされたリクエストだけの Log Groupと全リクエストログの2つのLogGroupを作成して、ログ調査の目的に合わせてLog Groupを使い分ける」といった構成を想定していました。

しかし、現状では **Web ACLあたり1つの出力先** しか設定できなかった※ので、この構成を諦めて、単純にWAFに来たリクエストログを全て指定したCloudWatch LogGroupに出力するように設定し、抽出時のクエリで目的に合わせて絞り込むことにしました。

※ [awscli-v2.7.12 ドキュメント]( https://awscli.amazonaws.com/v2/documentation/api/2.7.12/reference/wafv2/put-logging-configuration.html) より
> Note
> You can define one logging destination per web ACL.

<details>
<summary>没: ブロックされたリクエストのログだけCloudWatch LogGroupに出す設定例</summary>

![screenshot-waf-cloudwach-logs-insights-setup-example.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/434222/6f811aae-5fd3-443a-e106-3c4e03dd1f1b.png)
</details>

### 細かい仕様確認

- Log Group Nameは `aws-waf-logs-` から始まる必要がある
- 現状、Web ACLあたり1設定しかつけられない？
    -  CloudWatch Logsか S3 何か一方しか選択できない
    - filter条件ごとに CloudWatch Logs の Log Group を分ける、とかもできない

## ログ抽出

「AWS WAF」 -> 「Web ACLs」 -> {対象WAF ACL} -> 「CloudWatch Log Insights」 メニューから、下記のようなクエリを実行し、WAFのログを抽出してみました。


### クエリ例

```
fields 
  terminatingRuleId as ruleGroup, 
  httpRequest.clientIp as clientIp,
  httpRequest.country as country,
  httpRequest.httpMethod as method,
  httpRequest.uri as uri
| parse @message '"ruleId":"*",' as ruleId
| parse @message '{"name":"Host","value":"*"}' as host
| parse @message '{"name":"User-Agent","value":"*"}' as ua
| filter action = "BLOCK"
| sort timestamp desc
| limit 100
| display @timestamp, ruleGroup, ruleId, clientIp, country, method, host, uri, ua
```

#### 補足

**Blockされた** ログのうち、  
- なぜブロックされたのか : `ruleGroup` ,  `ruleId`
- そのブロックは妥当か（正規のリクエストかどうか） : `clientIp`, `country`, `method`, `host`, `uri` (path and query), `ua` (User-Agent)

をそれぞれチェックするのに必要と思われる情報を抽出しています。
また、発生時刻降順にソートし、先頭から100件に抽出件数制限をかけています。

### クエリ実行結果イメージ

![example-query-result.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/434222/5b4932ac-629c-7010-2033-8a01c1ae3755.png)

実行結果から、海外IPアドレスからELBのIPアドレス直指定でアクセスして来たBotらしきリクエストが、WAFでBlockできていることを確認できました。


### 参考資料

- https://docs.aws.amazon.com/ja_jp/AmazonCloudWatch/latest/logs/CWL_QuerySyntax.html
- https://docs.aws.amazon.com/ja_jp/AmazonCloudWatch/latest/logs/CWL_QuerySyntax-examples.html
- https://docs.aws.amazon.com/ja_jp/AmazonCloudWatch/latest/logs/CWL_AnalyzeLogData-discoverable-fields.html

### 所感

ブロックの原因となった ruleId や、request header に含まれる URL host 部が、 JSON配列形式で出力されるため、そこを抽出するのに苦心しました。
今回は結局JSONから文字列一致で値を抽出してしまいましたが、令和の時代になって早数年、もっといい方法があったはず。それを見つけられなかったのが悔やまれます。