Tech Racho エンジニアの「?」を「!」に。
  • Ruby / Rails関連

Rails: config.force_ssl = true によるHSTSの動作をローカル環境とChromeで試してみた

こんにちは、hachi8833です。RailsのHSTS周りでハマったので、自分のためにメモします。

Rails 5.0以降ではforce_sslでHSTSも有効になる

Rails 5.0以降では、アプリケーション設定でconfig.force_ssl = trueを指定すると、HSTS設定「も」同時に有効になります。

参考: Rails 5 adds more control to fine tuning SSL usage | BigBinary Blog

参考: HTTP Strict Transport Security - Wikipedia

HTTP Strict Transport Security (エイチティーティーピー・ストリクト・トランスポート・セキュリティ、略称 HSTS)とは、WebサーバーがWebブラウザに対して、現在接続しているドメイン(サブドメインを含む場合もある)に対するアクセスにおいて、次回以降HTTPの代わりにHTTPSを使うように伝達するセキュリティ機構である。RFC 6797 で規定されている。
Wikipediaより

Chromeでローカル環境のHSTSを試してみる

整理のため、ローカル環境でRailsとChromeのHSTSの挙動を試してみました。

使ったもの

  • 業務で使わない「捨て」のRailsアプリ環境(ここではRails 6.0.3.1を使いました)
  • Chrome(現時点では83.0.4103.97)
  • ngrok

自分はMac環境でやりました。

手順

作業内容はgitに登録する必要はありません。

1. 準備

  • Railsアプリのconfig/environments/development.rbのRails.application.configure doブロックに以下を追加しておきます。
  config.hosts << 'usousouso.lvh.me'
  config.hosts << ".ngrok.io"

念のため、config.hostsのローカルホスト名にはusousouso.lvh.meのような絶対使いそうにないものを使いました。

  • Chromeを起動し、念のためシークレットモードのウィンドウを開いておきます(コマンド+Shift+Nキー)。以後は常にここでブラウザ画面を表示します。

2. lvh.meでの通常起動を確認

  • ローカルでRailsアプリをbin/rails sで起動します。
  • ブラウザでhttp://usousouso.lvh.me:3000を開いてアプリ画面が表示されることを確認し、Railsをいったん終了します。

3. HSTSを有効にし、lvh.meにHTTP接続する

  • Railsアプリのconfig/environments/development.rbのRails.application.configure doブロックに以下を一時的に追加します。
config.force_ssl = true
  • ブラウザで再度http://usousouso.lvh.me:3000を開きます。以下の画面が表示されます。

このサイトは安全に接続できません
localhostから無効な応答が送信されました。
ERR_SSL_PROTOCOL_ERROR

Railsには以下のようなログが表示されます。

2020-04-30 12:19:55 +0000: HTTP parse error, malformed request (): #<Puma::HttpParserError: Invalid HTTP format, parsing fails.>
  • ここでRailsをいったん止め、config.force_ssl = trueをコメントアウトして再び起動しても、同じエラー画面が出ます。

ハマった点

話が横にそれますが、実はこのときの挙動がなかなかわからなくてebiさんとbabaさんに助けていただきました。このときのERR_SSL_PROTOCOL_ERRORは、HSTSそのもので設定されるのではなく、それより前の段階でHTTPSへの恒久的な301リダイレクトが発生して↓それがブラウザに記憶され、以後のHTTPアクセスでも同じリダイレクトが繰り返されます。

babaさんのご指摘のように、ブラウザ内のHSTSリストへの登録は、HTTPS接続を確立できてからでないと行えません。そしてここまでのセットアップではHTTPS通信を用意していません。

このとき表示されたERR_SSL_PROTOCOL_ERRORそのものは、単に「ブラウザから送ったプロトコルとサーバーが期待するプロトコルの不一致」を表すエラーであり、これ自体はHSTSともリダイレクトとも無関係であるとbabaさんから教わりました。

ebiさんに以下のリンクも教えていただきました。

参考: Strict-Transport-Security - HTTP | MDN

メモ: サイトに HTTP を使用してアクセスしたとき、ブラウザーは Strict-Transport-Security ヘッダーを無視します。これは攻撃者が HTTP 接続に介入して、ヘッダーを挿入したり削除したりするかもしれないからです。ウェブサイトに HTTPS でアクセスして、証明書のエラーがない場合、ブラウザーはサイトが HTTPS でアクセスできることを知り、 Strict-Transport-Security ヘッダーを信用します。
developer.mozilla.orgより

この記事ではすべてChromeのシークレットモードウィンドウで作業しているので、ウィンドウを閉じればいつでも元通りになりますが、これを通常ウィンドウでやってしまうと(以前localhostでこれをやってしまいました😅)、記事末尾に書いたようにブラウザのキャッシュを削除しないとそのURLではHTTPSへの301リダイレクトを取り消せません。

自分がこれを知らずに、RailsのHSTSをオンにして通常ウィンドウでlocalhost:3000にアクセスしてしまったとき、記事末尾のリンクを参考にChromeのchrome://net-internals/#hstsでHSTS設定からこのドメイン名を削除しようとしても、ドメイン名がそもそも登録されていなくて首を傾げたのですが、これが理由だったんですね...

4. HSTSを有効にし、ngrokでhttp接続する

ここからが通常のHSTSらしい動作になります。なおngrokでは同じURLでHTTP接続とHTTPS接続を両方使えます。

  • Railsをいったん終了し、再びconfig.force_ssl = trueを有効にして再度起動します。
  • ターミナルで別ウィンドウを開き、ngrok http 3000でngrokを起動します。

  • まずHTTPでアクセスしてみます。ngrok画面に表示されているhttp://で始まるURLをコピーして、シークレットモードウィンドウで開きます。

少々待たされますが、今度は以下のように307一時リダイレクトが発生し、正常にHTTPS接続できるようになります。

なおbabaさんによると、この307リダイレクトはChromeの内部実装によるもので、通信を伴わないそうです。まだ試していませんが、Firefoxでは起きないとも教わりました。

  • HTTPとHTTPSでそれぞれアクセスを繰り返します。

以後はHTTPでアクセスすると常に307でリダイレクトし、HTTPSでアクセスすれば普通にHTTP 200で接続されます。

以上でお試しはおしまいです。Chromeのシークレットモードウィンドウを閉じ、ngrokとRailsも終了します。development.rbの変更も元に戻しておきましょう。
あ〜すっきりした。

おまけ: Chromeを復元する方法

実は元々この部分を記事にしていました😅。

たとえばHTTPS接続のないRailsのdevelopment環境でforce_ssl = trueをオンにしたまま、うっかりhttp://localhost:3000をブラウザで開いてしまうと、ブラウザにlocalhostドメインが記憶されてしまい、先ほどと同じくHSTSが発動してしまいます(してしまいました)。こうなるとそのブラウザではローカルの開発サーバーをlocalhostで開けなくなってしまいます。

急いでいるときなら、ブラウザのシークレットモードウィンドウでhttp://localhost:3000にアクセスするなりlvh.meを使うなりすれば取りあえず回避はできますが。

解決方法その1: ChromeのキャッシュとCookieを削除する

301リダイレクトが原因の場合は、この方法で復元します。

Chromeで⌘ + ,キーを押して設定を表示し、「セキュリティとプライバシー」の「閲覧データの削除」を開きます。

「Cookieと他のサイトデータを削除」「キャッシュされた画像とファイル」チェックボックスをオンにします。発生してすぐなら期間を「1時間以内」にしておくとよいでしょう。
「データを削除」をクリックし、Chromeを再起動します。

解決方法その2: ChromeのHSTS設定を解除する

以下の記事では、Chromeのchrome://net-internals/#hstsでHSTS設定からドメイン名を削除する方法が紹介されています。HTTPS接続が確立してブラウザのHSTSリストに登録された場合は、この方法で復元します。

参考: Google Chrome SSL Error Guide for “ERR_SSL_PROTOCOL_ERROR”


CONTACT

TechRachoでは、パートナーシップをご検討いただける方からの
ご連絡をお待ちしております。ぜひお気軽にご意見・ご相談ください。