NGINXの条件付きログによるリクエストのサンプリング

NGINX | 2019年4月24日

NGINXでは、処理するすべてのトランザクションについて、非常に詳細なログを記録できます。このようなログは「アクセスログ」と呼ばれ、カスタマイズ可能なログファイル形式を使用して、サービスやロケーションごとに記録する詳細情報を細かく調整できます。

デフォルトでは、NGINXは処理するすべてのトランザクションをログに記録します。これはコンプライアンスやセキュリティのために必要な場合がありますが、アクセスの多いWebサイトでは、生成されるデータ量が膨大になる可能性があります。この記事では、さまざまな条件に基づいてトランザクションを選択的にログに記録する方法と、その知識を活用してリクエストに関するデータポイントを迅速かつ軽量にサンプリングする方法を紹介します。

特に記載がない限り、この記事はNGINX Open SourceとNGINX Plusの両方に適用されます。読みやすさを考慮し、以降は「NGINX」と表記します。

背景 – NGINXアクセスログ設定の概要

NGINXアクセスログは、log_formatディレクティブを使用して定義します。名前を付けた複数のログ形式を定義できます。たとえば、完全なログ形式にはmain、リクエストに関する3つのデータポイントを記録する簡略化されたログ形式にはnotesという名前を付けます。

log_format main  '$remote_addr - $remote_user [$time_local] "$request" '
                 '$status $body_bytes_sent "$http_referer" '
                 '"$http_user_agent" "$http_x_forwarded_for"';

log_format notes '$remote_addr "$request" $status';

ログ形式では、NGINX変数やログ記録時に算出されるその他の値を参照できます。

次に、NGINXに対し、トランザクションの完了時にログを記録するよう指示するため、access_logディレクティブを使用します。このディレクティブでは、ログファイルの保存先と使用するログ形式を指定します。

access_log  /var/log/nginx/access.log main;

デフォルトでは、NGINXは次の設定を使用してすべてのトランザクションをログに記録します。

access_log logs/access.log combined;

独自のaccess_logを定義すると、デフォルトのアクセスログが上書き(置換)されます。

条件付きロギング

特定のリクエストのみをログに記録したい場合があります。その場合は、次のように条件付きロギングを使用します。

server {
    listen 80;

    set $logme 0;
    if ( $uri ~ ^/secure ) {
        set $logme 1;
    }

    # Auditors require an additional log for requests to /secure
    access_log  /var/log/nginx/secure.log notes if=$logme;

    # If we have a global access log, we need to re-declare it here
    access_log  /var/log/nginx/access.log main;

    location / {
        # ...
    }
}

アクセスログは継承されない

アクセスログ設定は累積も継承もされません。あるコンテキストの「access_log」ディレクティブは、親コンテキストで宣言されたアクセスログを上書き(置換)します。

たとえば、URI「/secure」へのトラフィックに関する追加情報をログに記録する場合は、location /secure {...}ブロックでアクセスログを定義できます。このアクセスログは、設定内の別の場所で定義されている一般的なアクセスログを置き換えます。

この問題には、前のセクションの例で対処しています。この例では、同じコンテキスト内で2つのアクセスログを使用し、条件付きロギングによって/secureへのリクエストを専用のログファイルに記録します。

アクセスログに関する課題

Webサイトへのトラフィックに関する統計情報を確認するとします。

  • ユーザーは通常、地域別にどのような割合で分布していますか?
  • ユーザーはどのSSL/TLS暗号スイートとプロトコルを使用していますか?
  • Webブラウザの内訳はどうなっていますか?

一般的なアクセスログは、多くの場合、この情報の記録先として適切ではありません。調査に必要なフィールドを追加してアクセスログを煩雑にすることは避けたい場合があります。また、トラフィックの多いサイトでは、すべてのトランザクションを記録するとオーバーヘッドが大きくなりすぎます。

この場合、限定したフィールドのみを専用ログに記録できます。システムの負荷を軽減するために、リクエストの一部をサンプリングすることも検討できます。

サンプリング手法

リクエストの1%をサンプリング

次の設定では、$request_id変数を各リクエストの一意の識別子として使用します。また、split_clientsブロックを使用し、リクエストの1%のみをログに記録してデータをサンプリングします。

split_clients $request_id $logme {
    1%     1;
    *      0;
}

server {
    listen 80;

    access_log  /var/log/nginx/secure.log notes if=$logme;

    # ...
}

ユニークユーザーの1%をサンプリング

各ユーザーから1つずつ(またはユーザーの1%から)データポイントをサンプリングするとします。たとえば、User-Agentヘッダーです。すべてのリクエストから単純にサンプリングすると、リクエスト数の多いユーザーがデータ内で過剰に代表されることになります。

セッションCookieの有無を検出するためにmapブロックを使用します。これにより、リクエストが新規ユーザーからのものか、以前にアクセスしたユーザーからのものかを判別できます。その後、新規ユーザーからのリクエストのみをサンプリングします。

map $cookie_SESSION $logme {
    "" $perhaps;  # If the cookie is missing, we log if $perhaps
    default   0;
}

split_clients $request_id $perhaps {
    1%        1;  # $perhaps is true 1% of the time
    *         0;
}

server {
    listen 80;

    access_log  /var/log/nginx/secure.log notes if=$logme;
  
    # Optional: If the application does not generate a session cookie, we 
    # generate our own
    add_header Set-Cookie SESSION=1;

    # ...
}

一意な対象のサンプリング

ただし、すべてのクライアントがセッションCookieを受け入れるとは限りません。たとえば、WebスパイダーがCookieを無視すると、そのスパイダーからのリクエストはすべて新規ユーザーからのものと識別されるため、結果に偏りが生じます。

新しい「もの」を初めて検出したときに、リクエストからサンプリングできれば便利です。「もの」には、新しいIPアドレス、新しいセッションCookie値、新しいUser-Agentヘッダー、初めて検出されたHostヘッダー、さらにはこれらの組み合わせを指定できます。これにより、「もの」ごとにデータを1回だけサンプリングできます。

当然ながら、状態(これまでに確認した対象のリスト)を保存する必要があります。そのために、NGINX Plusのkey‑valueストアを使用します。key‑valueストアは、変数を使用してNGINX Plusの設定からアクセスできるインメモリのkey‑valueデータベースを維持します。このデータベースでは、必要に応じてエントリの自動失効(timeoutパラメーター)、永続ストレージ(state)、クラスタ同期(sync)を利用できます。ストアにまだ存在しない対象ごとにリクエストをログに記録し、その対象をストアに追加して、再度ログに記録されないようにします。

NGINX Plus R18以降では、トランザクションの処理中にキーと値のペアを設定することが非常に簡単です。

# Define a keyval zone with appropriate parameterskeyval_zone zone=clients:80m timeout=3600s;

# Create a variable $seen for each unique $remote_addr
keyval $remote_addr $seen zone=clients;

log_format notes '$remote_addr "$request" $status';

server {
    listen 80;

    # if $seen is empty, update the keyval (set $seen 1;) and log this 
    # request (set $logme 1;)
    # Otherwise, $logme is not set and we don't log the request
    # Note that $seen resets to "" after the configured timeout
    if ($seen = "") {
       set $seen 1;
       set $logme 1;
    }
    access_log  /var/log/nginx/secure.log notes if=$logme;

    location / {
        return 200 "All OK: -$seen-$logme-\n";
    }

    location /api {
        api;
    }
}

実環境での例 – TLSパラメーターのサンプリング

この記事は、実際に直面した課題から着想を得ています。レガシーデバイスを使用するユーザーを除外することなく、適切なプラクティスに従ってTLSを構成するにはどうすればよいのでしょうか。

TLSのベストプラクティスは常に変化しています。TLS 1.3は1年前に承認されましたが、多くのクライアントは以前のバージョンのTLSにしか対応していません。暗号スイートが「安全ではない」と判断されて廃止されても、古い実装では引き続き使用されています。ECC証明書はRSA証明書より高いパフォーマンスを発揮しますが、すべてのクライアントがECCに対応しているわけではありません。TLS攻撃の多くは「中間者」が暗号スイートのネゴシエーション時のハンドシェイクを傍受し、クライアントとサーバーに安全性の低い暗号スイートを選択させる手法を利用します。そのため、脆弱な暗号スイートやレガシー暗号スイートをサポートしないようにNGINX Plusを構成することが重要ですが、その場合、レガシークライアントが利用できなくなる可能性があります。

次の設定例では、各TLSクライアントをサンプリングし、SSLプロトコル、暗号スイート、User-Agentヘッダーをログに記録します。各クライアントが、対応している中で最新のプロトコルと最も安全な暗号スイートを選択すると仮定すれば、サンプリングしたデータを評価し、古いプロトコルと暗号スイートのサポートを終了した場合に除外されるクライアントの割合を特定できます。

各クライアントは、IPアドレスと「User-Agent」の一意の組み合わせによって識別しますが、セッションCookieなど、別の方法で識別しても同様に機能します。

log_format sslparams '$ssl_protocol $ssl_cipher '
                     '$remote_addr "$http_user_agent"';

# Define a keyval zone with appropriate parameters
keyval_zone zone=clients:80m timeout=3600s;

# Create a variable $seen for each unique combination of $remote_addr and 
# 'User-Agent' header
keyval $remote_addr:$http_user_agent $seen zone=clients;

server {
    listen 443 ssl;

    # default NGINX SSL configuration
    ssl_ciphers HIGH:!aNULL:!MD5;
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;

    if ($seen = "") {
       set $seen 1;
       set $logme 1;
    }
    access_log  /tmp/sslparams.log sslparams if=$logme;

    # ...
}

これにより、次のようなエントリを含むログファイルが生成されます。

TLSv1.2 AES128-SHA 1.1.1.1 "Mozilla/5.0 (X11; Linux x86_64; rv:45.0) Gecko/20100101 Firefox/45.0"
TLSv1.2 ECDHE-RSA-AES128-GCM-SHA256 2.2.2.2 "Mozilla/5.0 (iPhone; CPU iPhone OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1"
TLSv1.2 ECDHE-RSA-AES128-GCM-SHA256 3.3.3.3 "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:58.0) Gecko/20100101 Firefox/58.0"
TLSv1.2 ECDHE-RSA-AES128-GCM-SHA256 4.4.4.4 "Mozilla/5.0 (Android 4.4.2; Tablet; rv:65.0) Gecko/65.0 Firefox/65.0"
TLSv1 AES128-SHA 5.5.5.5 "Mozilla/5.0 (Android 4.4.2; Tablet; rv:65.0) Gecko/65.0 Firefox/65.0"
TLSv1.2 ECDHE-RSA-CHACHA20-POLY1305 6.6.6.6 "Mozilla/5.0 (Linux; U; Android 5.0.2; en-US; XT1068 Build/LXB22.46-28) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/57.0.2987.108 UCBrowser/12.10.2.1164 Mobile Safari/537.36"

次に、さまざまな方法でファイルを処理し、データの分布を確認できます。

$ cat /tmp/sslparams.log | cut -d ' ' -f 2,2 | sort | uniq -c | sort -rn | perl -ane 'printf "%30s %s\n", $F[1], "="x$F[0];'    ECDHE-RSA-AES128-GCM-SHA256 =========================
    ECDHE-RSA-AES256-GCM-SHA384 ========
                     AES128-SHA ====
    ECDHE-RSA-CHACHA20-POLY1305 ==
        ECDHE-RSA-AES256-SHA384 ==

使用量が少なく、安全性の低い暗号スイートを特定し、ログを確認して、それらを使用しているクライアントを把握します。そのうえで、NGINX Plusの構成から暗号スイートを削除するかどうかを適切に判断します。

まとめ

NGINXの条件付きロギングを使用すると、NGINXが処理するリクエストの一部をサンプリングし、標準ログまたは特定用途向けのログに書き込むことができます。この手法は、SSLパラメーターの分布を把握する場合など、統計分析のためにトラフィックをすばやくサンプリングする必要があるときに役立ちます。

アクセス頻度の高いユーザーやスパイダーが過度に多く含まれないよう、データのサンプリング方法を慎重に検討する必要があります。NGINX設定の変数と、mapおよびsplit_clientsディレクティブを使用して、リクエストを選択し、フィルタリングできます。

判断がより複雑な場合や、高い精度が求められる場合は、NGINXの設定で高度なセレクターを構築できます。NGINX Plusのキーバリューストアを使用すると、状態を蓄積し、必要に応じてクラスター内のNGINX Plusインスタンス間で共有できます。

NGINX Plusのリクエストサンプリングをぜひお試しください。今すぐ30日間の無料トライアルを開始するか、お問い合わせのうえ、ユースケースについてご相談ください。


共有
タグ: SSL/TLS, F5 NGINX, Tech, 2016

著者について

Owen Garrett
Owen Garrettプロダクトマネジメント担当シニアディレクター

Owen Garrettのその他のブログ記事

関連するブログ記事

チュートリアル: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グループの負荷を直接分散できるため、クラウドでの負荷分散がさらに柔軟になります