AWS WAFのWebACLを複数リソースで共有している場合、レートリミットはリソース単位で効きますか?ルール単位で効きますか?

WebACLを共有している環境でレートリミット使うときは気をつけてね
2022.04.16

AWS WAFのWebACLは複数のリソースに関連付けて共有することが可能です。

WebACLあたり$5/月の料金がかかりますので、コストを意識してWebACLを複数のリソースで共有されている環境も少なくないかと思います。

今回、お客様より「1つのWebACLを共有しているとき、レートリミットは関連付けた各リソース単位なのか、WebACL単位なのか?」というご質問をいただきました。

さっとAWSドキュメントを確認してみましたが、私の日本語読解力ではWebACLを共有している場合の考え方について明確に記載されている箇所を見つけられなかったので検証してみました。

先に3行まとめ

  • レートリミットはWebACLのルール単位で効く
  • 各リソース毎にレートリミットを効かせたい場合は、ルールを分け、スコープダウンステートメントでHostヘッダーなどの条件で分岐させる
  • 2行でまとまった

確認したいこと

今回確認したいことを図にすると以下のとおりです。

  • WebACLは1つ、レートリミットのルールも1つ
  • 2つのCloudFrontが関連づけられている
  • 特定のクライアントからそれぞれのリソースにリクエストを送るとき、レートリミットは合算されるか?

やってみる

検証1: 1つのRate-based ruleを共有する

今回は2つのCloudFrontに対して1つのWebACLを関連づけています。

また、当該WebACLにはRate limit:100でBLOCKするルールを1つだけを作成しました。

まず、一方のCloudFrontにレートリミットを越えるリクエストを送ります。レートリミットはしきい値を越えても即時にブロックされるわけではありませんので、110回のリクエストを送ってもこのタイミングではすべて正常に処理されます。

$ for i in `seq 110`
> do
>  curl -s https://d357oxdfq7yglz.cloudfront.net/index.html -o /dev/null -w '%{http_code}\n'
> done
200
200
200
…
200
$

先のリクエストでレートリミットの100を越えていますのでブロックが適用されるまで1~2分ほど待ったのち、もう一方のCloudFrontにリクエストを送ります。

$ curl -I https://dmicdmyajdzr0.cloudfront.net/index.html
HTTP/2 403
server: CloudFront
date: Sat, 16 Apr 2022 07:07:03 GMT
content-type: text/html
content-length: 919
x-cache: Error from cloudfront
via: 1.1 c7b74e12b97e13a0b0116342d7b1e06c.cloudfront.net (CloudFront)
x-amz-cf-pop: NRT51-C3
x-amz-cf-id: bQPv59jXwjnm4dPxUdOjcdL03sNii8QJOXSMaM15KetzncJhmHEu7Q==

dmicdmyajdzr0.cloudfront.netへのリクエストは1回目にも関わらずブロックされました。このことからリクエスト先のリソースでの区別はされておらずWebACLのルール単位でレートリミットが判定されていることが解りました。

検証2: Rate-based ruleで分岐する

では次に、もし共有WebACL内で各リソース毎にレートリミットを持ちたい場合の検証をやってみましょう。

この場合は、以下のようにレートベースのルールを複数に分けて登録します。各ルール内のスコープダウンステートメントでHostヘッダーなどを条件にルールごとでカウントされるリクエストを分けることで実現します。

先ほどと同様にまず一方にレートリミットを超えるリクエストを送ります。

$ for i in `seq 110`
> do
>  curl -s https://d357oxdfq7yglz.cloudfront.net/index.html -o /dev/null -w '%{http_code}\n'
> done
200
200
200
..
200
$

1〜2分経過した後、リクエストを送りレートリミット超過が反映されてブロックされる状態であることを確認しました。

$ curl -I https://d357oxdfq7yglz.cloudfront.net/index.html
HTTP/2 403
server: CloudFront
date: Sat, 16 Apr 2022 08:02:38 GMT
content-type: text/html
content-length: 919
x-cache: Error from cloudfront
via: 1.1 56eaaf829f4d1d68353f81d5f1643e7c.cloudfront.net (CloudFront)
x-amz-cf-pop: NRT51-C2
x-amz-cf-id: vpwwcCaWqsjxtYlaUoBNx-35_6VNTAoLuZ8XsVE0rLs6l_eoDcVKDg==

この状態でもう一方のCloudFrontにリクエストを送ってみると、正常にリクエストが通ることが確認できました。

$ curl -I https://dmicdmyajdzr0.cloudfront.net/index.html
HTTP/2 200
content-type: text/html
content-length: 103
date: Sat, 16 Apr 2022 08:02:49 GMT
last-modified: Sat, 16 Apr 2022 05:52:18 GMT
etag: "e33ba3242ce535006082aa67863b25ab"
accept-ranges: bytes
server: AmazonS3
x-cache: Miss from cloudfront
via: 1.1 eae246afe964f7c3bcdcd6a113b3570e.cloudfront.net (CloudFront)
x-amz-cf-pop: NRT51-C3
x-amz-cf-id: e1Vyoo0Y33um72vmk1lA6VTWjch3lq96_LuUwuJRjEk2SR3ZZbvwWg==

試しにコチラでもレートリミットを超えるリクエストを送り、1〜2分経過した後に再度リクエストを送るとブロックされることが確認できました。

$ for i in `seq 110`
> do
>  curl -s https://dmicdmyajdzr0.cloudfront.net/index.html -o /dev/null -w '%{http_code}\n'
> done
200
200
200
...
200
$

$ curl -I https://dmicdmyajdzr0.cloudfront.net/index.html
HTTP/2 403
server: CloudFront
date: Sat, 16 Apr 2022 08:04:46 GMT
content-type: text/html
content-length: 919
x-cache: Error from cloudfront
via: 1.1 c0de8e3a0a5bd76a28840b4643d652c8.cloudfront.net (CloudFront)
x-amz-cf-pop: NRT51-C3
x-amz-cf-id: W_-5RHUZQt01cDx6BTfsr0-2Klon8FVwLdYtKCrtu0p77gwfMwRKQw==

AWS WAFのログを確認してみるといずれもRATE_BASEDBLOCKされていますがterminatingRuleIdが異なっています。このことから、それぞれのルール単位でレートリミットが効いていることが解りますね。

検証は以上です!

さいごに

コストを節約するためにWebACLを共有している環境も少なくないと思います。レートリミットを1つのルールで利用される場合、スコープダウンステートメントで絞り込みをしていない限りは、共有しているすべてのリソースへのリクエストがレートリミットのカウント対象になっていることに気を付けてください。

リソース毎にレートリミットを持ちたい場合は、個別にルールを作り、スコープダウンステートメントでHostヘッダー等を条件指定するようにしてください。