Heroku で動かしている Rails アプリを SSL 対応した。SSL 証明書は Let's Encrypt で取得したので無料。 ただし、Heroku で SSL を使うためには、Paid Dyno が必要なので、月 7$ かかる。

:warning: 証明書の自動更新ができていないので、もっと有効期限が長い証明書に切り替えるかもしれない。

概要

  • Heroku で動かしている Rails アプリを SSL 対応する
  • Heroku SSL (Beta) を Hobby Dyno で使う
  • SSL 証明書は Let's Encrypt で取得する
  • :warning: SSL 証明書の自動更新はできていない

環境

  • macOS 10.11.6
  • Homebrew 0.9.9

基本的なこと

Certbot をインストールする

$ brew install certbot

証明書を取得する

方針

certbot を manual モードで実行し、証明書を取得する。 manual モードでは、証明書取得時に certbot がドメインの所有者を認証するために、指定された URL にアクセスされたときに指定されたテキストを返すようにする必要がある。 まずは、その設定をしてから、certbot で証明書を取得していく。

Rails アプリからテキストを返す

static ページを作るときと同じように、ページ用のコントローラーを作る。 render メソッドに Hash でテキストを渡せば、テキストを返せる。

render text: "your text here"

コントローラーを作って、アクションを実装して route する。いつもの Rails。

config/routes.rb

Rails.application.routes.draw do
  get '/.well-known/acme-challenge/:id' => 'pages#certbot'

  # and more
end

app/controller/pages_controller.rb

class PagesController < ApplicationController
  def certbot
    id = ENV['LETS_ENCRYPT_ID']
    secret = ENV['LETS_ENCRYPT_SECRET']

    if id.nil? || secret.nil?
      render_404 and return
    end

    unless id == params[:id]
      render_404 and return
    end

    render text: "#{id}.#{secret}"
  end

  private

  def render_404
    render file: Rails.root.join('public/404.html'), status: 404, layout: false, content_type: 'text/html'
  end
end

※ id とか secret とかは、ここで適当に呼んでいるだけで、正式な名称ではない。

:bulb: 認証に必要な値は、環境変数で指定できるようにしておく。更新の度に Heroku に git push するのは嫌なため。

毎回 git push しても良いのであれば、public ディレクトリにファイルを置くというのもあり。 public/.well-known/acme-challenge/your-id-here-xxxxxx というファイルに your-id-here-xxxxx.your-secret-here-yyyyy を書き込んでおけば良いのでは。

証明書を取得する

manual モードで cerbot を実行して、証明書を取得しにいく。

certbot certonly --manual -d example.com

:bulb: Permission Denied でこけるときは working directory を作ってオプションで指定すると良い。

# Permission でこけるとき
mkdir certbot
certbot certonly --manual -d example.com --logs-dir certbot --config-dir certbot --work-dir certbot

コマンドを実行すると、以下のところで一時停止する。

Make sure your web server displays the following content at
http://example.com/.well-known/acme-challenge/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx before continuing:

xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy

コマンドは一時停止させたまま、 Heroku に環境変数を設定する。

heroku config:set LETS_ENCRYPT_ID=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
heroku config:set LETS_ENCRYPT_SECRET=yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy

設定が終わったら、実際に URL を叩いてみて、ちゃんとテキストが返ってくることを確認する。

確認が済んだら Enter でコマンドを resume する。成功すれば、証明書を保存したというメッセージが表示されるはず。

IMPORTANT NOTES:
 - Congratulations! Your certificate and chain have been saved at
   /etc/letsencrypt/live/example.com/fullchain.pem. Your
   cert will expire on xxxx-xx-xx. To obtain a new or tweaked version
   of this certificate in the future, simply run certbot again. To
   non-interactively renew *all* of your certificates, run "certbot
   renew"
 - If you like Certbot, please consider supporting our work by:

   Donating to ISRG / Let's Encrypt:   https://letsencrypt.org/donate
   Donating to EFF:                    https://eff.org/donate-le

Heroku に証明書を設定する

Heroku で SSL と言えば、SSL Endpoint add-on ($20/month) が必要だったが、Heroku SSL (Beta) なら SSL 自体は無料で使えるようになった。 ただし、Paid Dyno が必要なので、最低でも Hobby ($7/month) はかかる。

Heroku SSL はまだ Beta なので、labs で有効にしてからインストールする。

heroku labs:enable http-sni -a your-app
heroku plugins:install heroku-certs

SSL 証明書と秘密鍵を設定する。

heroku _certs:add /etc/letsencrypt/live/example.com/cert.pem /etc/letsencrypt/live/example.com/privkey.pem
Resolving trust chain... done
Adding SSL certificate to ⬢ your-app... done
Certificate details:
Common Name(s): example.com
Expires At:     xxxx-xx-xx xx:xx UTC
Issuer:         /C=US/O=Let's Encrypt/CN=Let's Encrypt Authority X3
Starts At:      xxxx-xx-xx xx:xx UTC
Subject:        /CN=example.com
SSL certificate is verified by a root authority.

=== Your certificate has been added successfully.  Update your application's DNS settings as follows
Domain                    Record Type  DNS Target
────────────────────────  ───────────  ──────────────────────────────────────
example.com               CNAME        example.com.herokudns.com

と言われるので、自分の使っている DNS に CNAME レコードを設定する。

最後に、証明書が設定できているかを確認する。

heroku _certs:info

これで、ひとまず完了 :white_check_mark:

証明書の自動更新ができない問題

Let's Encrypt の SSL 証明書は、自動更新を前提としているため、有効期限が短い。ただし、今回の構成では自動更新ができなかった。

manual モードは interactive な shell じゃないと実行できないし、manual モードで取得した証明書は non-interactive モードで renew できない :angel:

まとめ・感想

  • Heroku で動かしている Rails アプリを SSL 対応した
  • Heroku SSL (Beta) を Hobby Dyno で使うことで、月 $7 で Rails アプリを SSL 対応できた
  • SSL 証明書は Let's Encrypt で取得した
  • :warning: SSL 証明書の自動更新はできていない

自動更新絶対したくて、色々調べてみたけどできなかった。

今回のようにサーバーの外で certbot を動かすのではなくて、サーバー内で動かす方がメインっぽい。 Heroku 側で certbot 動かしてくれたり(standalone モードで実行して、ごにょごにょ)すると神 :sun_with_face: な気がするんですけど、どうなんでしょうか。