ブログ | NGINX

NGINX を API ゲートウェイとして導入する、パート 2: バックエンドサービスの保護

NGINX-F5 水平黒タイプ RGB の一部
リアム・クリリー サムネイル
リアム・クリリー
2021年1月20日公開

これは、NGINX Open Source と NGINX Plus を API ゲートウェイとして導入する方法に関するシリーズの 2 番目のブログ投稿です。

注記: 特に記載がない限り、この記事のすべての情報は、NGINX Open Source と NGINX Plus の両方に適用されます。 読みやすくするために、このブログの残りの部分では単に「NGINX」と呼びます。

レート制限

ブラウザベースのクライアントとは異なり、個々の API クライアントは API に多大な負荷をかけることができ、システム リソースを大量に消費して他の API クライアントが事実上ロックアウトされることもあります。 この脅威をもたらすのは悪意のあるクライアントだけではありません。不正な動作をする、またはバグのある API クライアントがループに入り、バックエンドを圧倒する可能性があります。 これを防ぐために、各クライアントによる公平な使用を保証し、バックエンド サービスのリソースを保護するために、レート制限を適用します。

NGINX は、リクエストの任意の属性に基づいてレート制限を適用できます。 通常はクライアント IP アドレスが使用されますが、API の認証が有効になっている場合は、認証されたクライアント ID の方が信頼性が高く正確な属性になります。

レート制限自体は最上位の API ゲートウェイ構成ファイルで定義され、グローバル、API ごと、さらには URI ごとに適用できます。

 

この例では、4 行目のlimit_req_zoneディレクティブは、各クライアント IP アドレス ( $binary_remote_addr ) に対して 1 秒あたり 10 リクエストのレート制限を定義し、5 行目のディレクティブは、認証されたクライアント ID ( $http_apikey ) に対して 1 秒あたり 200 リクエストの制限を定義します。 これは、適用される場所に関係なく、複数のレート制限を定義する方法を示しています。 API は複数のレート制限を同時に適用したり、異なるリソースに異なるレート制限を適用したりする場合があります。

次に、次の構成スニペットで、 limit_reqディレクティブを使用して、パート 1<.htmla> で説明した「Warehouse API」のポリシー セクションの最初のレート制限を適用します。 デフォルトでは、NGINXは 503 (サービス 利用不可) レート制限を超えた場合の応答。 ただし、API クライアントがレート制限を超えたことを明示的に認識して、動作を変更できるようにすることは役に立ちます。 この目的のために、私たちは 制限要求ステータス 送信する指令 429 (あまりにも 多くの リクエスト 代わりに応答します。

 

limit_reqディレクティブに追加のパラメータを使用して、NGINX がレート制限を適用する方法を微調整できます。 たとえば、制限を超えたときにリクエストを完全に拒否するのではなく、リクエストをキューに入れて、リクエストのレートが定義された制限を下回るまでの時間を確保することができます。 レート制限の微調整の詳細については、ブログの「NGINX および NGINX Plus を使用したレート制限」を参照してください。

特定のリクエストメソッドの強制

RESTful API では、HTTP メソッド (または動詞) は各 API 呼び出しの重要な部分であり、API 定義にとって非常に重要です。 当社の Warehouse API の価格設定サービスを例に挙げてみましょう。

  • GET /api/warehouse/pricing/item001 はitem001の価格を返します。
  • PATCH /api/warehouse/pricing/item001 はitem001の価格を変更します

Warehouse API の URI ルーティング定義を更新して、価格設定サービスへのリクエストではこれら 2 つの HTTP メソッドのみを受け入れるようにすることができます (在庫サービスへのリクエストではGETメソッドのみを受け入れるようにすることができます)。

 

この構成では、22 行目にリストされている方法以外の方法を使用した価格設定サービスへのリクエスト (および 13 行目以外の在庫サービスへのリクエスト) は拒否され、バックエンド サービスに渡されません。 NGINXは 405 (方法 ない 許可された) 次のコンソール トレースに示すように、応答を送信して、API クライアントにエラーの正確な性質を通知します。 最小限の情報開示のセキュリティポリシーが必要な場合は、 error_pageディレクティブを使用して、この応答を代わりにあまり情報のないエラーに変換することができます。たとえば、400 要求の形式が正しくありません)

$ curl https://api.example.com/api/warehouse/pricing/item001 {"sku":"item001","price":179.99} $ curl -X DELETE https://api.example.com/api/warehouse/pricing/item001 {"status":405,"message":"メソッドは許可されていません"}

きめ細かなアクセス制御の適用

このシリーズのパート 1 では、 API キーJSON Web Token (JWT)などの認証オプションを有効にして、API を不正アクセスから保護する方法について説明しました。 認証された ID または認証された ID の属性を使用して、きめ細かいアクセス制御を実行できます。

ここではそのような例を 2 つ示します。

もちろん、 HTTP 基本認証OAuth 2.0 トークン イントロスペクションなど、他の認証方法もこれらのサンプルのユースケースに適用できます。

特定のリソースへのアクセスを制御する

「インフラストラクチャ クライアント」のみが Warehouse API インベントリ サービスの監査リソースにアクセスできるようにしたいとします。 APIキー認証を有効にすると、マップブロックを使用してインフラストラクチャクライアント名の許可リストを作成し、変数$is_infrastructureが次のように評価されます。1対応する API キーが使用される場合。

 

Warehouse API の定義では、在庫監査リソースのロケーションブロックを追加します ( 15 ~ 20 行目)。 ifブロックは、インフラストラクチャ クライアントだけがリソースにアクセスできるようにします。

 

15 行目のlocationディレクティブでは、監査リソースの完全一致を実現するために= (等号) 修飾子が使用されていることに注意してください。 完全一致は、他のリソースに使用されるデフォルトのパスプレフィックス定義よりも優先されます。 次のトレースは、この構成を適用すると、許可リストに登録されていないクライアントがインベントリ監査リソースにアクセスできなくなることを示しています。 表示される API キーはclient_twoに属します (パート 1で定義)。

$ curl -H "apikey: QzVV6y1EmQFbbxOfRCwyJs35" https://api.example.com/api/warehouse/inventory/audit {"ステータス":403、"メッセージ":"禁止"}

特定のメソッドへのアクセスを制御する

上記で定義したように、価格設定サービスはGETメソッドとPATCHメソッドを受け入れます。これらのメソッドにより、クライアントは特定のアイテムの価格を取得および変更できます。 (価格データの完全なライフサイクル管理を提供するために、 POST メソッドDELETEメソッドを許可することもできます。) このセクションでは、そのユースケースを拡張して、特定のユーザーが発行できるメソッドを制御します。 Warehouse API に対して JWT 認証を有効にすると、各クライアントの権限はカスタム クレームとしてエンコードされます。 価格データの変更を許可された管理者に発行される JWT には、クレーム"admin":trueが含まれます。 アクセス制御ロジックを拡張し、管理者のみが変更できるようになりました。

 

api_gateway.confの下部に追加されたこのマップブロックは、リクエスト メソッド ( $request_method ) を入力として受け取り、新しい変数$admin_permitted_methodを生成します。 読み取り専用メソッドは常に許可されます (62 ~ 64 行目) が、書き込み操作へのアクセスは JWT 内のadminクレームの値によって異なります (65 行目)。 管理者のみが価格を変更できるように、Warehouse API 構成を拡張しました。

 

Warehouse API では、すべてのクライアントが有効な JWT を提示する必要があります (7 行目)。 また、 $admin_permitted_method変数 (25 行目) を評価することで、書き込み操作が許可されているかどうかも確認します。 再度の注意として、JWT 認証は NGINX Plus 専用です。

リクエストサイズの制御

HTTP API では通常、バックエンド API サービスが処理する指示とデータを含めるためにリクエスト本文を使用します。 これは、XML/SOAP API だけでなく JSON/REST API にも当てはまります。 その結果、リクエスト本文はバックエンド API サービスへの攻撃ベクトルとなり、非常に大きなリクエスト本文を処理するときにバッファ オーバーフロー攻撃に対して脆弱になる可能性があります。

デフォルトでは、NGINX は本文が 1 MB を超えるリクエストを拒否します。 これは、画像処理などの大きなペイロードを特に処理する API の場合は増やすことができますが、ほとんどの API では低い値を設定します。

 

7 行目のclient_max_body_sizeディレクティブは、リクエスト本体のサイズを制限します。 この構成を設定すると、価格設定サービスへの 2 つの異なるPATCHリクエストを受信したときの API ゲートウェイの動作を比較できます。 最初のcurlコマンドは小さな JSON データを送信しますが、2 番目のコマンドは大きなファイル ( /etc/services ) の内容を送信しようとします。

$ curl -iX PATCH -d '{"price":199.99}' https://api.example.com/api/warehouse/pricing/item001 HTTP/1.1 204 コンテンツなし サーバー: nginx/1.19.5 接続: keep-alive $ curl -iX PATCH -d@/etc/services https://api.example.com/api/warehouse/pricing/item001 HTTP/1.1 413 リクエストエンティティが大きすぎます サーバー: nginx/1.19.5 コンテンツタイプ: application/json コンテンツ長: 45 接続: 閉じる {"status":413,"message":"ペイロードが大きすぎます"}

リクエスト本文の検証

[編集者– 次の使用例は、NGINX JavaScript モジュールのいくつかの使用例の 1 つです。 完全なリストについては、 「NGINX JavaScript モジュールの使用例」を参照してください。

バックエンド API サービスは、大きなリクエスト本文によるバッファ オーバーフロー攻撃に対して脆弱であるだけでなく、無効なデータや予期しないデータを含む本文に対しても影響を受ける可能性があります。 リクエスト本文に正しくフォーマットされた JSON を必要とするアプリケーションの場合、 NGINX JavaScript モジュール <.htmla> を使用して、JSON データがバックエンド API サービスにプロキシする前にエラーなしで解析されることを確認できます。

JavaScript モジュールがインストールされたら、 js_importディレクティブを使用して、JSON データを検証する関数の JavaScript コードを含むファイルを参照します。

 

js_setディレクティブは、 parseRequestBody関数を呼び出すことによって評価される新しい変数$json_validated を定義します。

 

parseRequestBody関数は、 JSON.parseメソッド (6 行目) を使用してリクエスト本文を解析しようとします。 解析が成功すると、このリクエストの対象のアップストリーム グループの名前が返されます (行 8)。 リクエスト本体を解析できない場合 (例外が発生する場合)、ローカル サーバーのアドレスが返されます (11 行目)。 returnディレクティブは$json_validated変数を設定するので、これを使用してリクエストの送信先を決定できます。

 

Warehouse API の URI ルーティング セクションで、22 行目のproxy_passディレクティブを変更します。 前のセクションで説明した Warehouse API 構成と同様に、リクエストをバックエンド API サービスに渡しますが、宛先アドレスとして$json_validated変数を使用します。 クライアント本体が JSON として正常に解析された場合は、15 行目に定義されているアップストリーム グループにプロキシします。 ただし、例外が発生した場合は、返された値127.0.0.1:10415を使用してクライアントにエラー応答を送信します。

 

リクエストがこの仮想サーバーにプロキシされると、NGINXは415(サポートされていないメディアタイプ)応答をクライアントに返します。

この完全な構成が完了すると、NGINX は、JSON 本文が正しくフォーマットされている場合にのみ、バックエンド API サービスへのリクエストをプロキシします。

$ curl -iX POST -d '{"sku":"item002","price":85.00}' https://api.example.com/api/warehouse/pricing HTTP/1.1 201 作成済み サーバー: nginx/1.19.5 場所: /api/warehouse/pricing/item002 $ curl -X POST -d 'item002=85.00' https://api.example.com/api/warehouse/pricing {"status":415,"message":"サポートされていないメディア タイプ"}

$request_body変数に関する注意

JavaScript 関数parseRequestBody は$request_body変数を使用して JSON 解析を実行します。 ただし、NGINX はデフォルトではこの変数を設定せず、中間コピーを作成せずにリクエスト本文をバックエンドにストリーミングするだけです。 URI ルーティング セクション (16 行目) 内でmirrorディレクティブを使用することで、クライアント要求のコピーが作成され、 $request_body変数に値が設定されます。

 

17 行目と 19 行目のディレクティブは、NGINX がリクエスト本体を内部的に処理する方法を制御します。 リクエスト本文がディスクに書き込まれないように、 client_body_buffer_size をclient_max_body_sizeと同じサイズに設定します。 これにより、ディスク I/O 操作が最小限に抑えられ、全体的なパフォーマンスが向上しますが、追加のメモリ使用率が犠牲になります。 リクエスト本文が小さいほとんどの API ゲートウェイの使用例では、これが適切な妥協案となります。

前述のように、ミラーディレクティブはクライアント要求のコピーを作成します。 $request_bodyにデータを入力する以外に、このコピーは必要ないので、トップレベルの API ゲートウェイ構成のサーバーブロックで定義した「行き止まり」の場所 ( /_get_request_body ) に送信します。

 

この場所は、 204(コンテンツなし)応答。 この応答はミラーリングされた要求に関連しているため無視され、元のクライアント要求の処理にわずかなオーバーヘッドが追加されます。

まとめ

API ゲートウェイとしての NGINX Open Source と NGINX Plus の導入に関するシリーズの 2 番目のブログ投稿では、実稼働環境のバックエンド API サービスを悪意のあるクライアントや不正なクライアントから保護するという課題に焦点を当てました。 NGINX は、今日のインターネット上で最も混雑しているサイトを運営し、保護するために使用されているものと同じテクノロジーを API トラフィックの管理に使用します。

このシリーズの他の投稿もご覧ください:

  • パート 1 では、いくつかの重要な API ゲートウェイの使用例で NGINX を構成する方法について説明します。
  • パート 3 では、 gRPC サービスの API ゲートウェイとして NGINX をデプロイする方法について説明します。

NGINX Plus を API ゲートウェイとしてお試しいただくには、今すぐ30 日間の無料トライアルを開始するか、弊社にお問い合わせの上、使用事例についてご相談ください。 試用期間中は、 GitHub Gist リポジトリから構成ファイルの完全なセットを使用してください。


「このブログ投稿には、入手できなくなった製品やサポートされなくなった製品が参照されている場合があります。 利用可能な F5 NGINX 製品およびソリューションに関する最新情報については、 NGINX 製品ファミリーをご覧ください。 NGINX は現在 F5 の一部です。 以前の NGINX.com リンクはすべて、F5.com の同様の NGINX コンテンツにリダイレクトされます。"