SSL証明書更新ついでに Let's Encrypt を導入した。

背景

先日12月15日からこのブログで使っているドメイン用のSSL証明書が失効してしまっていた。ということで更新作業を行った。

これまで StartCom が運営する StartSSL というサービスで無料の証明書を取得して運用していたが、Firefox や Chrome では新たに発行された証明書が信頼されなくなるということに気づいた。どうやら不正な日付の証明書を意図的に発行したり、同じく認証局を運営する WoCert から買収されたことを公表しなかったりと、不祥事を起こしたために信用を失ったことが原因らしい12

そこで、最近よく耳にするLet's Encrypt に乗り換えることにした。Let's Encrypt は非営利団体である Internet Security Research Group によって運営される無料でオープンな認証局だ。特徴として、Automated Certificate Management Environment (ACME) プロトコルに対応しており、対応するソフトウェアを利用することで、ドメインの認証から公開鍵基盤の展開を自動で行うことができる点がある。すなわち、WebサイトをSSL対応させる場合、一般的には

  1. ドメイン認証を行い(メールを受信したり、Webサイト上に特定のファイルを置いたりする)
  2. 証明書署名要求を送り
  3. 証明書を取得し
  4. サーバ上に展開する

という手順を踏まないといけないが、この部分を機械的に行うことができる3。このように気軽に証明書の取得や更新を手軽に行える一方で、証明書の期限は90日と短いものになっており、自動的に更新することが推奨されるデザインとなっている4

導入

というわけで、このブログを運用しているサーバに Let's Encrypt の証明書を導入してみた。クライアントとして、公式が推奨5している certbot を利用した。

サーバの構成として、Debian Linux Jessie (8.1) 上で、nginx をリバースプロキシとして動かしており、その裏で Ghost というブログエンジンを動かしている。

クライアントの導入

Jessie 用には jessie-backports リポジトリから certbot を入手することができる67。まず、/etc/apt/sources.list

deb http://ftp.debian.org/debian jessie-backports main  

の行を追加して、jessie-backports を有効にした。そして、以下の通りにインストールする

$ sudo aptitude update
$ sudo aptitude install certbot -t jessie-backports

いつも aptitude を使っているので今回も使ったが、当然 apt-get を使っても良い。

webroot 用ディレクトリの設定

証明書の取得のためには、発行を希望する者が、発行先ドメインの所有者であることを確認する必要がある(たとえば、ぼくが google.com のSSL証明書を取得できたら大問題だ)。certbot はドメイン認証のために複数の方法を提供しているが、今回は webroot を利用する7。これは、既に当該ドメインで動いているWebサーバのドキュメントルートに認証用のファイルを配置し、Let's Encrypt側からHTTPで当該ファイルが読めることを確認することで認証を行う方法だ。具体的にはドメインがexample.comだった場合 http://example.com/.well-known/acme-challenge/ 以下に配置された認証用ファイルを確認する。

困ったことに、これまでの構成では、http://blog.misosi.ru/* への要求をすべて https://blog.misosi.ru/ にリダイレクトしている上に、https://blog.misosi.ru/* への要求はすべて Ghost にプロキシしており、アプリケーションにより動的にコンテンツが生成されるため、ドキュメントルートといえるものがない。そこで、認証用ファイルが配置される専用のディレクトリを作成し、http://blog.misosi.ru/.well-known/* についてだけは HTTPS へのリダイレクトをとりやめ、作成したディレクトリの内容を直接返すようにした。

まず、ディレクトリを作成する。

$ sudo mkdir /var/letsencrypt_webroot

次に、nginxの設定ファイルを変更する。

以下がこれまでのもので、ホスト名全体へのHTTP接続をHTTPSにリダイレクトしていた。

server {  
        listen 80;
        server_name blog.misosi.ru;
        rewrite ^(.*)$ https://$host$1 permanent;
}

これを、以下の通り、/.well-known/ については /var/letsencrypt_webroot/ からコンテンツを返すようにし、その他については従来のようにHTTPSへのリダイレクトをするようにした。

server {  
        listen 80;
        server_name blog.misosi.ru;

        # 削除
        # rewrite ^(.*)$ https://$host$1 permanent;

        # 以下3行追加
        location /.well-known/ {
                root /var/letsencrypt_webroot/;
        }

        # 以下3行追加
        location / {
                rewrite ^(.*)$ https://$host$1 permanent;
        }
}

そして、

$ sudo service nginx reload

で設定の再読込をする。これでドメイン認証を行う準備ができた。

証明書の発行

早速認証を行い証明書を発行してみる。

$ sudo certbot certonly --webroot -w /var/letsencrypt_webroot/ -d blog.misosi.ru

-w オプションでドキュメントルートを指定し、 -d オプションでドメインを指定する。インタラクティブな画面が表示され、利用条件への同意や、連絡用のメールアドレスを入力した後に、証明書を配置した旨のメッセージが表示されれば成功だ。

以下のように、/etc/letsencrypt/live/[ドメイン名]/ 以下に証明書や秘密鍵が配置されている。これらはシンボリックリンクになっており、/etc/letsencrypt/live/[ドメイン名]/archive 内に実体がある。archive 内は、証明書を更新した際に、連番でファイルが追加されていき、live 内のリンクが最新のものを指すようになっている。

$ sudo ls -l /etc/letsencrypt/live/blog.misosi.ru
lrwxrwxrwx 1 root root 38 Dec 17 15:10 cert.pem -> ../../archive/blog.misosi.ru/cert1.pem  
lrwxrwxrwx 1 root root 39 Dec 17 15:10 chain.pem -> ../../archive/blog.misosi.ru/chain1.pem  
lrwxrwxrwx 1 root root 43 Dec 17 15:10 fullchain.pem -> ../../archive/blog.misosi.ru/fullchain1.pem  
lrwxrwxrwx 1 root root 41 Dec 17 15:10 privkey.pem -> ../../archive/blog.misosi.ru/privkey1.pem  

nginx側での証明書の設定

あとは、取得した証明書を利用するように、nginx の設定をすればよい。とはいえ、これまで利用していた証明書へのパスを、先ほど取得したものに変更すれば良いだけだ。なお、ssl_certificate には、中間証明書も連結されたfullchain.pemを指定するべきだ。

server {  
        listen 443 ssl http2;
        server_name blog.misosi.ru;
        ssl on;
        include /etc/nginx/ssl_params;
        ssl_certificate /etc/letsencrypt/live/blog.misosi.ru/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/blog.misosi.ru/privkey.pem;
        # ssl_certificate [秘密鍵の旧パスが書かれていた];
        # ssl_certificate_key [証明書の旧パスが書かれていた];

        ...
}

あとは、また

$ sudo service nginx reload

として設定の再読込をすることで設定が終わった。ブラウザでブログを表示すると、Let's Encrypt の証明書で暗号化されていることが確認できた。

Chromeで表示した本ブログの証明書情報。Let's Encrypt から発行されていることが確認できる。

(なお、このスクリーンショットは本記事執筆中に撮ったため、この後で説明する更新を行ったあとの証明書の情報が表示されている。)

証明書の更新

設定は終わったが、来たる90日後のために、証明書の更新も自動で行えることを確認する。

$ sudo certbot renew

で行える。ただし、有効期限が間近でない場合は行われない。

$ sudo certbot renew --force-renew

とすることで、有効期限を無視して強制的に更新することができた7

$ sudo ls -l /etc/letsencrypt/live/blog.misosi.ru
total 0  
lrwxrwxrwx 1 root root 38 Dec 17 15:21 cert.pem -> ../../archive/blog.misosi.ru/cert2.pem  
lrwxrwxrwx 1 root root 39 Dec 17 15:21 chain.pem -> ../../archive/blog.misosi.ru/chain2.pem  
lrwxrwxrwx 1 root root 43 Dec 17 15:21 fullchain.pem -> ../../archive/blog.misosi.ru/fullchain2.pem  
lrwxrwxrwx 1 root root 41 Dec 17 15:21 privkey.pem -> ../../archive/blog.misosi.ru/privkey2.pem

mecab@amerikaisou:~$ sudo ls -l /etc/letsencrypt/archive/blog.misosi.ru  
total 32  
-rw-r--r-- 1 root root 1793 Dec 17 15:10 cert1.pem
-rw-r--r-- 1 root root 1793 Dec 17 15:21 cert2.pem
-rw-r--r-- 1 root root 1647 Dec 17 15:10 chain1.pem
-rw-r--r-- 1 root root 1647 Dec 17 15:21 chain2.pem
-rw-r--r-- 1 root root 3440 Dec 17 15:10 fullchain1.pem
-rw-r--r-- 1 root root 3440 Dec 17 15:21 fullchain2.pem
-rw-r--r-- 1 root root 1704 Dec 17 15:10 privkey1.pem
-rw-r--r-- 1 root root 1704 Dec 17 15:21 privkey2.pem

と、archive 内に連番で新しい証明書が作成され、live 内のリンクが更新されたことが確認できた。

nginx には live 内のファイルへのパスを設定しているため、

$ sudo service nginx reload

とするのみで新しい証明書が読み込まれる。

自動更新の設定

先で述べたように、Let's Encrypt では証明書の取得と更新を自動で行えるかわりに、有効期限が短い。そこで、定期的な自動更新を設定する。

とはいえ、実は aptitudecertbot を導入した時点で、/etc/cron.d/certbot に1日2回の自動更新が設定されている8--force-renew が指定されていないので、期限間近でなければ何も行われない)。

$ cat /etc/cron.d/certbot
# コメント省略

SHELL=/bin/sh  
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin

0 */12 * * * root test -x /usr/bin/certbot -a \! -d /run/systemd/system && perl -e 'sleep int(rand(3600))' && certbot -q renew && perl -e 'sleep 10' && /etc/init.d/nginx reload  

ただし、nginx での証明書の再読込は行われないため、少し編集して再読込まで行われるようにした。下記が編集後である。

$ cat /etc/cron.d/certbot
# コメント省略

SHELL=/bin/sh  
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin

0 */12 * * * root test -x /usr/bin/certbot -a \! -d /run/systemd/system && perl -e 'sleep int(rand(3600))' && certbot -q renew && /etc/init.d/nginx reload  

証明書の更新後、再読込するようにした。

まだ正しく動くか分からないが、3ヶ月後に無事更新されていることを祈りたい。