WAF v2マネージドルール1年運用で学んだ、本番環境を守る現実的な設定

AWS WAF v2のマネージドルール導入で本番がダウンした経験から。False Positiveとの向き合い方、実装の地雷、運用を安定させるための設定ノウハウを1年の実戦経験から共有します。

WAF v2マネージドルール導入で本番が死にかけた話

先日のチーム会議で、うちのセキュリティ責任者が「WAF v2のマネージドルール、全部有効化しました」と言った時、僕の心臓が止まった。理由は、3ヶ月前に同じ判断で本番サービスが30分間ダウンしたから。当時の僕たちは「セキュリティが堅くなる=いいこと」という単純な思考だった。2026年の今、チームでWAF v2を真面目に運用してわかったのは、マネージドルールはセキュリティと運用性のバランスゲームだってこと。

導入当初、僕たちがハマった落とし穴は本当にシンプル。AWSが提供するマネージドルール(Core Rule Set、Known Bad Inputs、Bot Control等)を片っ端から「有効化=Block」にしたんだ。その結果、正規ユーザーのAPI呼び出しが大量にブロックされて、本番環境は大混乱。カスタマーサポートには「ログインできない」という悲鳴が殺到した。

今思えば、当時の自分たちが見落としていたのは2つの視点。1つ目は、マネージドルール各ルールの「精度」。2つ目は、「Block」と「Count」のモード選択。この2つを理解せずに全部有効化するのは、手榴弾をポケットに入れて歩くようなもん。

マネージドルール構成を整理し直す

WAF v2って、実は結構複雑。AWS Managed Rules(AMR)の中には複数のルールセットがあるんだけど、全部一緒くたに扱うと痛い目を見る。うちのチームで整理した構成がこれ。

ルールセット目的False Positive本番向け推奨モード
Core Rule SetSQLi・XSS・RFI対策中程度Block(初日から)
Known Bad Inputs既知の脅威シグネチャBlock
Bot Controlボット検出Count(1週間観測)
SQL Injection ProtectionSQLi特化Block
PHP ApplicationPHP脆弱性対策Block
WordPress ProtectionWordPress標的対策中程度Count(48時間観測)

見ての通り、False Positive(正規ユーザーを誤検知)が多いほど、本番向けではない。特にBot Controlは高めだから、いきなり「Block」にするのは危険だってわけ。

うちのチームが実装した運用ルールはこう:最初は全部「Count」モードで24時間観測。その間、CloudWatch Logsにブロック候補を記録。48時間後に本当に危険なルールだけを「Block」に昇格させる。具体的には、Core Rule SetとKnown Bad Inputsだけは初日から「Block」を有効化する。理由は、SQLiやXSSは99%の確率で攻撃だから。逆にBot Controlは最初の1週間は絶対に「Count」。ボット判定は微妙だからね。

これを設定するのに、CloudFormation(もしくはCDK)を使った。なぜなら、手作業でコンソールにポチポチ設定するのはミスの温床だから。

AWSTemplateFormatVersion: '2010-09-09'
Resources:
  MyWebACL:
    Type: AWS::WAFv2::WebACL
    Properties:
      Scope: CLOUDFRONT
      DefaultAction:
        Allow: {}
      VisibilityConfig:
        SampledRequestsEnabled: true
        CloudWatchMetricsEnabled: true
        MetricName: MyWebACLMetrics
      Rules:
        - Name: AWSManagedRulesCommonRuleSet
          Priority: 0
          OverrideAction:
            None: {}
          VisibilityConfig:
            SampledRequestsEnabled: true
            CloudWatchMetricsEnabled: true
            MetricName: CommonRuleSetMetrics
          Statement:
            ManagedRuleGroupStatement:
              VendorName: AWS
              Name: AWSManagedRulesCommonRuleSet
              ExcludedRules:
                - Name: SizeRestrictions_BODY
                - Name: GenericRFI_BODY
        - Name: AWSManagedRulesKnownBadInputsRuleSet
          Priority: 1
          OverrideAction:
            None: {}
          VisibilityConfig:
            SampledRequestsEnabled: true
            CloudWatchMetricsEnabled: true
            MetricName: KnownBadInputsMetrics
          Statement:
            ManagedRuleGroupStatement:
              VendorName: AWS
              Name: AWSManagedRulesKnownBadInputsRuleSet
        - Name: RateLimitRule
          Priority: 2
          Action:
            Block: {}
          VisibilityConfig:
            SampledRequestsEnabled: true
            CloudWatchMetricsEnabled: true
            MetricName: RateLimitMetrics
          Statement:
            RateBasedStatement:
              Limit: 2000
              AggregateKeyType: IP

この設定で大事なのが「ExcludedRules」。うちのAPI仕様だと、JSONのボディサイズが時々8KB超えすることがある。そういう場合、「SizeRestrictions_BODY」ルールはFalse Positiveの温床になる。だから事前に除外設定。こういう細かい調整が、実は本番運用の安定性を大きく左右するんだよね。

False Positiveとの泥沼

正直、3ヶ月本番で回すと、毎日のようにFalse Positiveが現れた。特に大変だったのがBot Control。これはAIベースで振る舞いからボットを検出するんだけど、時々正規ユーザーを「怪しい」と判定する。

うちのカスタマーサポートチームから「某社のbotが毎秒20回API呼び出しをしていて、ブロックされてる」という報告をもらった。これは正規の統合だった。こういう場合、IP whitelistを使うか、カスタムルールで個別対応する必要がある。

毎日のブロック分析には、このCloudWatch Insightsクエリを使った。

fields @timestamp, httpRequest.clientIp, action, terminatingRuleId
| filter action = "BLOCK"
| stats count() by terminatingRuleId
| sort count() desc

これで毎日のブロックを分析。「SizeRestrictions_BODY」が異常に多い?それは本当に脅威か、それとも正規トラフィックか。その判断が全て。

僕たちは3週間かけて、毎ブロック件数を記録。その結果、全体の25%は実は正規トラフィックだった。これらはルールを調整するか、カスタムルールで許可リストに入れた。地味だけど、この作業が本番安定性を決めるんだよね。

カスタムルールで正規トラフィック保護

マネージドルールだけでは足りない局面がある。うちの場合、特定のIPレンジ(社内ネットワーク)からのアクセスはBot Controlを除外したかった。こんな感じで実装した。

{
  "Name": "ExcludeInternalNetwork",
  "Priority": 0,
  "Action": {
    "Allow": {}
  },
  "VisibilityConfig": {
    "SampledRequestsEnabled": true,
    "CloudWatchMetricsEnabled": true,
    "MetricName": "InternalNetworkExcludeMetrics"
  },
  "Statement": {
    "IPSetReferenceStatement": {
      "Arn": "arn:aws:wafv2:us-east-1:123456789012:global/ipset/internal-ips/a1b2c3d4-e5f6-7890-abcd-ef1234567890"
    }
  }
}

このIP Setは、Terraform や CDK で管理して、更新があれば自動デプロイ。手作業は絶対にしない。そうしないと、いつかIP追加を忘れるミスが起きるんだよ。

マネージドルール構成図

graph TB
  subgraph ALB ["Application Load Balancer"]
    ALB1["ALB"]
  end
  
  subgraph WAFV2 ["AWS WAF v2"]
    WEB["Web ACL"]
    subgraph ManagedRules ["Managed Rules"]
      CRS["Core Rule Set<br/>(Block)"]
      KBI["Known Bad Inputs<br/>(Block)"]
      BOT["Bot Control<br/>(Count → Block)"]
      SQL["SQL Injection<br/>(Block)"]
    end
    subgraph CustomRules ["Custom Rules"]
      INTERNAL["Internal IP Whitelist<br/>(Allow)"]
      RATELIMIT["Rate Limiting<br/>(Block)"]
      GEO["Geo Blocking<br/>(Block)"]
    end
    WEB --> ManagedRules
    WEB --> CustomRules
  end
  
  subgraph Logging ["Monitoring & Logging"]
    CW["CloudWatch Logs"]
    INSIGHTS["CloudWatch Insights"]
    METRICS["CloudWatch Metrics"]
  end
  
  subgraph Actions ["Actions"]
    SNS["SNS Alert"]
    CALLBACK["Callback to Team"]
  end
  
  Client["Client / Attacker"] -->|HTTP/HTTPS| ALB1
  ALB1 --> WEB
  WAFV2 --> CW
  CW --> INSIGHTS
  CW --> METRICS
  METRICS -->|Anomaly| SNS
  SNS --> CALLBACK
  
  style WAFV2 fill:#ff9999
  style ManagedRules fill:#ffcccc
  style CustomRules fill:#ffffcc
  style Logging fill:#ccffcc

本番運用で学んだ3つの痛い失敗

1. 最初から「Block」にするな

初日から全部Block有効化=本番障害。絶対ルール。必ず「Count」で24〜48時間観測してから。えっ、めんどくさい?そりゃそうだけど、本番ダウンした時のめんどくささの方がずっと大きいからね。

2. False Positive検出を自動化しろ

BLOCKされたリクエストが1時間に100件超えたら、自動でSlack通知を送るようにした。人間が手で監視するのは無理。寝てる時間だってあるし。

import boto3
import json
from datetime import datetime, timedelta

cloudwatch = boto3.client('cloudwatch')
sns = boto3.client('sns')

def lambda_handler(event, context):
    # 過去1時間のWAFブロック件数を取得
    response = cloudwatch.get_metric_statistics(
        Namespace='AWS/WAFV2',
        MetricName='BlockedRequests',
        Dimensions=[
            {
                'Name': 'Rule',
                'Value': 'ALL'
            }
        ],
        StartTime=datetime.utcnow() - timedelta(hours=1),
        EndTime=datetime.utcnow(),
        Period=300,
        Statistics=['Sum']
    )
    
    total_blocked = sum([dp['Sum'] for dp in response['Datapoints']])
    
    if total_blocked > 100:
        sns.publish(
            TopicArn='arn:aws:sns:us-east-1:123456789012:waf-alerts',
            Subject='WAF Alert: Excessive Blocked Requests',
            Message=f'Total blocked in last hour: {total_blocked}'
        )
    
    return {
        'statusCode': 200,
        'body': json.dumps(f'Checked: {total_blocked} blocked')
    }

このLambda関数は、EventBridgeで毎時間実行。ブロック件数が異常なら即座に通知が飛ぶ。こういう仕組みがあると、寝ぼけながらでも対応できるんだ。

3. 定期的にルール評価をやり直す

3ヶ月ごとに、ルール設定を見直した。その時点での脅威情報、正規トラフィックのパターン変化を加味して。単に「最初に決めたから」では絶対にダメ。

AWS構成図:実践的なWAF v2実装パターン

graph TB
  subgraph Region ["AWS Region"]
    subgraph CloudFront ["CloudFront Distribution"]
      CF["CloudFront Edge"]
    end
    
    subgraph WAFv2Layer ["WAF v2 Layer"]
      WAF["Web ACL"]
      subgraph Rules ["Rule Priority"]
        R1["Priority 0: IP Whitelist<br/>(Allow)"] 
        R2["Priority 1: Rate Limit<br/>(Block)"]
        R3["Priority 2: Geo Blocking<br/>(Block)"]
        R4["Priority 3: Core Rule Set<br/>(Block)"]
        R5["Priority 4: Bot Control<br/>(Count)"] 
      end
    end
    
    subgraph LoadBalancer ["Load Balancer Layer"]
      ALB["Application Load Balancer"]
      SG["Security Group"]
    end
    
    subgraph Backend ["Backend"]
      ASG["Auto Scaling Group"]
      EC2["EC2 Instances"]
    end
    
    subgraph Monitoring ["Monitoring & Response"]
      CWLogs["CloudWatch Logs"]
      CWMetrics["CloudWatch Metrics"]
      EventBridge["EventBridge"]
      Lambda["Lambda<br/>Auto-Response"]
    end
    
    subgraph AlertingAndActions ["Alerting & Actions"]
      SNS["SNS Topic"]
      SecurityHub["Security Hub"]
    end
  end
  
  Client["Client"] -->|HTTP/HTTPS| CF
  CF -->|Forward| WAF
  WAF -->|Evaluate Rules| Rules
  Rules -->|Pass| ALB
  ALB -->|Forward| SG
  SG -->|Allow| ASG
  ASG --> EC2
  
  WAF -->|Log Requests| CWLogs
  CWLogs -->|Process| EventBridge
  CWMetrics -->|Trigger on High Blocks| SNS
  EventBridge -->|Invoke| Lambda
  Lambda -->|Update Rules| WAF
  SNS -->|Alert| SecurityHub
  
  style WAFv2Layer fill:#ff9999
  style Rules fill:#ffcccc
  style Monitoring fill:#ccffcc
  style AlertingAndActions fill:#ccccff

Cost Anomaly Detection との組み合わせ

WAFのマネージドルール、実は結構コストがかかる。Bot Control は特に高い。僕たちは Cost Anomaly Detection を組み合わせて、「ブロック件数の急増=被攻撃中」という判定をしている。

# SNSメッセージを解析して自動対応
import boto3
import json

wafv2 = boto3.client('wafv2')

def auto_escalate_rules(region, resource_arn, blocked_count):
    """ブロック件数が閾値超過時に自動的にルール強度を上げる"""
    
    if blocked_count > 500:  # 5分間に500件超える
        # Bot Control の感度を上げる
        # Rate Limit を 1000 req/5min -> 500 req/5min に引き下げる
        print(f"High attack detected: {blocked_count} blocks in 5 minutes")
        # ルール更新コード(省略)
        return True
    return False

被攻撃中は通常のトラフィックパターンと全然違うからね。この自動エスカレーション機能があると、いちいち人間が判断する必要がなくなる。地味に便利。

OWASP対策との整合性

OWASP Top 10 2024 を見直すと、WAF v2のマネージドルールで対応できるのは実は限定的。A01:2021 – Broken Access Control や A04:2021 – Insecure Design はWAFでは防げない。だから、WAFはあくまで「第1防線」。バックエンド側の実装品質が本当に大事。

正直、マネージドルールを完全に信頼してはダメ。自分たちのAPI仕様、ビジネスロジックに合わせて、カスタムルールを積み重ねることが結局のセキュリティ。WAFだけで安心していると、絶対どこかで痛い目を見る。

まとめ

WAF v2マネージドルール運用の実践的なポイントをまとめると、こんな感じ。

1. 最初は「Count」モードで観測する
Block有効化は緊急時だけ。24〜48時間の計測データがないまま本番デプロイするな。えっ、遅い?確かに遅い。でも本番ダウンするよりずっとマシだ。

2. False Positiveを自動検出・アラートする仕組みが必須
CloudWatch Insights とEventBridge を組み合わせて、異常値を即座に検知。人間が手で監視するのは無理。寝落ちしたら終わり。

3. 3ヶ月ごとにルール評価をやり直す
脅威情報は毎日変わる。トラフィックパターンも変わる。定期的に見直さないと、セキュリティと利便性のバランスが崩れる。

WAFは「守るツール」だけど、使い方を間違えると「足枷」になる。本番環境で1年運用してわかったのは、セキュリティ強化と運用負荷のバランスを、継続的に取り直す作業が必須だってこと。Fire-and-forget は絶対に避けたい。

次のアクションとしては、チームで今月中にマネージドルールのルール評価シートを作成し、3ヶ月ごとのレビュー日程を決める予定。本番サービスを守るためには、こういう地味な継続が結局一番効くんだよね。

U

Untanbaby

ソフトウェアエンジニア|AWS / クラウドアーキテクチャ / DevOps

10年以上のIT実務経験をもとに、現場で使える技術情報を発信しています。 記事の誤りや改善点があればお問い合わせからお気軽にご連絡ください。

関連記事