カスタム形式のフローログをCloudWatch Logs出力して思ったこと

## はじめに
つい先日、久しぶりにAWSのVPCでフローログの作成をしました。
するとなんと、、CloudWatch Logsへの送信で**Custom format(カスタム形式)**にチェックを入れられるではないですか！！(　ﾟдﾟ)/
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211371/dfeb58bc-5326-8a57-04da-606ed0e2cc8e.png)

ということで、クラスメソッドさんのDevelopers.IOを検索してみると、GW中にアップデートされていました。

**【参考】**
・[VPCフローログのカスタム形式でCloudwatch Logsがサポートされました][00]

## カスタム形式とは
2019年9月までは、**AWS default format(デフォルト形式)**と呼ばれる決まったログフォーマットを採用していました。

```text:デフォルト形式
<version> <account-id> <interface-id> <srcaddr> <dstaddr> <srcport> <dstport> <protocol> <packets> <bytes> <start> <end> <action> <log-status>
```

2019年9月に以下の7個のフィールドをカスタム形式のログフォーマットとして、選択式で指定することができるようになりました。
この時点では、送信先にAmazon S3のバケットしか指定することができませんでした。

・**vpc-id**
・**subnet-id**
・**instance-id**
・**tcp-flags**
・**type**
・**pkt-srcaddr**
・**pkt-dstaddr**

2020年5月には、さらに4個のフィールドが追加され、CloudWatch Logsに対しても送信することができるようになりました。

・**region**
・**az-id**
・**sublocation-type**
・**sublocation-id**

```text:カスタム形式(25フィールド全部入り)
<account-id> <action> <az-id> <bytes> <dstaddr> <dstport> <end> <instance-id> <interface-id> <log-status> <packets> <pkt-dstaddr> <pkt-srcaddr> <protocol> <region> <srcaddr> <srcport> <start> <sublocation-id> <sublocation-type> <subnet-id> <tcp-flags> <type> <version> <vpc-id>
```

※公式の日本語ドキュメントは古く、英語に切り替えないと新しい情報が表示されないという罠にやられて見逃しました(´;ω;｀)

**【参考】**
・[Amazon VPC フローログにメタデータを追加][01]
・[Flow log records(英語サイト)][02]
・[フローログレコード(日本語サイト)][03]

## CloudWatch Logs Insightでクエリしてみよう
トラブルシューティングで通信フローを見たく、VPCフローログをCloudWatch Logsに入れて
Insightからクエリをしようとしましたが、デフォル形式のログフォーマットでないと
フィールド抽出されないという罠にハマりました。

**【カスタム形式の場合】**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211371/2e7763fd-6ae7-da37-4a2e-039d1042c8b9.png)

**【デフォルト形式の場合】**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211371/599c9f93-ffe4-33ad-99cd-8d4b1c3c9a1b.png)

2020年6月時点において、自動検出されるフィールドは下記の参考URLの通りなっています。
ということでparseコマンドを用いて、@messageの内のフィールドをエフェメラルフィールドとして
抽出してクエリに利用することにしました。

**【参考】**
・[サポートされるログと検出されるフィールド][04]

## parseコマンドでパースする
まず、なにも変更せずにデフォルトのままクエリすると@messageにゴリっとログが出力されてしまいます。

```text:デフォルト設定
fields @timestamp, @message
| sort @timestamp desc
| limit 20
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211371/643faca2-1c85-d20b-72ce-2bbefbc27654.png)

次にparseコマンドでフィールド抽出してみます。

```text:parseコマンドでのフィールド抽出
fields @timestamp
| parse '* * * * * * * * * * * * * * * * * * * * * * * * *' as `account-id`, action, `az-id`, bytes, dstaddr, dstport, end, `instance-id`, `interface-id`, logstatus, packets, pktdstaddr, pktsrcaddr, protocol, region, srcaddr, srcport, start, sublocationid, sublocationtype, `subnet-id`, tcpflags, type, version, `vpc-id`
| sort @timestamp desc
```

parseコマンドは、glob式を使用する方式と正規表現を使用する方式の2通りの記述が可能です。
今回は、前者のglob式でのフィールド抽出を行いました。フィールド数は25個ありますので
*(アスタリスク)を25個、半角スペースを空けて記述し、as以降、フィールド名を指定しています。

※エフェメラルフィールド名に英数字以外が含まれる場合は、`(アクサングラーブ)で囲わないとエラーになります。

いい感じにフィールド抽出されましたね。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211371/c79e8099-e540-0608-ed46-8e15d6e1d2aa.png)

これでIPアドレスやポート番号などの抽出されたフィールドを条件にクエリを作成することができます。
以下では、元パケットの送信元IPアドレスが10.1.1.199となっているフローだけを検索することができます。

```text:filterコマンドで条件指定
fields @timestamp
| parse '* * * * * * * * * * * * * * * * * * * * * * * * *' as `account-id`, action, `az-id`, bytes, dstaddr, dstport, end, `instance-id`, `interface-id`, logstatus, packets, pktdstaddr, pktsrcaddr, protocol, region, srcaddr, srcport, start, sublocationid, sublocationtype, `subnet-id`, tcpflags, type, version, `vpc-id`
| filter pktsrcaddr = '10.1.1.199'
| sort @timestamp desc
```

※filter条件に利用する値は、数値以外は'(シングルクォーテーション)で囲わないとエラーになります。

以下では、宛先IPアドレスに10.1.10.127と10.1.20.126と10.1.30.186、宛先ポート443のHTTPS通信に絞り
元パケットの送信元IPごと(Group By)のフロー数を多い順(desc)で表示することができます。

```text:statsコマンドでカウント集計
fields @timestamp
| parse '* * * * * * * * * * * * * * * * * * * * * * * * *' as `account-id`, action, `az-id`, bytes, dstaddr, dstport, end, `instance-id`, `interface-id`, logstatus, packets, pktdstaddr, pktsrcaddr, protocol, region, srcaddr, srcport, start, sublocationid, sublocationtype, `subnet-id`, tcpflags, type, version, `vpc-id`
| filter dstport = '443'
| filter dstaddr in ['10.1.10.127','10.1.20.126','10.1.30.186']
| stats count(*) as FlowCount by pktsrcaddr
| sort pktsrcaddrCount desc
```

クエリ構文は、以下の公式ドキュメントも参考にしてみてください。

**【参考】**
・[CloudWatch Logs Insights クエリ構文][05]

## おまけ (2020/6/10追記)
CloudWatch Logsからサブスクリプションフィルタを使って、「Amazon Elasticsearch Serviceへのストリーム」を行う場合も同じでした。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211371/e4921deb-dd2e-7453-11dd-e8b29184e04f.png)

[Amazon VPC フローログ]を選択したときの**サブスクリプションフィルターのパターン**は、デフォルト形式のままです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211371/9e4359c3-6a2b-09a0-39ca-7532ed7752d9.png)

以下のように書き換えてみます。

```text:カスタム形式(25フィールド全部入り)のパターン
[account_id, action, az_id, bytes, dstaddr != "-", dstport != "-", end, instance_id, interface_id, logstatus, packets, pktdstaddr != "-", pktsrcaddr != "-", protocol, region, srcaddr != "-", srcport != "-", start, sublocationid, sublocationtype, subnet_id, tcpflags, type, version, vpc_id]
```

テスト結果もキレイにパースされていますね。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211371/cb7c24d0-0374-0dad-5403-32a5629e7bd8.png)

※フィールド名に**ハイフン(-)**が含まれているとパースエラーになってしまうため、**アンダースコア(_)**に置換しています。

**【参考】**
・[フィルターとパターンの構文][06]

## まとめ
いかがでしたでしょうか。
そのうち、CloudWatch Logs Insightの機能として、カスタム形式のログフォーマットも
自動検出フィールドでもっと便利にできるようになると思いましたが、小ネタとして記事にしてみました^^


[00]:https://dev.classmethod.jp/articles/add-enriched-metadata-to-amazon-vpc-flow-logs-published-to-cloudwatch-logs-and-s3/ "VPCフローログのカスタム形式でCloudwatch Logsがサポートされました"
[01]:https://aws.amazon.com/jp/about-aws/whats-new/2019/09/now-add-additional-metadata-to-amazon-vpc-flow-logs/ "Amazon VPC フローログにメタデータを追加"
[02]:https://docs.aws.amazon.com/vpc/latest/userguide/flow-logs.html#flow-logs-basics "Flow log records"
[03]:https://docs.aws.amazon.com/ja_jp/vpc/latest/userguide/flow-logs.html#flow-log-records "フローログレコード"
[04]:https://docs.aws.amazon.com/ja_jp/AmazonCloudWatch/latest/logs/CWL_AnalyzeLogData-discoverable-fields.html "サポートされるログと検出されるフィールド"
[05]:https://docs.aws.amazon.com/ja_jp/AmazonCloudWatch/latest/logs/CWL_QuerySyntax.html "CloudWatch Logs Insights クエリ構文"
[06]:https://docs.aws.amazon.com/ja_jp/AmazonCloudWatch/latest/logs/FilterAndPatternSyntax.html "フィルターとパターンの構文"