AWS WAFv2で条件付きrate-based ruleを設定したら凡ミスで詰まった話


前置き

「特定のURIパスだけにRate limitかけるAWS WAFのルール作れる?」
WAFv2で簡単にできた...はずがなんか上手く結果が出ず、しかし結局どうにかなったのでメモ。

関連リソース

AWS WAFv2

https://docs.aws.amazon.com/waf/latest/APIReference/Welcome.html#Welcome_AWS_WAFV2
今回WAF関連の操作は全てAWSコンソール上で実施。

その他

WAFの先にあるCloudFrontやリクエストテスト用のPCについては本題ではないため割愛...

設定ルール

json

GUIで設定してもjson出力をすぐ確認できるのいいですね。

{
  "Name": "wasshoi-path-rate",
  "Priority": 0,
  "Action": {
    "Block": {}
  },
  "VisibilityConfig": {
    "SampledRequestsEnabled": true,
    "CloudWatchMetricsEnabled": true,
    "MetricName": "wasshoi-path-rate"
  },
  "Statement": {
    "RateBasedStatement": {
      "Limit": 100,
      "AggregateKeyType": "IP",
      "ScopeDownStatement": {
        "ByteMatchStatement": {
          "FieldToMatch": {
            "UriPath": {}
          },
          "PositionalConstraint": "CONTAINS",
          "SearchString": "/hoge/fuga/wasshoi",
          "TextTransformations": [
            {
              "Type": "NONE",
              "Priority": 0
            }
          ]
        }
      }
    }
  }
}

GUI

こちらも平易な英語で細かく注釈がついた新設設計。

  • Type: Rate-based rule
  • Rate limitの値: 今回は検証目的のため最低値の100)
    • 注釈に記載の通り、単一IPからのリクエスト数について5分間で集計して、この値を超える数のリクエストが来た場合にルール適用となる。
  • 「Only consider requests that match the criteria in a rule statement」を選択して、rate limitを適用する条件を指定

  • URIパスを指定
  • ActionはBlock
    • (本番に入れるときはCountで試してからじゃないと事故った時エグい)

こんなルールをPriority 0(最優先)で入れて保存。

画面見ながらポチポチですげーかんたんだな!ワッハッハ!とイキりつつ検証を始めたんですが、そっからが地味に長かった。

※ネタバレ:設定はこれで合ってました。

その後の顛末

検証1: 「まずはRate limit部分だけで確認してみよ」→無事成功

先ほどの設定の、URIパス部分を削った設定です。consider all requestsを選択した、ただのRate Limit。

{
  "Name": "wasshoi-path-rate",
  "Priority": 0,
  "Action": {
    "Block": {}
  },
  "VisibilityConfig": {
    "SampledRequestsEnabled": true,
    "CloudWatchMetricsEnabled": true,
    "MetricName": "wasshoi-path-rate"
  },
  "Statement": {
    "RateBasedStatement": {
      "Limit": 100,
      "AggregateKeyType": "IP"
    }
  }
}

これの適用後、外部からURIパス/hoge/fuga/wasshoiを連続120回curlで叩くスクリプトを実行。
結果:成功。101回目のリクエストからしっかりBlockされ、レスポンスコード403が返ってくるように。

検証2: 「本番と同等の設定で確認」→上手くいかない。

検証1が成功したので、引き続きそのルールを編集し先述の「設定ルール」の通りに更新。
完了後に検証1と同じスクリプトを実行。
結果:失敗。curlの結果は全て素通りしレスポンスコード200、WAF側はルールに引っかかった様子無し

あれ???

検証3: 「何か勘違いしていたかもしれないので設定を微調整してみる」→当然上手くいかない。

パスに*を入れ込んでみたりContains String部分を別な条件にしてみたり。様々な設定で都度スクリプトを流してみたが結果は全て検証2と同じ。だめだなんもわからん。

結論: 一晩寝かせたら上手くいきました。

正確にはAWSサポートへの技術質問を経ての解決ですが、結論としては以下。

  • WAFのルールの反映にはタイムラグがある
  • 送信元のIPアドレスをブロックするまでにもタイムラグがある

検証1だと即時反映即時Blockだったよな...とは思いつつ、検証2以降でも適用後即スクリプト叩きをやっていたのは事実。
素直にタイムラグを意識して再度検証してみた。

Block成功!
改めてルールを設定し、今度は検証スクリプトを1分〜20分間隔で繰り返し実行(別作業の片手間で雑にやっていたので間隔バラバラです)。
すると、ルール設定から約2時間ほど経った頃、丸ごと120回分のリクエストがBlockされる挙動を確認。WAFの記録上もしっかり計上されていました。

さらにしばらく待ち、時間経過でBlockが解除されることも確認。めでたしめでたし。

まとめ

  • WAFv2のルール設定は反映に時間がかかる場合がある
  • 「検証用に一部だけ設定したものを使おう」が罠
    • 厳密には別の物で調べている事になり、(今回のように)検証として意味をなさない
  • 果報は寝て待て(諸説あります)

おわり。

おそらく余りにお間抜けな原因でハマってたからだと思うんですが、ネットでいくら調べても今回の事象に行き当たらずそこそこ辛かった。