ngx_mrubyを利用して、TLS 1.2以下のサポート終了予告ページを表示する

昨今、セキュリティ上の問題から、TLS 1.2以前のバージョンを利用するブラウザのサポート終了のニュースを目にすることが増えてきました。

Yahoo!セキュリティセンター | セキュリティ強化のお知らせ
Adobe Sign:TLS 1.0 および 1.1 のサポート終了

サポート終了にあたり、WEBサービス提供者としてはユーザーに何らかの方法で、ユーザーの対応の有無を通知したい場合がほとんどだと思います。このエントリでは先日、ngx_mrubyに追加された、 tls_version メソッドを利用して、クライアントのサポートするTLSバージョンに応じて表示するサイトのコンテンツを変更する方法を紹介したいと思います。

今回の例は下記のような構成を想定しています。HTTPS(TLS)通信はNginxで終端し、後段のOriginサーバへHTTPでリバースプロキシする構成です。

この構成の場合に、TLS1.2、1.3であれば、 /tls_ok にリバースプロキシし、 該当しなければ /tls_ng にリバースプロキシするコンフィグ例は下記になります。

mruby_ssl_handshake_handler_code '
  ssl = Nginx::SSL.new
  Userdata.new.ssl_tls_version = ssl.tls_version
';

location / {
  mruby_set_code $proxy_path "
    Userdata.new.ssl_tls_version =~ /TLSv1\.(2|3)/ ? 'ok' : 'ng'
  ";
  proxy_pass  http://<origin_ip>/tls_$proxy_path;
}

コンフィグの内容としてはSSLハンドシェーク中にクライアントのTLSバージョンを取得し、それをSSLハンドシェークの処理のコンテキスト外に値を持ち運ぶために、mruby-userdataのクラスであるUserDataに値を格納しています。

locationディレクティブにおいて、UserDataからTLSバージョンを取り出し、値を評価し、 okng をNginxの変数である $proxy_path に格納しています。そして、 $proxy_path の値を利用して、プロキシ先のパスを /tls_ok/tls_ng に振り分けるという実装です。

なお、 tls_version の戻り値はOpenSSLのSSL_get_version関数の戻り値である、次のいずれかです。

  • SSLv3
  • TLSv1
  • TLSv1.1
  • TLSv1.2
  • TLSv1.3
  • unknown

例えば、下記のような実装でも良いとは思います。


Userdata.new.ssl_tls_version.gsub(/TLSv/, '').to_f >= 1.2 ? 'ok' : 'ng'

実行結果は下記のようになります。

irb(main):007:0> "SSLv3".gsub(/TLSv/, '').to_f
=> 0.0
irb(main):008:0> "TLSv1".gsub(/TLSv/, '').to_f
=> 1.0
irb(main):009:0> "TLSv1.2".gsub(/TLSv/, '').to_f
=> 1.2
irb(main):010:0> "unknown".gsub(/TLSv/, '').to_f
=> 0.0
irb(main):011:0>

後者のほうが今後1.4が出た場合も何もしなくていいので便利かもしれません。

追記(2018/08/07)

ngx_mrubyを利用していない場合でも $ssl_protocol に同等の値が入るので、ngx_mrubyを未導入の場合はこちらの値を利用すると良いでしょう。

まとめ

今後、業界内で更に加速するTLS 1.2以下のサポート終了にあたり、 tls_version のAPIを利用すればミドウェアのレイヤで簡単にクライアントを振り分けることが可能です。ngx_mrubyを利用すればこれだけではなく、下記の資料のように多くの問題をプラガブルに解決できる仕組みが揃っているので、ぜひこの機会に導入を考えてみてはいかがでしょうか。