CloudWatch Logs Insightsを利用したWAFアクセスログの解析活用一例

この記事は デジタルキューブグループ エンジニアチームアドベントカレンダー2024の12月24日分の記事です。

# 前書き
CloudWatch Logsへ出力しているWAFのアクセスログの解析時に調べ直した、ログフィールド、クエリコマンド（主にparseとdisplay）について備忘録として書き留めた記事となっています。

# CloudWatch Logs Insightsとは

CloudWatch Logs Insightsは、CloudWatch Logsに保存されているログをクエリを使用して検索・分析することができる機能です。

## ログフィールド

（コンソール上でログ解析をすると自動的に選択されているログフィールドについて改めて確認しました。）

CloudWatch Logsへログを送信するサービス（ログタイプ）に応じて、CloudWatch Logs Insightsではシステムフィールドを自動的に生成します。標準的なログフィールドは以下の5つです。WAFのログは標準的なフィールドとなっています。

- @message 
    - WAFアクセスログの未解析のログが表示されます。アクセスログ調査では、主に@messageの内容を解析します。
- @timestamp 
    - ログイベント発生のタイムスタンプ情報です。任意にWAFログを選んで見る@messageのtimestampと同じでした。なお@messageのtimestampはミリ秒でのunixtime表記になっています。アクセスログ調査では@timestamp情報も参考にしていました。
- @ingestionTime
    - CloudWatchログがイベントを受信した時刻。私のアクセスログ調査では特にこれまで利用はなかったです。
- @logStream
    - ログイベントが書き込まれるストリーム。1つのロググループの中に複数のロググループが生成されます。
- @log
    - ロググループの識別子を表します。

CloudWatchのコンソール上でCloudWach Logs Insightsを利用する場合、私が意識して使うのは@messageと@timestampの2つです。

## ログデータの分析（parse/display）

CloudWatch Logs Insightsにはログ解析のためのクエリコマンドや演算子、関数が用意されています。
その中のparseとdisplayコマンドについて理解したことを記載します。

### parseコマンド

AWSドキュメントではparseコマンドについて以下の説明と、使い方の例が書かれています。

> ログフィールドからデータを抽出し、クエリで処理できる抽出フィールドを作成します。parse は、ワイルドカードを使用する glob モードと正規表現の両方をサポートします。


ワイルドカードや正規表現を使えることはわかるけれど、最初はコマンドページに書かれた例を読んでもあまり理解できず。しかし実際のログを見てコマンド実行すると理解できました。

WAFアクセスログの@messageにはHTTPリクエストのクライアントIPやヘッダ情報などがJSON形式で保存されています。例えばログからリクエストヘッダに含まれるリファラー情報などで解析をしたい場合、httpRequest.headerフィールドから情報を抽出します。httpRequest.headerフィールドには添付画像のように配列の要素としてリクエストのヘッダー情報が保存されています。ヘッダーの数は一定ではないため、httpRequest.headerフィールド内のリファラー情報の要素の番号はリクエストによって異なります。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/255474/4a8d68f1-cd38-b41a-b5dc-63716076b76b.png" width=80%>


配列内の特定の要素を抽出したいときに便利なのがparseコマンドです。以下はparseコマンドを利用したクエリの例です。

```
fields @timestamp, @message
| parse @message '{"name":"referer","value":"*"}' as Referer
| filter Referer = 'https://www.yyyy.xxx/'
| sort @timestamp desc
| display @timestamp, Referer
```

`parse @message '{"name":"referer","value":"*"}' as Referer`
では@message中のJSONフィールドのname:refererのすべての値を抽出し、抽出フィールドRefererを作成しています。

`filter Referer = 'https://www.yyyy.xxx/'`
parseコマンドの次に書かれたfilterコマンドでは、抽出したRefererフィールドのうち値が https://<foo>www</foo>.yyyy.xxx/ のログを抽出しているコマンド内容です。

httpRequest.headerフィールドを明示しなくても、ログイベント内の特定要素を抽出することができるのだと気づいた時は驚きました。とても便利!

### displayコマンド

parseコマンドのセクションのクエリ例にdisplayコマンドを記載しました。

`display @timestamp, Referer`
displayコマンドでは指定したフィールドのみを結果に表示させることができ、上記の例ではタイムスタンプとRefererの2列の情報を表示します。結果をcsvでダウンロードすることも可能で、displayコマンドで指定したフィールド情報での抽出結果を取得できます。

ログイベントの抽出結果を社内や顧客へ提示する場合に、displayコマンドで必要なフィールドを指定しcsvでダウンロードすると、第三者からわかりやすい形式のデータを提供することができます。

## 最後に

CloudWatch Logs Insightsのコマンドは、使う機会が出た時に適当に調べて実行していましたが、意識していなかったログフィールドは何を示すのか？あまり理解して使っていなかったコマンドについて調べ直しを行いました。
本記事には記載していませんが、改めてAWSドキュメントを読むとCloudWatch Logs Insightsではデータ抽出後にグラフ作成ができたり、過去のログイベントの比較や、パターン分析など使ったことはないけど便利そうな機能があること知れ、良い学びの機会となりました。

引用
[AWSドキュメント：CloudWatch Logs Insights](https://docs.aws.amazon.com/ja_jp/AmazonCloudWatch/latest/logs/AnalyzingLogData.html)
[AWSドキュメント：CloudWatch Logs Insights クエリ構文](https://docs.aws.amazon.com/ja_jp/AmazonCloudWatch/latest/logs/CWL_QuerySyntax.html)