これは、NGINX Open Source と NGINX Plus を API ゲートウェイとして導入する方法に関するシリーズの 2 番目のブログ投稿です。
パート 1 では、いくつかのユースケースの詳細な構成手順を示します。
この投稿では、これらのユースケースを拡張し、本番環境でバックエンド API サービスを保護し、セキュリティを確保するために適用できるさまざまな安全対策について説明します。
この投稿はもともと 2018 年に公開されましたが、書き換えルールの代わりにネストされたロケーション
ブロックを使用してリクエストをルーティングするという、API 構成の現在のベスト プラクティスを反映するように更新されました。
注記: 特に記載がない限り、この記事のすべての情報は、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 トラフィックの管理に使用します。
このシリーズの他の投稿もご覧ください:
NGINX Plus を API ゲートウェイとしてお試しいただくには、今すぐ30 日間の無料トライアルを開始するか、弊社にお問い合わせの上、使用事例についてご相談ください。 試用期間中は、 GitHub Gist リポジトリから構成ファイルの完全なセットを使用してください。
「このブログ投稿には、入手できなくなった製品やサポートされなくなった製品が参照されている場合があります。 利用可能な F5 NGINX 製品およびソリューションに関する最新情報については、 NGINX 製品ファミリーをご覧ください。 NGINX は現在 F5 の一部です。 以前の NGINX.com リンクはすべて、F5.com の同様の NGINX コンテンツにリダイレクトされます。"