TerraformでWAF v2(aws_wafv2_web_acl)をALBに適用する方法|マネージドルールとカスタムルールの設計

宮崎智広 この記事の監修:宮崎智広(Linux実務・教育歴20年以上・受講者3,100名超)
HOMELinux技術 リナックスマスター.JP(Linuxマスター.JP)Terraform > TerraformでWAF v2(aws_wafv2_web_acl)をALBに適用する方法|マネージドルールとカスタムルールの設計
「ALBにWAFを適用したいけど、マネージドルールだけで本当に防げるのか?カスタムルールはどこで書けばいい?」
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で設定する


「このままじゃマズい」と感じていませんか?
参考書を開く気力もない、同年代に取り残される不安——
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら

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 Classic(aws_waf_web_acl)はAWSが非推奨としており、新規構築にはWAF v2(aws_wafv2_web_acl)を使うべきです。Terraformリソース名が変わるので移行時は注意してください。

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 } }

`scope = "REGIONAL"` はALB・API Gateway・Cognitoに適用するときの指定です。CloudFrontに適用する場合は `scope = "CLOUDFRONT"` に変え、プロバイダーリージョンを `us-east-1` にする必要があります(CloudFrontのWAF v2はus-east-1専用)。

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 } }

マネージドルールの `override_action` には2種類あります。`none {}` はルールが定義するアクション(BlockやCount)をそのまま実行します。`count {}` はブロックせずカウントのみ行うモードで、本番適用前の動作確認に使います。まず `count {}` で適用してCloudWatchのメトリクスを確認し、誤検知がないことを確かめてから `none {}` に切り替えるのが安全な手順です。
現場で通用する安全なLinuxサーバー構築の「型」を体系的に身につけたい方へ、WAF v2設計・Terraformセキュリティ実装から本番運用まで、ハンズオン形式で習得できるセミナーを開催しています。
>> 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 } }

`priority` の値は小さいほど先に評価されます。「IPブロック(priority=5)→ マネージドルール(priority=10, 20)→ レートリミット(priority=30)」の順にすることで、明示的なブロック対象を最初に処理し、通過したリクエストのみマネージドルールで検査します。

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 }

`resource_arn` にはALBのARNを指定します。他にAPI GatewayのステージARN、AppRunnerのサービスARN、Cognitoのユーザープール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 }

重要な制約があります。CloudWatch Logsのロググループ名は必ず `aws-waf-logs-` で始まる必要があります。この命名規則を守らないと `terraform apply` 時にAPIエラーが返ります。本番で実際にこのエラーに遭遇したことがあるので、先に押さえておいてください。

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

403が返ればWAFがブロックしています。CloudWatch Logsのロググループ `aws-waf-logs-myapp` に詳細なリクエストログが記録されます。

よくあるエラーと対処法

1. ログ設定でInvalidParameterExceptionが返る

Error: creating WAFv2 Web ACL Logging Configuration: InvalidParameterException: Firehose Arn not in allowed format

`aws_wafv2_web_acl_logging_configuration` の `log_destination_configs` に渡すARNはロググループのARNが必要です。CloudWatch LogsのロググループARNは `arn:aws:logs:ap-northeast-1:123456789012:log-group:aws-waf-logs-myapp` の形式です。`aws_cloudwatch_log_group.waf.arn` を参照すれば自動的に正しいARNが取得されます。

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" } } }

全ルールをexcludeするくらいなら `override_action { count {} }` のままにした方が管理しやすいです。本番前に十分なテスト期間(1~2週間のcountモード)を設けることをおすすめします。

本記事のまとめ

やりたいこと 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でWAF v2を設定する際のポイントは3つです。①マネージドルールは `count {}` モードで動作を確認してから `none {}` に切り替える、②カスタムルールは `priority` 値で評価順序を管理する、③CloudWatch Logsのロググループ名は `aws-waf-logs-` で始める必要がある。Terraformでコード化することで、ルール変更の意図が明確になり、環境間の一貫性を保てます。
現場で通用する安全なLinuxサーバー構築の「型」を体系的に身につけたい方へ、WAF v2設計・Terraformセキュリティ実装から本番運用まで、ハンズオン形式で習得できるセミナーを開催しています。
>> Terraform実践セミナーの詳細はこちら

無料メルマガで学習を続ける

Linuxの実践スキルをメールで毎週お届け。
登録は30秒、解除もいつでも可。

登録無料・いつでも解除できます

暗記不要・1時間後にはサーバーが動く

3,100名以上が実践した「型」を無料で公開中

プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。

姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら

Linux無料マニュアル(図解60P) 名前とメールで30秒登録
宮崎 智広

この記事を書いた人

宮崎 智広(みやざき ともひろ)

株式会社イーネットマーキュリー代表。現役のLinuxサーバー管理者として20年以上の実務経験を持ち、これまでに累計3,100名以上のエンジニアを指導してきたLinux教育のプロフェッショナル。「現場で本当に使える技術」を体系的に伝えることをモットーに、実践型のLinuxセミナーの開催や無料マニュアルの配布を通じてLinux人材の育成に取り組んでいる。

趣味は、キャンプにカメラ、トラウト釣り。好きな食べ物は、ラーメンにお酒。休肝日が作れない、酒量を減らせないのが悩み。最近、ドラマ「フライトエンジェル」を観て涙腺が崩壊しました。