背景
先日12月15日からこのブログで使っているドメイン用のSSL証明書が失効してしまっていた。ということで更新作業を行った。
これまで StartCom が運営する StartSSL というサービスで無料の証明書を取得して運用していたが、Firefox や Chrome では新たに発行された証明書が信頼されなくなるということに気づいた。どうやら不正な日付の証明書を意図的に発行したり、同じく認証局を運営する WoCert から買収されたことを公表しなかったりと、不祥事を起こしたために信用を失ったことが原因らしい[1][2]。
そこで、最近よく耳にするLet's Encrypt に乗り換えることにした。Let's Encrypt は非営利団体である Internet Security Research Group によって運営される無料でオープンな認証局だ。特徴として、Automated Certificate Management Environment (ACME) プロトコルに対応しており、対応するソフトウェアを利用することで、ドメインの認証から公開鍵基盤の展開を自動で行うことができる点がある。すなわち、WebサイトをSSL対応させる場合、一般的には
- ドメイン認証を行い(メールを受信したり、Webサイト上に特定のファイルを置いたりする)
- 証明書署名要求を送り
- 証明書を取得し
- サーバ上に展開する
という手順を踏まないといけないが、この部分を機械的に行うことができる[3]。このように気軽に証明書の取得や更新を手軽に行える一方で、証明書の期限は90日と短いものになっており、自動的に更新することが推奨されるデザインとなっている[4]。
導入
というわけで、このブログを運用しているサーバに Let's Encrypt の証明書を導入してみた。クライアントとして、公式が推奨[5]している certbot
を利用した。
サーバの構成として、Debian Linux Jessie (8.1) 上で、nginx をリバースプロキシとして動かしており、その裏で Ghost というブログエンジンを動かしている。
クライアントの導入
Jessie 用には jessie-backports
リポジトリから certbot
を入手することができる[6][7]。まず、/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:1]。これは、既に当該ドメインで動いている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 の証明書で暗号化されていることが確認できた。
(なお、このスクリーンショットは本記事執筆中に撮ったため、この後で説明する更新を行ったあとの証明書の情報が表示されている。)
証明書の更新
設定は終わったが、来たる90日後のために、証明書の更新も自動で行えることを確認する。
$ sudo certbot renew
で行える。ただし、有効期限が間近でない場合は行われない。
$ sudo certbot renew --force-renew
とすることで、有効期限を無視して強制的に更新することができた[7:2]。
$ 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 では証明書の取得と更新を自動で行えるかわりに、有効期限が短い。そこで、定期的な自動更新を設定する。
とはいえ、実は aptitude
で certbot
を導入した時点で、/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ヶ月後に無事更新されていることを祈りたい。