AWS WAF v2 + Shield Advanced を1年本番運用して見えた現実
突然トラフィックが30倍になった夜から始まったDDoS対策の記録。誤検知・コスト爆発・ルールチューニングの泥臭い実体験、同じ状況で悩んでいる人に届けたい。
AWS WAF v2 + Shield Advanced で DDoS 対策した1年間の実録|本番運用で見えた設計の現実
去年の夏、本番の API サーバーに対して突然トラフィックが 30 倍に跳ね上がって、SLO が崩壊しかけた夜のことを今でも鮮明に覚えている。結果としては CDN 側で一時的に IP ブロックして乗り切ったんだけど、「これはまずいな」ということで AWS WAF v2 + Shield Advanced の本格導入を決断した。それから約 1 年、正直かなりしんどいこともあったけど、今は安定運用できているので、同じ目に遭いそうな人向けにその実録を書いておく。
先に言っておくと、WAF って「入れたら終わり」というプロダクトじゃない。誤検知との闘い、ルールのチューニング、コストの爆発——全部セットで付いてくる。でもちゃんと設計すれば確実に守れる。その設計の話をしたい。
導入前に整理した脅威モデルと構成図
まず「なにから守りたいか」を整理しないと、WAF のルールがとっちらかる。うちのケースでは主な脅威として以下の 3 パターンを想定した。
- L7 DDoS(HTTP フラッド) — 正常なリクエストに見せかけた大量アクセス
- OWASP Top 10 系の攻撃 — SQLi・XSS・パストラバーサルなど(OWASP Top 10 2024 の対策については別記事で詳しく書いたので、こちらも参照してほしい)
- スクレイピング・クレデンシャルスタッフィング — ボットによる認証エンドポイントへの連打
これを踏まえて設計した構成がこれ。
graph TB
subgraph Internet["Internet"]
User["👤 正規ユーザー"]
Attacker["⚠️ 攻撃者"]
end
subgraph AWS_Edge["AWS Edge Network"]
Shield["Shield Advanced\n(L3/L4 DDoS 自動緩和)"]
CF["CloudFront\n(CDN + キャッシュ)"]
WAF["WAF v2\n(L7 ルール検査)"]
end
subgraph VPC["VPC (ap-northeast-1)"]
subgraph Public["Public Subnet"]
ALB["Application\nLoad Balancer"]
end
subgraph Private["Private Subnet"]
ECS["ECS Fargate\n(API Service)"]
RDS["Aurora\nPostgreSQL"]
end
end
subgraph Monitoring["Monitoring & Response"]
CW["CloudWatch\nMetrics/Alarms"]
SNS["SNS → PagerDuty"]
Kibana["OpenSearch\nダッシュボード"]
end
User --> Shield
Attacker --> Shield
Shield --> CF
CF --> WAF
WAF --> ALB
ALB --> ECS
ECS --> RDS
WAF --> CW
CW --> SNS
WAF --> Kibana
CloudFront → WAF → ALB という多段防御にしたのがポイント。Shield Advanced は CloudFront と ALB の両方に紐づけてある。WAF のログは Kinesis Firehose 経由で OpenSearch に流して、Kibana で可視化している。
ちなみに AWS Network Firewall を本番に導入した際の失敗談も別記事にまとめてある。L3/L4 層の防御を組み合わせる場合は参考にしてほしい。
WAF v2 ルール設計と誤検知との戦い
これが一番しんどかったパートだった。WAF v2 のルールは大きく分けると以下の 5 種類になる。
| ルール種別 | 用途 | コスト(月額概算) |
|---|---|---|
| AWS Managed Rules | 汎用的な脅威対策 | 無料(WCU 消費分のみ) |
| Rate-based Rules | IP/セッション単位レート制限 | WCU 消費分のみ |
| Bot Control | ボット識別・制御 | $10/百万リクエスト追加 |
| Fraud Control (ATP) | 認証エンドポイント保護 | $10/百万ログイン試行 |
| カスタムルール | 独自ロジック | WCU 消費分のみ |
最初は「全部 Block で OK でしょ」と思って入れたら、翌日から Slack が騒がしくなった。正規ユーザーからの「ログインできない」「API が 403 を返す」という報告が多発。AWS Managed Rules の AWSManagedRulesCommonRuleSet に含まれる SizeRestrictions_BODY が引っかかっていたのが原因で、うちのサービスは画像のメタデータを JSON に含めてリクエストしていたのでボディサイズが大きかった。
この痛い経験から得た教訓がある。
ルール導入の鉄則:最初は全部 Count モードで 2 週間様子を見る
// CDK での WAF ルール定義例(Count モード → Block 切り替えを意識した設計)
new wafv2.CfnWebACL(this, 'AppWAF', {
scope: 'CLOUDFRONT',
defaultAction: { allow: {} },
rules: [
{
name: 'AWSManagedRulesCommonRuleSet',
priority: 10,
overrideAction: { count: {} }, // ← まず Count から
statement: {
managedRuleGroupStatement: {
vendorName: 'AWS',
name: 'AWSManagedRulesCommonRuleSet',
excludedRules: [
// 誤検知が多かったルールを個別に除外
{ name: 'SizeRestrictions_BODY' },
{ name: 'CrossSiteScripting_BODY' }, // ← リッチテキスト入力で誤爆
],
},
},
visibilityConfig: {
sampledRequestsEnabled: true,
cloudWatchMetricsEnabled: true,
metricName: 'CommonRuleSetCount',
},
},
{
name: 'RateLimitByIP',
priority: 1,
action: { block: {} }, // レート制限はいきなり Block でいい
statement: {
rateBasedStatement: {
limit: 2000, // 5分間で2000リクエスト
aggregateKeyType: 'IP',
},
},
visibilityConfig: {
sampledRequestsEnabled: true,
cloudWatchMetricsEnabled: true,
metricName: 'RateLimitByIP',
},
},
],
});
2 週間 Count モードで動かして、CloudWatch のメトリクスと WAF のサンプリングログを見ながら「これは本物の攻撃か?」「これは誤検知か?」を地道に分類していく。この工程、正直しんどいんだけどサボると後で絶対に後悔する。うちのチームは 1 人が 1 日 30 分ずつログを見る当番制にして 2 週間回した。
Bot Control の導入が地味に効いた
2026 年の攻撃トレンドとして、ヘッドレスブラウザを使ったボット攻撃が明らかに増えている。通常の IP レート制限だけでは捕まえられないやつが多い。Bot Control の Intelligence Targeting を有効にしてから、スクレイピング系のアクセスが激減した。
ただし Bot Control を有効にすると月のリクエストコストが跳ね上がるので注意が必要だ。うちの場合、Bot Control を API エンドポイント全体に適用するのではなく、特定のパス(/api/auth/*, /api/search/*)だけに絞ったカスタムルールと組み合わせた。これでコストを約 60% 抑えられた。
xychart-beta
title "WAF Bot Control 適用後のボットトラフィック推移(週次)"
x-axis ["導入前-2w", "導入前-1w", "導入直後", "1週間後", "2週間後", "1ヶ月後", "3ヶ月後"]
y-axis "ボットリクエスト数(万件/日)" 0 --> 120
bar [98, 112, 87, 34, 22, 18, 15]
line [98, 112, 87, 34, 22, 18, 15]
導入直後から劇的に下がってはいないんだけど、1 週間後あたりから明確に効いてきた感じ。攻撃者側も対策されたと気づいて標的を変えるのかもしれない。
Shield Advanced の実態と DDoS 発生時の対応フロー
「Shield Advanced って月額 3,000 USD 取られるけど本当に必要?」——これ、導入前に何度も社内で議論した。個人的にはビジネスインパクトが大きい本番サービスには入れるべきだと思っていて、理由は 2 つある。
- SRT(Shield Response Team)へのアクセス権 — 大規模 DDoS 発生時に AWS の専門チームが直接対応してくれる。夜中に自分たちだけで戦うより何倍も心強い。
- コスト保護 — DDoS 攻撃によって発生した CloudFront・Route 53・ALB の料金増分を払い戻してもらえる。
実際に DDoS が来たときのフローはこう設計している。
sequenceDiagram
participant Attacker as 攻撃者
participant Shield as Shield Advanced
participant WAF as WAF v2
participant CW as CloudWatch
participant PD as PagerDuty
participant OnCall as オンコール担当
participant SRT as AWS SRT
Attacker->>Shield: 大規模DDoS開始
Shield->>Shield: L3/L4 自動緩和
Shield->>CW: DDoS検知メトリクス送信
CW->>PD: アラート発火(5分以内)
PD->>OnCall: 電話・SMS通知
OnCall->>WAF: カスタムIPブロックルール追加
OnCall->>SRT: サポートケース起票(緊急)
SRT->>Shield: L7対応策を協議・実装
Shield->>Attacker: 緩和完了
OnCall->>CW: 正常化確認
このフローで一番大事なのは PagerDuty へのアラート発火を 5 分以内にできるようにしていること。CloudWatch のアラームの閾値設定を適切にしないと、攻撃が始まってから気づくのが遅くなる。実際うちでも閾値設定を甘くしていた初期に、攻撃開始から 15 分気づかなかったことがあった。
具体的にうちが監視している主要メトリクスを表にまとめる。
| メトリクス | 閾値 | アクション |
|---|---|---|
DDoSDetected | 1以上 | PagerDuty P1 アラート |
BlockedRequests(WAF) | 1分で 1,000 以上 | Slack 通知 |
ALB 5xx エラー率 | 5% 超 | PagerDuty P2 アラート |
CloudFront RequestErrorRate | 3% 超 | Slack 通知 |
WAF RateLimitByIP 発火数 | 1時間で 100 IP 以上 | Slack + 手動確認 |
インシデント対応の全体的なプロセスについては別記事でまとめているので、ここでは WAF・DDoS 特有の話に絞る。
コスト設計と最適化、実際の請求額
ここが正直、導入前の想定とかなりズレた部分だった。WAF のコストモデルを甘く見てた。
WAF v2 のコスト構造はざっくりこんな感じ。
- WebACL: $5/月
- ルール: $1/ルール/月
- リクエスト処理: $0.60/百万リクエスト
- Bot Control: $10/百万リクエスト(追加)
- Shield Advanced: $3,000/月(固定)+ データ転送料
月 1 億リクエストを捌いているうちのサービスで試算してみた。
xychart-beta
title "月次 WAF 関連コスト内訳(USD)"
x-axis ["WebACL\n基本料", "ルール料", "リクエスト\n処理料", "Bot Control\n(一部適用)", "Shield\nAdvanced"]
y-axis "コスト(USD)" 0 --> 3500
bar [5, 20, 600, 280, 3000]
合計で月 3,905 USD。Bot Control を全体適用していたら +800 USD ほどになっていたはず。パス絞り込みで浮かせた分は大きい。
コスト最適化で実際にやったこと
最も効果があったのは Rate-based Rule の粒度を上げることだった。単純な IP ベースのレート制限だけでなく、「同一 IP × 同一 User-Agent」の組み合わせに対するルールを追加したら、ボット検出精度が上がって Bot Control の適用範囲をさらに絞れた。
もうひとつ、WAF ログの保管コストも地味に痛い。フルログを S3 に保存すると月に数十 GB になる。うちは Kinesis Firehose で OpenSearch に流す際にサンプリング率を設定して、Block されたリクエストは 100%、Allow されたリクエストは 10% だけ保存するようにした。これでストレージコストが約 70% 削減できた。
# Kinesis Firehose Lambda 変換関数でサンプリング実装
import json
import base64
import random
def lambda_handler(event, context):
output = []
for record in event['records']:
payload = json.loads(
base64.b64decode(record['data']).decode('utf-8')
)
# Blocked リクエストは必ず保存
# Allowed リクエストは 10% だけ保存
action = payload.get('action', 'ALLOW')
if action == 'BLOCK' or random.random() < 0.10:
output.append({
'recordId': record['recordId'],
'result': 'Ok',
'data': record['data']
})
else:
# DROP することで S3 に書き込まない
output.append({
'recordId': record['recordId'],
'result': 'Dropped',
'data': record['data']
})
return {'records': output}
実装自体は単純なんだけど、「Allowed を 10% だけ」というサンプリング率は攻撃のパターン分析をするときに後悔しないラインを考える必要がある。正直まだ最適値だと確信はしていなくて、20% に戻すことも検討中。
1年運用してわかった「やっておくべきだった」こと
後悔した点を素直に書く。同じ轍を踏まないでほしい。
① WAF ルールの変更管理を最初から IaC に乗せるべきだった
最初の数週間、コンソールから手作業でルールをチューニングしていたら「誰がいつ何を変えたかわからない」状態になった。CDK で管理するようにしてからは、PR でレビューして Terraform の State と同じように管理できるようになった。Terraform と IaC の State 管理については別記事の知見が参考になるはず。
② Geo ブロックは最後の手段として最初から設計すべきだった
DDoS 攻撃の多くは海外の踏み台サーバーから来る。うちのサービスは日本国内向けなので、海外 IP を全ブロックしても問題ないケースがほとんど。でも「グローバル展開したらどうするか」を考えて最初は入れなかった。結果、攻撃時に「Geo ブロックするか?」の判断で 20 分ロスした。最初からオプションとして Count モードで様子を見ておくべきだったと今は思う。
// Geo 制限ルール(Block したい場合は action: { block: {} } に変更)
{
name: 'GeoBlockNonJP',
priority: 5,
action: { count: {} }, // 平時は Count、攻撃時に Block へ切り替え
statement: {
geoMatchStatement: {
countryCodes: ['JP'],
forwardedIPConfig: {
headerName: 'X-Forwarded-For',
fallbackBehavior: 'MATCH',
},
},
},
// NOT 条件で「JP 以外」をブロック
}
③ セキュリティ関連の変更履歴を SOC2 監査に使える形で残す設計
SOC2 対応については別記事でもっと詳しく書いているけど、WAF のルール変更ログは CloudTrail に自動的に残る。それを AWS Config のカスタムルールで検知して、変更があったら都度 Slack に通知する仕組みを入れておくと監査証跡として使えて便利だった。これ、後から整備しようとすると意外と面倒なので本当に最初からやっておいてほしい。
皆さんのチームでは WAF のチューニングってどうやって回してますか? 僕の周りだと「入れたは良いけど誤検知対応が辛くて実質素通しになってる」という話をよく聞くんですよね。あるある過ぎて辛い。
まとめ
1 年間の AWS WAF v2 + Shield Advanced 運用から得た知見をまとめると、結局「最初の 2 週間の丁寧さ」が全てを決める気がしている。
- WAF ルールは必ず Count モードから始めて 2 週間様子見する — いきなり Block にすると誤検知で正規ユーザーを弾いて障害になる
- Bot Control はパス絞り込みで適用範囲を限定する — 全体適用するとコストが倍以上になるが、認証・検索エンドポイントだけに絞れば費用対効果が高い
- Shield Advanced の 3,000 USD/月は DDoS 対応の人件費・SRT アクセス・コスト保護と考えると妥当 — ただし小規模サービスならまず WAF + Rate Limiting で十分
- WAF ログのサンプリング保存でストレージコスト 70% 削減 — Block は 100%、Allow は 10% が現時点のバランス
- ルール変更管理は最初から CDK/Terraform に乗せる — コンソール手作業は確実に後悔する
まだ導入していない場合はまず WAF v2 を Count モードで入れてみることを強くすすめる。コストはほぼかからないし、自分のサービスにどんな攻撃が来ているか可視化するだけでも価値がある。その後 2 週間のログ分析を経て、Block するルールを段階的に増やしていく——これが一番失敗しないやり方だと思う。