RailsアプリをLet's Encryptで常時SSL化する方法

f:id:boost-up:20171214003033j:plain

常時SSLはもはや現代の常識!?

今を遡ること3年前、2014年のことになりますが、GoogleがSSL化されたサイトをサーチエンジンがポジティブに評価することを発表して以来、新しくWebサービスを作る場合にはSSL化することが必須タスクの一つになってきました。

私が運営するWebサービスでは、今年2017年に立ち上げたSCDB JAPANはサービス開始当時から常時SSL化の対応を行っていますが、2014年から運営している上場企業サーチ.comはユーザが情報を登録する機能を実装していないこともあり、これまでSSL対応が出来ていませんでした。

Let's Encryptの登場

従来、WebサイトのSSL化を実現するためには、認証局に対してお金を払ってサーバ証明書を発行してもらう必要がありました。 このコストは最も安いものでも年間数千円必要であり、個人が格安のレンタルサーバで運営しているような小規模なWebサイトでは無視できないケースもあったのではないかと思います。 また、証明書の発行を申請するためには、サーバにOSログインして証明書署名要求(CSR( Certificate Signing Request ))を作成する必要があり、若干のハードルがありますので、対応できずにいたWebサイト運営者もいたのではないかと思います。

そんな中、2016年に登場したLet's Encryptは通信の暗号化に特化した無料のSSL証明書発行サービスです。

ドメインを認証するDV認証にしか対応しておらず、発行される証明書の有効期限が3ヶ月と短く定期的に更新手続きが必要にはなるものの、サーバのルート権限を持っている管理者であれば簡単な操作で署名済みの秘密鍵(KEY)と公開鍵(CRT)を入手することができます。

今回、上場企業サーチ.comに導入しましたので、以下ではその手順を紹介します。 OSはCentOS7.3になります。

Let's Encryptのインストール

1.epelリポジトリを有効にしてcertbotをインストールします。

sudo yum install epel-release
sudo yum install certbot

2.certbotのコマンドを実行してサーバ証明書を作成する

path-to-rootにはRailsアプリのルートディレクトリを指定します。

certbot certonly --webroot -w path-to-root -d xn--vckya7nx51ik9ay55a3l3a.com

私は上記のコマンドを実行したところ、次のエラーが発生しました。

Saving debug log to /var/log/letsencrypt/letsencrypt.log
Plugins selected: Authenticator webroot, Installer None
Obtaining a new certificate
Performing the following challenges:
http-01 challenge for xn--vckya7nx51ik9ay55a3l3a.com
Using the webroot path *** for all unmatched domains.
Waiting for verification...
Cleaning up challenges
Failed authorization procedure. xn--vckya7nx51ik9ay55a3l3a.com (http-01): urn:acme:error:unauthorized :: The client lacks sufficient authorization :: Invalid response from http://xn--vckya7nx51ik9ay55a3l3a.com/.well-known/acme-challenge/NLgdUKK3PeaxTjwbURWZYmidDjUDfoLs61aAOggMjiQ [210.140.40.129]: 404

IMPORTANT NOTES:
 - The following errors were reported by the server:

   Domain: xn--vckya7nx51ik9ay55a3l3a.com
   Type:   unauthorized
   Detail: Invalid response from
   http://xn--vckya7nx51ik9ay55a3l3a.com/.well-known/acme-challenge/NLgdUKK3PeaxTjwbURWZYmidDjUDfoLs61aAOggMjiQ
   [210.140.40.129]: 404

   To fix these errors, please make sure that your domain name was
   entered correctly and the DNS A/AAAA record(s) for that domain
   contain(s) the right IP address.

エラーメッセージを見る限り、サーバ直下に作成される.well-knownディレクトリにインターネットからアクセスしようとして404エラーが出たことが問題のようです。 Let's EncryptはSSL化しようとしているサーバに作ったファイルに外部からアクセスできることをもって、実在性の確認を行っているようです。

3..well-knownへのアクセスを許可する

上場企業サーチ.comはRailsで構築しており、publicフォルダ内に置いた静的ファイル以外はunicornに渡す設定にしているのですが、 .well-knownはpublic外にあり、当然ながらルーティングも設定されていないため、設定上正しい動きとして404になります。

今回、WebサーバにはH2Oを使っていますので、h2o.confに下記の末尾2行を追加してサービスを再起動します。

  "xn--vckya7nx51ik9ay55a3l3a.com:80":
    listen:
      port: 80
      host: 0.0.0.0
    paths:
      "/.well-known":
        file.dir: path-to-root/.well-known/

4.改めて、certbotのコマンドを実行してサーバ証明書を作成する

Saving debug log to /var/log/letsencrypt/letsencrypt.log
Plugins selected: Authenticator webroot, Installer None
Starting new HTTPS connection (1): acme-v01.api.letsencrypt.org
Obtaining a new certificate
Performing the following challenges:
http-01 challenge for xn--vckya7nx51ik9ay55a3l3a.com
Using the webroot path *** for all unmatched domains.
Waiting for verification...
Cleaning up challenges

IMPORTANT NOTES:
 - Congratulations! Your certificate and chain have been saved at:
   /etc/letsencrypt/live/xn--vckya7nx51ik9ay55a3l3a.com/fullchain.pem
   Your key file has been saved at:
   /etc/letsencrypt/live/xn--vckya7nx51ik9ay55a3l3a.com/privkey.pem
   Your cert will expire on 2018-03-12. 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

今度は上手くいきました。 fullchain.pemが公開鍵、privkey.pemが秘密鍵になりますので、適切な場所に保管します。

5.h2o.confにSSL接続の設定を追加する

  "xn--vckya7nx51ik9ay55a3l3a.com:443":
    listen:
      port: 443
      host: 0.0.0.0
      ssl:
        certificate-file: "path-to-fullchain.pem"
        key-file: "path-to-privkey.pem"
        cipher-suite: "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:DES-CBC3-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4"
        cipher-preference: server

ポート443の設定を追加します。 certificate-filekey-fileには先ほど作成した公開鍵と秘密鍵のパスを指定します。 cipher-suitecipher-preferenceはなくても動きますが、脆弱な暗号方式を使わないようにするための設定です。

設定後、h2oサービスを再起動するとSSL通信が可能となります。

6.その他

今回は詳しくは触れませんが、上記まででSSL接続ができないときは、ファイヤーウォールでインバウンド443が空いているかを確認してください。 それから、80ポート(HTTP)へのアクセスを443ポートにリダイレクトしておいた方が良いと思いますので、適宜この設定も追加してください。

7.おまけ

昔から慣れているので今でもSSLと言ってしまいますが、暗号化方式としてSSLは既に時代遅れと言われており、世の中的にはTLS通信に移行が進んでいます。

上で書いた5.のcipher-suiteのところで、頭に"!"が付いているのはそのアルゴリズムを「許可しない」ことを意味するのですが、今回の設定ではSSLを軒並み拒否しています。

冒頭で「常時SSLは世の中の常識!?」とまで書きましたが、正しくは「常時TLSは世の中の常識!?」ですね。