# WAF で多層防御を設計する：AWS WAF / Cloud Armor の OWASP 対策・レート制限・DDoS 緩和を、誤検知を出さず本番投入する

> AWS WAFとGoogle Cloud Armorで多層防御を本番構築する実装ガイド。Web ACL/セキュリティポリシー、OWASPマネージドルール、レート制限、DDoS/適応型防御、そしてcount/stagingで誤検知を出さず安全にロールアウトする運用までを実設定で解説します。WAFは多層防御の『一層』であって銀の弾丸ではない——その前提から設計します。

- 公開日: 2026-06-24
- 著者: 友田 陽大
- タグ: セキュリティ, AWS, GCP, WAF, アーキテクチャ設計
- URL: https://tomodahinata.com/blog/waf-defense-in-depth-aws-waf-cloud-armor-owasp-guide

## 要点

- WAF は L7 のリクエストフィルタという一層に過ぎず、認可・入力検証・最小権限を代替しない
- WAF の最大の運用リスクは攻撃を防げないことでなく、正規トラフィックを誤ってブロックすること
- OWASP 対策は自前で正規表現を書かず、AWS のマネージドルールや Cloud Armor の preconfigured WAF rules を使う
- 新ルールは必ず Count（AWS）/ preview（Cloud Armor）で観測してから Block / enforce へ昇格させる
- 安全なロールアウトは staging 全面有効化→本番 Count/preview→個別チューニング→段階 enforce→継続観測の順

---

「WAF を入れておけば安全ですよね？」——案件の打ち合わせで、最も多く、そして最も危険な質問の一つです。

WAF（Web Application Firewall）は確かに強力です。SQL インジェクションも XSS も、既知の攻撃パターンも、リクエストがアプリに届く前に入口で弾けます。けれど **WAF は「銀の弾丸」ではありません**。認可（誰が何をできるか）も、入力検証（受け取った値が妥当か）も、最小権限も、WAF は一切代替しません。WAF は **多層防御（defense in depth）の一層**——L7 のリクエストフィルタ——にすぎません。

そしてもう一つ、現場でこそ効いてくる真実があります。**WAF の最大の運用リスクは「攻撃を防げないこと」ではなく、「正規トラフィックを誤ってブロックすること」**です。決済ボタンが 403 を返す、API クライアントが弾かれる、検索クエリが SQLi と誤判定される——これらは攻撃よりはるかに高い確率で、しかも自分の手で起こします。

この記事は、**AWS WAF** と **Google Cloud Armor** で多層防御を**本番品質**で設計・運用するための実装ガイドです。題材として、私が国内大手放送事業者向けに構築した[社内AIプラットフォーム](/case-studies/broadcaster-ai-content-platform)での設計判断——入口に **Cloud Armor（OWASP CRS 3.3 ＋ 適応型DDoS防御 ＋ レート制限）** を据え、**ステージングで WAF を全面有効化して本番前に誤検知を潰した**運用——も交えます。

> **この記事のルール**：仕様・設定値・ルール名は **AWS / Google Cloud 公式ドキュメント（2026年6月時点）** に基づきます。マネージドルールのバージョンや料金は改定されるため、本番投入前に必ず公式の最新情報を確認してください。コードは実運用で使える形（Terraform 中心）に整えていますが、**WAF は多層防御の一層であって、認可・入力検証・最小権限を代替しません**。そして鉄則がもう一つ——**新しいルールは必ず Count（観測）から始め、Block（強制）はその後**です。

---

## 0. メンタルモデル：WAF は「L7 のリクエストフィルタ」

設計を始める前に、WAF が何で、何でないかを一行で固定します。

> **WAF ＝ HTTP/HTTPS リクエストを、内容（SQLi/XSS/既知の攻撃パターン）とレート（異常な頻度）で評価し、入口で Allow / Block / Count / Challenge する L7 のフィルタ。**

ここから3つの帰結が出ます。

1. **WAF は「通信の中身」しか見ない。** 認可ロジック（このユーザーはこのリソースにアクセスしてよいか）はアプリの仕事です。WAF は「リクエストが攻撃パターンに一致するか」しか判断しません。**`/admin` への正規の管理者アクセスと攻撃者のアクセスを、WAF は区別できません。**
2. **WAF は「既知のパターン」に強く、「ロジックの穴」に弱い。** OWASP マネージドルールは既知の攻撃を網羅的に弾きますが、あなたのアプリ固有の認可バグ（IDOR、権限昇格）は素通しします。だから**入力検証と最小権限は別途必要**です。
3. **だからこそ多層防御。** WAF（入口）＋ 認証・MFA ＋ 認可 ＋ 入力検証（Zod 等）＋ 最小権限の IAM ＋ Secrets 管理 ＋ ネットワーク分離。WAF はこの**一番外側の層**であり、内側の層を省く理由にはなりません。

放送局のプラットフォームでも、Cloud Armor は入口の一層にすぎず、内側には **Cloud SQL（IAM認証・TLS必須・プライベートIP）、Secret Manager、最小権限のサービスアカウント、Identity Platform（SMS MFA・reCAPTCHA Enterprise）** を重ねています。WAF はその玄関のフィルタです。

---

## 1. 二大 WAF の地図：AWS WAF と Cloud Armor

クラウドが違えば WAF の構造も用語も違います。まず対応表で全体像を掴みます。

| 概念 | AWS WAF | Google Cloud Armor |
| --- | --- | --- |
| ルールの容れ物 | **Web ACL**（protection pack） | **security policy**（セキュリティポリシー） |
| 適用先 | CloudFront / ALB / API Gateway / AppSync / Cognito user pool / App Runner / Verified Access / Amplify | 外部 Application Load Balancer（backend / edge セキュリティポリシー） |
| OWASP 対策 | **AWSManagedRulesCommonRuleSet**（CRS, WCU 700） | **preconfigured WAF rules**（OWASP ModSecurity CRS 3.3 / 4.22） |
| ルールのアクション | Allow / Block / **Count** / CAPTCHA / Challenge | allow / deny / throttle / rate_based_ban / redirect |
| レート制限 | **rate-based rule**（`RateBasedStatement`） | **rateLimitOptions**（throttle / rate_based_ban） |
| 観測モード（誤検知洗い出し） | **Count アクション** / rule action override | **preview** フラグ |
| DDoS（L7） | Shield Standard（標準）/ Shield Advanced（有償） | **Adaptive Protection**（ML による L7 検知） |
| ログ | WAF ログ → CloudWatch / S3 / Firehose | Cloud Logging |

**どちらを使うかは「アプリがどのクラウドのロードバランサ配下にいるか」でほぼ決まります。** AWS の ALB/CloudFront 配下なら AWS WAF、GCP の外部 Application Load Balancer 配下なら Cloud Armor。マルチクラウドなら両方を、**同じ思想（OWASP マネージド ＋ レート制限 ＋ Count/preview 先行）** で組むことになります。本記事は両方を扱います。

---

## 2. AWS WAF：Web ACL とマネージドルールを Terraform で組む

### 2.1 Web ACL とは何か（公式の定義）

AWS WAF は、保護対象リソースに転送される HTTP/HTTPS リクエストを監視する Web アプリケーションファイアウォールです。リクエストの容れ物が **Web ACL（web access control list）** で、保護できるリソースは公式に次のとおりです。

- Amazon CloudFront distribution
- Amazon API Gateway REST API
- Application Load Balancer
- AWS AppSync GraphQL API
- Amazon Cognito user pool
- AWS App Runner service
- AWS Verified Access instance
- AWS Amplify

Web ACL は**デフォルトアクション**（マッチしなかったリクエストをどうするか＝`allow` か `block`）を持ち、その中に**ルール**を優先度順に並べます。公衆向けサイトなら「**指定したもの以外はすべて許可（Allow all except…）**」＝デフォルト Allow ＋ 攻撃を Block、が基本形です。

公式が定義するルールアクションは次の4系統です。ここを正確に押さえることが、安全なロールアウトの起点になります。

| アクション | 意味 |
| --- | --- |
| **Allow** | マッチしたリクエストを通す |
| **Block** | マッチしたリクエストを 403 か カスタムレスポンスで弾く |
| **Count** | **ハンドリングを変えずに**マッチ数だけ数える（観測・テスト用） |
| **CAPTCHA / Challenge** | CAPTCHA / サイレントチャレンジで bot を抑制 |

> **公式が `Count` をどう説明しているか**：「Count アクションは、トラフィックの扱いを変えずに数えるために使える。新しいルールをテストするのにも使える。新しい性質に基づいて allow/block したいとき、**まず Count で数え、設定が正しいことを確認してから allow/block に切り替えられる**」。——**これが本記事の背骨です。**

### 2.2 マネージドルール：OWASP 対策の主力

自前で SQLi/XSS の正規表現を書くのは愚策です（DRY 違反であり、メンテ不能）。AWS が**マネージドルールグループ**を提供しており、主力は**ベースライン**の以下3つです。

| ルールグループ | VendorName / Name | WCU | 目的 |
| --- | --- | --- | --- |
| Core rule set (CRS) | `AWS` / `AWSManagedRulesCommonRuleSet` | 700 | OWASP Top 10 を含む広範な脆弱性対策（**全 WAF ユースケース推奨**） |
| Known bad inputs | `AWS` / `AWSManagedRulesKnownBadInputsRuleSet` | 200 | Log4j（CVE-2021-44228 ほか）等、既知の不正パターンを遮断 |
| Admin protection | `AWS` / `AWSManagedRulesAdminProtectionRuleSet` | 100 | 露出した管理画面パスへの外部アクセスを遮断 |

CRS の中身は具体名で構成されています（一部）。すべてデフォルト `Block` で、ラベル（`awswaf:managed:aws:core-rule-set:*`）を付与します。

- `NoUserAgent_HEADER` — `User-Agent` ヘッダ欠落を遮断
- `SizeRestrictions_BODY` — リクエストボディ 8KB（8,192 バイト）超を遮断
- `SizeRestrictions_QUERYSTRING` — クエリ文字列 2,048 バイト超を遮断
- `CrossSiteScripting_BODY` / `_QUERYARGUMENTS` / `_COOKIE` / `_URIPATH` — XSS パターン
- `GenericLFI_*` / `GenericRFI_*` — ローカル/リモートファイルインクルージョン（`../../` 等）
- `EC2MetaDataSSRF_*` — EC2 メタデータ窃取（SSRF）

Known bad inputs には `Log4JRCE_HEADER/BODY/URIPATH/QUERYSTRING`（`${jndi:ldap://…}` を検知）、`JavaDeserializationRCE_*` などが含まれます。**これらは「自分で書くべきでないルール」の典型**で、AWS がバージョン管理してくれます。

### 2.3 Terraform：Count で出してから Block にする

ここが本記事の核心です。**マネージドルールをいきなり Block で本番に入れてはいけません。** `SizeRestrictions_BODY` は 8KB 超のボディを問答無用で弾きます——あなたのファイルアップロード API やリッチな JSON ペイロードが、その日から 403 を返し始めるかもしれません。

だから **まず Count で本番トラフィックに当て、サンプリングされたリクエストと CloudWatch メトリクスで「何が引っかかるか」を観測**します。

#### ステップ1：マネージドルールを Count で観測する

```hcl
resource "aws_wafv2_web_acl" "app" {
  name        = "app-web-acl"
  description = "Defense-in-depth WAF for the app (count-first rollout)"
  scope       = "REGIONAL" # ALB / API Gateway 用。CloudFront は "CLOUDFRONT"

  # デフォルトは「許可」。公衆向けサイトの基本形（指定したものだけ弾く）
  default_action {
    allow {}
  }

  # --- OWASP Core Rule Set ---
  rule {
    name     = "AWSManagedRulesCommonRuleSet"
    priority = 10

    # rule group 自体は「ブロックを上書きしない」(none) が、
    # 個別ルールを Count に上書きして観測する = 誤検知の洗い出し
    override_action {
      none {}
    }

    statement {
      managed_rule_group_statement {
        vendor_name = "AWS"
        name        = "AWSManagedRulesCommonRuleSet"

        # 誤爆しやすい個別ルールだけ Count に落として様子を見る
        rule_action_override {
          name = "SizeRestrictions_BODY" # 8KB超ボディ。アップロードAPIで誤爆しがち
          action_to_use {
            count {}
          }
        }
        rule_action_override {
          name = "CrossSiteScripting_BODY" # リッチエディタ等で誤爆しがち
          action_to_use {
            count {}
          }
        }
      }
    }

    visibility_config {
      cloudwatch_metrics_enabled = true
      metric_name                = "CommonRuleSet"
      sampled_requests_enabled   = true # サンプリングされたリクエストを必ず有効に
    }
  }

  # --- Known bad inputs（Log4j 等。これはほぼ誤爆しないので最初から有効寄り）---
  rule {
    name     = "AWSManagedRulesKnownBadInputsRuleSet"
    priority = 20
    override_action {
      none {}
    }
    statement {
      managed_rule_group_statement {
        vendor_name = "AWS"
        name        = "AWSManagedRulesKnownBadInputsRuleSet"
      }
    }
    visibility_config {
      cloudwatch_metrics_enabled = true
      metric_name                = "KnownBadInputs"
      sampled_requests_enabled   = true
    }
  }

  visibility_config {
    cloudwatch_metrics_enabled = true
    metric_name                = "appWebAcl"
    sampled_requests_enabled   = true
  }

  tags = { Environment = "staging" }
}
```

ポイントは `rule_action_override`（API の `RuleActionOverrides`）です。**ルールグループ全体は有効化しつつ、誤爆が怖い個別ルールだけ Count に「上書き」**できます。CloudWatch でそのルールのマッチ数を数日観測し、「正規トラフィックが引っかかっていない」と確認できたら `count {}` を外して Block（本来のアクション）に戻します。

> **`override_action` と `rule_action_override` の違い（混同注意）**：`override_action { none {} }` は「ルールグループのアクションをそのまま使う（上書きしない）」、`override_action { count {} }` は「**グループ内全ルールを Count に上書き**」です。一方 `rule_action_override` は「**個別ルール単位**で上書き」。最初はグループごと `override_action { count {} }` で丸ごと観測 → 慣れたら個別 `rule_action_override` で危ないルールだけ Count、という二段構えが安全です。

#### ステップ2：レート制限を足す（rate-based rule）

OWASP ルールは「中身」を見ますが、ブルートフォースやスクレイピングは「頻度」の問題です。**rate-based rule** で頻度を制御します。公式の高レベル設定は次のとおりです。

- **Evaluation window**（評価ウィンドウ）：`60` / `120` / `300` / `600` 秒から選択。**デフォルトは 300（5分）**。「現在時刻から何秒さかのぼって数えるか」。
- **Rate limit**：そのウィンドウ内の上限リクエスト数。**最小値は 10**。超過すると以降のマッチリクエストにルールアクションを適用。
- **Aggregation**（集約キー）：`IP`（送信元IP）/ `FORWARDED_IP`（`X-Forwarded-For` 等の先頭IP）/ `CONSTANT`（全リクエストを一括＝Count all）/ `CUSTOM_KEYS`。
- **Action**：`Allow 以外`の任意アクション（＝ Block / Count / Challenge）。

```hcl
  # web_acl リソースに追記する rule ブロック
  rule {
    name     = "RateLimitPerIP"
    priority = 5 # マネージドルールより前に評価したいので小さい優先度

    # ★ まずは Count。閾値が妥当か観測してから block に変える
    action {
      count {}
    }

    statement {
      rate_based_statement {
        limit                 = 2000 # 5分ウィンドウ・1IPあたり2000req。最小は10
        aggregate_key_type    = "IP"
        evaluation_window_sec = 300 # 60/120/300/600。デフォルト300

        # scope-down: 重い検索エンドポイントだけに絞ることも可能
        scope_down_statement {
          byte_match_statement {
            field_to_match { uri_path {} }
            positional_constraint = "STARTS_WITH"
            search_string         = "/api/search"
            text_transformation {
              priority = 0
              type     = "NONE"
            }
          }
        }
      }
    }

    visibility_config {
      cloudwatch_metrics_enabled = true
      metric_name                = "RateLimitPerIP"
      sampled_requests_enabled   = true
    }
  }
```

**閾値（`limit`）の決め方は「観測してから」**です。正規ユーザーの正常時のピーク req/IP/5分 を CloudWatch で測り、その **2〜3倍**を初期閾値に置きます。最初から Block にすると、CDN/プロキシ配下で多数ユーザーが同一 IP に見えるケース（オフィス NAT、モバイルキャリア CGNAT）で正規ユーザーを巻き込みます。だから `action { count {} }` で出し、誤爆ゼロを確認してから `block {}` に変えます。

> **scope-down の効能（SRP）**：レート制限を**全パスにかけず、重いエンドポイント（検索・ログイン・生成API）だけに絞る**と、静的アセットや軽い GET を巻き込まずに済みます。「1つのルールが1つの関心事だけを担う」——`scope_down_statement` はそれを実現します。

#### ステップ3：観測が済んだら Block に切り替える

数日〜2週間 Count で観測し、「サンプリングされたリクエストに正規トラフィックが含まれていない」ことを確認したら、`count {}` を本来のアクションに戻すだけです。

```hcl
  # RateLimitPerIP の action を Count から Block に
  rule {
    name     = "RateLimitPerIP"
    priority = 5
    action {
      block {} # 観測完了 → 強制へ
    }
    # statement / visibility_config は同じ
  }
```

これが **Count(観測) → Block(強制)** の安全なロールアウトです。差分は1行——だがその1行の前に、観測という「証拠」を必ず置きます（検証ファースト）。

---

## 3. Google Cloud Armor：security policy と preconfigured WAF rules

### 3.1 security policy とは何か

Cloud Armor の **security policy（セキュリティポリシー）** は、ロードバランサ配下のアプリを DDoS や Web 攻撃から守る設定単位です。優先度付きの**ルール**（match 条件 ＋ action）を持ちます。

- **backend security policy**：バックエンドサービスに紐づくポリシー（OWASP ルール・レート制限・Adaptive Protection はここ）。
- **edge security policy**：CDN キャッシュ手前のエッジでフィルタするポリシー。
- 適用先は**外部 Application Load Balancer**（classic を含む）。

ルールの **action** は `allow` / `deny(403|404|502)` / `throttle` / `rate_based_ban` / `redirect`。そして最重要——**`preview` フラグ**です。

> **`preview` ＝ Cloud Armor 版の Count。** ルールを `--preview` で作ると、**マッチはログに記録されるが実際にはブロックされません**。AWS の Count と同じ思想で、**本番に強制する前に「何がマッチするか」を観測**できます。

### 3.2 preconfigured WAF rules（OWASP CRS）

Cloud Armor は OWASP ModSecurity Core Rule Set を**事前定義ルール**として提供します。利用可能な CRS バージョンは **4.22 / 3.3 / 3.0**（3.0 は非推奨）。放送局の案件では **CRS 3.3** を採用しました。ルールは `evaluatePreconfiguredWaf()` 式で参照します（CRS 3.3 系の代表例）。

| 事前定義ルール | 防御対象 |
| --- | --- |
| `sqli-v33-stable` | SQL インジェクション |
| `xss-v33-stable` | クロスサイトスクリプティング |
| `lfi-v33-stable` | ローカルファイルインクルージョン |
| `rfi-v33-stable` | リモートファイルインクルージョン |
| `rce-v33-stable` | リモートコード実行 |
| `scannerdetection-v33-stable` | スキャナ検知 |
| `protocolattack-v33-stable` | プロトコル攻撃 |
| `sessionfixation-v33-stable` | セッションフィクセーション |

**感度（sensitivity）レベルは 0〜4**。公式いわく「デフォルトでは Cloud Armor は**感度レベル 4**で動作し、有効化後はルールセット内の全シグネチャを評価する」。感度を上げるほど検知は広がりますが、**誤検知も増える**——だからこそ preview が要ります。

### 3.3 gcloud / Terraform：preview で出してから enforce する

#### preview（観測）で OWASP ルールを入れる

```bash
# セキュリティポリシーを作成
gcloud compute security-policies create app-armor-policy \
  --description "Defense-in-depth WAF (preview-first rollout)"

# OWASP SQLi ルールを *preview* で追加（マッチはログるが弾かない）
gcloud compute security-policies rules create 1000 \
  --security-policy app-armor-policy \
  --description "OWASP SQLi (CRS 3.3) - PREVIEW" \
  --expression "evaluatePreconfiguredWaf('sqli-v33-stable', {'sensitivity': 1})" \
  --action deny-403 \
  --preview   # ★ ここが肝。preview = 観測のみ、ブロックしない

# XSS も同様に preview で
gcloud compute security-policies rules create 1010 \
  --security-policy app-armor-policy \
  --expression "evaluatePreconfiguredWaf('xss-v33-stable', {'sensitivity': 1})" \
  --action deny-403 \
  --preview
```

`{'sensitivity': 1}` に注目してください。**感度は最初から 4（全シグネチャ）にせず、低め（1）から始めて preview で誤検知を観測し、必要なら上げる**のが安全側です。Cloud Logging で `previewSecurityPolicyName` がマッチしたリクエストを精査し、正規トラフィックが含まれないと確認できたら preview を外します。

```bash
# 観測完了 → preview を解除して enforce（強制）に切り替え
gcloud compute security-policies rules update 1000 \
  --security-policy app-armor-policy \
  --no-preview
```

#### Terraform でも同じ思想（preview = true → false）

```hcl
resource "google_compute_security_policy" "app" {
  name        = "app-armor-policy"
  description = "Defense-in-depth WAF (preview-first)"

  # OWASP SQLi（CRS 3.3）— まずは preview で観測
  rule {
    action   = "deny(403)"
    priority = 1000
    preview  = true # ★ 観測モード。enforce 前に誤検知を洗い出す
    match {
      expr {
        expression = "evaluatePreconfiguredWaf('sqli-v33-stable', {'sensitivity': 1})"
      }
    }
    description = "OWASP CRS 3.3 SQLi (preview)"
  }

  # デフォルトルール：マッチしないものは許可（優先度は最大値固定）
  rule {
    action   = "allow"
    priority = 2147483647
    match {
      versioned_expr = "SRC_IPS_V1"
      config {
        src_ip_ranges = ["*"]
      }
    }
    description = "default allow"
  }
}
```

### 3.4 レート制限（throttle / rate_based_ban）

Cloud Armor のレート制限は2アクションです。

- **throttle**：閾値まで通し、超過分を `exceed_action`（`deny(429)` 等）で弾く。
- **rate_based_ban**：閾値を超えたクライアントを**一定時間 ban** する。

```hcl
  # レート制限ルール（IPごと・1分100リクエスト超で10分 ban）
  rule {
    action   = "rate_based_ban"
    priority = 900
    preview  = true # ★ 閾値が妥当か観測してから enforce
    match {
      versioned_expr = "SRC_IPS_V1"
      config { src_ip_ranges = ["*"] }
    }
    rate_limit_options {
      enforce_on_key = "IP" # ALL / IP / HTTP_HEADER / HTTP_COOKIE / XFF_IP / ...
      conform_action = "allow"
      exceed_action  = "deny(429)"
      rate_limit_threshold {
        count        = 100
        interval_sec = 60 # 10/30/60/120/.../3600 から
      }
      ban_duration_sec = 600 # 超過クライアントを 600 秒 ban
    }
    description = "per-IP rate limit (preview)"
  }
```

ここでも**閾値は観測で決める**。`enforce_on_key` を `IP` ではなく `XFF_IP`（`X-Forwarded-For` 先頭IP）にすべきか、`HTTP_COOKIE`（セッション単位）にすべきかは、**自分のトラフィックの実態を Cloud Logging で見てから**判断します。NAT 配下の正規ユーザーを巻き込まないために。

### 3.5 Adaptive Protection（L7 DDoS の適応型防御）

**Adaptive Protection** は、機械学習でアプリを **L7 DDoS** から守る Cloud Armor の機能です。**セキュリティポリシー単位で有効化**でき、平常時トラフィックのベースラインを学習（公式いわく**最低1時間**の学習でベースライン確立）し、異常を検知すると以下を含むアラートを生成します。

- **confidence score**（0〜1 の確信度）
- **attack signature**（攻撃トラフィックの特徴記述）
- **suggested rule**（そのまま展開できる Cloud Armor WAF ルール案）
- **alert ID**

```bash
# セキュリティポリシーに Adaptive Protection（L7 DDoS 防御）を有効化
gcloud compute security-policies update app-armor-policy \
  --enable-layer7-ddos-defense
```

重要なのは、**Adaptive Protection が出す「suggested rule」も、いきなり enforce せず preview で当ててから**にすること。ML の提案であっても、人間の確認ゲートを通します。放送局の案件では、この適応型防御 ＋ 固定のレート制限 ＋ OWASP CRS の三段で、入口の異常を多層に受け止めました。

> **AWS 側の対応物**：L3/L4 の DDoS は **Shield Standard** が無償で常時保護。L7 の高度な自動緩和や専門チーム（SRT）支援が要るなら **Shield Advanced**（有償）。Cloud Armor の Adaptive Protection に最も近いのは Shield Advanced の自動アプリケーション層緩和です。「常時無償の基礎保護＋必要なら有償の高度保護」という構造は両クラウド共通です。

---

## 4. 安全なロールアウト：Count/preview を「運用」に落とす

技術要素が揃ったので、**誤検知を出さない運用**を段階表にまとめます。これが放送局案件で実際に踏んだ手順の一般化です。

| 段階 | AWS WAF | Cloud Armor | この段階のゴール |
| --- | --- | --- | --- |
| ① staging 全面有効化 | Web ACL を stg の ALB に紐づけ、全ルール Block | security policy を stg LB に、全ルール enforce | **設定ミス・構文エラー・明らかな誤爆を本番前に潰す** |
| ② 本番 Count/preview | 全マネージド/カスタムルールを `count {}` / `override_action { count {} }` | 全ルール `preview = true` | **本番トラフィックで誰が引っかかるかを観測** |
| ③ 個別チューニング | 誤爆ルールを `rule_action_override` で Count 据置 or 除外 | 誤検知シグネチャを `opt_out_rule_ids` で除外、感度調整 | **正規トラフィックの巻き込みをゼロにする** |
| ④ 段階的 enforce | 安全確認できたルールから `count {}` を外す | `--no-preview` / `preview = false` へ | **検証済みのルールだけ強制に昇格** |
| ⑤ 継続観測 | CloudWatch メトリクス＋サンプリング＋WAFログ常時監視 | Cloud Logging で deny/throttle を常時監視 | **新しい誤爆・新しい攻撃の早期発見** |

**①の「staging 全面有効化」が効きます。** 放送局の案件では、**stg で WAF を全面有効化し、本番に出す前に誤検知や設定ミスを洗い出す運用**にしました。stg なら正規ユーザーを巻き込んでも実害が小さく、攻撃パターンに似た正規リクエスト（例：管理者の正当な操作、リッチな JSON ボディ）を事前に発見できます。本番には「stg で検証済み ＋ 本番 Count で再確認済み」のルールだけが昇格します。

> **公式の警告（AWS）**：「本番トラフィックに展開する前に、**staging またはテスト環境でテスト・チューニング**し、その後、本番トラフィックに対して **count モード**でテストしてから有効化せよ」。——公式が二段階（staging → 本番 count）を明示しています。私の運用はこれに忠実です。

### 誤検知への対処：除外とスコープダウン

観測で誤爆が見つかったときの打ち手は2つです。

1. **除外（exclusion / opt_out）**：そのルールだけ無効化。AWS なら `rule_action_override` で当該ルールを Count 据置、Cloud Armor なら `evaluatePreconfiguredWaf(..., {'opt_out_rule_ids': ['...']})` で特定シグネチャを除外。**ルールグループ全体を切らず、誤爆した1ルールだけを外す**のが鉄則（最小の無効化）。
2. **スコープダウン（scope-down）**：ルールの**適用範囲を狭める**。「`/api/upload` だけ `SizeRestrictions_BODY` を免除」のように、誤爆する経路だけ対象外にする。AWS の `scope_down_statement`、Cloud Armor の match 式（CEL）で実現します。

**「誤爆したから WAF を切る」は最悪手**です。1ルール・1経路の最小単位で外し、防御の穴を最小化します。

---

## 5. 本番運用：バージョン管理・ログ・コスト・過信しない

### 5.1 マネージドルールのバージョン管理

AWS のマネージドルールグループは**バージョン管理**されています（`DescribeManagedRuleGroup` で取得可、changelog あり）。**バージョンを固定せず最新追従にすると、AWS 側の更新で突然挙動が変わり誤爆が増える**ことがあります。本番では**バージョンを明示固定**し、changelog を見て計画的に上げ、上げる際は再び Count → Block を踏みます。Cloud Armor の CRS も同様に、`v33` / `v422` のように**バージョンを式に明示**します（暗黙の最新追従にしない）。これは ETC（Easy To Change）の原則——変更を予測可能な単位に閉じ込めるためです。

### 5.2 ログと可観測性：WAF ログを分析につなぐ

WAF は「弾いた／数えた」を**ログに残してこそ運用できます**。

- **AWS WAF ログ**：CloudWatch Logs / S3 / Kinesis Data Firehose に出力。サンプリングされたリクエスト（`sampled_requests_enabled = true`）と CloudWatch メトリクス（`cloudwatch_metrics_enabled = true`）は**常に有効化**。ラベル（`awswaf:managed:aws:core-rule-set:*`）でどのルールが効いたか追跡。
- **Cloud Armor**：Cloud Logging に出力。`enforcedSecurityPolicy` と **`previewSecurityPolicyName`** を見れば「enforce で弾いた」「preview ならマッチしていた」が区別できる。

**観測の質がロールアウトの安全性を決めます。** サンプリングを切ったまま Count で出しても「何がマッチしたか」が見えず、Block 移行の判断ができません。

### 5.3 コスト

WAF は無料ではありません。AWS WAF は **Web ACL 数 ＋ ルール数 ＋ 処理リクエスト数**で課金され、マネージドルール（Bot Control / ATP / ACFP 等の高度なもの）には追加料金がかかります。Cloud Armor も Enterprise 階層で Adaptive Protection のフルアラート等が有償です。

- **WCU（Web ACL Capacity Unit）を意識する**：AWS の Web ACL には WCU の上限があります。CRS だけで 700 WCU を消費するので、**マネージドルールを盛りすぎると上限に当たる**。必要なルールだけ入れる（YAGNI）。
- **rate-based / 高度なボット対策は効果と費用を天秤に**：Bot Control や ATP は強力ですが追加課金。**まず基礎（CRS ＋ Known bad inputs ＋ rate-based）で守れる範囲を固め、足りない分だけ高度機能を足す**のがコスト効率の良い順序です。

### 5.4 WAF を過信しない——多層防御の再確認

最後に、最初の一行に戻ります。**WAF は一層です。** WAF をすり抜ける攻撃は必ず存在します（ロジック起因の認可バグ、ゼロデイ、正規に見える悪用）。だから内側の層を省いてはいけません。

- **認証・MFA**：Identity Platform / Cognito の MFA、reCAPTCHA Enterprise でボットと認証情報詰め込みを抑える。
- **認可**：アプリ／DB（RLS 等）で「誰が何にアクセスできるか」を強制。WAF はこれを代替しない。
- **入力検証**：境界で Zod 等によりスキーマ検証。WAF の OWASP ルールは既知パターン用で、アプリ固有の妥当性検証ではない。
- **最小権限**：サービスアカウント / IAM を最小権限に。万一侵入されても被害を局所化。
- **Secrets 管理**：Secret Manager / 環境変数。WAF とは無関係に必須。
- **データ層**：TLS 必須・プライベートIP・IAM 認証（放送局案件の Cloud SQL 構成）。

放送局のプラットフォームで Cloud Armor が担ったのは、この**最も外側の一層**です。中身（認証・認可・検証・最小権限・暗号化）が揃っているからこそ、WAF が意味を持ちます。順序が逆——「WAF を入れたから中身は適当でいい」——では、城門だけ立派で城壁のない城になります。

---

## 6. まとめ：WAF 多層防御チートシート

迷ったときの早見表です。

- **どの WAF を使うか**：AWS の ALB/CloudFront 配下 → **AWS WAF**。GCP の外部 Application Load Balancer 配下 → **Cloud Armor**。マルチクラウドは両方を同じ思想で。
- **OWASP 対策**：AWS は **`AWSManagedRulesCommonRuleSet`（CRS, WCU 700）＋ `AWSManagedRulesKnownBadInputsRuleSet`**。Cloud Armor は **`evaluatePreconfiguredWaf('sqli-v33-stable' ...)` 等の preconfigured WAF rules**。自前で正規表現を書かない。
- **レート制限**：AWS は **rate-based rule**（window 60/120/300/600、デフォルト300、最小 limit 10、`IP`/`FORWARDED_IP` 集約）。Cloud Armor は **throttle / rate_based_ban**（`enforce_on_key`、`ban_duration_sec`）。閾値は**観測してから**。
- **L7 DDoS**：AWS は Shield Standard（無償）/ Advanced（有償）。Cloud Armor は **Adaptive Protection**（ML・per-policy・最低1時間学習・confidence score / suggested rule）。提案ルールも preview から。
- **安全なロールアウト（最重要）**：**① staging 全面有効化 → ② 本番 Count/preview → ③ 個別チューニング（除外・スコープダウン）→ ④ 段階 enforce → ⑤ 継続観測**。新ルールは必ず **Count（AWS）/ preview（Cloud Armor）から**。
- **運用**：マネージドルールは**バージョン固定**、サンプリング＆メトリクスは常時 ON、誤爆は**1ルール・1経路の最小単位で除外**、WCU とコストを意識。
- **多層防御**：WAF は**一層**。認証・MFA・認可・入力検証・最小権限・Secrets・データ層を省かない。

WAF は「入れれば安全」な装置ではなく、**「正規トラフィックを巻き込まずに攻撃だけを弾く」設定とロールアウトの設計**です。最大のリスクは攻撃ではなく自分の誤検知——だからこそ Count/preview を運用の中心に据えます。

私は国内大手放送事業者向けの社内AIプラットフォームを **100% Terraform（約71モジュール）** で構築し、入口に **Cloud Armor（OWASP CRS 3.3 ＋ 適応型DDoS防御 ＋ レート制限）** を据えました。そして **stg で WAF を全面有効化し、本番に出す前に誤検知と設定ミスを洗い出す運用**で、正規トラフィックを巻き込まずに防御層を昇格させました。WAF はそこで「玄関のフィルタ」であり、内側には IAM 認証・TLS 必須・プライベートIP の Cloud SQL、Secret Manager、最小権限のサービスアカウント、SMS MFA ＋ reCAPTCHA Enterprise を重ねた——**多層防御の一層**として機能させています。

**「自社のアプリに WAF をどう設計し、どう誤検知を出さずに本番投入するか」——その設計から Terraform 実装、Count/preview ロールアウト、運用設計まで、一人 ×生成AI（Claude Code）で速く・安全に伴走できます。** 要件の整理段階からでも、お気軽にご相談ください。

---

### 参考（公式ドキュメント）

- [What are AWS WAF, AWS Shield, and AWS Firewall Manager?](https://docs.aws.amazon.com/waf/latest/developerguide/what-is-aws-waf.html) — Web ACL・ルールアクション（Allow/Block/Count/CAPTCHA）・保護対象リソース
- [AWS Managed Rules rule groups list](https://docs.aws.amazon.com/waf/latest/developerguide/aws-managed-rule-groups-list.html) — マネージドルールグループ一覧
- [Baseline rule groups（Core rule set 他）](https://docs.aws.amazon.com/waf/latest/developerguide/aws-managed-rule-groups-baseline.html) — `AWSManagedRulesCommonRuleSet`（WCU 700）/ `AWSManagedRulesKnownBadInputsRuleSet` の中身
- [Using rate-based rule statements in AWS WAF](https://docs.aws.amazon.com/waf/latest/developerguide/waf-rule-statement-type-rate-based.html) — `RateBasedStatement`・評価ウィンドウ・集約キー
- [Testing and tuning your AWS WAF protections](https://docs.aws.amazon.com/waf/latest/developerguide/web-acl-testing.html) — staging → 本番 count モードの安全なロールアウト
- [Google Cloud Armor overview](https://cloud.google.com/armor/docs/cloud-armor-overview) — security policy・preview・Adaptive Protection
- [Cloud Armor preconfigured WAF rules（OWASP CRS）](https://cloud.google.com/armor/docs/waf-rules) — `evaluatePreconfiguredWaf('sqli-v33-stable' ...)`・感度レベル 0〜4
- [Cloud Armor rate limiting overview](https://cloud.google.com/armor/docs/rate-limiting-overview) — throttle / rate_based_ban・`enforce_on_key`・`ban_duration_sec`
- [Cloud Armor Adaptive Protection overview](https://cloud.google.com/armor/docs/adaptive-protection-overview) — ML による L7 DDoS 検知・confidence score・suggested rule
