発生した問題
Apacheのセキュリティアップデート後、HostヘッダーだけではSSL証明書を判断できず、特定のアクセスで421 Misdirected Request エラーが発生するようになりました。
「421 Misdirected Request」は、RFC 7540(HTTP/2)で導入されたステータスで、クライアントが意図したリクエスト(例: サイトA)を、サーバー側が「このTLSコネクションはサイトB用だ」 と判断した際に返すエラーです。
今回のケースでは、SNI(Server Name Indication)が付いていないため、TLSハンドシェイクの時点ではApache側で「どの証明書/どのVirtualHostか」を特定できません。
その状態で、HostヘッダーのみでVirtualHostを切り替えようとする動作を、Apacheのセキュリティアップデート(厳格化)によってより厳しく拒否するようになった、というのが421エラーの主な原因と考えられます。
通常はSNIを付けてリクエストすれば解決しますが、今回は上位のロードバランサ(LB)側がSNIを付けられないという制約があり、別の対策が必要となりました。
対策
インターネット上にもSNIを付ける以外の根本的な対応策が少ないため、SNIの仕組みから調査を開始しました。
SNIの仕組みについて
複数のWebサイトが1つのサーバーでホストされ、単一のIPアドレスを共有し、各Webサイトに独自のSSL証明書がある場合、クライアントが安全に接続しようとしてもサーバー側で表示する証明書が特定できないことがあります。これは、クライアントがHTTPを介して接続先Webサイトを示す前にSSL/TLSハンドシェイクが発生するためです。SNIはこの問題を解決する仕組みです。
(参考サイト:https://www.cloudflare.com/ja-jp/learning/ssl/what-is-sni/ )
Apacheでは、同じIPアドレス・ポートで 複数のTLS VirtualHost(=複数証明書)を出し分けたい場合に、どの証明書を出すかをSNIで識別 します。
■httpd -S (変更前)
VirtualHost configuration:
*:443 is a NameVirtualHost
default server site-a.example.com (/etc/httpd/conf.d/vh-site-a.conf:2)
port 443 namevhost site-b.example.com (/etc/httpd/conf.d/vh-site-b.conf:2)
この状態では、site-b.example.com にSNIなしでアクセスするとdefaultである site-a.example.com の証明書が提示され、Hostヘッダーでルーティングしようとする際にApacheの厳格なチェックに引っかかり、421エラーが発生していました。( site-a.example.com はアクセス可能)
上記から「同じ IP・ポートで複数証明書を使うから SNI が必要になる」という理解に基づき、IPアドレスを分ければSNIなしでも正しいサイトに飛ばせるはずと考えました。
■Apache 設定例
<VirtualHost 192.168.100.10:443>
ServerName site-a.example.com
DocumentRoot "/var/www/html/sitea"
SSLEngine on
SSLCertificateFile /etc/ssl/certs/example-a.crt
SSLCertificateKeyFile /etc/ssl/private/example-a.key
</VirtualHost>
<VirtualHost 192.168.100.20:443>
ServerName site-b.example.com
DocumentRoot "/var/www/html/siteb"
SSLEngine on
SSLCertificateFile /etc/ssl/certs/example-b.crt
SSLCertificateKeyFile /etc/ssl/private/example-b.key
</VirtualHost>
VirtualHostを異なるIPアドレスに紐づけます。
httpd -SでVirtualHostの配置を確認し、IPアドレスが分けられたことを確認します。
■httpd -S (変更後)
VirtualHost configuration:
192.168.100.10:443 site-a.example.com (/etc/httpd/conf.d/vh-site-a.conf:2)
192.168.100.20:443 site-b.example.com (/etc/httpd/conf.d/vh-site-b.conf:2)
この対応により、上位LB側で各ドメインへのアクセス時に宛先IPを分ける設定にすることで、421エラー問題は解消しました。
更にcurlやブラウザアクセスではデフォルトでSNIが付帯されてしまい再現試験ができなかったので、下記からopensslをインストール試験を実施しました。(opensslには SNI無しオプション -noservername があり)
■ダウロードサイト
■opensslコマンド
openssl s_client -connect localhost:10001 -noservername
まとめ
・IP を分ける方法は泥臭いけど、現場ではこういう局所的な対応が求められることもある
・理想は上位 LB で SNI 対応してもらうこと
・個人的にもマイナーな情報の記事に救われたこともあるので、記録として残しておきます