最初に結論
AWS WAF のマネージドルールを COUNT にすると、リクエストにラベル(ARN 形式の文字列)が付与されます。このラベルを後段のカスタムルールで拾ってリクエスト制御するパターンはよく使われますが、ラベルの文字列を1文字でも間違えると、エラーは一切出ないのにルールが機能しないという状態に陥ります。
前提:マネージドルール + カスタムルールの構成
AWS WAF でマネージドルールを柔軟に運用するとき、以下のような構成がよく使われます。
Step 1: マネージドルールの特定ルールを COUNT に切り替える
マネージドルールの中で、特定パスだけ除外したいルールがあるとします。マネージドルール単体では「このパスだけ除外」のような細かい制御ができないため、一旦 COUNT モードにしてリクエストをブロックせずにラベルだけ付与させます。
managed_rule_group_statement {
name = "AWSManagedRulesCommonRuleSet"
vendor_name = "AWS"
rule_action_override {
name = "SizeRestrictions_BODY" # ← これはルール名
action_to_use {
count {}
}
}
}
このとき AWS は、マッチしたリクエストに対して以下のようなラベルを付与します。
awswaf:managed:aws:core-rule-set:SizeRestrictions_Body
### Step 2: カスタムルールでラベルを拾ってリクエスト制御
後段のカスタムルールで `LabelMatchStatement` を使い、Step 1 で付与されたラベルを拾います。これにより「特定パスだけ許可、それ以外はブロック」という細かい制御が可能になります。
```hcl
rule {
name = "Check_SizeRestrictions_BODY"
action = "block"
statement {
and_statement {
statements {
label_match_statement {
scope = "LABEL"
key = "awswaf:managed:aws:core-rule-set:SizeRestrictions_Body"
# ^^^^
# ここが正しければ機能する
}
}
statements {
not_statement {
byte_match_statement {
# 除外したいパスの条件
search_string = "/api/image/upload"
positional_constraint = "STARTS_WITH"
field_to_match { type = "uri_path" }
}
}
}
}
}
}
マネージドルール(COUNT)→ ラベル付与 → カスタムルールで拾ってブロック、この流れ自体は AWS が推奨するパターンです。
落とし穴:ラベルの ARN はキャメルケース
ここからが本題です。
マネージドルールが付与するラベルはこういう形式です。
awswaf:managed:aws:core-rule-set:SizeRestrictions_Body
^^^^
PascalCase
一方、ルール名はこうです。
SizeRestrictions_BODY
^^^^
全大文字
ルール名とラベルで大文字小文字が違います。
| ルール名 | 付与されるラベル |
|---|---|
SizeRestrictions_BODY |
...SizeRestrictions_Body |
CrossSiteScripting_BODY |
...CrossSiteScripting_Body |
ルール名は _BODY(全大文字)ですが、ラベルは _Body(PascalCase)です。
ルール名を見て key を書くと、こうなります。
label_match_statement {
scope = "LABEL"
key = "awswaf:managed:aws:core-rule-set:SizeRestrictions_BODY"
# ^^^^
# ルール名からコピーして _BODY と書いてしまう
}
LabelMatchStatement は case-sensitive です。_BODY と _Body は別の文字列なので、このルールは一生マッチしません。
最大の罠:間違えてもエラーにならない
ここが一番厄介なところです。
間違ったラベルで terraform plan → terraform apply しても、すべて成功します。 AWS API もエラーを返しません。
なぜか。
AWS WAF のラベルはもともと任意の文字列を付けられる仕組みです。マネージドルールが付与するラベルだけでなく、カスタムルールでも自分で定義したラベルを自由に付けることができます。LabelMatchStatement はリクエストに付いたラベル文字列をただ比較しているだけです。
つまり AWS から見れば、SizeRestrictions_BODY というラベルを参照しようとしているのが「マネージドルールのラベルのタイポ」なのか「カスタムラベルの正しい参照」なのか区別がつきません。どちらも有効な文字列なので、エラーにしようがないのです。
IAM ポリシーで存在しないリソースの ARN を書けばエラーになりますが、WAF のラベルマッチはそうはいきません。
どうなるか:一見動いているように見える
間違ったラベルを設定しても、WAF 全体としてはエラーなく動き続けます。
リクエスト着信
↓
マネージドルール発火 → ラベル付与: SizeRestrictions_Body (正しいラベル)
↓
カスタムルールで LabelMatchStatement 評価
key = "SizeRestrictions_BODY" ← 設定した値
≠ "SizeRestrictions_Body" ← 実際のラベル
↓
マッチしない → and_statement = false → ルール不発火
↓
リクエスト通過(ブロックされない)
- WAF のコンソールにルール自体は表示されている
-
terraform planでも差分なし - エラーログも出ない
- ただし、ブロックすべきリクエストがすべて素通りしている
「セキュリティルールを設定したはずなのに、実は何も守っていなかった」という状態が、気づかれないまま続きます。
気づくには
WAF ログを確認します。
- マネージドルールが COUNT で付与したラベルがログに記録されている
- しかし後段のカスタムルールが BLOCK していない
- リクエストの最終アクションが
ALLOWになっている
この組み合わせが見えたら、ラベルの不一致を疑ってください。
対策
1. 公式ドキュメントの Labels 列からコピーする
ルール名ではなく、必ず 公式ドキュメントの Labels 列 からラベル文字列をコピーしてください。
2. locals で一元管理する
locals {
waf_labels = {
size_restrictions_body = "awswaf:managed:aws:core-rule-set:SizeRestrictions_Body"
cross_site_scripting_body = "awswaf:managed:aws:core-rule-set:CrossSiteScripting_Body"
}
}
ラベルを変数化して参照すれば、複数箇所で同じ文字列を書くリスクがなくなります。
3. 設定直後にテストリクエストで確認する
意図的にルール発火条件を満たすリクエスト(8KB超のボディなど)を投げて、ブロックされることを確認しましょう。dev/stg で「なぜか通る」場合はラベルを疑ってください。
まとめ
マネージドルールの「ルール名」 ≠ 「付与されるラベル」
- マネージドルールを COUNT にするとラベル(ARN 形式)が付与される
- そのラベルを
LabelMatchStatementで拾ってリクエスト制御するのはよくあるパターン - ラベルの ARN はキャメルケースで書かれているが、ルール名は全大文字の箇所がある
- 間違った文字列を書いても AWS はエラーを返さない(ラベルは任意の文字列なのでバリデーションできない)
- 結果、一見ルールが動いているように見えて、実は何もブロックしていない状態になる
ルール名をそのままコピーしてラベルの key に使うのは自然な行動ですが、それが罠です。