Apache 2.4系でHTTP/2サーバを構築してみるテスト。
User Agent Client Hintsをサーバ側で取得してみる。[2020年6月1日修正:Chrome/83の挙動に合わせました。] [2020年7月15日修正:Chrome/84の挙動を加えました。] [2021年6月4日修正:Chrome/91の挙動を加え、Accept-CHのトークンを修正し、Sec-Ch-Ua-Bitnessを追加しました。] これまでのUser Agentリクエストヘッダを置き換える技術として、Google社が推進しているUser Agent Client Hintsですが、実験的ながら、Chromeが対応を始めたようです。そこで、早速、サーバで取得してみようと思います。 結論から言うと、これまでのリクエストベースのアクセスログに、User Agent Client Hintsを含めることは難しく、User Agent Client Hintsをログ取得したい場合、TLSセッションベースのログへ移行していくことになると思われます。 まず、独自モジュールを使用しないで、リクエストヘッダに、User Agent Client Hintsを含め方法を補足しておきます。 最後に、独自にモジュールを作成して、User Agent Client Hintsを取得してみます。 なお、お使いのブラウザが、User Agent Client Hintsに対応しているか、及び、送信しているUser Agent Client Hints(Sec-Ch-Ua)は、「あなたのブラウザのTLS/HTTP接続情報」から確認できます。
User Agent Client Hintsを利用するためのブラウザ側の準備User Agent Client Hintsは、いずれ何もしなくてもブラウザがサーバに伝えるようになると思われますが、いまのところChrome 79以降が実験的に対応しているようです。 Chrome 79で、User Agent Client Hintsを有効にする方法は以下の通りです まず、①アドレスバーに「chrome://flags」と打ち込み、以下の図のような、フラグ設定画面に進みます。 ![]() 続いて、②フラグ設定画面の検索ボックスに「Experimental Web Platform feature」と入力して、検索を行います。 ③「Experimental Web Platform features」を見つけたら、④のように「Enabled」にして、ブラウザを再起動します。 これで、サーバにUser Agent Client Hintsを送信するようになります。 お使いのブラウザが、送信しているUser Agent Client Hints(Sec-Ch-Ua)は、「あなたのブラウザのTLS/HTTP接続情報」から確認できます。
サーバ側でやならなければならないこと最初のリクエストを受信したときに、リクエストヘッダに「Sec-Ch-Ua」ヘッダがあるか確認します。 このヘッダがあれば、User Agent Client Hintsに対応しています。 このあと、何もしなければこれ以上の情報を得ることはできません。つまり、いずれ「User-Agent」ヘッダが固定化、縮小、廃止された場合には、この「Sec-Ch-Ua」ヘッダ(Chrome 83以降は、Sec-Ch-Ua-Mobileも)の情報だけが、リクエストログに残せる情報となります。 それでは困る人は、レスポンスヘッダに、「Accept-CH」ヘッダを返します。 Accept-CHレスポンスヘッダには、欲しい情報をカンマ区切りで入れます。Apacheの設定だと、ヘッダモジュールを有効にしてから、 Header set Accept-CH "Sec-CH-UA, Sec-CH-UA-Arch, Sec-CH-UA-Bitness, Sec-CH-UA-Full-Version, Sec-CH-UA-Mobile, Sec-CH-UA-Model, Sec-CH-UA-Platform, Sec-CH-UA-Platform-Version" と設定します。 Chrome/83の場合サーバが「Accept-CH」ヘッダを返すと、ブラウザは、次に送信するリクエストから、 Sec-Ch-Ua "Google Chrome"; v="83" Sec-Ch-Ua-Arch "Win64; x64" Sec-Ch-Ua-Full-Version "83.0.4103.61" Sec-Ch-Ua-Mobile ?0 Sec-Ch-Ua-Model "" Sec-Ch-Ua-Platform "Windows"; v="10.0; " といった、これまでのUserAgentヘッダに近い情報を送ってくれるようになります。 上記例では、Sec-Ch-Ua-Modelが取得できていませんが、Androidのスマートフォンでは取得できるようです。Windowsは、UserAgentから、機種名までは取得できていなかったので、いまのところ、もともと、UserAgentで取得できていたものが、取れるようです。 Chrome/84の場合Chrome/83以前は、Sec-Ch-Uaが初回と、2回目以降で微妙に異なっていたのですが、Chrome/84の場合、Sec-Ch-Uaが毎回同じ内容が帰るようになりました。 また、Chrome/84からは、Sec-Ch-Uaに派生元のブラウザの情報を含むようになっています。こちらは、まだ開発中といった感じの文字列ですが、Chromiumから派生したことが読み取れそうな情報が入っています。 そして、Sec-Ch-Ua-PlatformVersionが値を返すようになりました。 Sec-Ch-Ua: "\\Not\"A;Brand";v="99", "Chromium";v="84", "Google Chrome";v="84" Sec-Ch-Ua-Full-Version: "84.0.4147.89" Sec-Ch-Ua-Arch: "x86" Sec-Ch-Ua-Platform: "Windows" Sec-Ch-Ua-PlatformVersion: "10.0" Sec-Ch-Ua-Mobile ?0 Sec-Ch-Ua-Model "" Chrome/91の場合Sec-Ch-Ua-Archから、ビット数の情報が削られました。 削られたビット数が入ると思われる、Sec-Ch-Ua-Bitnessはまだのようです。 Sec-Ch-Ua " Not;A Brand";v="99", "Google Chrome";v="91";, "Chromium";v="91" Sec-Ch-Ua-Arch "x86" Sec-Ch-Ua-Full-Version "91.0.4472.77" Sec-Ch-Ua-Mobile ?0 Sec-Ch-Ua-Model "" Sec-Ch-Ua-Platform "Windows" Sec-Ch-Ua-Platform-Version "10.0" 当面は、バージョンアップ毎に挙動が変わりそうな予感がします。 もうお気づきのことと思いますが、TLSセッションの1ファイル目のリクエスト処理中は、詳しい情報が記載される「Sec-CH-UA-*」を取得できません。 TLSセッションの2ファイル目以降のリクエストから、上記のような詳しい「Sec-CH-UA」を取得きます。 そのため、詳しい「Sec-CH-UA-*」を取得するためには、必要なくても、1ファイル目のHTMLに、cssなどのファイルを読み込むように記述しておく必要があります。 たとえば、HTMLに、以下のようなlinkタグを入れて、cssファイルを読み込むようにします。 <link href="common.css" rel="stylesheet" type="text/css"> 画像でも構いませんが、サーバ上で公開している、すべてのHTMLに共通して設定しやすいcssファイルがおすすめです。 これで、cssファイルをリクエストする際に、詳しいSec-Ch-Uaリクエストヘッダ群を取得できます。
設定変更だけで、User Agent Client HintsをログにとるにはApache Httpdの設定変更だけで、User Agent Client Hintsの情報を取得する方法です。 まず、ヘッダモジュールをロードされているか確認します。ロードされていなければ、以下の設定を追加します。
/usr/local/apache/conf/httpd.conf
LoadModule headers_module modules/mod_headers.so 次に、以下の設定によって、Accept-CHヘッダを常に返すようにします。
/usr/local/apache/conf/httpd.conf
Header set Accept-CH "UA-Full-Version, UA-Arch, UA-Model, UA-Platform, UA-Pla tform-Version, UA-Mobile" 今回は、以下のようなCustomLogの場合を想定しています。 combinedのログフォーマットを変更することで、User-Agentヘッダに変わって、Sec-Ch-Uaヘッダのログをとります。
/usr/local/apache2/conf/extra/httpd-ssl-vhost.confの一部
<VirtualHost *:443> …(省略)… CustomLog "|/usr/local/apache2/bin/rotatelogs -l /var/log/https/example.com/access_log.%Y%m%d 86400" combined CustomLog "|/usr/local/apache2/bin/rotatelogs -l /var/log/https/example.com/ssl_request_log.%Y%m%d 86400" "%t %h %{SSL_PROTOCOL}x %{SSL_CIPHER}x \"%r\" %b" …(省略)… </VirtualHost> ということで、combinedのフォーマットを以下の様にします。
/usr/local/apache/conf/httpd.conf
LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{Sec-Ch-Ua}i\":\"%{Sec-Ch-Ua-Full-Version}i\":\"%{Sec-Ch-Ua-Arch}i\":\"%{Sec-Ch-Ua-Model}i\":\"%{Sec-Ch-Ua-Platform}i\"" combined 最後に、
/usr/local/apache/conf/httpd.conf
SetEnvIf Request_URI "\.(gif)|(jpg)|(png)|(js)|(css)$" nolog のように、SetEnvIfでログ出力する拡張子を除外している場合、cssのログをとるように、
/usr/local/apache/conf/httpd.conf
SetEnvIf Request_URI "\.(gif)|(jpg)|(png)|(js)$" nolog として、cssのログも取ります。 すると、cssファイルだけになりますが、アクセスログのUser-Agentヘッダを出力していた場所に、User Agent Client Hintsのヘッダ群がコロン区切りで入るようになります。 実は、上記のLogFormatに弱点があって、Chrome 79が最初に返すSec-Ch-Uaは、ダブルクォーテーションに囲われていないのですが、Accept-CHを送信後に受信するSec-Ch-Ua等は、ダブルクォーテーションで囲われています。Chrome79が最初に返す、Sec-Ch-Uaもスペース文字を含むため、ダブルクォーテーションで囲わないと、スペース文字を区切りとして解析できなくなるので、ダブルクォーテーションで囲う必要があります。そのため、Accept-CHを送信後に受信するSec-Ch-Uaは、ダブルクォーテーションが重なるため、ログ解析には注意が必要です。 ということで、理想的な、User Agent Client Hintsの取得は、モジュールを開発しないとうまくいかなさそうです。
セッションログのススメということで、当サイトでは、既存のaccess_logに加えて、モジュールを組んで、以下のような、セッションログをとっています。 これは、User Agent Client Hintsや、TLS接続の情報など同一セッションであれば、どのリクエストからも同じログが書かれる部分と、リクエストごとにログが異なる部分を分けて書き込む方法です。 下記は典型的な1セッション分のログです。これが延々とセッション毎に繰り返されます。 2020-01-DD HH:MM:SS.358011 605236383 XXX.XXX.XXX.XXX:YYYYY HTTP/2.0 TLSv1.3 TLS_AES_256_GCM_SHA384 "X25519:253 bits" "Google Chrome 79" "Google Chrome 79.0.3945.130":"":"":"Windows":"" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36" 0x1A1A:TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-RSA-AES128-SHA:ECDHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA:AES256-SHA:DES-CBC3-SHA 0x9A9A:X25519:P-256:P-384 ECDSA+SHA256:RSA-PSS+SHA256:RSA+SHA256:ECDSA+SHA384:RSA-PSS+SHA384:RSA+SHA384:RSA-PSS+SHA512:RSA+SHA512:RSA+SHA1 +0 4761 GET /tls_check 200 11859 "/tls_check" +324833 621 GET /logo_dark.svg 304 0 "/tls_check" +5095269 80064 GET /favicon.ico 200 99678 "/tls_check" 1行目に、TLSセッション全体にあてはまるTLS接続や、User Agent Client Hintsといった情報を羅列し、2行目以降、タブ文字から始まる行がリクエストログになります。 1行目の先頭は、タイムスタンプ(マイクロ秒)です。これが、続くリクエスト行の起点にもなります。 そのほかに、1行目には、セッションの長さ(マイクロ秒)、クライアントのIP/Port、HTTP バージョン、TLSバージョン、暗号スイート、鍵交換方式、User Agent Client Hints(初回:上記、赤色の部分)、User Agent Client Hints(詳細:上記、紫色の部分)、User-Agent、提示された暗号スイート、提示された鍵交換方式、提示された署名アルゴリズムと続きます。 リクエストログの先頭は、1行目のタイムスタンプからの差分(マイクロ秒)で、次に、リクエスト処理にかかった時間(マイクロ秒)、リクエスト本体に、レスポンスコード、転送バイト数、リファラになります。 このようなセッションログは、すべてのリクエストが揃わないと、ファイルに書き込むことができません。 そのため、このログは、TLSセッションが切断されたところで書かれます。 どうやったらこんなログが出せるモジュールが作れるのか、不思議に思われる方もいらっしゃると思いますが、その話はまた別の機会に。
|