NGINX Open SourceとNGINX PlusをAPIゲートウェイとして導入する方法を紹介するブログシリーズの第2回です。
- パート1では、複数のユースケースについて詳細な設定手順を説明しています。
- 2018年に初めて公開されたこの記事は、現在のAPI構成のベストプラクティスを反映するよう更新され、ネストされた
locationブロックを使用して、書き換えルールではなくリクエストをルーティングするようになりました。
この記事では、これらのユースケースをさらに掘り下げ、本番環境のバックエンドAPIサービスを保護し、セキュリティを確保するために適用できるさまざまな対策について説明します。
- パート3では、NGINX Open SourceとNGINX PlusをgRPCサービスの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ディレクティブを使用して、Part 1<.htmla>で説明した「Warehouse API」のポリシーセクションに最初のレート制限を適用します。デフォルトでは、レート制限を超えると、NGINXは503 (Service Unavailable)レスポンスを送信します。ただし、APIクライアントが動作を変更できるように、レート制限を超えたことを明示的に通知すると便利です。そのため、limit_req_statusディレクティブを使用して、代わりに429 (Too Many Requests)レスポンスを送信します。
コード例を読み込み中…
You can use additional parameters to the limit_reqディレクティブに追加のパラメーターを指定すると、NGINXによるレート制限の適用方法を細かく調整できます。たとえば、制限を超えたリクエストを即座に拒否せずにキューに入れ、リクエストレートが設定された制限を下回るまで待機させることができます。レート制限の詳細な調整については、F5ブログの「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ルーティング定義を更新し、pricingサービスへのリクエストではこの2つのHTTPメソッドのみを受け付けるようにできます(inventoryサービスへのリクエストではGETメソッドのみ)。
コード例を読み込み中…
この設定を適用すると、価格サービスに対する22行目に記載されているもの以外のメソッドを使用したリクエスト(および在庫サービスに対する13行目に記載されているもの以外のメソッドを使用したリクエスト)は拒否され、バックエンドサービスには渡されません。NGINXは、以下のコンソールトレースに示すように、エラーの正確な内容をAPIクライアントに通知するため、405 (Method Not Allowed)レスポンスを送信します。情報開示を最小限に抑えるセキュリティポリシーが必要な場合は、error_pageディレクティブを使用して、このレスポンスを、たとえば400 (Bad Request)のような、情報量の少ないエラーに変換できます。
$ 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":"Method not allowed"}きめ細かなアクセス制御の適用
本シリーズのパート1では、APIキーやJSON Web Token(JWT)などの認証オプションを有効にして、APIへの不正アクセスを防ぐ方法について説明しました。認証済みIDまたはその属性を使用して、きめ細かなアクセス制御を実施できます。
ここでは、そのような例を2つ紹介します。
- The 最初の例では、特定のAPIリソースへのアクセスを制御します。これは、パート1で紹介した構成を拡張し、APIキー認証を使用して、対象のAPIクライアントが許可リストに登録されていることを確認するものです。
- The 2つ目の例では、クライアントに使用を許可するHTTPメソッドを制御します。パート1で説明したJWT認証方式を実装し、カスタムクレームを使用して、条件を満たすAPIクライアントを識別します。(JWTがサポートされるのはNGINX Plusのみです。)
もちろん、これらのユースケース例には、HTTP Basic認証やOAuth 2.0トークンイントロスペクションなど、ほかの認証方式も適用できます。
特定のリソースへのアクセス制御
Warehouse APIインベントリサービスのauditリソースへのアクセスを「インフラストラクチャクライアント」のみに許可するとします。APIキー認証を有効にした状態で、mapブロックを使用してインフラストラクチャクライアント名の許可リストを作成し、対応するAPIキーが使用されたときに変数$is_infrastructureが1と評価されるようにします。
コード例を読み込み中…
Warehouse APIの定義に、在庫locationのauditリソース用ブロックを追加します(15~20行目)。ifブロックにより、インフラクライアントのみがこのリソースにアクセスできます。
コード例を読み込み中…
15行目のlocationディレクティブでは、=(等号)修飾子を使用してauditリソースとの完全一致を指定している点に注意してください。完全一致は、他のリソースに使用されるデフォルトのパスプレフィックス定義よりも優先されます。次のトレースは、この設定を適用すると、許可リストに含まれていないクライアントがインベントリのauditリソースにアクセスできないことを示しています。表示されているAPIキーは、client_two(Part 1で定義)に属します。
$ curl -H "apikey: QzVV6y1EmQFbbxOfRCwyJs35" https://api.example.com/api/warehouse/inventory/audit{"status":403,"message":"Forbidden"}特定のメソッドへのアクセス制御
As defined 上記で定義したとおり、価格設定サービスはGETメソッドとPATCHメソッドを受け付けます。これにより、クライアントはそれぞれ特定の商品の価格を取得、変更できます。(価格設定データのライフサイクル全体を管理できるように、POSTメソッドとDELETEメソッドを許可することもできます。)このセクションでは、このユースケースを拡張し、特定のユーザーが実行できるメソッドを制御します。Warehouse APIでJWT認証を有効にすると、各クライアントの権限がカスタムクレームとしてエンコードされます。価格設定データの変更を許可された管理者に発行されるJWTには、クレーム"admin":trueが含まれます。ここでは、管理者のみが変更できるようにアクセス制御ロジックを拡張します。
コード例を読み込み中…
This mapブロックを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サービスへの攻撃経路となる可能性があります。バックエンド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/item001HTTP/1.1 204 No Content
Server: nginx/1.19.5
Connection: keep-alive
$ curl -iX PATCH -d@/etc/services https://api.example.com/api/warehouse/pricing/item001
HTTP/1.1 413 Request Entity Too Large
Server: nginx/1.19.5
Content-Type: application/json
Content-Length: 45
Connection: close
{"status":413,"message":"Payload too large"}リクエストボディの検証
[編集者 – 以下は、NGINX JavaScriptモジュールの複数のユースケースのうちの1つです。完全な一覧については、「NGINX JavaScriptモジュールのユースケース」を参照してください。]
バックエンドAPIサービスは、大きなリクエストボディによるバッファオーバーフロー攻撃に対して脆弱であるだけでなく、無効または予期しないデータを含むリクエストボディの影響を受ける可能性もあります。リクエストボディに正しい形式のJSONを必要とするアプリケーションでは、NGINX JavaScript module<.htmla>を使用して、バックエンドAPIサービスにプロキシする前に、JSONデータがエラーなく解析されることを確認できます。
With the JavaScriptモジュールをインストールした状態で、JSONデータを検証する関数のJavaScriptコードを含むファイルを参照するために、js_importディレクティブを使用します。
コード例を読み込み中…
The js_setディレクティブは、新しい変数$json_validatedを定義します。この変数は、parseRequestBody関数を呼び出すことで評価されます。
コード例を読み込み中…
The 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/pricingHTTP/1.1 201 Created
Server: nginx/1.19.5
Location: /api/warehouse/pricing/item002
$ curl -X POST -d 'item002=85.00' https://api.example.com/api/warehouse/pricing
{"status":415,"message":"Unsupported media type"}A Note about the $request_body変数に関する注意事項
JavaScript関数「parseRequestBody」は、JSONを解析するために変数「$request_body」を使用します。ただし、NGINXはデフォルトではこの変数に値を設定せず、中間コピーを作成することなく、リクエストボディをバックエンドにそのままストリーミングします。URIルーティングセクション(16行目)内で「mirror」ディレクティブを使用すると、クライアントリクエストのコピーが作成され、その結果、変数「$request_body」に値が設定されます。
コード例を読み込み中…
17行目と19行目のディレクティブは、NGINXがリクエストボディを内部で処理する方法を制御します。リクエストボディがディスクに書き込まれないよう、client_body_buffer_sizeをclient_max_body_sizeと同じサイズに設定します。これにより、メモリ使用量は増加しますが、ディスクI/O処理が最小限に抑えられ、全体的なパフォーマンスが向上します。リクエストボディが小さいほとんどのAPIゲートウェイのユースケースでは、適切な妥協点です。
前述のとおり、mirrorディレクティブはクライアントリクエストのコピーを作成します。このコピーは、$request_bodyへの値の設定以外には必要ないため、最上位のAPIゲートウェイ設定にある/_get_request_bodyブロックで定義した「行き止まり」のロケーション(server)に送信します。
コード例を読み込み中…
このlocationは、単に「204 (No Content)」レスポンスを送信するだけです。このレスポンスはミラーリングされたリクエストに関連するため無視され、元のクライアントリクエストの処理に加わるオーバーヘッドはごくわずかです。
まとめ
NGINX Open SourceとNGINX PlusをAPIゲートウェイとして導入する方法を取り上げるシリーズの第2回となる本ブログ記事では、本番環境のバックエンドAPIサービスを、悪意のあるクライアントや不適切な動作をするクライアントから保護するという課題に焦点を当てました。NGINXは、APIトラフィックの管理に、現在インターネット上で最もアクセスの多いサイトの運用と保護に使用されているものと同じテクノロジーを使用しています。
このシリーズの他の記事もご覧ください:
NGINX PlusをAPIゲートウェイとしてお試しいただくには、今すぐ30日間の無料トライアルを開始するか、ユースケースについてご相談ください。トライアル期間中は、GitHub Gistリポジトリにある構成ファイル一式をご利用ください。
著者について

関連するブログ記事
チュートリアル:NGINXを使用してアプリケーション向けにOpenTelemetryを構成する
OpenTelemetryを使用してNGINXサーバーの状態と健全性をより明確に把握することで、問題のトラブルシューティングを行い、Webアプリケーションのパフォーマンスを向上できます。
AkitaのNGINX Plus認定モジュールでAPIエンドポイントの問題を特定・修正
Akita SoftwareのAPIオブザーバビリティ向け新しいNGINX Plus Certified Moduleを活用して、APIエンドポイントの問題をより迅速に特定し、修正できます。
JavaプロジェクトをスキャフォールディングしてKubernetesネイティブアプリを構築する方法
Red HatのシニアプリンシパルデベロッパーアドボケイトであるDaniel Oh氏が、Javaプロジェクトをゼロから構築するためのひな形を作成し、Kubernetesネイティブアプリケーションとしてビルドする方法を解説します。
F5 NGINXaaS for Azureのご紹介:Microsoft Azureでネイティブに利用できるSaaS型ロードバランシング
NGINXaaS for Azureは、NGINX PlusをAzureネイティブのSaaSとして利用できるサービスです。NGINXの設定を容易にリフト&シフトできるため、クラウド導入とモダンアプリの展開を加速できます。

SELinuxでNGINXおよびNGINX Plusを使用する
Red Hat Enterprise Linux(RHEL)および関連ディストリビューションでSecurity-Enhanced Linux(SELinux)を有効にすると、デフォルト設定によりNGINXとNGINX Plusの一部の操作が制限されます。この記事では、すべての機能を利用できるようにSELinuxの設定を変更する方法について説明します。
NGINX PlusによるAWS Auto Scalingグループの負荷分散
新しいnginx-asg-sync統合ソフトウェアにより、NGINX PlusはAWS Auto Scalingグループの負荷を直接分散できるため、クラウドでの負荷分散がさらに柔軟になります