はじめに
こんにちは。開発部 SRE チームの柴山です。
技術構成の記事でもご紹介した通り、弊社ではWebアプリケーションの脆弱性保護を目的として Scutum を中心としたセキュリティ対策を実施してきました。
しかし、昨今のAI技術の急速な発展に伴い、悪意のある攻撃だけでなく、データ取得(AI学習等)を目的とした突発的なスクレイピングといった「予測不可能なトラフィックスパイク」に直面することが増えてきました。
これらはバックエンドのリソース逼迫を招き、正規のユーザー様にも影響を与えかねません。
こういった機械的なアクセスには、アクセス数を制限するレートリミットを設定するのが定石として挙げられますが、導入には様々な検討事項があり、単純に導入するだけでは誤検知によって正規のトラフィックさえも巻き添えにしてしまうリスクがあります。
インフラを保護できても、正規ユーザーまでブロックして機会損失を生んでしまっては本末転倒です。
弊社では「正規トラフィックを保護するためのアーキテクチャ設計」として、最終的に AWS WAF を採用しました。
本記事では、その採用に至った経緯や設計思想についてご紹介いたします。
技術選定
突発的なトラフィックスパイクを制御するにあたり、まずは「どこで制限をかけるか」という実装ポイントの比較検討を行いました。
弊社ではWebサーバー(Nginx) + AWS ALBという構成で、サービスを提供しています。導入のしやすさから、以下二つのアプローチを比較検討しました。
- マネージドサービスでの制御(AWS WAF の レートベース ルールステートメント)
- サーバーサイドでの制御(Nginx の limit_req + geo モジュール)
それぞれの特性と優位性について、弊社での運用環境をもとに評価した結果がこちらです。
| 評価軸 | AWS WAF | Nginx |
|---|---|---|
| 初期リソースコスト | △ 中 | ◎ 低 |
| 運用保守コスト | ◎ ほぼゼロ(フルマネージド) | △ 運用保守のフローを要検討 |
| GeoIP/Bot DB更新 | ◯ 自動 | △ 手動(要自前構築・保守) |
| 導入時の安全性 (DryRun) | ◎ 可能 (Countモード) | △ 工夫が必要(OSS版) |
| DDoS対応 | ◯ 優位(専用ルール有/一時的な適用が可能) | △ 困難(分散型は対応不可) |
| ログの可視性 | ◯ 優位(専用ダッシュボード/Log ツール連携) | △ 自前構築(ログ転送・別途可視化のコストが発生) |
表からもわかる通り、初期のリソースコスト面では Nginx に分がありますが、保守・運用の観点で AWS WAF が弊社の要件にマッチしていました。
その中でも以下の点は非常にメリットとして大きく、採用の決め手となりました。
- フルマネージドの恩恵
GeoIP データベースやBotリストを都度更新し続けるのは長期的に人的な運用コストがかかることになります。
ここをAWS側にほぼ丸投げできるのは大きなメリットの一つです。 - Countモードによる段階的導入
表題通り、今回一番懸念していた点が正規ユーザーの誤ブロックです。
AWS WAF にはブロックせずにルール評価だけを行う Countモード が存在するため、事前にルールの妥当性を精度高く検証することができます。
設計の壁
いざ、AWS WAF でレートリミットを導入しようと思いましたが、早速いくつかの壁にぶつかりました。
「どうやって閾値を決定しよう」「正規ユーザーの誤ブロックを防ぐには?」「これって一律に制限かけていいの?」
たとえば、レートリミットの閾値を「1IPアドレスあたり、5分間に100リクエストまで」と決め打ちしたとします。
それだけで単純に適用をすると、以下のような絶対に弾いてはいけない正規のトラフィックまで遮断してしまいます。
- 優良Bot(クローラー): Googlebot 等の検索エンジンや外部連携先の巡回Bot
- 正規ユーザーアクセス: 本命の絶対にブロックしてはいけないトラフィック
- アプリ・サービス連携: 外部連携サービスからのWebhookや、自社アプリなどからの通信
- 監視系: 外形監視など
これらのトラフィックを踏まえた上で、悪意のあるトラフィックだけをそぎ落とし、可能な限り正規のトラフィックを通す必要がありました。
アーキテクチャ
今回の肝である誤検知の対策として、フラットにレートリミットをかけるのではなく WAF の評価順位の仕様を利用した多段フィルターを設計しました。
AWS WAF は設定された優先順位(Priority)の小さい順にルールが評価されていきます。
全ての通信に対してフラットにレートリミットをかけるのではなく、「既知の脅威をブロック」->「正規トラフィックの救済」->「その他トラフィックへのレートリミット」という段階的にふるいへかけるアーキテクチャを構築しました。
前段フィルター: 既知の脅威をブロック
最前段では明らかに不正なトラフィックをAWSが提供するマネージドルールなどを用いて遮断します。中段バイパス: 正規トラフィックの保護
ここが正規トラフィック保護の要になります。
前段を通過したトラフィックに対して、前述した項目(優良Bot、アプリ・サービス連携、監視系等)を独自の複合的な条件で識別を行います。
条件に合致した場合は Allow(明示的な許可)とし、この段階で以降のルール評価をスキップさせてバックエンドへのトラフィックを許可します。
ここに引き算のルールを挟むことにより、死守すべき正規トラフィックが後段のレートリミットにかかることを防ぎます。後段フィルター: レートリミットの適用
最終的に残ったトラフィックの大量アクセスにのみレートリミットを適用します。
ルールステートメントにより、特定の属性を持つ通信が閾値を超えた場合にのみ遮断をすることができます。
誤検知を防ぐための Tips
静的ファイルの考慮
Webページへの初回アクセス時、ブラウザはページにリンクされた大量の静的ファイル(画像、CSS、JS等)を同時にリクエストします。
これをレートリミットのカウントに含めると、正常なユーザーでも一瞬で閾値に達してしまうリスクがあります。
これらを考慮した閾値を設定するか、静的ファイルはCDN等でキャッシュさせて、静的ファイルをカウント対象外にすることが重要です。
閾値の決定について
レートリミットの閾値を「だいたい1分間にn回くらいだろう」と勘で決めるのは非常に危険です。
過去のアクセスログを分析し、以下のようなステップで閾値を決定しました。一例として共有させていただきます。
外れ値を除外してベースとなる値の算出
まずサービスのアクセスログから各時間(分)あたりで上限の基準値として95パーセンタイルを抽出しました。
分析対象のログにはすでに悪意のあるアクセスやスクレイピングなど異常なスパイク(外れ値)が含まれていました。
そのまま最大値を基準にしてしまうと、閾値がかなり甘めに出てしまうことになります。

上位5%のノイズを取り除くことによって、Botなどによる外れ値を極力排除した値を抽出することができました。安全係数の適用と基準値の厳格化
抽出したベース値に対し、トラフィックの属性(エンドポイント等)ごとに数値を比較し、より厳格な(小さい)値を採用しました。
その上で、正常な突発的アクセスを許容して安全側に寄せるため、安全係数(1.x)を掛け合わせて最終的な閾値を決定しています。
これにより、正規トラフィックの多少の突発的なアクセスは許容しつつ、Botによる攻撃だけを制限するような閾値を導き出すことができました。
導入
アーキテクチャと閾値の設計が完了し、いよいよ導入フェーズですが、いきなり本番環境で遮断を開始するのは危険です。
いくら念入りに計算をしていても、あくまで絵に描いた餅で正規トラフィックの誤ブロックリスクは依然として存在します。
そこで、技術選定の項で紹介したCountモードを活用し、以下のような4段階のフェーズに分けて慎重にリリースを行いました。
| フェーズ | 環境 | モード | 実施内容のハイライト |
|---|---|---|---|
| フェーズ1 | テスト環境 | Count -> Block | IaC(Terraform)でのインフラ構築と、テストシナリオに基づく動作(遮断)検証。 |
| フェーズ2 | ステージング環境 | Block | 本番同等環境でのリハーサル。 正規系動作の確認と、意図した通信のみが遮断されるかの最終確認。 |
| フェーズ3 | 本番環境 | Count | 【最重要】 本番環境へCountモードで適用。 およそ二週間かけて全曜日・時間帯の実際のトラフィックログを取得・精査し、机上の計算とズレがないかを確認 |
| フェーズ4 | 本番環境 | Block | ログ精査で正規トラフィックの巻き込みがないことを確信した後、本番運用(Block)へ切り替え。 |
このリリース計画の中で最も重要なのが フェーズ3(本番環境でのCount運用) になります。
本番のトラフィックがWAFに流れ、CloudWatch Logs に出力された検出結果を2週間かけて分析しました。
ここでは「正規ユーザーを誤って検出してしまっていないか」「正常にレートリミットが効いているか」「連携や監視に考慮漏れの項目が無いか」の答え合わせを実施しました。
結果として、設計時には検討できていなかった「優良Bot」や「連携系のアクセス」、「状況に応じて遮断されてしまう正規ユーザー」の存在に気づくことができました。
もし最初からBlockモードで導入していた場合、これらの通信も遮断してしまい、サービス影響に繋がっていた可能性があります。
誤検知の可能性を事前に全て摘み取ることで、満を持して最終フェーズのBlockモードへの切り替えを実施しました。
その成果が、導入後に発生した突発的な大量アクセス時のメトリクスに表れました。

上記グラフの通り、トータルアクセス(赤線)が急増した際、レートリミットが即座に反応し、悪質なトラフィックのみを選択的にブロック(紫線)してくれています。
一方で、正規ユーザーの通信(緑線)はスパイクの影響を一切受けることなく一定に保たれていることが見て取れます。
無事、正常なトラフィックを保護したまま、悪質なトラフィックのみを遮断するレートリミット環境を本番稼働させることに成功しました。
まとめ
今回は、AWS WAFを用いたレートリミットの導入と、正規トラフィックを保護するためのアーキテクチャ設計についてご紹介しました。
単に「アクセスを一定数でブロックする」という安易な設定をせず、以下のステップを踏むことで、「正規トラフィックを保護するアーキテクチャの設計」を実現できました。
- アーキテクチャの工夫: WAFのPriority仕様を活用した「段階的なフィルター」と、静的ファイルの考慮。
- 統計に基づいた閾値設計: 勘に頼らず、過去のアクセスログのp95値から算出した「ノイズを極力排除した閾値」の決定。
- 安全なリリース戦略: 本番環境でのCountモード運用と、ログ精査による誤検知の事前排除。
Scutumに加えてAWS WAFを導入したことで、より堅牢な構成を実現できました。
細かなチューニング内容や今後の展望があれば、またこの場でご紹介させていただけたらと思います。
この記事が、もし今後レートリミットを導入しようと検討されている方の参考になれば幸いです。












