CloudFront WAF v2 Shield 多層防御 本番運用 Terraform Athena

目次

1. この記事について

fig01: CloudFront × WAF × Shield 多層防御 全体像

Web に公開するシステムは毎日、複合的な攻撃にさらされている。帯域を消費する L3/L4 DDoS、SQL インジェクションや XSS などの L7 アプリケーション攻撃、credential stuffing や大量スクレイピングの Bot 攻撃——これらは同時に、異なるレイヤーから到来する。「1 つのサービスですべて防ぐ」という発想は設計上の誤りであり、各層に適切な防御を重ねる 多層防御 (Defense in Depth) が唯一の正解だ。

AWS には CloudFront (Edge)・WAF v2 (L7)・Shield (L3/L4) という 3 つのサービスがそれぞれの層に対応している。しかし「とりあえず WAF を有効にした」「CloudFront のデフォルト設定のまま運用している」という状態では、層間に防御の隙間が生まれる。本記事はこの隙間をなくし、3 層を正しく連携させた 本番運用可能な多層防御 を Terraform で構築する完全ガイドだ。

既存の関連記事 CloudFront × WAF Bot Control 実践ガイド (WP ID:1392) は Bot Control 単機能にフォーカスした内容だった。本記事はその上位互換として、L3/L4 から L7・Edge まで全層を実装し、WAF v2 Managed Rules 8 セット・Bot Control v2・Rate Limit v2・Shield 判断軸・Athena ログ分析を 1 本で完走する

本記事でできること — 差別化 6 軸

  • 多層防御モデル習得: L3/L4 (Shield) + L7 (WAF) + Edge (CloudFront) の防御責任を層別に整理し、「どの層で何を防ぐか」を体系化する
  • WAF v2 Managed Rules 完全網羅: Core Rule Set / Known Bad Inputs / SQLi / XSS / PHP / WordPress / Linux / Windows の 8 セットを WCU・対象・誤検知率で比較し、適用順序と excluded_rules を設計する
  • Bot Control v2 + Rate Limit v2 実装: 2024 GA の v2 仕様 (CAPTCHA/Challenge・Token Bucket・aggregate_key) を AWS CLI + Terraform で実装する
  • Shield Standard vs Advanced 判断軸: $3,000/月 + 1 年契約の Shield Advanced を「いつ採用するか」を月間トラフィック・被害想定額・予算の 3 軸で判断するフローを提供する
  • Athena ログ分析パイプライン: WAF Logs → S3 → Glue Catalog → Athena のパイプラインと、攻撃元 IP TOP 10 / Block 理由集計 / Bot signal 別カウントの 3 クエリ例を実装する
  • Terraform 完全実装: aws_wafv2_web_acl / aws_cloudfront_distribution / aws_shield_protection / aws_athena_workgroup を含む本番投入可能な HCL を §3–§6 で段階的に提供する

1-1. 本記事のゴール

本記事を最後まで読むと、次の状態を達成できる。

  • CloudFront ディストリビューションに WAF v2 Web ACL (Managed Rules 8 セット + Bot Control v2 + Rate Limit v2) を関連付け、OWASP Top 10 をカバーした状態
  • Shield Standard が提供する自動 DDoS 保護の範囲を正確に把握し、Shield Advanced の採用是非を費用対効果フローで判断できる状態
  • WAF Logs を S3 に出力し、Athena で攻撃トラフィックをクエリ分析・可視化できる状態
  • 上記すべてを Terraform HCL で宣言的に管理し、terraform apply 1 回で再現できる状態

目指すのは「動作確認できた」で終わらないレベルだ。IAM 最小権限・Terraform state 分離・WAF Log のパーティション設計まで含む、本番投入可能な実装を目標とする。「どの設定値を選ぶか」の判断基準も合わせて解説するため、記事を通じて WAF・Shield 設計力が身につく構成になっている。

1-2. 読者像

本記事は次のすべてに当てはまる AWS エンジニアを想定する。

前提知識具体的に必要なスキル
CloudFront 基本経験Distribution 作成・オリジン設定・キャッシュ動作・カスタムエラーレスポンスを実務で扱ったことがある
WAF v2 基本経験Web ACL 作成・Rule 追加・Block/Count モード切替を手動で操作したことがある
Terraform 基礎terraform init / plan / apply と resource / variable / output ブロックを自分で書いたことがある
セキュリティ運用の課題感「WAF を入れたが何を防いでいるか把握できていない」「攻撃元を分析したい」という問題意識を持つ

Shield や Bot Control v2 の詳細知識は不要。Terraform の module 分割や高度な HCL パターンも本記事ではカバーしない。「まず動かして、理解してから深堀りする」アプローチで進める。

1-3. なぜ今これを書くか

① Bot Control v2 / Rate Limit v2 の 2024 GA 後、国内体系解説が不足している

Bot Control v2 (CAPTCHA/Challenge アクション) と Rate Limit v2 (Token Bucket・aggregate_key による集計方式の拡張) は 2024 年に GA となり、v1 から仕様が大きく変わった。しかし日本語で v2 の設計判断まで踏み込んだ実装記事は少なく、古い v1 情報が混在している状態だ。本記事は最新の v2 仕様に完全対応する。

② Shield Advanced の採用判断材料がない

$3,000/月 + 1 年契約という高コストの Shield Advanced を採用すべきかどうか、費用対効果を定量的に判断できる情報が国内に乏しい。本記事は月間トラフィック・DDoS 被害想定額・予算を軸にした判断フローを体系化して提供する。「Advanced は高いから Standard で十分」という感覚的判断から脱却できる。

③ 多層防御の横断的実装記事がない

CloudFront・WAF・Shield を個別解説する記事は多い。しかし 3 層を Terraform で一体として実装し、Athena ログ分析まで含む「本番運用できる状態」まで完走する記事は希少だ。本記事はその空白を埋める。

④ cmd_083 の WAF 言及を深掘りする

前記事 API Gateway + Lambda Authorizer 本番運用ガイド (WP ID:2005) の §7 では CloudFront + WAF 統合を簡単に言及するにとどめた。本記事はその WAF 部分を完全解説する姉妹記事として位置づける。

AWS WAF v2 Developer Guide — 公式ドキュメント

1-4. 関連記事との差別化

1-5. 本記事の構成

§タイトル内容
§1この記事について動機・読者像・ゴール・差別化・全体アーキ概要
§2前提・環境・準備AWS/Terraform/権限/us-east-1 制約・3 点セット初期化
§3多層防御アーキテクチャ (L3/L4/L7+Edge)Shield/WAF/CloudFront 役割分担・防御責任マトリクス
§4AWS WAF v2 Managed Rules 完全網羅8 セット比較・WCU 計算・excluded_rules・適用順序
§5Bot Control v2 + Rate Limit v2 実装CAPTCHA/Challenge・Token Bucket・aggregate_key
§6Shield Standard vs Advanced + Terraform費用対効果フロー・aws_shield_protection 完全 HCL
§7WAF Logs → S3 → Athena ログ分析Glue Catalog・クエリ 3 例・QuickSight 連携
§8まとめ + 落とし穴 10 選 + 次回予告チートシート・scope 混同/WCU 上限/月コスト 等
QG-1: CloudFront × WAF v2 × Shield 多層防御 — 全体アーキテクチャ

Web 公開システムの防御は 3 層 + Origin で構成する。

Internet (HTTP / HTTPS リクエスト)
  │
  ▼
┌──────────────────────────────────────────────────────────┐
│  Amazon CloudFront  (Edge POP / グローバル配置) │
│  役割: キャッシュオフロード / Geo 制限 / HTTPS 強制│
│  WAF Web ACL を scope=CLOUDFRONT で関連付け (us-east-1 必須) │
└──────────────────────┬───────────────────────────────────┘
  │ WAF ルール評価 (通過 or BLOCK / COUNT)
  ▼
┌──────────────────────────────────────────────────────────┐
│  AWS WAF v2  (Web ACL / scope=CLOUDFRONT) │
│  役割: SQLi / XSS / Bot Control / Rate Limit │
│  OWASP Top 10 Managed Rules — WCU 上限 1,500 に注意  │
└──────────────────────┬───────────────────────────────────┘
  │ DDoS 検知・自動軽減レイヤー
  ▼
┌──────────────────────────────────────────────────────────┐
│  AWS Shield Standard  (全顧客に自動適用・追加費用なし)│
│  AWS Shield Advanced  ($3,000/月 + 1 年契約 + DRT サポート) │
│  役割: SYN Flood / UDP Reflection / Volumetric DDoS 軽減  │
└──────────────────────┬───────────────────────────────────┘
  │
  ▼
┌──────────────────────────────────────────────────────────┐
│  Origin  (ALB / API Gateway / EC2 / Lambda)  │
│  保護: Security Group = CloudFront IP レンジのみ許可  │
└──────────────────────────────────────────────────────────┘

Internet からのリクエストは Internet → CloudFront Edge POP → WAF v2 ルール評価 → Shield DDoS 検知 → Origin の順で処理される。各層は独立して判断し、一層が突破されても次の層で止める設計が多層防御の本質だ。

各層が担う攻撃種別をより詳しく整理すると次のようになる。

攻撃種別主な防御層サービス / 手段
SYN Flood / UDP Flood (帯域消費型)L3/L4Shield Standard 自動軽減 / Advanced は DRT 介入
HTTP Flood (大量リクエスト)L7WAF Rate Limit v2 (Token Bucket / IP aggregate)
SQL InjectionL7WAF SQLi Rule Group (Managed Rules)
XSS (Cross-Site Scripting)L7WAF XSS Rule Group (Managed Rules)
OWASP Top 10 全般L7WAF Core Rule Set (CRS)
Bot スクレイピング / Credential StuffingL7WAF Bot Control v2 (CAPTCHA / Challenge)
特定 IP / 国家レベルの攻撃EdgeCloudFront Geo 制限 + WAF IP Set Block

CloudFront + WAF の必須制約: WAF v2 Web ACL を CloudFront に関連付ける場合、Web ACL は必ず us-east-1 (バージニア北部) リージョンで scope = CLOUDFRONT として作成しなければならないREGIONAL スコープの Web ACL は CloudFront に割り当てられない。この制約は §2 の環境構築で最初に反映する。

1-6. 本記事を最大活用する読み方

完全に通読する場合: §1 → §2 → §3 → §4 → §5 → §6 → §7 → §8 の順に進む。§3 で設計基盤を固め、§4–§6 で実装し、§7 で運用監視まで仕上げる流れだ。

特定トピックだけ確認したい場合:

目的参照先
CloudFront + WAF の us-east-1 制約と関連付け手順§2 (3 点セット)
WAF Managed Rules の選定・WCU 計算・適用順序§4
Bot Control v2 / Rate Limit v2 設定方法§5
Shield Advanced 採用是非の判断フロー§6
WAF Logs を Athena で分析するクエリ例§7
よくある落とし穴だけ確認したい§8 落とし穴 10 選

Terraform のみ参照したい場合: 各セクションの「Terraform HCL」サブセクションを拾い読みすると、実装コードを直接参照できる。


2. 前提・環境・準備

fig02: 検証環境 + IAM 構成

本記事の Terraform リソースはすべて us-east-1 を WAF 専用リージョン として扱う。CloudFront に関連付ける WAF v2 Web ACL は scope = CLOUDFRONT で作成する必要があり、このスコープの Web ACL は us-east-1 にのみ作成できるためだ。Origin (ALB・EC2 など) が ap-northeast-1 にある場合でも、WAF の Terraform リソースは us-east-1 プロバイダで作成する。

2-1. 前提環境

項目要件確認コマンド
AWS アカウント本番または検証用アカウント (Organizations SCP 環境可)aws sts get-caller-identity
主リージョン (WAF)us-east-1 (バージニア北部) — scope=CLOUDFRONT 必須aws configure get region
Terraform1.9.x 以上terraform -version
AWS CLIv2.x 以上aws --version
IAM 権限wafv2: / cloudfront: / shield: / athena: / s3: / glue: / logs:*AWS IAM Policy Simulator

scope=CLOUDFRONT と us-east-1 の関係を理解する

AWS WAF v2 の Web ACL には 2 種類のスコープがある。

スコープ作成リージョン割り当て先
CLOUDFRONTus-east-1 固定CloudFront Distribution のみ
REGIONAL任意のリージョンALB / API Gateway / AppSync / Cognito

REGIONAL スコープで ap-northeast-1 に作成した Web ACL は CloudFront には割り当てられない。Terraform でこの制約を制御するには provider "aws" のエイリアスを 2 つ定義する。

2-2. 使用技術スタック

技術バージョン / スペック用途
AWS WAF v22024 GA 仕様 (v2)Managed Rules + Bot Control v2 + Rate Limit v2
AWS Shield Standard自動適用 ($0)L3/L4 DDoS 自動保護 (全 AWS 顧客)
AWS Shield Advancedオプション ($3,000/月 + 1 年契約)DRT サポート・DDoS Cost Protection
Amazon CloudFront最新Edge CDN・WAF Web ACL 関連付け
Amazon S3最新WAF ログ保存先・Terraform state バックエンド
Amazon Athena最新WAF ログ SQL 分析 ($5/TB スキャン)
AWS Glue Data Catalog最新Athena 用テーブル定義・パーティション管理
Terraform1.9.x (aws provider ~> 5.0)IaC 全リソース宣言的管理
AWS CLIv2.x動作確認・ログ確認コマンド

2-3. Terraform 初期化 — 3 点セット

Terraform HCL

provider.tf — us-east-1 を WAF 用プロバイダとして宣言する。

terraform {
  required_version = ">= 1.9"
  required_providers {
 aws = {
source  = "hashicorp/aws"
version = "~> 5.0"
 }
  }
  backend "s3" {
 bucket= "YOUR_TFSTATE_BUCKET"
 key= "cloudfront-waf/terraform.tfstate"
 region= "ap-northeast-1"
 dynamodb_table = "YOUR_TFSTATE_LOCK_TABLE"
 encrypt  = true
  }
}

# デフォルトプロバイダ — Origin (ALB / EC2 等) 用
provider "aws" {
  region = var.aws_region
}

# WAF scope=CLOUDFRONT 専用プロバイダ (us-east-1 固定)
# この provider で作成した Web ACL のみ CloudFront に割り当て可能
provider "aws" {
  alias  = "us_east_1"
  region = "us-east-1"
}

variables.tf

variable "aws_region" {
  description = "Primary region for Origin resources (e.g. ap-northeast-1)"
  type  = string
  default  = "ap-northeast-1"
}

variable "environment" {
  description = "Deployment environment: prod or staging"
  type  = string
  default  = "prod"
}

variable "project_name" {
  description = "Project name prefix applied to all resource names"
  type  = string
  default  = "myapp"
}

variable "waf_log_bucket" {
  description = "S3 bucket name for WAF logs (must start with 'aws-waf-logs-')"
  type  = string
}

variable "cloudfront_distribution_id" {
  description = "CloudFront distribution ID to associate the WAF Web ACL with"
  type  = string
  default  = ""
}

AWS CLI 確認

provider 設定後、terraform init の前に CLI で現状を確認する。

# us-east-1 に既存の CloudFront スコープ Web ACL があるか確認
aws wafv2 list-web-acls \
  --scope CLOUDFRONT \
  --region us-east-1 \
  --output table

# ap-northeast-1 の REGIONAL Web ACL (参考: CloudFront には使えない)
aws wafv2 list-web-acls \
  --scope REGIONAL \
  --region ap-northeast-1 \
  --output table

# CloudFront ディストリビューション一覧と現在の Web ACL 関連付け確認
aws cloudfront list-distributions \
  --query 'DistributionList.Items[*].{ID:Id,Domain:DomainName,WebACL:WebACLId}' \
  --output table

コンソール確認手順

  1. AWS コンソール → リージョンを us-east-1 に切り替える (WAF コンソールはリージョン依存)
  2. WAF & ShieldWeb ACLs → タブから “Global (CloudFront)” を選択
  3. “Create web ACL” をクリック → Resource type: CloudFront distributions を選択していることを確認
  4. 既存 Web ACL がある場合、Associated AWS resources 列に CloudFront ディストリビューション ID が表示される

2-4. IAM 構成

本構成で必要な IAM リソースは 3 種類だ。

① Terraform 実行ロール — CI/CD パイプライン (GitHub Actions / CodeBuild) 用

resource "aws_iam_policy" "terraform_waf_cloudfront" {
  name  = "${var.project_name}-terraform-waf-cf-policy"
  description = "Terraform execution: WAF v2 + CloudFront + Shield + Athena"

  policy = jsonencode({
 Version = "2012-10-17"
 Statement = [
{
  Sid= "WAFv2FullAccess"
  Effect= "Allow"
  Action= ["wafv2:*"]
  Resource = "*"
},
{
  Sid = "CloudFrontAssociation"
  Effect = "Allow"
  Action = [
 "cloudfront:GetDistribution",
 "cloudfront:UpdateDistribution",
 "cloudfront:CreateDistribution",
 "cloudfront:TagResource",
 "cloudfront:ListDistributions"
  ]
  Resource = "*"
},
{
  Sid = "ShieldProtection"
  Effect = "Allow"
  Action = [
 "shield:CreateProtection",
 "shield:DescribeProtection",
 "shield:DeleteProtection",
 "shield:ListProtections",
 "shield:DescribeSubscription"
  ]
  Resource = "*"
},
{
  Sid = "AthenaAndGlue"
  Effect = "Allow"
  Action = [
 "athena:*",
 "glue:CreateDatabase",
 "glue:CreateTable",
 "glue:GetDatabase",
 "glue:GetTable",
 "glue:UpdateTable"
  ]
  Resource = "*"
}
 ]
  })
}

② WAF ログ用 S3 バケット — バケット名は aws-waf-logs- プレフィックスが必須

resource "aws_s3_bucket" "waf_logs" {
  provider = aws.us_east_1
  bucket= "aws-waf-logs-${var.project_name}-${data.aws_caller_identity.current.account_id}"
}

WAF Logs を S3 に直接配信する場合、バケット名は必ず aws-waf-logs- で始まる必要がある。この制約は AWS マネージドポリシーでチェックされるため、名前を変えると LogDestinationConfigs 設定時にエラーになる。

③ コンソール確認 — IAM ポリシー適用確認

# IAM Policy Simulator で wafv2:ListWebACLs が Allow か確認
aws iam simulate-principal-policy \
  --policy-source-arn "arn:aws:iam::ACCOUNT_ID:role/YOUR_ROLE" \
  --action-names "wafv2:ListWebACLs" \
  --output table

# ロールに Terraform 実行ポリシーがアタッチされているか確認
aws iam list-attached-role-policies \
  --role-name YOUR_ROLE_NAME \
  --output table

2-5. ゴール状態の定義

本記事の全手順を実行すると次の状態が達成できる。

コンポーネントゴール状態
CloudFront DistributionWAF v2 Web ACL (us-east-1 / scope=CLOUDFRONT) が関連付けられた状態
WAF v2 Web ACLManaged Rules 8 セット + Bot Control v2 + Rate Limit v2 が有効
ShieldStandard は自動有効。Advanced は §6 の費用対効果フローで採用可否を判断
WAF LogsS3 バケット (aws-waf-logs-*) に JSON Lines 形式で出力中
AthenaGlue Catalog テーブル作成済み・3 種クエリが実行可能な状態
Terraform stateS3 バックエンドで管理・DynamoDB ロックで二重 apply を防止

次の §3 では、この全体構成の「防御責任マトリクス」を詳細に設計する。L3/L4・L7・Edge の各層がどの攻撃をどのように処理するかを整理し、Terraform 実装に入る前の設計基盤を固める。


3. 多層防御アーキテクチャ (L3/L4/L7 + Edge)

fig03: L3/L4/L7+Edge 防御責任マトリクス図

AWS の Web セキュリティは「単一の防御機構で守る」設計では成立しない。DDoS の帯域攻撃・SYN flood・SQLi/XSS・Bot・不正 API 呼び出しはそれぞれ異なる防御レイヤーで対処する必要がある。本章では CloudFront × WAF v2 × Shield の3層モデルを体系化し、どの層で何を防ぐかを防御責任マトリクスで整理する。

3-1. 防御層の責任分担

L3/L4 層: AWS Shield(ネットワーク/トランスポート層)

AWS Shield Standard はすべての AWS アカウントに自動で適用される無償の DDoS 防御機構だ。L3/L4 の volumetric 攻撃(UDP reflection/amplification)と SYN/ACK flood を吸収する。CloudFront のグローバルエッジロケーション全体に適用され、追加設定は不要である。

AWS Shield Advanced は月額 $3,000 の有償サービスで、以下を追加提供する。

  • 24時間 DDoS Response Team(DRT): インシデント発生時に AWS スペシャリストが直接対応
  • DDoS Cost Protection: Auto Scaling / CloudFront / Route 53 / ELB の攻撃由来コスト増を補填
  • Health-based detection: Route 53 ヘルスチェックと統合した異常検知精度向上
  • Global Threat Dashboard: 組織全体の DDoS 攻撃状況の可視化
  • Proactive Engagement: ヘルスチェック失敗時に DRT が自動的に連絡し対処を開始

Shield のカバー範囲はネットワーク帯域攻撃に特化しており、HTTP レベルの攻撃は WAF v2 が担当する。

L7 層: AWS WAF v2(アプリケーション層)

WAF v2 は HTTP/HTTPS リクエストを検査し、OWASP Top 10・Bot トラフィック・不正 API 呼び出しを遮断する。CloudFront に紐付ける場合は scope=CLOUDFRONT を指定し、us-east-1 リージョンで Web ACL を作成する必要がある(CLOUDFRONT scope は us-east-1 固定・他リージョンでの作成は不可)。

主要な保護機能を示す。

  • Managed Rules: AWS が管理するルールセット(Core Rule Set・Known Bad Inputs・SQLi/XSS 等)
  • Bot Control v2: CAPTCHA/Challenge アクションで Bot を識別・遮断(COMMON/TARGETED の2モード)
  • Rate Limit v2(rate_based_statement): Token Bucket 方式で IP / フォワーディング IP 別にリクエストレートを制限
  • Geo-match: 国別アクセス制御(WAF WCU を消費する点に注意)

WAF v2 の Web ACL Capacity Unit(WCU)は上限 1,500 である。Managed Rules 全積みは WCU を超過するため、適用ルールの選定と順序設計が最重要の設計制約となる。

Edge 層: Amazon CloudFront(エッジキャッシュ/Geo 制御)

CloudFront はグローバルエッジネットワークで以下の防御機能を提供する。

  • キャッシュによる Origin 負荷軽減: 静的コンテンツをエッジでキャッシュし、Origin への無用なリクエストを遮断
  • Geo-restriction: 国単位でのアクセス拒否(WAF Geo-match と機能は重複するが CloudFront 側は WCU を消費しない)
  • HTTPS 強制: HTTP → HTTPS リダイレクトで平文通信を排除
  • カスタム HTTP ヘッダー: Origin との間に秘密ヘッダーを付与し、CloudFront をバイパスした直接 Origin アクセスを防止
  • Origin Access Control(OAC): S3 Origin への直接アクセスを禁止し CloudFront 経由のみを許可

CloudFront はエッジで WAF v2 を呼び出すため、日本ユーザーからのリクエストも東京エッジロケーション上でルール評価が行われる(us-east-1 の Web ACL がエッジに配布される仕組み)。

Origin 層: ALB / API Gateway(最終防衛ライン)

ALB と API Gateway は Origin 側の最終防衛ラインだ。CloudFront → WAF → ALB の経路では、ALB に WAF で付与したカスタムヘッダーの検証を行わせることで直接アクセスを遮断できる。API Gateway は throttling(デフォルト 10,000 req/sec)と WAF Regional Web ACL の二重防御が可能である。

3-2. 防御責任マトリクス表

攻撃種別ごとに各防御層の役割を整理する。「主担当」は第一対処層、「補完」は補助的に機能する層、「対象外」はその層の責任外を示す。

攻撃種別Shield(L3/L4)WAF v2(L7)CloudFront(Edge)ALB/API GW(Origin)
Volumetric DDoS(UDP/ICMP flood)主担当(Standard/Advanced)対象外補完(エッジ吸収)対象外
SYN flood / TCP exhaustion主担当(Standard)対象外補完(エッジ終端)対象外
HTTP flood(L7 DDoS)対象外主担当(Rate Limit v2)補完(キャッシュ命中で軽減)補完(throttling)
SQLi(SQL Injection)対象外主担当(SQL Database RuleSet)対象外補完(WAF Regional)
XSS(Cross-Site Scripting)対象外主担当(Core Rule Set)対象外補完(WAF Regional)
Bot / スクレイピング対象外主担当(Bot Control v2)補完(Geo-restriction)対象外
不正 API 呼び出し(SSRF 等)対象外主担当(Known Bad Inputs)対象外主担当(throttling)
国別 IP 制限対象外補完(Geo-match)主担当(Geo-restriction)対象外

読み方のポイント:
– Shield が主担当になるのは帯域・プロトコル層攻撃のみ。HTTP レベルに Shield は介入しない。
– WAF v2 は L7 攻撃全般の主担当だが、Bot Control/Rate Limit は WCU と追加コストを消費する。
– CloudFront の Geo-restriction は WAF WCU を消費しないため、国単位制限を多数設定する場合は CloudFront 側が効率的だ。

3-3. CloudFront + WAF + Shield 統合設計

scope=CLOUDFRONT の必須制約: us-east-1

WAF v2 Web ACL を CloudFront に紐付ける場合、Web ACL の scope を CLOUDFRONT に設定し、必ず us-east-1 で作成する。ALB/API Gateway 向けの REGIONAL scope と混在させると InvalidParameter エラーが発生する。Terraform では provider = aws.us_east_1 を明示して分離管理する。

OAC + カスタムヘッダー認証の組み合わせ

S3 オリジンには OAC を適用し、CloudFront ディストリビューションの署名付きリクエストのみを S3 バケットポリシーで許可する。ALB/API Gateway オリジンには CloudFront が秘密ヘッダー(X-CloudFront-Secret)を付与し、ALB 側のリスナールールでヘッダー不一致を 403 で返すことで CloudFront バイパスを防止する。

キャッシュポリシーと WAF の相互影響

WAF はキャッシュミス時のリクエストを Origin へ転送する前にルール評価する。キャッシュヒットしたレスポンスはエッジから直接返却されるため WAF ルールは評価されない。これは正常な動作であり、Cache-Control ヘッダーと TTL 設計を適切に行い、攻撃コンテンツをキャッシュしないよう設計する必要がある。

3-4. 3点セット: Terraform HCL / AWS CLI / コンソール

Terraform HCL

CloudFront Distribution と WAF v2 Web ACL(scope=CLOUDFRONT)を紐付ける最小構成の HCL を示す。WAF リソースは us-east-1 provider で作成し、CloudFront の web_acl_id に ARN を渡す。

# providers.tf
provider "aws" {
  region = "ap-northeast-1"
}

provider "aws" {
  alias  = "us_east_1"
  region = "us-east-1"  # CLOUDFRONT scope は us-east-1 必須
}

# waf.tf — scope=CLOUDFRONT の Web ACL は us-east-1 provider を使用
resource "aws_wafv2_web_acl" "cloudfront_acl" {
  provider = aws.us_east_1
  name  = "cloudfront-web-acl"
  description = "Multi-layer defense Web ACL: scope=CLOUDFRONT (us-east-1)"
  scope = "CLOUDFRONT"

  default_action {
 allow {}
  }

  # Core Rule Set (OWASP Top 10) — 初期は COUNT で誤検知を確認
  rule {
 name  = "AWSManagedRulesCommonRuleSet"
 priority = 40

 override_action {
count {}
 }

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

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

  visibility_config {
 cloudwatch_metrics_enabled = true
 metric_name = "cloudfront-web-acl"
 sampled_requests_enabled= true
  }
}

# cloudfront.tf — WAF Web ACL を CloudFront に紐付け
resource "aws_cloudfront_distribution" "main" {
  web_acl_id = aws_wafv2_web_acl.cloudfront_acl.arn  # WAF ACL 紐付け

  origin {
 domain_name  = "example-bucket.s3.amazonaws.com"
 origin_id = "s3-origin"
 origin_access_control_id = aws_cloudfront_origin_access_control.oac.id
  }

  enabled = true
  default_root_object = "index.html"

  default_cache_behavior {
 target_origin_id = "s3-origin"
 viewer_protocol_policy = "redirect-to-https"
 allowed_methods  = ["GET", "HEAD"]
 cached_methods= ["GET", "HEAD"]

 forwarded_values {
query_string = false
cookies { forward = "none" }
 }
  }

  restrictions {
 geo_restriction {
restriction_type = "none"
 }
  }

  viewer_certificate {
 cloudfront_default_certificate = true
  }
}

resource "aws_cloudfront_origin_access_control" "oac" {
  name= "s3-oac"
  origin_access_control_origin_type = "s3"
  signing_behavior= "always"
  signing_protocol= "sigv4"
}

AWS CLI

WAF Web ACL とディストリビューションの状態確認コマンドを示す。scope=CLOUDFRONT の操作は必ず --region us-east-1 を指定する

# WAF Web ACL 一覧 (scope=CLOUDFRONT の --region us-east-1 指定必須)
aws wafv2 list-web-acls \
  --scope CLOUDFRONT \
  --region us-east-1 \
  --output table

# Web ACL の詳細確認(WCU 使用量・紐付けルール一覧)
aws wafv2 get-web-acl \
  --scope CLOUDFRONT \
  --region us-east-1 \
  --id "<web-acl-id>" \
  --name "cloudfront-web-acl" \
  --query '{Name:WebACL.Name, Capacity:WebACL.Capacity, Rules:WebACL.Rules[].Name}'

# CloudFront ディストリビューションに紐付く WAF ACL ARN を確認
aws cloudfront get-distribution \
  --id "<distribution-id>" \
  --query 'Distribution.DistributionConfig.WebACLId'

コンソール手順(CloudFront に WAF Web ACL を紐付ける)

  1. AWS CloudFront コンソールを開き、対象のディストリビューション名をクリック
  2. 「全般」タブ「セキュリティ」セクション「編集」をクリック
  3. 「AWS WAF Web ACL」ドロップダウンから us-east-1 で作成済みの Web ACL を選択(REGIONAL scope の Web ACL は表示されないため注意)
  4. 「変更を保存」をクリック → デプロイ完了まで約 1〜3 分待機して完了

3-5. QG-1: 多層防御アーキテクチャ

QG-1: L3/L4/L7+Edge 多層防御アーキテクチャ

CloudFront × WAF v2 × Shield の防御責任を層別に整理した俯瞰図だ。

Internet (HTTP / HTTPS リクエスト)
  │
  ▼
┌──────────────────────────────────────────────────────────────┐
│  Amazon CloudFront  (Edge POP / グローバル) │
│  脅威: 帯域吸収 / Geo 制限 / HTTPS 強制 │
│  コスト: 転送量課金│
└──────────────────────────┬───────────────────────────────────┘
│ scope=CLOUDFRONT WAF ルール評価
▼
┌──────────────────────────────────────────────────────────────┐
│  AWS WAF v2  (scope=CLOUDFRONT / us-east-1 必須) │
│  脅威: SQLi / XSS / Bot Control / Rate Limit  │
│  コスト: $5/月 + $0.6/100 万 req (WCU 上限 1,500) │
└──────────────────────────┬───────────────────────────────────┘
│ L3/L4 DDoS 検知・自動軽減
▼
┌──────────────────────────────────────────────────────────────┐
│  AWS Shield Standard  (全顧客自動適用・無償)│
│  AWS Shield Advanced  ($3,000/月 + 1 年契約 + DRT 24/7)│
│  脅威: Volumetric DDoS / SYN Flood / UDP Reflection │
└──────────────────────────┬───────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────┐
│  Origin  (ALB / API Gateway / EC2 / Lambda)│
│  保護: SG = CloudFront IP のみ許可 / OAC でバイパス遮断 │
│  コスト: リクエスト課金 (リージョン内)  │
└──────────────────────────────────────────────────────────────┘

攻撃フロー例(SQLi 攻撃の防御シーケンス)

  1. 攻撃リクエストがグローバルエッジロケーションに到達する
  2. Shield Standard が L3/L4 レベルの帯域異常を監視(volumetric でなければ通過)
  3. CloudFront が HTTPS 終端・キャッシュ判定(ミス → WAF 評価へ転送)
  4. WAF v2 の SQL Database RuleSet がリクエストを検査 → Block(ここで遮断)
  5. 遮断されたリクエストはサンプリングログに記録 → S3 → Athena で事後分析

設計の必須確認事項

  • WAF scope=CLOUDFRONT の Web ACL は us-east-1 必須。REGIONAL scope の Web ACL は CloudFront に紐付け不可。
  • WAF WCU 上限は 1,500。Managed Rules の WCU 合計が超過すると Web ACL 作成・更新が失敗(WAFLimitsExceededException)する。
  • CloudFront Geo-restriction は WAF WCU を消費しない。国単位制限が多数の場合は CloudFront 側での設定が WCU 効率的だ。
  • OAC + カスタムヘッダーで Origin への直接アクセスを二重に遮断すること。
  • キャッシュヒット時は WAF ルール評価が行われないため、攻撃コンテンツをキャッシュしないよう TTL を設計すること。

4. AWS WAF v2 Managed Rules 完全網羅

fig04: WAF v2 Managed Rules 適用順序+excluded_rules フロー

4-1. Managed Rules の役割と WCU 制約

AWS WAF v2 の Managed Rules は、AWS セキュリティチームがリアルタイムに脅威インテリジェンスを更新するマネージドルールセットである。CloudFront に関連付けた Web ACL にルールグループを追加するだけで OWASP Top 10 を含む広範な攻撃パターンへ対応でき、自前でルール定義・維持する運用コストを大幅に削減できる。

WCU (Web ACL Capacity Units) 上限

⚠️ WCU 上限 1500: Web ACL 設計の最重要制約

  • 1 つの Web ACL で使用できる全ルールの合計 WCU 上限: 1500 WCU/WebACL
  • Managed Rules は各セットごとに固定 WCU を消費する (CRS=700・SQLi=200 等)
  • Bot Control・Rate Limit・カスタムルールも WCU を消費するため 事前の WCU 設計が必須
  • 上限超過時は Web ACL の更新が失敗する (WAFLimitsExceededException)
  • 現在 WCU 確認: aws wafv2 get-web-acl --scope CLOUDFRONT --region us-east-1 --query 'WebACL.Capacity'

Managed Rules の動作モードは 2 種類ある:

  • BLOCK モード (override_action: none): マッチしたリクエストを即時遮断。誤検知リスクを事前評価してから有効化する
  • COUNT モード (override_action: count): マッチしても通過させカウントのみ記録。新ルール追加時の監視期間に使用し、安全に誤検知を確認できる

導入推奨フロー: 新規 Managed Rules は COUNT モードで 1〜2 週間運用 → CloudWatch CountedRequests で誤検知ゼロを確認 → BLOCK モードへ切り替え。


4-2. 8 セット完全比較表

Web アプリケーション防御で最頻用される 8 つの Managed Rule Group を WCU・対象攻撃・誤検知リスク・推奨 overrideAction の観点で整理する。

#ルールグループ名WCU主な対象攻撃誤検知推奨 overrideAction
1AWSManagedRulesCommonRuleSet (CRS)700OWASP Top 10 全般 (XSS/LFI/RFI/SSI/Shellshock 等)COUNT → 精査後 BLOCK
2AWSManagedRulesKnownBadInputsRuleSet200既知悪性入力 (Log4j/Spring4Shell/Proxyshell 等)即 BLOCK
3AWSManagedRulesSQLiRuleSet200SQL インジェクション (UNION/SELECT/DROP 等)COUNT → 確認後 BLOCK
4AWSManagedRulesLinuxRuleSet200Linux Path traversal・/etc/passwd・/proc 参照BLOCK
5AWSManagedRulesUnixRuleSet100UNIX コマンド注入 (ls/cat/wget/curl 等)低〜中COUNT → 確認後 BLOCK
6AWSManagedRulesWindowsRuleSet200PowerShell/cmd.exe 呼び出し・Windows パス操作BLOCK (Windows 環境のみ)
7AWSManagedRulesPHPRuleSet100php:// ラッパー・require/include 操作等BLOCK (PHP 環境のみ)
8AWSManagedRulesWordPressRuleSet100wp-login.php ブルートフォース・xmlrpc.php 悪用BLOCK (WordPress のみ)

WCU 合計 (全 8 セット採用時): 700+200+200+200+100+200+100+100 = 1800 WCU → 上限 1500 超過

環境に合わせて必要なセットのみ選択すること。Linux + PHP + WordPress 環境の標準推奨構成:

  • CRS(700) + KnownBadInputs(200) + SQLi(200) + Linux(200) + PHP(100) + WP(100) = 1500 WCU (上限ちょうど)

Unix/Windows セットを追加する場合は、Bot Control・Rate Limit の WCU 消費分も含めて 1500 以内に収まる設計が必要となる。

ルール適用順序 (priority) の設計

Web ACL のルールは priority 番号が小さい順に評価される。以下の優先度設計を推奨する:

priorityルール種別理由
1〜9IP レピュテーション・カスタム IP セット既知悪性 IP を最速遮断しダウンストリームコストを削減
10KnownBadInputs悪性ペイロードを先行除去
20〜30SQLi・LinuxOSインジェクション系を早期遮断
40CRS (OWASP Top 10)汎用ルールは後に広くカバー
50〜60PHP・WordPress 等環境固有ルールは最後に適用
100+Bot Control・Rate LimitManaged Rules 通過後にボット・レート制限

4-3. excluded_rules と COUNT モード運用

誤検知が発生した場合、ルールグループ全体を無効化するのではなく 特定ルールのみを rule_action_override (Terraform) で除外 することで防御を維持しつつ誤検知を回避する。

excluded_rule_action の選択肢

アクション動作使用場面
ALLOW除外ルールに一致しても他ルール評価を省略して許可誤検知と確認済みの正常トラフィック
COUNTカウントのみ記録して通過調査継続中・誤検知判断中
CAPTCHACAPTCHA チャレンジを要求ヒューマン確認が有効なシナリオ

CRS の SizeRestrictions_BODY ルールは大容量ファイルアップロード機能で頻繁に誤検知を起こす代表例だ。このルール単体を COUNT に落とし、他の CRS ルールは BLOCK のまま維持するのが定番の対処となる。

CloudWatch Metrics + get-sampled-requests で誤検知を特定

# ルール別カウント数確認 (COUNT モード中の誤検知候補を監視)
aws cloudwatch get-metric-statistics \
  --namespace AWS/WAFV2 \
  --metric-name CountedRequests \
  --dimensions \
 Name=WebACL,Value=cloudfront-waf-managed \
 Name=Region,Value=us-east-1 \
 Name=Rule,Value=AWSManagedRulesCommonRuleSet \
  --start-time $(date -u -d '1 hour ago' +%Y-%m-%dT%H:%M:%SZ) \
  --end-time $(date -u +%Y-%m-%dT%H:%M:%SZ) \
  --period 3600 \
  --statistics Sum \
  --region us-east-1

# サンプルリクエスト取得 (実際の誤検知内容を確認)
aws wafv2 get-sampled-requests \
  --web-acl-arn arn:aws:wafv2:us-east-1:123456789012:global/webacl/cloudfront-waf-managed/abc123 \
  --rule-metric-name AWSManagedRulesCommonRuleSet \
  --scope CLOUDFRONT \
  --time-window StartTime=$(date -u -d '1 hour ago' +%s),EndTime=$(date -u +%s) \
  --max-items 100 \
  --region us-east-1

get-sampled-requests の結果から matchingRuleName を確認し、繰り返し誤検知しているルール名を特定して rule_action_override に追加する。


4-4. 3点セット: Terraform HCL + AWS CLI + コンソール

① Terraform HCL: managed_rule_group_statement 全設定例

CloudFront scope の Web ACL は provider = aws.us_east_1 を必ず指定すること。scope = "CLOUDFRONT" は us-east-1 以外で作成できない。

# providers.tf
provider "aws" {
  alias  = "us_east_1"
  region = "us-east-1"
}

# waf_managed_rules.tf
resource "aws_wafv2_web_acl" "main" {
  provider = aws.us_east_1
  name  = "cloudfront-waf-managed"
  description = "CloudFront WAF v2 Managed Rules"
  scope = "CLOUDFRONT"  # CLOUDFRONT scope: us-east-1 必須

  default_action { allow {} }

  # priority 10: KnownBadInputs (WCU=200, 誤検知低, 即 BLOCK)
  rule {
 name  = "AWSManagedRulesKnownBadInputsRuleSet"
 priority = 10
 override_action { none {} }
 statement {
managed_rule_group_statement {
  name  = "AWSManagedRulesKnownBadInputsRuleSet"
  vendor_name = "AWS"
}
 }
 visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "KnownBadInputs"
sampled_requests_enabled= true
 }
  }

  # priority 20: SQLi (WCU=200, 誤検知中, 初期 COUNT)
  rule {
 name  = "AWSManagedRulesSQLiRuleSet"
 priority = 20
 override_action { count {} }
 statement {
managed_rule_group_statement {
  name  = "AWSManagedRulesSQLiRuleSet"
  vendor_name = "AWS"
}
 }
 visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "SQLiRuleSet"
sampled_requests_enabled= true
 }
  }

  # priority 30: Linux OS (WCU=200)
  rule {
 name  = "AWSManagedRulesLinuxRuleSet"
 priority = 30
 override_action { none {} }
 statement {
managed_rule_group_statement {
  name  = "AWSManagedRulesLinuxRuleSet"
  vendor_name = "AWS"
}
 }
 visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "LinuxRuleSet"
sampled_requests_enabled= true
 }
  }

  # priority 40: CRS (WCU=700, 誤検知高, 初期 COUNT)
  rule {
 name  = "AWSManagedRulesCommonRuleSet"
 priority = 40
 override_action { count {} }
 statement {
managed_rule_group_statement {
  name  = "AWSManagedRulesCommonRuleSet"
  vendor_name = "AWS"
  # 大容量アップロード機能での誤検知対策: SizeRestrictions_BODY を COUNT
  rule_action_override {
 action_to_use { count {} }
 name = "SizeRestrictions_BODY"
  }
}
 }
 visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "CommonRuleSet"
sampled_requests_enabled= true
 }
  }

  # priority 50: PHP (WCU=100, PHP 環境のみ)
  rule {
 name  = "AWSManagedRulesPHPRuleSet"
 priority = 50
 override_action { none {} }
 statement {
managed_rule_group_statement {
  name  = "AWSManagedRulesPHPRuleSet"
  vendor_name = "AWS"
}
 }
 visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "PHPRuleSet"
sampled_requests_enabled= true
 }
  }

  # priority 60: WordPress (WCU=100, WordPress 環境のみ)
  rule {
 name  = "AWSManagedRulesWordPressRuleSet"
 priority = 60
 override_action { none {} }
 statement {
managed_rule_group_statement {
  name  = "AWSManagedRulesWordPressRuleSet"
  vendor_name = "AWS"
}
 }
 visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "WordPressRuleSet"
sampled_requests_enabled= true
 }
  }

  visibility_config {
 cloudwatch_metrics_enabled = true
 metric_name = "cloudfront-waf-managed"
 sampled_requests_enabled= true
  }

  tags = {
 Environment = "production"
 ManagedBy= "terraform"
  }
}

WCU 合計: KnownBadInputs(200) + SQLi(200) + Linux(200) + CRS(700) + PHP(100) + WP(100) = 1500 WCU (上限ちょうど)。Bot Control や Rate Limit を追加する場合は SQLi/Linux を COUNT モード維持か Unix/Windows セットを外してバジェットを確保すること。

② AWS CLI: 利用可能な Managed Rule Group 一覧と WCU 確認

# CloudFront スコープの Managed Rule Group 一覧 (us-east-1 で実行必須)
aws wafv2 list-available-managed-rule-groups \
  --scope CLOUDFRONT \
  --region us-east-1 \
  --output table

# 特定ルールグループの WCU・ルール詳細確認
aws wafv2 describe-managed-rule-group \
  --vendor-name AWS \
  --name AWSManagedRulesCommonRuleSet \
  --scope CLOUDFRONT \
  --region us-east-1 \
  --query '{Capacity:Capacity, Rules:Rules[].Name}'

# Web ACL の現在 WCU 使用量確認
aws wafv2 get-web-acl \
  --name cloudfront-waf-managed \
  --scope CLOUDFRONT \
  --id <WEB-ACL-ID> \
  --region us-east-1 \
  --query 'WebACL.Capacity'

③ コンソール: Web ACL に Managed Rule Group を追加する手順 (4 ステップ)

  1. AWS マネジメントコンソール → 「WAF & Shield」→ 左ペイン「Web ACLs」→ 対象 Web ACL 名をクリック
  2. 「Rules」タブ → 「Add rules」ボタン → 「Add managed rule groups」を選択
  3. 「AWS managed rule groups」セクションを展開 → 追加するルールグループの「Add to web ACL」をクリック (必要セット分繰り返す)
  4. 「Set rule priority」画面でドラッグ&ドロップで優先度を調整 (数値が小さいほど先に評価) → 「Save rules」

4-5. QG-2: WAF v2 Managed Rules 完全比較 (重要チェックポイント)

QG-2: AWS WAF v2 Managed Rules 8セット完全比較 — WCU・対象攻撃・誤検知率・推奨設定

#ルールグループWCU主な対象攻撃誤検知推奨 overrideAction
1AWSManagedRulesCommonRuleSet (CRS)700OWASP Top 10 全般 (XSS/LFI/RFI/SSI)COUNT → 精査後 BLOCK
2AWSManagedRulesKnownBadInputsRuleSet200Log4j/Spring4Shell 等の既知悪性入力即 BLOCK
3AWSManagedRulesSQLiRuleSet200SQL インジェクション (UNION/SELECT/DROP)COUNT → 確認後 BLOCK
4AWSManagedRulesLinuxRuleSet200Linux: Path traversal・/etc/passwd・/procBLOCK
5AWSManagedRulesUnixRuleSet100UNIX コマンド注入 (ls/cat/wget/curl)低〜中COUNT → 確認後 BLOCK
6AWSManagedRulesWindowsRuleSet200PowerShell/cmd.exe・Windows パス操作BLOCK (Windows 環境のみ)
7AWSManagedRulesPHPRuleSet100php:// ラッパー・require/include 操作BLOCK (PHP 環境のみ)
8AWSManagedRulesWordPressRuleSet100wp-login.php ブルートフォース・xmlrpc.phpBLOCK (WordPress のみ)

WCU 設計ポイント
全 8 セット合計 1800 WCU → 上限 1500 を 300 WCU 超過
Linux + PHP + WordPress 環境の推奨構成: CRS(700)+KnownBadInputs(200)+SQLi(200)+Linux(200)+PHP(100)+WP(100) = 1500 WCU
Bot Control (50〜200 WCU 追加) や Rate Limit を加える場合は Unix/Windows を外すか、CRS を COUNT モード維持でバジェットを調整すること。


5. Bot Control v2 + Rate Limit v2 実装

fig05: Bot Control v2 + Rate Limit v2 リクエスト処理フロー

5-1. Bot Control v2 概要

AWS WAF Bot Control は、マネージドルールグループとして提供される Bot 識別・制御機能です。2024年に v2 仕様が GA となり、COMMON モードTARGETED モードの2段階が選択可能になりました。

項目COMMON モードTARGETED モード
対象 Botスクレイパー・スキャナー・一般Botヘッドレスブラウザ・人間模倣型高度Bot
検出方式シグネチャ + User-Agent 照合JA3/JA4 フィンガープリント + 行動分析
WCU 消費50 WCU50 WCU(TARGETED 追加)
追加コスト$10/100万リクエストさらに $10/100万リクエスト 追加
ML 有効化不可可(推奨)

コスト注意: Bot Control は通常の WAF リクエスト料金($0.60/100万 req)に加え、$10/100万リクエストの追加課金が発生します。月間 10 億リクエストのサービスでは追加 $10,000/月 になるため、有効化前に必ず月次コストを試算してください。

Bot Control アクション種別

アクション動作主な使用場面
Blockリクエストを即時遮断悪質 Bot の確定排除
CAPTCHACAPTCHA チャレンジを表示疑わしい Bot への人間確認
ChallengeJavaScript チャレンジを実行(非可視)ブラウザ自動化 Bot の検出
Countカウントのみ・通過本番投入前の観察フェーズ
Allow通過させ以後のルールをスキップ正規クローラー(Google/Bing)の明示許可

JA3 / JA4 フィンガープリント(TARGETED モード)

TARGETED モードでは TLS ハンドシェイク情報から計算したフィンガープリント(JA3/JA4)で Bot を識別します。ヘッドレスブラウザ(Puppeteer/Playwright)は固有のフィンガープリントを持つため、User-Agent を偽装しても検出されます。Machine Learning(enable_machine_learning = true)を有効にすると行動パターン分析も加わり、検出精度がさらに向上します。


5-2. Rate Limit v2(rate_based_statement)

Rate Limit v2 は Token Bucket アルゴリズムを採用した WAF v2 のレートリミット機能です。2024 GA のアップデートで aggregate_key_type の種別が大幅に拡張されました。

aggregate_key 種別一覧

aggregate_key_type集計単位典型用途
IP送信元 IP アドレス基本的な DDoS 制限
FORWARDED_IPX-Forwarded-For ヘッダの IPCDN / プロキシ経由トラフィック
HTTP_HEADER任意ヘッダ値x-api-key 別レート制限
QUERY_ARGUMENTURL クエリパラメータ値ユーザー ID などパラメータ別制限
LABEL_NAMESPACEWAF ラベル名前空間Bot Control ラベル付きリクエスト
COOKIE任意 Cookie 値セッション別制限
URI_PATHリクエストパスエンドポイント別制限(2024 GA)
CUSTOM_KEYS複数キーの組み合わせヘッダ+IP などカスタム集計

Token Bucket パラメータ

# 基本形: FORWARDED_IP ベースのレートリミット
rate_based_statement {
  limit  = 2000  # evaluation_window 内の最大リクエスト数
  evaluation_window_sec = 300# 評価ウィンドウ: 60 / 120 / 300 / 600 秒のみ有効
  aggregate_key_type = "FORWARDED_IP"

  forwarded_ip_config {
 header_name = "X-Forwarded-For"
 fallback_behavior = "MATCH" # ヘッダ未存在時の扱い: MATCH=制限対象 / NO_MATCH=除外
  }
}

複合条件 Rate Limit(scope_down_statement)

scope_down_statement で「特定エンドポイントへの POST のみ制限」など絞り込みが可能です。

# ログイン試行: /login への POST を 5回/5分 で CAPTCHA
rate_based_statement {
  limit  = 5
  evaluation_window_sec = 300
  aggregate_key_type = "IP"

  scope_down_statement {
 and_statement {
statement {
  byte_match_statement {
 field_to_match { uri_path {} }
 search_string= "/login"
 positional_constraint = "STARTS_WITH"
 text_transformation { priority = 0; type = "LOWERCASE" }
  }
}
statement {
  byte_match_statement {
 field_to_match { method {} }
 search_string= "post"
 positional_constraint = "EXACTLY"
 text_transformation { priority = 0; type = "LOWERCASE" }
  }
}
 }
  }
}

5-3. Bot 制御設計パターン

パターン①: 正規クローラー許可(Google / Bing Bot)

Bot Control は Google Bot や Bing Bot に awswaf:managed:aws:bot-control:bot:category:search-engine ラベルを付与します。このラベルを検出して Allow することで SEO クローラーをホワイトリスト化できます。ラベル付与は Bot Control ルールより先に評価されるため、優先度(priority)は Bot Control のルールより小さい値を設定してください。

rule {
  name  = "allow-search-engine-bots"
  priority = 5# Bot Control (priority=20) より優先

  action { allow {} }

  statement {
 label_match_statement {
scope = "MANAGED"
key= "awswaf:managed:aws:bot-control:bot:category:search-engine"
 }
  }

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

パターン②: API エンドポイント Rate Limit(x-api-key 別)

rule {
  name  = "rate-limit-by-api-key"
  priority = 10

  action { block {} }

  statement {
 rate_based_statement {
limit  = 10000
evaluation_window_sec = 300
aggregate_key_type = "CUSTOM_KEYS"

custom_keys {
  header {
 name = "x-api-key"
 text_transformations { priority = 0; type = "NONE" }
  }
}
 }
  }

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

パターン③: ログイン試行制限(5回/5分 + CAPTCHA)

前述の scope_down_statement を使いログイン試行を制限します。Block ではなく CAPTCHA にすることで正規ユーザーへの影響を最小化し、自動化 Bot だけを排除できます。


5-4. QG-3: Bot Control v2 + Rate Limit v2 設計パターン

QG-3: Bot Control v2 + Rate Limit v2 設計判断フロー

① Bot Control モード選択フロー

月間リクエスト × $10/100万 でコスト試算
 │
 ├─ スクレイパー・スキャナー程度
 │  └→ COMMON モード(低コスト)
 │
 ├─ ヘッドレスブラウザ・人間模倣型 Bot
 │  └→ TARGETED モード + ML 有効化(追加 $10/100万 req)
 │
 └─ アクション選択
 ├─ ユーザー向けページ  → CAPTCHA(人間確認)
 └─ API / バックグラウンド → Challenge(透過的検証)

※ 本番投入前は必ず Count モードで 1 週間観察 → 誤検知確認 → Block/CAPTCHA 昇格

② Rate Limit v2 — aggregate_key 選択基準

シナリオ推奨 aggregate_keylimit / window
一般的な DDoS 防御IP2,000 / 5 分
CDN / プロキシ経由FORWARDED_IP + fallback=MATCH2,000 / 5 分
API Key 別制限CUSTOM_KEYS (x-api-key)10,000 / 5 分
ログイン試行制限IP + scope_down /login POST5 / 5 分 + CAPTCHA
エンドポイント別URI_PATHパス毎に個別設定

③ 本番投入前チェックリスト

  • ☐ Count モード 1 週間観察 → 誤検知率が許容範囲内か確認
  • ☐ 正規クローラー(Google/Bing)が search-engine ラベルで Allow されているか
  • ☐ Bot Control 月額コスト試算(月間リクエスト数 × $10/100万)を予算承認
  • evaluation_window_sec は 60 / 120 / 300 / 600 のいずれか(任意値は API エラー)
  • ☐ FORWARDED_IP 使用時: fallback_behavior を MATCH / NO_MATCH どちらにするか決定

5-5. 3点セット: Terraform HCL / AWS CLI / コンソール

Terraform HCL — Bot Control v2 + Rate Limit v2 完全実装

# provider.tf — WAF CLOUDFRONT scope は us-east-1 必須
provider "aws" {
  alias  = "us_east_1"
  region = "us-east-1"
}

resource "aws_wafv2_web_acl" "main" {
  provider = aws.us_east_1
  name  = "cloudfront-defense-in-depth"
  scope = "CLOUDFRONT"  # CLOUDFRONT scope は us-east-1 のみ
  description = "Multi-layer defense: Managed Rules + Bot Control v2 + Rate Limit v2"

  default_action { allow {} }

  # 優先度1: 正規クローラー許可(Bot Control より先に評価)
  rule {
 name  = "allow-search-engine-bots"
 priority = 5
 action { allow {} }
 statement {
label_match_statement {
  scope = "MANAGED"
  key= "awswaf:managed:aws:bot-control:bot:category:search-engine"
}
 }
 visibility_config {
sampled_requests_enabled= true
cloudwatch_metrics_enabled = true
metric_name = "AllowSearchEngineBot"
 }
  }

  # 優先度2: ログイン試行制限(5回/5分 + CAPTCHA)
  rule {
 name  = "login-rate-limit"
 priority = 10
 action { captcha {} }
 statement {
rate_based_statement {
  limit  = 5
  evaluation_window_sec = 300
  aggregate_key_type = "IP"
  scope_down_statement {
 byte_match_statement {
field_to_match { uri_path {} }
search_string= "/login"
positional_constraint = "STARTS_WITH"
text_transformation { priority = 0; type = "LOWERCASE" }
 }
  }
}
 }
 visibility_config {
sampled_requests_enabled= true
cloudwatch_metrics_enabled = true
metric_name = "LoginRateLimit"
 }
  }

  # 優先度3: Bot Control v2 TARGETED モード
  rule {
 name  = "AWSManagedRulesBotControlRuleSet"
 priority = 20
 override_action { none {} }
 statement {
managed_rule_group_statement {
  name  = "AWSManagedRulesBotControlRuleSet"
  vendor_name = "AWS"
  managed_rule_group_configs {
 aws_managed_rules_bot_control_rule_set {
inspection_level  = "TARGETED"
enable_machine_learning = true
 }
  }
  # 検索エンジン Bot はカウントのみ(allow-search-engine-bots で処理済)
  rule_action_override {
 name = "CategorySearchEngine"
 action_to_use { count {} }
  }
}
 }
 visibility_config {
sampled_requests_enabled= true
cloudwatch_metrics_enabled = true
metric_name = "BotControlRuleSet"
 }
  }

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

AWS CLI — Bot Control 設定確認

# Web ACL の全ルールを確認(scope=CLOUDFRONT は --region us-east-1 必須)
aws wafv2 get-web-acl \
  --name "cloudfront-defense-in-depth" \
  --scope CLOUDFRONT \
  --id "<web-acl-id>" \
  --region us-east-1 \
  --query 'WebACL.Rules[].{Name:Name,Priority:Priority,Action:Action}' \
  --output table

# Bot Control でサンプリングされたリクエストとラベルを確認
aws wafv2 get-sampled-requests \
  --web-acl-arn "arn:aws:wafv2:us-east-1:<account-id>:global/webacl/cloudfront-defense-in-depth/<id>" \
  --rule-metric-name "BotControlRuleSet" \
  --scope CLOUDFRONT \
  --time-window "StartTime=$(date -v-1H +%s),EndTime=$(date +%s)" \
  --max-items 100 \
  --region us-east-1

コンソール — Bot Control COMMON → TARGETED 切替手順

  1. [WAF & Shield] → [Web ACLs] → 対象 Web ACL を選択
  2. [Rules] タブ → [AWSManagedRulesBotControlRuleSet] → [Edit]
  3. [Inspection level] を COMMONTARGETED に変更
  4. [Enable machine learning] をオン
  5. [Save rule] → [Save web ACL] で確定

運用ポイント: 切替直後は必ず Count モードで維持し、CloudWatch メトリクス BotControlRuleSetCountedRequests を 1 週間監視して誤検知がないことを確認してから Block/CAPTCHA に昇格させてください。


6. Shield Standard vs Advanced 判断軸 + Terraform完全実装

6-1. Shield Standard(無償・自動適用)

AWS Shield Standard はすべての AWS アカウントで追加コストなし・設定不要で自動適用されます。L3/L4 レイヤの DDoS 攻撃を自動軽減します。

項目内容
コスト無償(追加設定不要)
自動保護対象CloudFront / Route 53 / Global Accelerator / ALB / EC2 EIP / NLB
軽減できる攻撃L3/L4: Volumetric(帯域幅枯渇)/ SYN flood / UDP reflection / ICMP flood
SLAAWS インフラ SLA に準拠(個別 SLA なし)
サポート標準サポートのみ(DRT 対応なし)

Shield Standard の限界:
– L7 アプリケーション層(HTTP Flood・Slowloris・CC 攻撃)は対象外 → WAF v2 で対処
– 大規模・持続的 DDoS(数 Gbps 以上)への対応は限定的
– DDoS 攻撃によるスケールアウトコスト(EC2/CloudFront 転送量増加)は補填されない


6-2. Shield Advanced 判断フロー

コスト: Shield Advanced は $3,000/月(年間 $36,000)+ 1 年契約必須。解約違約金あり。さらに Shield Advanced 保護リソースの AWS WAF 料金が別途発生します。

導入判断チェックリスト

判断基準推奨
DDoS による年間被害推定額 > $36,000→ Advanced 推奨(コスト保護で投資回収可能)
99.99% 以上の SLA が必要→ Advanced 推奨(DRT 24/7 直接対応)
CloudFront + ALB + Route 53 全リソース保護が必要→ Advanced 推奨(protection_group で統合管理)
DDoS 攻撃時の自動 WAF ルール生成が必要→ Advanced 推奨
小規模サービス・月次コスト最優先→ Standard 継続
月間 DDoS インシデントがほぼない→ Standard 継続

6-3. Shield Advanced 機能詳細

DDoS Response Team(DRT)直接対応

Shield Advanced 契約者は AWS の DDoS Response Team(DRT)に 24/7 で連絡できます。攻撃発生時に DRT がリアルタイムで WAF ルールを手動生成・適用します。サポートプランはEnterprise または Business が前提です(Technical Account Manager 経由の申請が必要)。

Proactive Engagement(プロアクティブエンゲージメント)

ELB/CloudFront/Route 53 のヘルスチェックと Shield Advanced を連携させると、ヘルスチェックが失敗した瞬間に DRT が自動的に連絡してきて対処を開始します(申し込み制)。

# Proactive Engagement を有効化
aws shield update-emergency-contact-settings \
  --emergency-contact-list \
 EmailAddress=security@example.com,PhoneNumber="+81312345678",ContactNotes="Shield DRT contact"

aws shield enable-proactive-engagement \
  --region us-east-1

WAF との一体化

攻撃検知時、DRT が直接 WAF Web ACL にルールを追加・修正します。aws_shield_protectionapplication_layer_automatic_response を設定すると、L7 攻撃を自動的に WAF でカウントまたはブロックします。

コスト保護(DDoS Cost Protection)

Shield Advanced 保護下のリソース(CloudFront / Route 53 / ALB / EC2 / Global Accelerator)で DDoS 攻撃によりスケールアウトした場合、攻撃由来の追加コストを AWS がクレジットで補填します。補填申請は DRT 経由で行います。


6-4. QG-4: Shield Standard vs Advanced 判断フロー

QG-4: Shield Standard vs Advanced 判断フロー

コスト比較

項目Shield StandardShield Advanced
月額$0(無償)$3,000/月(年間 $36,000)
契約不要1 年契約必須(解約違約金あり)
WAF 料金通常課金保護リソースの WAF 料金別途
DRT サポートなし24/7 直接対応(Enterprise/Business プラン必須)
L7 自動対応なしautomatic_response で COUNT/BLOCK
コスト保護なしDDoS 由来スケールアウト費用を補填

判断チェックリスト(Advanced 推奨条件)

  • ☐ 年間 DDoS 被害推定額 > $36,000 → Advanced のコスト保護で回収可能
  • ☐ SLA 99.99% 以上必須 → DRT 24/7 対応が不可欠
  • ☐ CloudFront + ALB + Route 53 を統合保護したい → protection_group 推奨
  • ☐ 攻撃時に自動 WAF ルール生成が必要 → Proactive Engagement + automatic_response
  • ☐ Enterprise/Business サポートプランを契約済み → DRT 連絡の前提条件

上記に該当しない場合は Shield Standard($0)を継続し、WAF v2 + CloudFront で L7 を防御するのがコスト最適解です。


6-5. 3点セット: Terraform HCL / AWS CLI / コンソール

Terraform HCL — Shield Advanced 完全実装

# provider.tf — Shield Advanced はグローバルリソース (us-east-1)
provider "aws" {
  alias  = "us_east_1"
  region = "us-east-1"
}

# --- Shield Advanced 有効化(アカウント単位)---
# terraform import で既存サブスクリプションを取り込む場合:
# terraform import aws_shield_subscription.main "aws"
resource "aws_shield_subscription" "main" {
  auto_renew = "ENABLED"
}

# --- CloudFront ディストリビューションを Shield Advanced で保護 ---
resource "aws_shield_protection" "cloudfront" {
  name= "cloudfront-main-protection"
  resource_arn = aws_cloudfront_distribution.main.arn

  tags = {
 Environment = "production"
 ManagedBy= "terraform"
  }

  depends_on = [aws_shield_subscription.main]
}

# --- Route 53 Hosted Zone を保護 ---
resource "aws_shield_protection" "route53" {
  name= "route53-hosted-zone-protection"
  resource_arn = "arn:aws:route53:::hostedzone/${var.hosted_zone_id}"

  depends_on = [aws_shield_subscription.main]
}

# --- Shield Protection Group(CloudFront + Route 53 をまとめて管理)---
resource "aws_shield_protection_group" "web_tier" {
  protection_group_id = "web-tier-protection-group"
  aggregation= "MAX"# SUM / MEAN / MAX
  pattern = "ARBITRARY"

  members = [
 aws_shield_protection.cloudfront.resource_arn,
 aws_shield_protection.route53.resource_arn,
  ]

  tags = {
 Environment = "production"
  }

  depends_on = [
 aws_shield_protection.cloudfront,
 aws_shield_protection.route53,
  ]
}

# --- L7 自動対応(Shield Advanced + WAF 連携)---
resource "aws_shield_application_layer_automatic_response" "cloudfront" {
  resource_arn = aws_cloudfront_distribution.main.arn
  action = "BLOCK"  # COUNT(観察)または BLOCK(遮断)

  depends_on = [
 aws_shield_protection.cloudfront,
 aws_wafv2_web_acl_association.cloudfront,
  ]
}

# --- DDoS 検知 CloudWatch Alarm ---
resource "aws_cloudwatch_metric_alarm" "ddos_detected" {
  alarm_name = "shield-ddos-detected-cloudfront"
  comparison_operator = "GreaterThanThreshold"
  evaluation_periods  = 1
  metric_name= "DDoSDetected"
  namespace  = "AWS/DDoSProtection"
  period  = 60
  statistic  = "Sum"
  threshold  = 0
  alarm_description= "Shield: DDoS attack detected on CloudFront distribution"
  treat_missing_data  = "notBreaching"

  dimensions = {
 ResourceArn = aws_cloudfront_distribution.main.arn
  }

  alarm_actions = [aws_sns_topic.security_alerts.arn]
}

# --- DDoS Attack Bits/Second アラーム(帯域幅急増検知)---
resource "aws_cloudwatch_metric_alarm" "ddos_attack_bits" {
  alarm_name = "shield-ddos-attack-bits-cloudfront"
  comparison_operator = "GreaterThanThreshold"
  evaluation_periods  = 2
  metric_name= "DDoSAttackBitsPerSecond"
  namespace  = "AWS/DDoSProtection"
  period  = 60
  statistic  = "Maximum"
  threshold  = 1000000000  # 1 Gbps
  alarm_description= "Shield: DDoS attack traffic exceeded 1 Gbps"
  treat_missing_data  = "notBreaching"

  dimensions = {
 ResourceArn = aws_cloudfront_distribution.main.arn
 AttackLayer = "NETWORK"
 AttackVector = "TCP_SYN_FLOOD"
  }

  alarm_actions = [aws_sns_topic.security_alerts.arn]
}

AWS CLI — Shield 保護状況確認

# Shield Advanced サブスクリプション確認
aws shield describe-subscription \
  --region us-east-1 \
  --output json | jq '.Subscription | {Status:.SubscriptionState, AutoRenew:.AutoRenew, StartTime:.StartTime}'

# 保護リソース一覧
aws shield list-protections \
  --region us-east-1 \
  --output table

# 特定リソースの保護詳細
aws shield describe-protection \
  --resource-arn "arn:aws:cloudfront::<account-id>:distribution/<dist-id>" \
  --region us-east-1

# DDoS アタックイベント履歴(直近 30 日)
aws shield list-attacks \
  --start-time "$(date -v-30d -u +%Y-%m-%dT%H:%M:%SZ)" \
  --end-time "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
  --region us-east-1 \
  --output json | jq '.AttackSummaries[]'

# Protection Group 一覧
aws shield list-protection-groups \
  --region us-east-1 \
  --output table

コンソール — Shield Advanced 有効化手順(5 ステップ)

  1. [WAF & Shield] → [AWS Shield] → [Get started with Shield Advanced] をクリック
  2. 保護するリソースを選択(CloudFront ディストリビューション / Route 53 / ALB など)
  3. DDoS Cost Protection の有効化を確認(デフォルトオン)
  4. 緊急連絡先メールアドレスを設定(DRT から直接連絡が届くアドレス)
  5. 利用規約(1 年契約・$3,000/月)に同意して [Activate Shield Advanced] をクリック

注意: Shield Advanced は有効化した翌月から課金が開始されます。1 年間の解約ができないため、導入前に年間 $36,000 のコスト対効果を必ず試算してください。Proactive Engagement は有効化後に別途 DRT に申請が必要です。


7. WAF Logs → S3 → Athena ログ分析

fig06: WAF Logs → S3 → Athena 分析パイプライン

WAF が検知・ブロックしたリクエストのログを S3 に蓄積し、Athena でアドホック分析する運用パターンを構築します。攻撃元 IP TOP 10・ルール別ブロック数・Bot Control ラベル分析の 3 クエリを実装し、インシデント対応と継続チューニングを自動化します。

7-1. WAF ログ有効化と S3 保存設計

AWS WAF v2 のログ送信先は S3・CloudWatch Logs・Kinesis Data Firehose の 3 択です。コスト効率と長期保存の観点から S3 + Athena 構成が本番推奨です。

送信先月額コスト目安特徴
S3 + Athena低 (S3 ストレージ + $5/TB スキャン)長期保存・アドホック分析向き
CloudWatch Logs中〜高 ($0.76/GB 取り込み)Insights 連携・短期モニタリング向き
Kinesis Firehose中 ($0.029/GB)リアルタイム転送・OpenSearch 連携向き

S3 バケット命名規則: WAF ログ専用バケット名は aws-waf-logs- プレフィックスが必須です (コンソール設定時に自動検証されます)。

S3 ライフサイクル設計 (90 日後 Glacier 移行を推奨):
– 0〜90 日: S3 Standard (高頻度アクセス・Athena 分析用)
– 90〜365 日: S3 Glacier (低コスト長期保存)
– 365 日以降: 削除 (または Glacier Deep Archive へ移行)

7-2. Glue Catalog + Athena テーブル定義

WAF ログの Athena 分析には Glue Data Catalog のテーブル定義が必要です。Glue Crawler (自動スキャン) より 手動 DDL を推奨します。理由: Crawler は実行コスト ($1/DPU 時間) がかかる上、WAF ログの JSON スキーマは既知のため自動検出の利点が薄いためです。

Partition Projection を使う理由 (MSCK REPAIR TABLE より効率的):
MSCK REPAIR TABLE は全パーティションをスキャンするためログ量が増えると実行時間が増大します。Partition Projection を使うと Athena がパーティション情報を動的に計算するため追加コストなしで高速化できます。

-- WAF ログ Athena テーブル DDL (Partition Projection 対応)
CREATE EXTERNAL TABLE IF NOT EXISTS waf_logs (
  timestamp  BIGINT,
  formatversion INT,
  webaclidSTRING,
  terminatingruleidSTRING,
  terminatingruletype STRING,
  action  STRING,
  httpsourcenameSTRING,
  httpsourceid  STRING,
  rulegrouplist ARRAY<STRUCT<
 ruleGroupId:STRING,
 terminatingRule:STRUCT<ruleId:STRING, action:STRING>,
 nonTerminatingMatchingRules:ARRAY<STRUCT<ruleId:STRING, action:STRING>>
  >>,
  nonterminatingmatchingrules ARRAY<STRUCT<ruleId:STRING, action:STRING>>,
  httprequest STRUCT<
 clientIp:STRING,
 country:STRING,
 headers:ARRAY<STRUCT<name:STRING, value:STRING>>,
 uri:STRING,
 args:STRING,
 httpversion:STRING,
 httpmethod:STRING,
 requestId:STRING
  >,
  labels ARRAY<STRUCT<name:STRING>>
)
PARTITIONED BY (year STRING, month STRING, day STRING)
ROW FORMAT SERDE 'org.openx.data.jsonserde.JsonSerDe'
WITH SERDEPROPERTIES ('ignore.malformed.json' = 'true')
LOCATION 's3://aws-waf-logs-ACCOUNT_ID/AWSLogs/ACCOUNT_ID/WAFLogs/cloudfront/WebACL-Name/'
TBLPROPERTIES (
  'projection.enabled'  = 'true',
  'projection.year.type'= 'integer',
  'projection.year.range'  = '2024,2030',
  'projection.month.type'  = 'integer',
  'projection.month.range' = '01,12',
  'projection.month.digits'= '2',
  'projection.day.type' = 'integer',
  'projection.day.range'= '01,31',
  'projection.day.digits'  = '2',
  'storage.location.template' = 's3://aws-waf-logs-ACCOUNT_ID/AWSLogs/ACCOUNT_ID/WAFLogs/cloudfront/WebACL-Name/${year}/${month}/${day}/'
);

7-3. Athena クエリ例 3 件

クエリ1: 攻撃元 IP TOP 10 (BLOCK されたリクエスト)

WAF がブロックしたリクエストの送信元 IP を集計し、悪意ある IP を特定します。繰り返しブロックされる IP は IP Set に追加して明示的なブロックを強化できます。

SELECT
 httprequest.clientip AS client_ip,
 httprequest.country  AS country,
 COUNT(*) AS block_count
FROM waf_logs
WHERE year= '2026'
  AND month  = '04'
  AND action = 'BLOCK'
GROUP BY
 httprequest.clientip,
 httprequest.country
ORDER BY block_count DESC
LIMIT 10;

クエリ2: Block 理由集計 (terminatingRuleId 別)

どのルール (Managed Rules ルールグループ ID) が最もブロックしているかを把握します。誤検知が多いルールは excluded_rules で除外・Count モードに切り替えてチューニングします。

SELECT
 terminatingruleid  AS rule_id,
 COUNT(*)  AS block_count,
 COUNT(DISTINCT httprequest.clientip) AS unique_ips,
 ROUND(
COUNT(*) * 100.0 / SUM(COUNT(*)) OVER (),
2
 ) AS block_pct
FROM waf_logs
WHERE year= '2026'
  AND month  = '04'
  AND action = 'BLOCK'
GROUP BY terminatingruleid
ORDER BY block_count DESC;

クエリ3: Bot Control ラベル別リクエスト数

Bot Control v2 が付与したラベル (Bot 種別) 別リクエスト数を集計します。正規クローラー (search-engine) と悪意ある Bot の比率を把握し、CAPTCHA/Challenge の適用範囲を調整します。

SELECT
 label.name AS bot_label,
 action,
 COUNT(*)AS request_count
FROM waf_logs
CROSS JOIN UNNEST(labels) AS t(label)
WHERE year  = '2026'
  AND month = '04'
  AND label.name LIKE 'awswaf:managed:aws:bot-control:%'
GROUP BY label.name, action
ORDER BY request_count DESC;
QG-5: WAF Logs → Athena 分析運用チェックリスト

  • ✅ WAF ログ送信先 S3 バケット名が aws-waf-logs- プレフィックスで始まっているか確認
  • ✅ S3 バケットに Block Public Access が有効化されているか確認
  • ✅ Athena テーブルに Partition Projection が設定済みか(MSCK REPAIR TABLE は S3 全スキャンのため非推奨)
  • ✅ クエリに WHERE year=... AND month=... AND day=... の partition フィルタが含まれているか(フルスキャン = 高額請求)
  • ✅ Athena Workgroup でデータスキャン上限(例: 1 TB/クエリ)が設定済みか
  • ✅ WAF ログフィルタ: BLOCK のみ保存 vs 全ログ保存のコストを試算済みか(全ログ保存はストレージ・スキャン費が数倍になる場合あり)
  • ✅ S3 ライフサイクル: 90 日後 Glacier・365 日後削除が設定済みか
  • terminatingRuleId = "Default_Action" は WAF WebACL のデフォルトアクション(ALLOW/BLOCK)で処理されたリクエストを意味することを把握済みか

7-4. 3 点セット: Terraform HCL + AWS CLI + コンソール

Terraform HCL (S3 + ライフサイクル + aws_wafv2_logging_configuration + Athena)

# WAF ログ保存用 S3 バケット
resource "aws_s3_bucket" "waf_logs" {
  provider = aws.us_east_1
  bucket= "aws-waf-logs-${data.aws_caller_identity.current.account_id}"
}

resource "aws_s3_bucket_public_access_block" "waf_logs" {
  provider = aws.us_east_1
  bucket= aws_s3_bucket.waf_logs.id
  block_public_acls = true
  block_public_policy  = true
  ignore_public_acls= true
  restrict_public_buckets = true
}

resource "aws_s3_bucket_server_side_encryption_configuration" "waf_logs" {
  provider = aws.us_east_1
  bucket= aws_s3_bucket.waf_logs.id
  rule {
 apply_server_side_encryption_by_default {
sse_algorithm = "AES256"
 }
  }
}

resource "aws_s3_bucket_lifecycle_configuration" "waf_logs" {
  provider = aws.us_east_1
  bucket= aws_s3_bucket.waf_logs.id
  rule {
 id  = "waf-logs-tiering"
 status = "Enabled"
 transition {
days = 90
storage_class = "GLACIER"
 }
 expiration {
days = 365
 }
  }
}

# WAF ログ設定 (BLOCK されたリクエストのみ保存してコスト最適化)
resource "aws_wafv2_logging_configuration" "main" {
  provider = aws.us_east_1
  log_destination_configs = [aws_s3_bucket.waf_logs.arn]
  resource_arn= aws_wafv2_web_acl.main.arn

  logging_filter {
 default_behavior = "DROP"
 filter {
behavior = "KEEP"
requirement = "MEETS_ANY"
condition {
  action_condition {
 action = "BLOCK"
  }
}
 }
  }
}

# Glue Database (Athena テーブル管理)
resource "aws_glue_catalog_database" "waf_analysis" {
  name = "waf_analysis"
}

# Athena Workgroup (スキャン上限 1 TB/クエリ)
resource "aws_athena_workgroup" "waf" {
  name = "waf-analysis"
  configuration {
 enforce_workgroup_configuration = true
 publish_cloudwatch_metrics_enabled = true
 result_configuration {
output_location = "s3://${aws_s3_bucket.waf_logs.bucket}/athena-results/"
 }
 bytes_scanned_cutoff_per_query = 1073741824000
  }
}

AWS CLI

# WAF ログ設定の確認
aws wafv2 get-logging-configuration \
  --resource-arn "arn:aws:wafv2:us-east-1:123456789012:global/webacl/MyWebACL/abc123" \
  --scope CLOUDFRONT \
  --region us-east-1

# WAF ログ有効化 (CLI から直接設定する場合)
aws wafv2 put-logging-configuration \
  --logging-configuration '{
 "ResourceArn": "arn:aws:wafv2:us-east-1:123456789012:global/webacl/MyWebACL/abc123",
 "LogDestinationConfigs": ["arn:aws:s3:::aws-waf-logs-123456789012"]
  }' \
  --region us-east-1

# Athena クエリ実行 (攻撃元 IP TOP 10)
QUERY_ID=$(aws athena start-query-execution \
  --query-string "SELECT httprequest.clientip, COUNT(*) AS cnt FROM waf_logs WHERE year='2026' AND month='04' AND action='BLOCK' GROUP BY httprequest.clientip ORDER BY cnt DESC LIMIT 10" \
  --work-group waf-analysis \
  --query-execution-context Database=waf_analysis \
  --region us-east-1 \
  --query 'QueryExecutionId' \
  --output text)

# クエリ完了確認 + 結果取得
aws athena get-query-execution \
  --query-execution-id "$QUERY_ID" \
  --region us-east-1 \
  --query 'QueryExecution.Status.State' \
  --output text

aws athena get-query-results \
  --query-execution-id "$QUERY_ID" \
  --region us-east-1

コンソール: WAF ログ記録の有効化 (3 ステップ)

  1. AWS WAF コンソール → 左メニュー「Web ACLs」→ 対象の Web ACL を選択
  2. 「ログ記録とメトリクス」タブ → 「ログ記録の有効化」ボタンをクリック
  3. ログ送信先タイプ「Amazon S3 バケット」→ バケット名 (aws-waf-logs-...) を入力 → 「ログ記録を有効にする」で保存

8. まとめ + 落とし穴10選 + 次回予告

8-1. 本記事の学習成果

本記事を通じて、AWS の Web セキュリティを支える 3 層防御スタック全体を Terraform で本番構築・運用できるようになりました。

防御層AWS サービス主な実装内容
L3/L4 DDoS 防御Shield Standard / Advanced§3 自動保護確認・§6 費用対効果判断フロー
L7 アプリ防御WAF v2 Managed Rules (8 セット)§4 WCU 計算・excluded_rules 設計
Bot 対策WAF Bot Control v2§5 COMMON/TARGETED 判断・CAPTCHA/Challenge
流量制限WAF Rate Limit v2§5 Token Bucket・aggregate_key 多種設計
Edge + GeoIPCloudFront§3 Geo restriction・キャッシュ最適化
ログ分析WAF Logs + S3 + Athena§7 Glue Catalog・クエリ 3 件・Partition Projection
IaCTerraform (aws_wafv2_* + aws_shield_protection)§2〜§7 全ハンズオン対応

習得スキル:
– WAF v2 Managed Rules 8 セットの WCU 計算・優先度設計・誤検知対策 (excluded_rules)
– Bot Control v2 (COMMON vs TARGETED) と Rate Limit v2 (aggregate_key 多種) の使い分け
– Shield Standard vs Advanced の費用対効果を定量的に判断するフロー ($3,000/月 + 1 年契約)
– WAF Logs → Athena による攻撃パターン可視化と継続チューニング
scope=CLOUDFRONT 特有の制約 (us-east-1 必須・Provider 分離) の完全理解

Terraform リソース一覧 (本記事実装):

Terraform リソース役割
aws_wafv2_web_aclWAF WebACL 定義 (スコープ・ルール・デフォルトアクション)
aws_wafv2_web_acl_associationWAF と CloudFront の関連付け
aws_wafv2_ip_set許可/ブロック IP セット管理
aws_wafv2_logging_configurationWAF ログ → S3 設定
aws_shield_protectionShield Advanced 保護対象リソース設定
aws_s3_bucket + lifecycleWAF ログ保存先・Glacier 移行設定
aws_glue_catalog_databaseAthena テーブル管理用 Glue Database
aws_athena_workgroupAthena クエリ実行環境・スキャン上限設定

8-0. 本番導入前 最終チェックリスト

本番環境への WAF v2 + Shield 適用前に以下を確認してください。

WAF v2 導入チェック:
– [ ] Web ACL scope=CLOUDFRONT かつ us-east-1 で作成済みか
– [ ] WCU 合計が 1500 以内に収まっているか (aws wafv2 get-web-acl --query 'WebACL.Capacity' で確認)
– [ ] 全 Managed Rules を COUNT モードで 1〜2 週間テスト済みか
– [ ] CRS の SizeRestrictions_BODY など高誤検知ルールを rule_action_override で除外済みか
– [ ] Bot Control の月額コスト試算済みか ($10/100 万 req × 月間リクエスト数)
– [ ] Rate Limit の aggregate_key が FORWARDED_IP または CUSTOM_KEYS に対応済みか

Shield 導入チェック:
– [ ] Shield Advanced の年間コスト ($36,000) vs DDoS 被害想定額の比較済みか
– [ ] Enterprise または Business サポートプランが契約済みか (DRT 対応の前提条件)
– [ ] DRT 緊急連絡先メールアドレスを update-emergency-contact-settings で設定済みか
– [ ] CloudFront + Route 53 + ALB を Protection Group で統合管理しているか

ログ・監視チェック:
– [ ] WAF ログ → S3 有効化済みか (バケット名 aws-waf-logs- プレフィックス確認)
– [ ] Athena テーブルに Partition Projection 設定済みか
– [ ] Athena Workgroup でスキャン上限 (1 TB/クエリ) 設定済みか
– [ ] DDoS 検知 CloudWatch Alarm (DDoSDetected メトリクス) 設定済みか
– [ ] WAF ルール変更時の CloudTrail アラート設定済みか

8-2. 落とし穴10選

本番運用で実際に遭遇したトラブル・設定ミス・コスト爆発の 10 事例をまとめました。Terraform 適用前に必ず確認してください。

QG-5 (続): WAF + Shield 本番運用 落とし穴10選

  1. scope=CLOUDFRONT → WAF WebACL は us-east-1 必須
    CloudFront に関連付ける WAF WebACL は必ず us-east-1 に作成が必要。REGIONAL スコープ (ap-northeast-1 等) に作成した WebACL は CloudFront に関連付け不可。Terraform では provider = aws.us_east_1 を忘れると ap-northeast-1 に作成されエラーになる。
  2. WCU 1500 上限 → Managed Rules 全有効化で超過する場合あり
    Web ACL の Capacity Unit (WCU) 上限はデフォルト 1500。Core Rule Set (700) + Known Bad Inputs (200) + SQLi (200) + Bot Control (最大 1500) を全て有効化すると超過する場合がある。事前に WCU 合計を計算し、必要であれば Service Quotas で上限引き上げを申請する。
  3. Bot Control 追加課金 → $10/100 万リクエスト
    Bot Control は通常の WAF 料金 ($0.60/100 万リクエスト) に加えて $10/100 万リクエストの追加課金が発生。CloudFront の月間リクエスト数を事前に確認し、月次コストを試算した上で有効化を判断する。
  4. Shield Advanced 1 年契約必須 → 解約時は残余期間分の費用が請求
    Shield Advanced は月額 $3,000 + 1 年契約。契約期間中の解約は残余期間分の費用が請求される。DDoS 被害想定額 vs $36,000/年 のコスト比較と DRT 24h サポートの必要性を慎重に評価した上で契約を決断する。
  5. CloudFront + WAF の関連付けは us-east-1 provider で実施
    aws_cloudfront_distributionweb_acl_id に WAF WebACL ARN を指定する際、WAF は us-east-1 で作成されているが CloudFront は global リソースのため provider 指定を省略しがち。WAF ARN が arn:aws:wafv2:us-east-1:...:global/webacl/... 形式であることを必ず確認する。
  6. OAC + カスタムヘッダー未設定 → Origin 直アクセスで WAF バイパス可能
    CloudFront + WAF を設定しても Origin (ALB/API Gateway) への直接アクセスが可能な状態だと WAF を完全にバイパスできる。OAC による S3 保護、ALB のセキュリティグループで CloudFront マネージドプレフィックスリストのみ許可することで Origin 直アクセスを防ぐ。
  7. excluded_rules 未設定で Managed Rules を本番 BLOCK 有効化 → 誤検知多発
    Managed Rules を COUNT モードでテストせずに BLOCK モードで有効化すると、正規リクエストをブロックして障害が発生する。必ず COUNT モードで 1〜2 週間テスト → WAF ログで誤検知確認 → excluded_rules 設定 → BLOCK モード移行の手順を守る。
  8. WAF ログ未設定 → インシデント時の攻撃元 IP・ルール特定不能
    WAF ログを有効化せずに本番運用すると、DDoS・攻撃インシデント発生時に攻撃元 IP・発動ルール・タイムラインを追跡できない。ログは最低でも S3 に有効化し、Athena テーブルを事前定義しておくことでインシデント時に即時分析に着手できる。
  9. Rate Limit の aggregate_key=IP のみ → X-Forwarded-For 偽装で回避可能
    Rate Limit で aggregate_key=IP のみを使用する場合、攻撃者が X-Forwarded-For ヘッダーを偽装することで制限を回避できる場合がある。FORWARDED_IP (信頼できるプロキシからの XFF) や HTTP_HEADER (API キー等) を aggregate_key に追加し、多角的な制限を実装する。
  10. Athena クエリの partition 未指定 → S3 フルスキャンで高額請求
    WHERE year=... AND month=... AND day=... の partition フィルタがないと S3 全データをスキャンし $5/TB の費用が発生する。WAF ログが 1 年分蓄積されると数百 GB〜数 TB になる場合があるため、クエリには必ず partition フィルタを含め、Athena Workgroup でスキャン量上限 (例: 1 TB/クエリ) を設定する。

8-3. 月次コスト試算まとめ

本番環境で本記事の構成を全採用した場合の月次コスト目安を整理します。実際のコストはトラフィック量・リクエスト数・ログ量に依存するため、適用前に自環境の数値で試算してください。

コンポーネント基本料金変動要素月額目安 (中規模)
WAF WebACL$5/WebACL/月$5
WAF ルール処理$0.60/100 万 req月間 1 億 req → $60$60
Bot Control$10/100 万 req月間 1 億 req → $1,000$1,000
Shield Advanced$3,000/月固定任意 (標準は $0)$0〜$3,000
WAF ログ S3$0.023/GBログ量依存$5〜$30
Athena スキャン$5/TBクエリ頻度依存$1〜$20
合計$71〜$4,115/月

コスト最適化のポイント:
– Bot Control は月間リクエスト数を確認してから有効化 (10 億 req/月 → $10,000/月)
– WAF ログフィルタで BLOCK のみ保存してストレージを削減
– Athena クエリには必ず partition フィルタを付けてスキャン量を最小化
– Shield Advanced は DDoS 被害想定額 > $36,000/年 のサービスのみ検討

8-4. 次回予告・関連記事

本記事で実装した CloudFront + WAF v2 + Shield の多層防御基盤をさらに発展させるリソースを紹介します。

記事テーマ関連度
CloudFront × WAF Bot Control 実践Bot Control 単機能に特化した実装解説 (WP ID:1392)
WAF + CloudFront Sorryページ完全実装ガイドWAF Block時のSorryページを3方式(S3静的/WAF Custom Response/Lambda@Edge)で設計。本記事の「Block後の出口」を完成させる
API Gateway + Lambda Authorizer 本番運用§7 で WAF 統合に言及・本記事で完全解説
CloudFront × ALB × S3 構成 Vol.1CloudFront 基本構成・本記事の前提知識

次回予告: CloudFront Functions / Lambda@Edge との連携セキュリティ (JWT 検証・A/B テスト・画像リサイズ) および WAF Fraud Control (Account Takeover Prevention・Credential Stuffing 対策) を解説予定です。

CloudFront × WAF Bot Control 実践ガイド

Sorryページ完全実装ガイド(WAF Block後の出口設計・S3静的/WAF Custom Response/Lambda@Edge)