PRで解説する、Jekyll + Heroku で HTTPS 対応

Appleの[App Transport Securityが延期になった](https://developer.apple.com/news/?id=12212016b)とはいえ、SEO的にも信頼性的にも<b>「とりあえず https にしとくか」</b>みたいな時代になってきましたね 🤔

ということで、最近話題の [Let's Encrypt](https://letsencrypt.jp/) を使って Jekyll + Heroku で構成されている[Railsガイド](https://railsguides.jp)を HTTPS に対応してみました。

具体的な実装は下記のプルリクエスト (以下、PR) で公開しています。

Enable SSL in Production #277
https://github.com/yasslab/railsguides.jp/pull/277

上記PRから汎用的なコミットをpickupして、１つずつ解説していこうと思います。

- :beginner: __Let's Encryptの使い方や、SSL証明書の取得手順などについては[総合ポータル](https://letsencrypt.jp/)をご参照ください。__
- :warning: __本番環境で動作確認するのは危険なので、[HerokuのFork機能](https://devcenter.heroku.com/articles/fork-app)などでテスト用の環境を作り、そこで動作確認しましょう。__

# TL;DR

- [acme_challenge](https://github.com/soffes/acme_challenge) gem で証明書取得が便利 
- [rack-rewrite](https://github.com/jtrupiano/rack-rewrite) gem で https にリダイレクト
- [rack-contrib](https://github.com/rack/rack-contrib/) gem でヘッダーを適宜修正

## 1. [Add acme_challenge gem to be certified](https://github.com/yasslab/railsguides.jp/pull/277/commits/8ac644e3be038db227e038e6fd60f74d97d556d2)

Let's EncryptでSSL証明書を取得するためには、特定のURLにアクセスしたら特定の結果を返す必要があります (ACME CHALLENGEと呼ばれています)。環境変数を使えば良さそうですが、「Jekyllで環境変数を扱うにはどうしたら良いのか?🤔」という点で結構はまっていました :sweat: 

`_plugin`を使った実装とか色々と試してみましたが、結論としては [acme_challenge](https://github.com/soffes/acme_challenge) gem が便利でした。

Simple rack middeware for responding to ACME challenge like LetsEncrypt uses.
https://github.com/soffes/acme_challenge

上記のREADMEに詳細がありますが、Jekyll + Heroku の場合は `config.ru` の[最初で呼び出す](https://github.com/yasslab/railsguides.jp/pull/277/commits/8ac644e3be038db227e038e6fd60f74d97d556d2#diff-0a400c8217d53ff4f978163d7c61868cR1)だけです (これだけ!)。

```rb
# config.ru

require 'acme_challenge'
use AcmeChallenge, ENV['ACME_CHALLENGE'] if ENV['ACME_CHALLENGE']
```

あとはローカル環境とプロダクション環境の環境変数 (`ACME_CHALLENGE`) に、Let's Encrypt で取得したキーを入力します。キーの取得方法は総合ポータルの「[Let's Encrypt の使い方](https://letsencrypt.jp/usage/)」を参照してください。

なお、環境変数に代入する値は戻り値の方です。

```
Make sure your web server displays the following content at
http://railsguides.jp/.well-known/acme-challenge/FOOBAR before continuing:

FOOBAR.HOGEHOGE
※👆ここに表示される値を ACME_CHALLENGE に代入

If you don't have HTTP server configured, you can run the following
command on the target server (as root):

(中略)

Press ENTER to continue
```

本番環境でのセットアップが終わったら、総合ポータルの手順に従ってSSL証明書を取得しましょう。

## 2. [Enforce SSL by rack-rewrite gem](https://github.com/yasslab/railsguides.jp/pull/277/commits/0cc483958d33e1781d607f3649ee424058222391) 

`1.`での手続きが無事に終わると、`*.pem`が入手できるはずです。まずはそのファイルを Heroku にアップロードします。

```sh
$ heroku certs:add \
  /etc/letsencrypt/live/railsguides.jp/fullchain.pem \
  /etc/letsencrypt/live/railsguides.jp/privkey.pem
```

これで http と https の両方にアクセスできるようになりますが、せっかくなので https に統一させたいです。そのための実装がこちら。

Enforce SSL by rack-rewrite gem
https://github.com/yasslab/railsguides.jp/pull/277/commits/0cc483958d33e1781d607f3649ee424058222391

上のコミットでは、[rack-rewrite gem](https://github.com/jtrupiano/rack-rewrite) を使って `config.ru` に次のコードを追記しています (細かな記法は省略)。

```rb
require 'rack/rewrite'
use Rack::Rewrite do
  if ENV['RACK_ENV'] == 'production'
    r301 %r{.*}, 'https://railsguides.jp$&', :scheme => 'http'
  end
end
```

これをデプロイすれば、ブックマークなどから http://railsguides.jp/ に来る人も https に切り替わるようになります。ただし、後述の問題がまだ残っているので、この時点ではまだ本番環境にデプロイしない方が良いです (テスト用の環境だけにデプロイする) :warning: 

## 3. [Implement custom headers with rack-contrib gem](https://github.com/yasslab/railsguides.jp/pull/277/commits/39f80899d456633576b2568e0e8b2516e4967607)

`2.` で https でも表示できるようになりましたが、実際に https でアクセスしてみると、Facebook の Like ボタンや Twitter の Tweet ボタンなど、他サイトのドメインから埋め込んだコードに対してエラーまたは警告が表示されてしまいます。

<div align="center">
<img src=https://qiita-image-store.s3.amazonaws.com/0/1857/ca926851-e55f-c1e8-0cff-e780c164d0bb.png "railsguides-jp_SSL-Error.png" />
エラー例 (Chromeなら Option + Cmd + i で確認)
</div>

ここで表示されるエラーおよび警告は、そのサイトでどんなコードを使っているかによって変わります。したがって解決方法もその内容によるところが大きいですが、１つの汎用的な解決策としては、[Content-Security-Policy](https://developer.mozilla.org/ja/docs/Web/Security/CSP) (CSP) に許容するドメインを適宜追加していく方法があります。

例えばRailsガイドの場合は、はてなブックマークやGoogleアナリティクスなどをサイト内で使っているため、次のようなコードを  `config.ru` に追記しています。

```rb
require 'rack/contrib/try_static'
use Rack::TryStatic,
  urls: %w[/],
  root: '_site',
  try: ['.html', 'index.html', '/index.html'],
  header_rules: [
    [:all, {
      'Strict-Transport-Security' => 'max-age=31536000; preload',
      'X-Xss-Protection'          => '1; mode=block',
      'X-Content-Type-Options'    => 'nosniff',
      'X-Frame-Options'           => 'DENY',
      'Content-Security-Policy'   => "default-src 'self' 'unsafe-inline' 'unsafe-eval' *.dropboxusercontent.com *.google-analytics.com *.facebook.net *.facebook.com *.twitter.com ghbtns.com *.hatena.ne.jp *.st-hatena.com *.google.com;"
    }],

# (後略)
```

コミットの詳細: [Implement custom headers with rack-contrib gem
](https://github.com/yasslab/railsguides.jp/pull/277/commits/39f80899d456633576b2568e0e8b2516e4967607) (`rack/jekyll` や `run Rake::Jekyll.new` を[置き換えるようにして追記](https://github.com/yasslab/railsguides.jp/pull/277/commits/39f80899d456633576b2568e0e8b2516e4967607#diff-0a400c8217d53ff4f978163d7c61868cL11)している点に注意してください :warning: )

CSPをどのように対応するかはサイトごとに異なると思うので、必要に応じて適宜コードを修正してください。目安としては、(Chromeの場合) アイコンが鍵マーク🔒に変わったかどうか、Console上にエラーや警告が出ないかどうかなどがあります。

![railsguidesjp_SSL-Check.png](https://qiita-image-store.s3.amazonaws.com/0/1857/f0127ca6-6d0f-e77d-d6aa-0eba274a1a93.png)

他の目安としては、[SSL Server Test](https://www.ssllabs.com/ssltest/)などの外部サービスを使ったチェックもあります。どこまで対応するかはケースバイケースだと思うので、その塩梅については各ケースに応じて判断してもらえればと思います :bow: 

<div align="center">
<img src=https://qiita-image-store.s3.amazonaws.com/0/1857/37beac78-e77c-6554-c040-bce67a552dbf.png "railsguides-jp_SSL-Report.png" />
railsguides.jpの<a href="https://www.ssllabs.com/ssltest/analyze.html?d=railsguides.jp&s=50.19.93.247">SSL Reportの結果</a>
</div>

## まとめ (再掲)

- [acme_challenge](https://github.com/soffes/acme_challenge) gem で証明書取得が便利 
- [rack-rewrite](https://github.com/jtrupiano/rack-rewrite) gem で https にリダイレクト
- [rack-contrib](https://github.com/rack/rack-contrib/) gem でヘッダーを適宜修正

https 対応のご参考になれば幸いです 😆

### 参考記事

- [Moving to Heroku - Daniel Groves](https://danielgroves.net/development/2016/11/move-to-heroku)

## 宣伝 :loudspeaker: 

[Railsガイド](https://railsguides.jp/)のソースコードはオープンソースとして [yasslab/railsguides.jp](https://github.com/yasslab/railsguides.jp) から公開され、[YassLab社](http://yasslab.jp/)によって運営されています。

コンテンツ内の誤字・脱字はもとより、コードとして改善できそうなところがあれば、お気軽に Issue または PR を送っていただけると嬉しいです! :smile_cat: 

[![yasslab_logo_copy.png](https://qiita-image-store.s3.amazonaws.com/0/1857/5ede2b6a-5386-14a4-2783-da61ed424bab.png)](http://yasslab.jp/)