Cloudfront を導入時はレートリミットにも気をつけよう

TECH記事

はじめに

先日ALBの前段にCloudFrontを導入した際に、4XX系のエラーが爆増する現象に遭遇しました

開発環境での確認段階で気づけたのでセーフでしたが、ある程度リクエスト量をかけないと再現しない現象で見落としそうになったため、備忘録を残しておきます

同様の構成を検討されている方々の参考になれば幸いです。

構成図

今回想定しているインフラの構成は以下です

なお、Cloudfront の後ろに WAF を配置した理由は、 CDNキャッシュ後にWAFを通すことでWAFルールが検査するリクエスト数が減るのでコストメリットがありそうと考えたためです。

4XX 系エラーの原因

Cloudfront 導入後に 4XX 系エラーが増加してしまった原因は大きく 2 つありました

  1. WAFのRatelimitルールによるブロック
  2. アプリケーション(PHP, Laravel)のRatelimit

両方に共通する根本原因は、Cloudfrontを経由することで、WAFやアプリケーションから見たクライアントのIPアドレスが、実際のユーザーのIPではなくCloudfrontのエッジサーバーのIPアドレスに置き換わってしまったことでした。

図解すると、以前は以下のように User IP を直接とれていましたが

Cloudfront導入後は IP が置換されてしまうため、正しいクライアントIPが認識できなくなり、多数のリクエストがレートリミットでブロックされてしまいました。

原因1. WAF の Ratelimit による 403 エラー

400 系エラーが増えた 1 つ目の原因は WAF によるレート制限です

WAFには、DDos的なアクセスへの対策として、同一IPからの一定レートを超えるアクセスはブロックするルールが設定されていました

ただ構成図で示した通り、今回はWAFの前段にCloudfrontがいるため、何も意識をしないと Cloudfront の IP を見てレート制限をかけてしまいます

対策としては、WAFのレート制限はIPアドレスをどの値で集計するか選ぶことが出来るため、Source IP を使った集計ではなく、X-Fowarded-For ヘッダーの値から集計するように設定することで、クライアントのIPを正しく判定できるようになりました。

コンソールから設定する場合、リクエストの集約を [ヘッダー内のIPアドレス]、ヘッダーフィールド名 を「X-Forwarded-For」に設定することで、Cloudfrontの前段にあるIPアドレスを見てレート制限をかけることができます

原因2. アプリケーション (laravel) API Throttle による 429 エラー

Webアプリケーション側でも、IPベースでのAPIのレート制限機能を持っています。
今回の環境でも、Laravel の API Throttle 機能が有効化されており、アプリケーションレイヤーでも 429 エラーを返していました

API Throttleの参考: https://kinsta.com/jp/blog/laravel-throttle/

そこで、対策として以下の設定を追加しました

  1. Cloudfront で、CloudFront-Viewer-Address をヘッダーを付与して後続のALBに流す
  2. Apche で CloudFront-Viewer-Address を受け取り、RemoteIP として扱う設定を追加する

Cloudfront で、CloudFront-Viewer-Address をヘッダーを付与

Cloudfront では後続のALBになどにHTTPリクエストをリレーする際、追加ヘッダーを設定することができ、そのうちの一つに「CloudFront-Viewer-Address」というヘッダーがあります。

CloudFront-Viewer-Address – ビューワーの IP アドレスと、リクエストのソースポートを示します。例えば、198.51.100.10:46532 のヘッダー値は、ビューワーの IP アドレスが 198.51.100.10 で、リクエストのソースポートが 46532 であることを意味します。

CloudFront のリクエストヘッダーを追加する - Amazon CloudFront
CloudFront HTTP リクエストヘッダーを追加して、ビューワーのデバイスタイプ、IP アドレス、地理的位置、リクエストプロトコル (HTTP または HTTPS)、HTTP バージョン、TLS 接続の詳細、および JA4 フィンガ...

これを使えば後続処理に、実際のリクエスト元の IP を渡すことができるため、オリジンリクエストポリシーとして以下のようなルールを作成し、対象のビヘイビアのオリジンリクエストポリシーとして紐づけを行いました。

  • ポリシー名等: 任意
  • ヘッダー: すべてのビューワーヘッダーと次の Cloudfront ヘッダー
  • add header: CloudFront-Viewer-Address

ApacheでCloudFront-Viewer-Addressを受け取り、RemoteIP に設定

上記ステップにより、Cloudfront から CloudFront-Viewer-Address ヘッダーが送られてくるため、これを Apache 側で RemoteIP としてセットしてあげることで、PHPなどアプリケーション層でも正しいIPを認識できるようになります。

CloudFront-Viewer-Address は形式に少し癖があり、198.51.100.10:46532 のように IP アドレスの後ろにポート番号がついてしまうため、これを取り除いてあげる必要があります。

apache の httpd.conf ファイルに以下を追加してあげることで、Remote IP に正しいIPが設定されました。

... (もとの設定) ...

# CloudFront-Viewer-Address から IP 部分のみを抽出 (例: CloudFront-Viewer-Address: 192.30.252.129:443)
SetEnvIf CloudFront-Viewer-Address "^([0-9.]+):" VIEWER_IP=$1

# 抽出した IP アドレスを RemoteIPHeader として使用
RequestHeader set X-VIEWER-IP %{VIEWER_IP}e env=VIEWER_IP
RemoteIPHeader X-VIEWER-IP

... (もとの設定) ...

まとめ

Cloudfront 導入時などはビヘイビアの設定がメインになると思いますが、IPがつけ変わるため、後段のアプリケーションやWAFなどでそれを見ているケースでは、そこにも注意が必要だよというお話でした。

コメント

タイトルとURLをコピーしました