TerraformでALB＋WAFによる同一IPによるアクセス数制御の構築

# 前書き
Webサービスの運用において、特定のIPアドレスからの過剰なアクセスは、サービスのパフォーマンス低下や不正アクセスの温床となる恐れがあります。特に公開`API`や`Web`アプリケーションでは、短時間に大量のリクエストを送信する悪意あるユーザーやボットへの対策が不可欠です。

本記事では、AWSの**Application Load Balancer（ALB）** と**Web Application Firewall（WAF）** を組み合わせ、`Terraform`を用いてインフラをコードで構築する方法を記載します。

# AWS WAF
## 概要
Webアプリケーションへの 悪意のあるアクセス（不正リクエスト） をブロックしたり制御したりできるサービス。
例えば、SQLインジェクション（データベースへの不正な命令）やクロスサイトスクリプティング（XSS）などの攻撃を防ぐ。

Webアプリケーションが配置されている以下のAWSリソースに組み込んで使う
- [Amazon CloudFront](https://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/Introduction.html)（CDN：コンテンツ配信ネットワーク）
- [Application Load Balancer](https://docs.aws.amazon.com/ja_jp/elasticloadbalancing/latest/application/introduction.html)（アプリケーション用の負荷分散装置）
- [Amazon API Gateway](https://docs.aws.amazon.com/ja_jp/apigateway/)（APIを公開するためのサービス）

たとえば、以下のような不正アクセスを防ぐことができる
- Webサイトに対する 攻撃（例：SQLインジェクション）
- 特定のIPアドレスからの 不審なアクセス
- 短時間に大量のリクエストを送る ボットやDDoS攻撃
- 特定の国や地域からのアクセス制限

## 主な制御機能
**IPアドレスの制御**
- 特定のIPアドレスまたはIP範囲を、**許可（Allow）または遮断（Block）** できる
(例：海外IPからのアクセスを遮断したい場合等に行う)

**リクエストの内容による制御**
- リクエストの中の情報（ヘッダー、URI、クエリ文字列、ボディなど）を条件にできる
(特定のURLパターンへのアクセスを制限する)

**SQLインジェクション対策**
- 入力フォームなどから送られるデータに SQL文のような不審な文字列 が含まれているか検査してブロック

**クロスサイトスクリプティング（XSS）対策**
- 悪意あるスクリプトがWebページに埋め込まれるのを防ぐ

**レートベースの制御（Rate-based rules）**
- 一定時間内に同じIPアドレスから異常に多くのリクエストが来た場合にブロック

例：DDoS（分散型サービス拒否）対策の一部として使える。

**地理的な制御（Geo match）**
- アクセス元の国や地域を指定して制御できる

**カスタムルールの作成**
- 独自の条件を組み合わせて、柔軟なルールを作れる
(例：「特定のパスに対してPOSTメソッドかつUser-Agentが○○のときだけブロック」など)


# 構築の際の前提条件
- Terraformの環境がある
- Terrformにて、**ALB**の構築が完了済み

# 構築内容

## Terraformの設定内容
```tf
# WAFの設定
resource "aws_wafv2_web_acl" "alb_waf_acl" {
  name        = # 任意の名前 入力例:"alb-waf"
  scope       = "REGIONAL"
  
  default_action {
    allow {}
  }

  rule {
    name     = # 任意の名前 入力例: "limit-50-requests-per-second"
    priority = 1
    action {
      block {}
    }

    statement {
      rate_based_statement {
        # 15000 req / 5分 ≒ 平均 50 req/sec
        limit              = 15000
        aggregate_key_type = "IP"
      }
    }

    visibility_config {
      cloudwatch_metrics_enabled = true
      metric_name                = # 任意の名前 入力例: "limit-50-requests-per-second"
      sampled_requests_enabled   = true
    }
  }

  visibility_config {
    cloudwatch_metrics_enabled = true
    metric_name                = # 任意の名前 入力例: "alb-waf-acl"
    sampled_requests_enabled   = true
  }
}

# ALBにWAFを関連付ける
resource "aws_wafv2_web_acl_association" "alb_waf_attach" {
  resource_arn = # albのarnを記述する
  web_acl_arn  = aws_wafv2_web_acl.alb_waf_acl.arn
}

```

## メソッドと説明
### aws_wafv2_web_aclで使用したメソッドと説明

| メソッド | 説明 |
| :-: | - |
| `name` | WAF（Web ACL）の名前を設定する。WAFをTerraformやAWS管理画面で識別しやすくするために付けるラベル。任意の文字列で良い。 |
| `scope` | WAFをどこに適用するかを指定する。`REGIONAL`を指定すると、ALBなどのリージョンサービスに適用される。CloudFrontの場合は `CLOUDFRONT` を使う。 |

### default_actionで使用したメソッドと説明
すべてのルールに一致しなかったリクエストに対する最終処理を指定する。

| メソッド | 説明 |
| :-: | - |
| `allow {}` | 一致しなかったリクエストを許可する |
| `block {}` | （<font color="red">※ 今回は未使用</font>）一致しなかったリクエストをブロックする |

**他にできる設定**
- `count {}` を使うと、リクエストを記録するだけで実際にはブロックしないテスト運用ができる


### ruleで使用したメソッドと説明
1つのWAFルールを定義する。複数記述することで複数の対策が可能になる。

| メソッド | 説明 |
| :-: | - |
| `rule` | 1つのWAFルールを定義する。複数記述することで複数の対策が可能になる |
| `name` | このルールの識別名。任意のわかりやすい名前をつける |
| `priority` | ルールの優先順位を数字で指定する。小さいほど優先される |
| `action` | このルールに一致した場合の動作を指定する |
| `block {}` | 一致したリクエストをブロックする |
| `allow {}` | （<font color="red">※ 今回は未使用</font>）一致したリクエストを許可する |
| `count {}` | （<font color="red">※ 今回は未使用</font>）一致したリクエストを記録のみする |

**他にできる設定**
- `override_action` を使えば、マネージドルール使用時にアクションを上書きできる


### statementで使用したメソッドと説明
ルールの対象条件（どんなリクエストに適用するか）を設定する

| メソッド | 説明 |
| :-: | - |
| `rate_based_statement` | 一定期間内のリクエスト数が閾値を超えたIPを対象にする設定 |
| `limit` | 一定期間内の最大リクエスト数を指定する（今回は、5分間で15,000回のアクセス数制御にしている） |
| `aggregate_key_type` | 集計の基準を指定する。`IP`にする事で、**IPアドレス**ごとにカウントする |

**他にできる設定**
- byte_match_statement（文字列一致）
- geo_match_statement（国別制限）
- ip_set_reference_statement（IPリスト）
- regex_pattern_set_reference_statement（正規表現マッチ）
- size_constraint_statement（リクエストサイズ制限）

:::note info
他にも制御できる項目がある為、公式ドキュメントを参照する
:::


### 1回目で使用したvisibility_configのメソッドと説明
ルールごとに、どれくらいそのルールが発動したかをCloudWatchで確認できるようにするための設定。分かりやすく言うと、「このルールが何回ブロックしたか」などを可視化するための設定。

| メソッド | 説明 |
| :-: | - |
| `cloudwatch_metrics_enabled = true` | CloudWatchにメトリクス（統計情報）を送信する設定 |
| `metric_name` | CloudWatchに表示されるメトリクスの名前を指定する |
| `sampled_requests_enabled = true` | 実際にWAFが検知したリクエストを一部CloudWatchに記録する設定 |


### 2回目で使用したvisibility_configのメソッドと説明
WAF全体としてのリクエスト状況をCloudWatchで把握するための設定。

| メソッド | 説明 |
| :-: | - |
| `cloudwatch_metrics_enabled = true` | WAF全体に対してCloudWatchにメトリクスを送る設定 |
| `metric_name` | WAF全体用のメトリクス名を指定する |
| `sampled_requests_enabled = true` | 全体で検知したリクエストの一部を記録する設定 |


### visibility_configが2回記載してある理由
WAFは、**ルールごと** と **全体** でそれぞれモニタリングため、2箇所で設定を行っている

| `visibility_config` の場所 | 監視対象 | 目的 |
| :-: | :-: | - |
| WAF本体（最上位） | Web ACL全体 | 全体のリクエスト・メトリクスをCloudWatchで確認する為に設定 |
| 各 `rule` 内 | ルール個別 | 各ルールの発動状況や効果を確認する為に設定 |

### visibility_configのCloudWatchの設定の注意点

:::note alert
注意点
 - **visibility_config** をTerraformに書くだけでは、**CloudWatch Logs** にログが出力されない
 - 別途、CloudWatch Logs用のロググループと、WAFログ出力設定（[aws_wafv2_web_acl_logging_configuration](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/wafv2_web_acl_logging_configuration)）の定義が必要
 - 運用時には **metric_name** とロググループ名を対応付けておくと「障害対応」や「問題解決」が容易になる
- `aws_wafv2_web_acl_logging_configuration`のサンプルコードは下記に記載

```terraform
# CloudWatch Logsのロググループを作成
resource "aws_cloudwatch_log_group" "waf_logs" {
  name              = "/aws/waf/alb-waf-logs"
  retention_in_days = 30
}

# WAFログ設定（ACLとLogGroupを紐付け）
resource "aws_wafv2_web_acl_logging_configuration" "example" {
  log_destination_configs = [
    aws_cloudwatch_log_group.waf_logs.arn
  ]

  resource_arn = aws_wafv2_web_acl.example.arn
}
```
:::


# 参考資料
- [[AWS公式] WAF](https://docs.aws.amazon.com/ja_jp/waf/latest/developerguide/waf-chapter.html)
- [[hashicorp 公式] aws_wafv2_web_acl](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/wafv2_web_acl)
- [[hashicorp 公式] aws_wafv2_web_acl_association](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/wafv2_web_acl_association)


# 感想
今回、WAFを導入したことで、WAFの設定箇所について理解できました。しかし、今回はアクセス数の制御目的でWAFを導入したため、WAF本来のセキュリティ機能に関する設定はあまり行えていません。今後は、WAFで設定可能な機能を把握し、案件に応じて効果的に活用できるようにしたいと思います。