してみるテストロゴ
Apache 2.4系でHTTP/2サーバを構築してみるテスト。

User Agent Client Hintsをサーバ側で取得してみる。

これまでの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」と打ち込み、以下の図のような、フラグ設定画面に進みます。

図:Chromeのフラグ設定画面

続いて、②フラグ設定画面の検索ボックスに「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」ヘッダの情報だけが、リクエストログに残せる情報となります。

それでは困る人は、レスポンスヘッダに、「Accept-CH」ヘッダを返します。

Accept-CHレスポンスヘッダには、欲しい情報をカンマ区切りで入れます。

Header set Accept-CH "UA, Arch, Platform, Model, Mobile"

サーバが「Accept-CH」ヘッダを返すと、ブラウザは、次に送信するリクエストから、

Sec-Ch-Ua:	"Google Chrome 79.0.3945.130"
Sec-Ch-Ua-Arch:	
Sec-Ch-Ua-Model:	
Sec-Ch-Ua-Platform:	"Windows"	

といった、これまでのUserAgentヘッダに近い情報を送ってくれるようになります。

もうお気づきのことと思いますが、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, Arch, Platform, Model, 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-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セッションが切断されたところで書かれます。

どうやったらこんなログが出せるモジュールが作れるのか、不思議に思われる方もいらっしゃると思いますが、その話はまた別の機会に。

NEXT >> トップページに戻る

©Copyrights 2015-2020, non-standard programmer

このサイトは、あくまでも私の個人的体験を、綴ったものです。 軽く参考程度にご利用ください。