TerraformでWAF v2を設定しようとすると、こういった疑問が次々と出てきます。AWSコンソールでは画面に沿ってクリックすれば一応動きますが、コードで書こうとすると構造の把握から始める必要があります。
この記事では、TerraformでAWS WAF v2(aws_wafv2_web_acl)をALBに適用する方法を解説します。マネージドルールの選定からカスタムルールの書き方、CloudWatch Logsへのログ出力設定まで、実際に動く構成を使って設計の「型」を身につけてください。
動作確認環境:Terraform v1.9.x、AWS provider 5.x、ap-northeast-1(東京リージョン)
この記事のポイント
・TerraformでWAF v2をALBに適用するにはaws_wafv2_web_aclとaws_wafv2_web_acl_associationを使う
・マネージドルール(AWSManagedRulesCommonRuleSet等)はoveride_actionで有効・無効を切り替えられる
・IPブロック・レートリミットのカスタムルールはstatementブロックで宣言的に記述できる
・CloudWatch Logsへのログ出力はaws_wafv2_web_acl_logging_configurationで設定する
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
WAF v2をTerraformで管理する理由
AWS WAF v2(Web Application Firewall)は、SQLインジェクションやXSSなどの一般的なWeb攻撃をALB・CloudFront・API Gateway等の前段でブロックするサービスです。コンソールから設定する場合は画面に沿ってクリックすれば動きますが、複数環境(dev/stg/prod)への展開や変更履歴の管理がしにくいという問題があります。Terraformでコード化すると以下のメリットがあります。
・環境ごとのルールの差分を変数で管理できる
・`terraform plan` で変更内容を適用前に確認できる
・ルール追加・削除の履歴がGitで追跡できる
・CIパイプラインでセキュリティルールの変更を自動レビューできる
WAF v2とWAF Classic(旧版)の違いも整理しておきます。
| 比較項目 | WAF v2 | WAF Classic(非推奨) |
|---|---|---|
| Terraformリソース | aws_wafv2_web_acl | aws_waf_web_acl |
| マネージドルール | AWSマネージドルールグループで利用可 | なし |
| レートベースルール | ステートメントとして記述 | 別リソースで定義が必要 |
| ログ出力 | CloudWatch Logs・Kinesis・S3に対応 | Kinesisのみ |
| スコープ指定 | REGIONAL / CLOUDFRONT を選択 | なし |
WAF v2の構成要素
WAF v2をTerraformでコード化する際に作成するリソースを整理します。| リソース | Terraformリソース名 | 役割 |
|---|---|---|
| WebACL | aws_wafv2_web_acl | ルールをまとめる親要素。デフォルトアクションを定義する |
| WebACLとALBの紐付け | aws_wafv2_web_acl_association | WebACLをALBなどのリソースに適用する |
| IPセット | aws_wafv2_ip_set | ブロック・許可するIPアドレスのリスト |
| ログ出力設定 | aws_wafv2_web_acl_logging_configuration | WAFのリクエストログをCloudWatch Logsへ送信する |
マネージドルールを含むWebACLを作成する
1. WebACLの基本構造
`aws_wafv2_web_acl` リソースはネストが深くなりがちです。まず最小構成から書いて理解を進めましょう。# waf.tf resource "aws_wafv2_web_acl" "main" { name = "${var.app_name}-web-acl" scope = "REGIONAL" # ALB/API Gateway用。CloudFront用はCLOUDFRONT default_action { allow {} # マッチしないリクエストはすべて許可 } visibility_config { cloudwatch_metrics_enabled = true metric_name = "${var.app_name}-web-acl" sampled_requests_enabled = true } }
2. AWSマネージドルールを追加する
AWSが提供するマネージドルールグループを追加します。最もよく使われるのは `AWSManagedRulesCommonRuleSet`(一般的なWebアプリ攻撃をまとめてブロック)と `AWSManagedRulesKnownBadInputsRuleSet`(ログ4jのような重大脆弱性の悪用を防ぐ)です。resource "aws_wafv2_web_acl" "main" { name = "${var.app_name}-web-acl" scope = "REGIONAL" default_action { allow {} } # ルール1: AWSマネージドルール(共通ルールセット) rule { name = "AWSManagedRulesCommonRuleSet" priority = 10 override_action { none {} # マネージドルールのアクションをそのまま使う } statement { managed_rule_group_statement { name = "AWSManagedRulesCommonRuleSet" vendor_name = "AWS" } } visibility_config { cloudwatch_metrics_enabled = true metric_name = "AWSManagedRulesCommonRuleSet" sampled_requests_enabled = true } } # ルール2: Log4j等の重大脆弱性の悪用を防ぐ rule { name = "AWSManagedRulesKnownBadInputsRuleSet" priority = 20 override_action { none {} } statement { managed_rule_group_statement { name = "AWSManagedRulesKnownBadInputsRuleSet" vendor_name = "AWS" } } visibility_config { cloudwatch_metrics_enabled = true metric_name = "AWSManagedRulesKnownBadInputsRuleSet" sampled_requests_enabled = true } } visibility_config { cloudwatch_metrics_enabled = true metric_name = "${var.app_name}-web-acl" sampled_requests_enabled = true } }
>> Terraform実践セミナーの詳細はこちら
カスタムルールを追加する
マネージドルールでカバーできない要件は、カスタムルールで対応します。代表的なのは「特定IPのブロック」「レートリミット(DDoS対策)」です。1. 特定IPをブロックするIPセット
# waf_ip_set.tf resource "aws_wafv2_ip_set" "blocked" { name = "${var.app_name}-blocked-ips" scope = "REGIONAL" ip_address_version = "IPV4" addresses = [ "203.0.113.0/24", "198.51.100.128/25", ] } # WebACLにIPブロックルールを追加 resource "aws_wafv2_web_acl" "main" { # ... (前述の設定に以下のruleブロックを追加) rule { name = "BlockedIPs" priority = 5 # マネージドルールより高い優先度(数値が小さいほど先に評価) action { block {} } statement { ip_set_reference_statement { arn = aws_wafv2_ip_set.blocked.arn } } visibility_config { cloudwatch_metrics_enabled = true metric_name = "BlockedIPs" sampled_requests_enabled = true } } }
2. レートベースルールでDDoSを緩和する
同一IPからの過剰なリクエストを自動的にブロックします。`limit` は5分間の閾値です。rule { name = "RateLimit" priority = 30 action { block {} } statement { rate_based_statement { limit = 2000 # 5分間で2000リクエストを超えたらブロック aggregate_key_type = "IP" } } visibility_config { cloudwatch_metrics_enabled = true metric_name = "RateLimit" sampled_requests_enabled = true } }
WebACLをALBに適用する
# waf_association.tf resource "aws_wafv2_web_acl_association" "alb" { resource_arn = aws_lb.main.arn web_acl_arn = aws_wafv2_web_acl.main.arn }
CloudWatch Logsへのログ出力を設定する
WAFのログを取得すると、どのリクエストがブロックされたかを確認できます。本番運用では必ず設定します。# waf_logging.tf resource "aws_cloudwatch_log_group" "waf" { # WAFのログはロググループ名が "aws-waf-logs-" で始まる必要がある name = "aws-waf-logs-${var.app_name}" retention_in_days = 90 } resource "aws_wafv2_web_acl_logging_configuration" "main" { log_destination_configs = [aws_cloudwatch_log_group.waf.arn] resource_arn = aws_wafv2_web_acl.main.arn }
terraform plan・applyで確認する
$ terraform plan # aws_cloudwatch_log_group.waf will be created # aws_wafv2_ip_set.blocked will be created # aws_wafv2_web_acl.main will be created # aws_wafv2_web_acl_association.alb will be created # aws_wafv2_web_acl_logging_configuration.main will be created Plan: 5 to add, 0 to change, 0 to destroy. $ terraform apply -auto-approve aws_cloudwatch_log_group.waf: Creating... aws_wafv2_ip_set.blocked: Creating... aws_wafv2_web_acl.main: Creating... aws_wafv2_web_acl.main: Creation complete after 3s aws_wafv2_web_acl_association.alb: Creating... aws_wafv2_web_acl_logging_configuration.main: Creating... aws_wafv2_web_acl_association.alb: Creation complete after 1s aws_wafv2_web_acl_logging_configuration.main: Creation complete after 1s Apply complete! Resources: 5 added, 0 changed, 0 destroyed.
# SQLインジェクションパターンを含むリクエストでブロックを確認 $ curl -s -o /dev/null -w "%{http_code} " "http://myapp-alb-1234567890.ap-northeast-1.elb.amazonaws.com/?id=1%20OR%201%3D1" 403
よくあるエラーと対処法
1. ログ設定でInvalidParameterExceptionが返る
Error: creating WAFv2 Web ACL Logging Configuration: InvalidParameterException: Firehose Arn not in allowed format
2. WAFルールを変更するたびにWebACL全体が再作成される
`aws_wafv2_web_acl` のruleブロックの順序を変更すると、Terraformがリソースの再作成(destroy → create)を提案することがあります。これはpriority値で順序を管理しているため、実際には順序変更を`terraform plan`で確認しても内容が同じなら再作成は不要です。planの出力をよく確認して意図しない再作成でないことを確かめてください。3. マネージドルールが正規のリクエストをブロックする(誤検知)
`override_action { count {} }` に変更してブロックせずカウントに切り替え、CloudWatchのメトリクスとWAFログを確認します。誤検知しているルール名が分かったら、そのルールのみを `excluded_rule` で除外できます。statement { managed_rule_group_statement { name = "AWSManagedRulesCommonRuleSet" vendor_name = "AWS" # 特定ルールのみ除外する(誤検知対策) excluded_rule { name = "SizeRestrictions_BODY" } } }
本記事のまとめ
| やりたいこと | Terraformリソース・設定 |
|---|---|
| WebACLを作成する | aws_wafv2_web_acl(scope=REGIONALまたはCLOUDFRONT) |
| AWSマネージドルールを追加する | managed_rule_group_statement(vendor_name="AWS") |
| 本番前に動作確認する | override_action { count {} }でカウントモードに切り替え |
| 特定IPをブロックする | aws_wafv2_ip_set + ip_set_reference_statement |
| レートリミットを設定する | rate_based_statement(limit=5分間の閾値) |
| ALBに適用する | aws_wafv2_web_acl_association |
| ログを取得する | aws_wafv2_web_acl_logging_configuration(aws-waf-logs-で始まるロググループ名が必須) |
>> Terraform実践セミナーの詳細はこちら
3,100名以上が実践した「型」を無料で公開中
プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。
姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら
- 前のページへ:TerraformでEKSクラスターを構築する方法|managed node groupとPod Identityの実践設計
- この記事の属するカテゴリ:Terraformへ戻る

無料メルマガで学習を続ける
Linuxの実践スキルをメールで毎週お届け。
登録は30秒、解除もいつでも可。
登録無料・いつでも解除できます