NGINXおよびNGINX PlusのSecure LinkモジュールによるURLの保護

NGINX | 2016年7月29日

オープンソースのNGINXソフトウェアとNGINX Plusは、いずれもコンテンツ用のWebサーバー、リバースプロキシ、キャッシュとして高いセキュリティと信頼性を備えています。許可されていないクライアントからのアクセスに対する保護をさらに強化するには、Secure Link moduleのディレクティブを使用して、クライアントがリクエストするアセットのURLに特定のハッシュ文字列を含めるよう要求できます。

このブログ記事では、Secure Linkモジュールに実装されている2つの方式の設定方法について説明します。サンプルの設定スニペットはHTMLファイルとメディアプレイリストファイルを保護するものですが、あらゆる種類のHTTP URLに適用できます。これらの方式はNGINXとNGINX Plusの両方に適用できますが、簡潔にするため、以降はNGINX Plusのみを使用して説明します。

Secure Linkモジュールの各方式の概要

The Secure Link moduleは、HTTPリクエストのURLに含まれるエンコード済み文字列と、そのリクエストに対して算出した文字列を比較し、リクエストされたリソースの有効性を検証します。リンクに有効期間が設定されており、その期間が終了している場合、リンクは期限切れと見なされます。これらのチェック結果は$secure_link変数に格納され、処理フローの制御に使用されます。

前述のとおり、このモジュールには2つの方式があります。特定のhttp、server、またはlocationコンテキストで設定できるのは、いずれか一方のみです。

  • 1つ目の、よりシンプルなモードは、secure_link_secretディレクティブで有効にします。エンコードされた文字列は、URLの末尾部分とNGINX Plusの設定で定義された秘密の単語という2つのテキスト文字列を連結して算出したMD5ハッシュです。(1つ目のテキスト文字列の詳細については、「基本的なセキュアURLの使用」を参照してください。)保護されたリソースにアクセスするには、クライアントはURLプレフィックスの直後にハッシュを含める必要があります。このプレフィックスには、スラッシュを含まない任意の文字列を指定できます。次のサンプルURLでは、プレフィックスは「videos」、保護されたリソースはファイル「bunny.m3u8」です:/videos/80e2dfecb5f54513ad4e2e6217d36fd4/hls/bunny.m3u8 この方法は、たとえばユーザーが共有用の画像やドキュメントをサーバーにアップロードし、正式なリンクが公開されるまでは、ファイル名を知っている人からのアクセスを防ぎたい場合に使用できます。
  • 2つ目の、より柔軟な方法は、secure_linkディレクティブとsecure_link_md5ディレクティブで有効にします。この方法では、エンコードされた文字列は、NGINX Plus構成ファイルで定義された変数のMD5ハッシュです。通常は、特定のクライアントIPアドレスにアクセスを制限するために$remote_addr変数を含めますが、ほかの値も使用できます。たとえば、$http_user_agentはUser-Agentヘッダーを取得し、特定のブラウザーにアクセスを制限します。必要に応じて有効期限を指定できます。有効期限を過ぎると、ハッシュが正しくてもURLは機能しなくなります。クライアントは、ハッシュを指定するためにリクエストURLにmd5引数を追加する必要があります。ハッシュ化する文字列に有効期限が含まれている場合は、日付を指定するためにexpires引数も追加する必要があります。保護されたファイルpricelist.htmlをリクエストするURLの例を次に示します:/files/pricelist.html?md5=AUEnXC7T-Tfv9WLsWbf-mw&expires=1483228740

Secure Linkモジュールは、ビルド済みのオープンソース版NGINX binaries from nginx.org、オペレーティングシステムベンダーが提供するNGINXパッケージ、およびNGINX Plusに含まれています。NGINXをソースからビルドする場合、デフォルトでは含まれません。有効にするには、--with-http_secure_link_module引数をconfigureコマンドに指定します。

基本的なセキュアURLの使用

URLを保護するより基本的な方法は、secure_link_secretディレクティブを使用することです。次のサンプルスニペットでは、HTTP Live Streaming(HLS)のメディアプレイリストファイル「/bunny.m3u8」を保護します。このファイルは「/opt/secure/hls」ディレクトリに保存されていますが、クライアントには「videos」プレフィックスで始まるURLを使用して公開されます。

server {
    listen 80;
    server_name secure-link-demo;

    location /videos {
        secure_link_secret enigma;
        if ($secure_link = "") { return 403; }

        rewrite ^ /secure/$secure_link;
    }

    location /secure {
        internal;
        root /opt;
    }
}

この設定では、ファイル「/opt/secure/hls/bunny.m3u8」にアクセスするために、クライアントは次のURLを提示する必要があります。

/videos/80e2dfecb5f54513ad4e2e6217d36fd4/hls/bunny.m3u8

ハッシュ化された文字列は、スラッシュを含まない任意の文字列であるプレフィックス(ここでは「videos」)の直後に続きます。

ハッシュは、2つの要素を連結したテキスト文字列に対して計算されます。

  • URLのハッシュに続く部分(この例では「hls/bunny.m3u8」)。
  • The parameter to the secure_link_secretディレクティブのパラメーター(ここでは「enigma」)。

クライアントのリクエストURLに正しいハッシュが含まれていない場合、NGINX Plusは$secure_link変数を空文字列に設定します。ifテストは失敗し、NGINX PlusはHTTPレスポンスで403 Forbiddenステータスコードを返します。

それ以外の場合(つまり、ハッシュが正しい場合)、rewriteディレクティブはURLを書き換えます。この例では、/secure/hls/bunny.m3u8に書き換えます($secure_link変数は、URLのハッシュより後の部分を取得します)。/secureで始まるURLは、2番目のlocationブロックで処理されます。このブロックのrootディレクティブは、リクエストされたファイルのルートディレクトリを/optに設定し、internalディレクティブは、このブロックが内部で生成されたリクエストにのみ使用されることを指定します。

基本的なセキュアURL用のハッシュをクライアントで生成

クライアントがURLに含める必要がある16進数形式のMD5ハッシュを取得するには、openssl md5コマンドを-hexオプション付きで実行します:

# echo -n 'hls/bunny.m3u8enigma' | openssl md5 -hex(stdin)= 80e2dfecb5f54513ad4e2e6217d36fd4

プログラムによるハッシュ生成については、「プログラムによるハッシュの生成」を参照してください。

基本的なセキュアURLに対するサーバーの応答

以下のサンプルcurlコマンドは、保護された各種URLに対するサーバーの応答を示しています。

URLに正しいMD5ハッシュが含まれている場合、レスポンスは「200 OK」です:

# curl -I http://secure-link-demo/videos/80e2dfecb5f54513ad4e2e6217d36fd4/hls/bunny.m3u8 | head -n 1HTTP/1.1 200 OK

MD5ハッシュが正しくない場合、レスポンスは「403 Forbidden」です:

# curl -I http://secure-link-demo/videos/2c5e80de986b6fc80dd33e16cf824123/hls/bunny.m3u8 | head -n 1HTTP/1.1 403 Forbidden

If the hash for bunny.m3u8のハッシュを別のファイルに使用した場合も、レスポンスは「403 Forbidden」になります:

# curl -I http://secure-link-demo/videos/80e2dfecb5f54513ad4e2e6217d36fd4/hs/oven.m3u8 | head -n 1HTTP/1.1 403 Forbidden

有効期限付きのセキュアURLの使用

URLを保護するためのより柔軟な方法では、secure_linkディレクティブとsecure_link_md5ディレクティブを使用します。この例では、これらを使用して、IPアドレス192.168.33.14のクライアントに限り、2016年12月31日まで/var/www/files/pricelist.htmlファイルへのアクセスを許可します。

この仮想サーバーはポート80でリクエストを待ち受け、セキュリティで保護されたすべてのHTTPリクエストをlocation /filesブロックで処理します。このブロックでは、rootディレクティブにより、リクエストされたファイルのルートディレクトリが/var/wwwに設定されます。

The secure_linkディレクティブでは、リクエストURLの引数を取得する2つの変数を定義します。$arg_md5にはmd5引数の値が設定され、$arg_expiresにはexpires引数の値が設定されます。

The secure_link_md5ディレクティブでは、リクエストのMD5値を生成するためにハッシュ化する式を定義します。URLの処理中に、このハッシュが$arg_md5の値と比較されます。ここで示す式には、リクエストで渡された有効期限($secure_link_expires変数に格納)、URL($uri)、クライアントIPアドレス($remote_addr)、およびenigmaという単語が含まれています。

server {
    listen 80;
        server_name secure-link-demo;

        location /files {
            root /var/www;
            secure_link $arg_md5,$arg_expires;
            secure_link_md5 "$secure_link_expires$uri$remote_addr enigma";

            if ($secure_link = "") { return 403; }
            if ($secure_link = "0") { return 410; }
        }
}

この設定では、IPアドレス192.168.33.14のクライアントが/var/www/files/pricelist.htmlにアクセスするには、2016年12月31日(土)23:59:00 UTCまでに次のリクエストURLを送信する必要があります。

/files/pricelist.html?md5=AUEnXC7T-Tfv9WLsWbf-mw&expires=1483228740

クライアントから送信されたURL内のハッシュ($arg_md5変数に格納)が、secure_link_md5ディレクティブから計算されたハッシュと一致しない場合、NGINX Plusは$secure_link変数を空文字列に設定します。その結果、ifテストは失敗し、NGINX PlusはHTTPレスポンスで403 Forbiddenステータスコードを返します。

ハッシュは一致していてもリンクの有効期限が切れている場合、NGINX Plusは$secure_link変数を0に設定します。この場合もifテストは失敗しますが、NGINX PlusはHTTPレスポンスで410 Goneステータスコードを返します。

クライアントでのハッシュと有効期限の生成

次に、クライアントがURLに含めるmd5引数とexpires引数をどのように計算するかを見てみましょう。

最初に、有効期限に相当するUnix時間を求めます。この値は、ハッシュ化される式に$secure_link_expires変数として含まれるためです。Unix時間(Epoch(1970-01-01 00:00:00 UTC)からの経過秒数)を取得するには、dateコマンドで-dオプションと+%s書式指定子を使用します。

この例では、有効期限を2016年12月31日(土)23:59:00 UTCに設定するため、コマンドは次のようになります。

# date -d "2016-12-31 23:59" +%s1483228740

クライアントは、この値をリクエストURLの「expires=1483228740」引数として含めます。

次に、secure_link_md5ディレクティブで定義された文字列「$secure_link_expires$uri$remote_addr enigma」を3つのコマンドで処理します。

  • The openssl md5コマンドに-binaryオプションを指定すると、MD5ハッシュがバイナリ形式で生成されます。
  • The openssl base64コマンドは、ハッシュ値をBase64でエンコードします。
  • The trコマンドは、エンコードされた値のプラス記号(+)をハイフン(-)に、スラッシュ(/)をアンダースコア(_)に置き換え、等号(=)を削除します。

この例で使用する完全なコマンドは次のとおりです。

# echo -n '1483228740/files/pricelist.html192.168.33.14 enigma' | openssl md5 -binary | openssl base64 | tr +/ -_ | tr -d =AUEnXC7T-Tfv9WLsWbf-mw

クライアントは、この値をリクエストURLの「md5=AUEnXC7T-Tfv9WLsWbf-mw」引数として含めます。

プログラムによるハッシュの生成

NGINX Plus Webサーバーがアプリケーションサーバーから動的コンテンツを配信する場合、NGINX Plusとアプリケーションサーバーの両方で、同じセキュアURLを使用する必要があります。URL内のmd5引数に使用するハッシュは、プログラムで生成できます。次のNode.js関数は、上記のNGINX Plus構成スニペットで定義されているものと一致するハッシュを生成します。有効期限、URL、クライアントIPアドレス、秘密の文字列を引数として受け取り、Base64でエンコードされたバイナリ形式のMD5ハッシュを返します。

var crypto = require("crypto");

function generateSecurePathHash(expires, url, client_ip, secret) {
    if (!expires || !url || !client_ip || !secret) {
        return undefined;
    }
    
    var input = expires + url + client_ip + " " + secret;
    var binaryHash = crypto.createHash("md5").update(input).digest();
    var base64Value = new Buffer(binaryHash).toString('base64');
    return base64Value.replace(/=/g, '').replace(/+/g, '-').replace(///g, '_');
}

この例のハッシュを計算するには、次の引数を渡します。

generateSecurePathHash(new Date('12/31/2016 23:59:00').getTime()), '/files/pricelist.html', “192.168.33.14”, "enigma");

有効期限付きセキュアURLに対するサーバー応答

以下のサンプルcurlコマンドは、セキュリティで保護されたURLに対するサーバーの応答を示しています。

IPアドレス192.168.33.14のクライアントが正しいMD5ハッシュと有効期限を含めると、レスポンスは「200 OK」になります。

# curl -I --interface "192.168.33.14" 'http://secure-link-demo/files/pricelist.html?md5=AUEnXC7T-Tfv9WLsWbf-mw&expires=1483228740' | head -n 1
HTTP/1.1 200 OK

異なるIPアドレスのクライアントが同じURLを送信すると、レスポンスは「403 Forbidden」になります:

# curl -I --interface "192.168.33.33" 'http://secure-link-demo/files/pricelist.html?md5=AUEnXC7T-Tfv9WLsWbf-mw&expires=1483228740' | head -n 1HTTP/1.1 403 Forbidden

If the hash value of the md5引数のハッシュ値が正しくない場合、レスポンスは「403 Forbidden」になります:

# curl -I --interface "192.168.33.14" 'http://secure-link-demo/files/pricelist.html?md5=qeUNjiY2FTIVMaXUsxG-7w&expires=1483228740' | head -n 1HTTP/1.1 403 Forbidden

URLの有効期限が切れている場合(expires引数で示される日付が過去の場合)、レスポンスは410 Goneです:

# curl -I --interface "192.168.33.14" 'http://secure-link-demo/files/pricelist.html?md5=Z2rNva2InyVcRTlhqAkT4Q&expires=1467417540' | head -n 1HTTP/1.1 410 Gone

例:有効期限を設定してセグメントファイルを保護

次に、有効期限付きのセキュアURLを使用して、メディアアセットのプレイリストとセグメントファイルの両方を保護する例を示します。

前の例との違いの1つは、ここでmap設定ブロックを追加し、ファイル名を .m3u8ファイルであるプレイリストとHLSセグメント( .tsファイル)から拡張子を除いたうえで$file_name変数に格納し、secure_link_md5ディレクティブに渡す点です。これにより、プレイリストだけでなく、個々の .tsセグメントへのリクエストも保護できます。

最初の例とのもう1つの違いは、特定のWebブラウザーを使用するクライアントにアクセスを制限するため、$http_user_agent変数(User-Agentヘッダーを取得)をsecure_link_md5ディレクティブに含めている点です(たとえば、SafariではURLにアクセスできる一方、ChromeやFirefoxではアクセスできないようにします)。

map $uri $file_name {
    default none;
    "~*/s/(?<name>.*).m3u8" $name;
    "~*/s/(?<name>.*).ts" $name;
}

server {
    listen 80;
    server_name secure-link-demo;

    location /s {
        root /opt;
        secure_link $arg_md5,$arg_expires;
        secure_link_md5 "$secure_link_expires$file_name$http_user_agent enigma";

        if ($secure_link = "") { return 403; }
        if ($secure_link = "0") { return 410; }
    }
}

まとめ

NGINXのSecure Linkモジュールでは、URLの特定部分のハッシュなどのエンコード済みデータを追加することで、不正アクセスからファイルを保護できます。有効期限を追加すると、リンクの有効期間も制限できるため、セキュリティをさらに強化できます。

NGINX Plusをお試しいただくには、今すぐ30日間の無料トライアルを開始するか、ユースケースについてご相談ください。


共有
タグ: Security, F5 NGINX, Tech, 2016

著者について

Kunal Pariani
Kunal Parianiテクニカルソリューションアーキテクト

Kunal Parianiのその他のブログ記事

関連するブログ記事

チュートリアル:NGINXを使用してアプリケーション向けにOpenTelemetryを構成する
NGINX | 2024/01/18

チュートリアル:NGINXを使用してアプリケーション向けにOpenTelemetryを構成する

OpenTelemetryを使用してNGINXサーバーの状態と健全性をより明確に把握することで、問題のトラブルシューティングを行い、Webアプリケーションのパフォーマンスを向上できます。

AkitaのNGINX Plus認定モジュールでAPIエンドポイントの問題を特定・修正
NGINX | 2023/05/31

AkitaのNGINX Plus認定モジュールでAPIエンドポイントの問題を特定・修正

Akita SoftwareのAPIオブザーバビリティ向け新しいNGINX Plus Certified Moduleを活用して、APIエンドポイントの問題をより迅速に特定し、修正できます。

JavaプロジェクトをスキャフォールディングしてKubernetesネイティブアプリを構築する方法
NGINX | 2023/02/09

JavaプロジェクトをスキャフォールディングしてKubernetesネイティブアプリを構築する方法

Red HatのシニアプリンシパルデベロッパーアドボケイトであるDaniel Oh氏が、Javaプロジェクトをゼロから構築するためのひな形を作成し、Kubernetesネイティブアプリケーションとしてビルドする方法を解説します。

F5 NGINXaaS for Azureのご紹介:Microsoft Azureでネイティブに利用できるSaaS型ロードバランシング
NGINX | 2022/05/24

F5 NGINXaaS for Azureのご紹介:Microsoft Azureでネイティブに利用できるSaaS型ロードバランシング

NGINXaaS for Azureは、NGINX PlusをAzureネイティブのSaaSとして利用できるサービスです。NGINXの設定を容易にリフト&シフトできるため、クラウド導入とモダンアプリの展開を加速できます。

SELinuxでNGINXおよびNGINX Plusを使用する
NGINX | 2018/08/17

SELinuxでNGINXおよびNGINX Plusを使用する

Red Hat Enterprise Linux(RHEL)および関連ディストリビューションでSecurity-Enhanced Linux(SELinux)を有効にすると、デフォルト設定によりNGINXとNGINX Plusの一部の操作が制限されます。この記事では、すべての機能を利用できるようにSELinuxの設定を変更する方法について説明します。

NGINX PlusによるAWS Auto Scalingグループの負荷分散
NGINX | 2017/03/06

NGINX PlusによるAWS Auto Scalingグループの負荷分散

新しいnginx-asg-sync統合ソフトウェアにより、NGINX PlusはAWS Auto Scalingグループの負荷を直接分散できるため、クラウドでの負荷分散がさらに柔軟になります