AWS WAF と CloudWatch だけで国別の BLOCK 数を可視化する

# 注意事項
今回の構成は、カスタムメトリクス料金が高騰する可能性があります。
AWS WAF ログなどの JSON 形式のログと、メトリクスフィルターを組み合わせると、柔軟にメトリクス作成ができる、という参考程度にご覧ください。

# CloudWatch Logs メトリクスフィルターで AWS WAF の国別 BLOCK 数をメトリクス化する
## 構成
AWS WAF のログを CloudWatch Logs に送信し、メトリクスフィルターを設定します。
緑の枠内を Terraform で構築しました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2412686/d2fcb268-49f9-5e51-9725-23f99de7778a.png)

WAF 保護対象のリソース (今回は API Gateway) 構築と、AWS WAF (Web ACL) との関連付けは別途行っています。ここでは割愛します。

## Terraform
```terraform:variables.tf
variable "name" {
  type    = string
  default = "waf-metrics-example"
}

variable "region" {
  type    = string
  default = "ap-northeast-1"
}
```
```terraform:terraform.tf
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 4.0"
    }
  }
}

provider "aws" {
  region = var.region
}
```
```terraform:main.tf
#------------------------
# WAF
#------------------------
resource "aws_wafv2_web_acl" "example" {
  name  = var.name
  scope = "REGIONAL"

  default_action {
    allow {}
  }

  rule {
    name     = "rule-1"
    priority = 1

    override_action {
      none {}
    }

    statement {
      managed_rule_group_statement {
        name        = "AWSManagedRulesCommonRuleSet"
        vendor_name = "AWS"
      }
    }
    visibility_config {
      cloudwatch_metrics_enabled = true
      metric_name                = "aws-managed-common-rule"
      sampled_requests_enabled   = true
    }
  }


  visibility_config {
    cloudwatch_metrics_enabled = true
    metric_name                = var.name
    sampled_requests_enabled   = true
  }
}

resource "aws_wafv2_web_acl_logging_configuration" "example" {
  log_destination_configs = [aws_cloudwatch_log_group.waf.arn]
  resource_arn            = aws_wafv2_web_acl.example.arn
}


#------------------------
# CloudWatch
#------------------------
resource "aws_cloudwatch_log_group" "waf" {
  name = "aws-waf-logs-${var.name}"
}

# 国別 Block 数を抽出するメトリクスフィルター
resource "aws_cloudwatch_log_metric_filter" "waf-block-by-country" {
  name           = "${var.name}-block-by-country"
  pattern        = "{ $.action = \"BLOCK\"}"
  log_group_name = aws_cloudwatch_log_group.waf.name

  metric_transformation {
    name      = "waf-block"
    namespace = "${var.name}-by-country"
    value     = "1"
    dimensions = {
      country = "$.httpRequest.country"
    }
  }
}
```
検証用のため、Web ACL およびルールは適当に設定しています。

今回メインで試したかったのは一番下の aws_cloudwatch_log_metric_filter 部分です。
`pattern = "{ $.action = \"BLOCK\"}"`
で WAF ログから BLOCK されたもののみを抽出しています。
また、
`dimensions = {
      country = "$.httpRequest.country"
    }`
で BLOCK 数をリクエスト送信国という切り口で見られるようにしています。

メトリクスフィルターにはディメンションを３つまで設定可能なので、例えば
```
dimensions = {
  country = "$.httpRequest.country"
  ruleId  = "$.ruleGroupList[0].terminatingRule.ruleId"
}
```
とすれば、国別・ルール ID 別の集計を見ることができます (が、組み合わせが大量になるのでおすすめしません)。

## メトリクス確認
日本とアメリカから API Gateway にアクセスしてログを生成し、メトリクスがどう見えるか確認しました。
テスト環境なので点が疎らですが、国別に集計できています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2412686/23e27421-5ae0-eb18-386a-1f63d8607fdc.png)
(点があまりに少なかったので、別途メトリクスフィルター作成した action = 'ALLOW' のログ数も一緒にプロットしています)

# おわりに
CloudWatch メトリクスフィルターを使って、Athena などを使わずに簡単に AWS WAF の国別 BLOCK 数を可視化することができました。
これまで AWS WAF のログは S3 に出力して分析することが多かったのですが、 小規模なサイトなら CloudWatch Logs 直接送信でも十分かもしれません。

将来的にログ使わなくてもリクエスト元国別メトリクス見れるようになったりしないかなぁ...と期待。