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 Set | SQLi・XSS・RFI対策 | 中程度 | ○ | Block(初日から) |
| Known Bad Inputs | 既知の脅威シグネチャ | 低 | ◎ | Block |
| Bot Control | ボット検出 | 高 | △ | Count(1週間観測) |
| SQL Injection Protection | SQLi特化 | 低 | ◎ | Block |
| PHP Application | PHP脆弱性対策 | 低 | ◎ | Block |
| WordPress Protection | WordPress標的対策 | 中程度 | ○ | 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ヶ月ごとのレビュー日程を決める予定。本番サービスを守るためには、こういう地味な継続が結局一番効くんだよね。