メインコンテンツへスキップ
友田 陽大
インフラ・IaC・CI/CD
セキュリティ
AWS
GCP
WAF
アーキテクチャ設計

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

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

公開日
読了時間
25分
著者
友田 陽大
シェア

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

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

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

この記事は、AWS WAFGoogle Cloud Armor で多層防御を本番品質で設計・運用するための実装ガイドです。題材として、私が国内大手放送事業者向けに構築した社内AIプラットフォームでの設計判断——入口に 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 WAFGoogle 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 / Challengeallow / deny / throttle / rate_based_ban / redirect
レート制限rate-based ruleRateBasedStatementrateLimitOptions(throttle / rate_based_ban)
観測モード(誤検知洗い出し)Count アクション / rule action overridepreview フラグ
DDoS(L7)Shield Standard(標準)/ Shield Advanced(有償)Adaptive Protection(ML による L7 検知)
ログWAF ログ → CloudWatch / S3 / FirehoseCloud 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 はデフォルトアクション(マッチしなかったリクエストをどうするか=allowblock)を持ち、その中にルールを優先度順に並べます。公衆向けサイトなら「指定したもの以外はすべて許可(Allow all except…)」=デフォルト Allow + 攻撃を Block、が基本形です。

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

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

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

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

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

ルールグループVendorName / NameWCU目的
Core rule set (CRS)AWS / AWSManagedRulesCommonRuleSet700OWASP Top 10 を含む広範な脆弱性対策(全 WAF ユースケース推奨
Known bad inputsAWS / AWSManagedRulesKnownBadInputsRuleSet200Log4j(CVE-2021-44228 ほか)等、既知の不正パターンを遮断
Admin protectionAWS / AWSManagedRulesAdminProtectionRuleSet100露出した管理画面パスへの外部アクセスを遮断

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

  • NoUserAgent_HEADERUser-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 で観測する

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_actionrule_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_IPX-Forwarded-For 等の先頭IP)/ CONSTANT(全リクエストを一括=Count all)/ CUSTOM_KEYS
  • ActionAllow 以外の任意アクション(= Block / Count / Challenge)。
  # 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 {} を本来のアクションに戻すだけです。

  # 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 を含む)。

ルールの actionallow / 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-stableSQL インジェクション
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 ルールを入れる

# セキュリティポリシーを作成
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 を外します。

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

Terraform でも同じ思想(preview = true → false)

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_actiondeny(429) 等)で弾く。
  • rate_based_ban:閾値を超えたクライアントを一定時間 ban する。
  # レート制限ルール(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_keyIP ではなく XFF_IPX-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
# セキュリティポリシーに 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 WAFCloud Armorこの段階のゴール
① staging 全面有効化Web ACL を stg の ALB に紐づけ、全ルール Blocksecurity 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 に出力。enforcedSecurityPolicypreviewSecurityPolicyName を見れば「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_banenforce_on_keyban_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)で速く・安全に伴走できます。 要件の整理段階からでも、お気軽にご相談ください。


参考(公式ドキュメント)

友田

友田 陽大

経済産業大臣賞 受賞プロダクト開発者。TypeScript + Python + AWS で、SaaS・業界DX・ 実用レベルの生成AI(RAG)を、要件定義からインフラ・運用まで一人で完遂します。

この記事で解説した技術の適用事例

国内大手放送事業者の社内AIプラットフォーム(Cloud Armor OWASP CRS + 適応型DDoS防御 + レート制限で多層防御)

ケーススタディを見る