無償SSLサーバー証明書Let's Encryptの普及とHTTP/2および常時SSL化 2ページ

Let’s Encryptで証明書を取得する

 Let’s Encryptでの基本的な利用手順をまとめると以下のようになる。

  1. Gitを導入
  2. 証明書取得ツールを導入
  3. /.well-known/のアクセス確認
  4. root権限のあるユーザーで証明書取得ツールを実行して証明書を取得
  5. 取得した証明書をWebサーバーに適切に設定する
  6. (証明書更新用のスクリプトなどをcronなどに仕掛ける)

 Let’s Encryptでのドメイン認証は、Webサーバーに認証用のファイルがあるかどうかでチェックしている。

図5 証明書取得ツールの動作
図5 証明書取得ツールの動作

 ドメイン認証の際、証明書取得ツールの実行によってドキュメントルートへの動的書き込みが発生することになるので、基本的には証明書取得ツールはWebサーバー上で実行することになる(Webサーバーと証明書を取得するサーバーを分ける例については後述)。認証用ファイルが設置される場所はドキュメントルート下の/.well-known/ディレクトリである。ここにファイル名が不定の検証ファイルが置かれ、そのファイルの存在によってドメイン認証が行われる。

証明書取得ツールを導入

 Gitが利用できる環境が準備できたら、Let’s Encryptの証明書取得ツールをGitHub上の公開リポジトリからクローンして取得する。ここでは/var/opt/letsencryptに配置している。

$ git clone https://github.com/certbot/certbot /var/opt/letsencrypt

 証明書取得ツールの実行にはPythonなどが必要になるが、実行の度に必要な環境が整っているかチェックされ、足りなければ自動的にインストールされるので前準備は不要だ。そのため、実行時にはOS上にソフトウェアをインストールできるroot権限が必要である。一般ユーザーから実行した場合はsudoも自動実行される。

/.well-known/のアクセス制限を確認

 証明書を取得する際、実際にそのドメインのWebサイトが稼働し、Let’s EncryptのサーバーからWebサイトにアクセスできるようしておく必要があるので、あらかじめWebサイトがファイアウォールでブロックしていないか、BASIC認証でアクセスを制限していないかなどを確認しておく。Webサーバーのアクセス制限として、ドットファイル(.htaccessファイルなど)を閲覧できないようにしていることもよくある。念のため、Let’s Encryptでの認証に必要な/.well-known/ディレクトリが閲覧可能か確認しておく。具体的にはドキュメントルート下に任意文字を記載した、

/.well-known/test.txt

のようなファイルを置き、実際にWebブラウザでhttp://<ドメイン名>/.well-known/test.txtをアクセスして、test.txtが閲覧できるか確認する。もし該当ファイルにアクセスできない場合は、Webサーバーの設定ファイルに以下に代表されるような「/.」に関係した設定箇所があるかもしれない。

・Apache
<Directory ~ "/\.">
<DirectoryMatch "/\.">
<LocationMatch "/\.">
RedirectMatch 403 /\.

・Nginx
location ~ /\.
if ($uri ~ "/\.") {

 設定ファイルをチェックし、/.well-known/へのアクセスを許可する設定に調整しておく。

 なお、Let’s Encryptから認証ファイルをチェックする際のユーザーエージェントは、2016年6月時点で、

Mozilla/5.0 (compatible; Let's Encrypt validation server; +https://www.letsencrypt.org)

となっている。

証明書の取得

 証明書を取得するには、以下のコマンドを実行する。

$ ./certbot-auto certonly --webroot --agree-tos -w <ドキュメントルート> \
-m <メールアドレス> \
-d <ドメイン名>

 ドメイン名のオプションは複数記述でき、ルートドメインとwww付きのドメインの2つに対応した証明書が必要な場合は、

$ ./certbot-auto certonly --webroot --agree-tos -w <ドキュメントルート> \
-m <メールアドレス> \
-d <ルートドメイン名> -d www.<ルートドメイン名>

のように記述すれば良い。「-d」オプションの指定内容は証明書の「サブジェクトの別名」(Subject Alternative Names)の項目になる。任意のサブドメインで使えるワイルドカードには対応していないので、複数のサブドメインで利用する場合は必要なサブドメインごとに「-d」オプションを追記していく。

 上記のコマンドを実行し、無事証明書が取得できると、以下のようなメッセージが表示される。

IMPORTANT NOTES:
 - Congratulations! Your certificate and chain have been saved at
   /etc/letsencrypt/live/<ドメイン名>/fullchain.pem. Your cert
   will expire on 2016-09-18. To obtain a new or tweaked version of
   this certificate in the future, simply run certbot-auto again. To
   non-interactively renew *all* of your certificates, run
   "certbot-auto 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

 Congratulationsのメッセージ文中に記載されているように、/etc/letsencrypt/live/<ドメイン名>ディレクトリ下に、ドメインに対応した秘密鍵、中間証明書、証明書のほか、中間証明書を含んだ証明書の4ファイルが配置される。実際に必要なのは以下の2ファイルである。

privkey.pem	秘密鍵
fullchain.pem	中間証明書を含む証明書

 この2ファイルをWebサーバーに設定すればHTTPSのWebサーバーが運用できる。

NginxのSSL設定例

 証明書が無事入手できれば、一般的なSSLサーバー証明書の設定と同じである。Apacheの設定例は/etc/letsencrypt/options-ssl-apache.confにサンプルが用意されているので参照していただきたい。ここではNginxにおける一例として、共通的なSSL設定全般を/etc/nginx/conf.d/ssl.confの別ファイルで追加し、Webサーバーごとの設定(/etc/nginx/conf.d/example.com.confなど)は別ファイルに分けて記述している環境を想定する。

 まず共通的な/etc/nginx/conf.d/ssl.confは以下のようになる。

ssl_session_cache shared:SSL:30m;
ssl_session_timeout 30m;
ssl_dhparam /etc/ssl/private/dhparam.pem;
ssl_stapling on;
ssl_stapling_verify on;
ssl_prefer_server_ciphers on;
ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA';

 NginxではSSLでの圧縮はデフォルトでOFFなので、とくに設定を記述しなくてもCRIMEアタックには対応できている(Apacheの場合は圧縮しないように設定を入れる)。ssl_session_cacheやssl_session_timeoutのキャッシュやセッションタイムアウトは運用状態によって適宜調整となる。ssl_dhparamの部分は強度の高い鍵を別途/etc/ssl/private/dhparam.pemに作成し指定しているものである。

Diffie-Hellman鍵の強化

 Diffie-Hellman鍵は2048ビット以上が推奨されているので、セキュリティ向上のために新規に作成したものを利用する。ここでは上記設定のように鍵を/etc/ssl/private/dhparam.pemに配置するものとする。

$ sudo openssl dhparam -out /etc/ssl/private/dhparam.pem 2048

 鍵生成には環境によって数分程度はかかるのでしばらく待つことになる。

Webサーバーごとの設定

 Webサーバー設定(/etc/nginx/conf.d/example.com.confのserver内の部分)では、ポート番号の設定追加(もしくは修正)と、そのドメインに対応する証明書と鍵の設定を追加する。

server {
	listen 443 ssl http2;
	:
	:
	ssl_certificate /etc/letsencrypt/live/<ドメイン名>/fullchain.pem;
	ssl_certificate_key /etc/letsencrypt/live/<ドメイン名>/privkey.pem;
	:
	:

 この3行がHTTP/2サーバーとしての最低限の設定になる。

HSTS対応

 ブラウザ側の仕組みによってHTTPアクセスをHTTPSに誘導するHSTSも設定しておく。基本的にはHTTPS通信において1つヘッダーを送信するだけなので、上記server内のどこかにadd_headerを1行追加するだけである。

	add_header Strict-Transport-Security "max-age=31536000" always;

 Nginxにおいては3番目の引数に「always」を入れることで、404などのエラーでもHSTSのヘッダーを送出するようになる。404エラーページでこのヘッダーを送出したくない場合は「always」を削除しておく。

 なお、HTTP接続の際にこのヘッダーを送信しても意味がない(443ポートの待ち受けをしているserver設定内にのみ記載する)。HSTSは、HTTPS接続の際に「Strict-Transport-Security」ヘッダーがあることで「次からはこちらです」という使われ方をする機能である。

SSL化の動作検証

 以上でNginxの設定は完了である。Nginxをリスタートすれば無償の証明書によるHTTPSに対応したWebサーバーの出来上がりである。

$ sudo /etc/init.d/nginx restart

 もし、うまく動作していないようなら、まずはnetstatを使い、Nginxが正しく起動しHTTPSの443ポートで待ち受けしているかを確認する。

$ sudo netstat -pantu |grep nginx
tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN      14303/nginx.conf
tcp        0      0 0.0.0.0:443             0.0.0.0:*               LISTEN      14303/nginx.conf

 上記のようにきちんと443ポートで待ち受けをしているのにも関わらず、サイトが見えないとう場合は、iptablesなどのファイアウォールで443ポートが塞がれていないかチェックするといいだろう。

$ sudo iptables -L -n -v
	:
	:
    0     0 ACCEPT     tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:443
	:
	:

厳密にSSLの実装をチェックする

 共通的なSSLの設定例で示したように、証明書の設定はやや煩雑になり不安が残るところである。証明書の設定を正しく行えたかを確認するには、以下のようなWebサービスとして用意されているSSLのチェッカーを使うのがよい。

https://globalsign.ssllabs.com/analyze.html

図6 SSLチェックツール
図6 SSLチェックツール

 上記サイトにアクセスしたら、Hostnameの入力欄にURLを記述し、「Submit」ボタンを押すとチェックが行われる。このサイトでのチェック内容は多岐にわたり、すべての検査が終わるのに数分は待つことになる。

 しばらくしてチェックが終わると総合評価とチェック項目の詳細などが表示される。

図7 診断結果
図7 A+の診断結果

 この図の結果のように「A+」と評価されるのが目標である。前述の設定であれば「A+」と評価されているだろう。もし減点になる部分があれば、問題個所が提示されているのでチェックしていく。

Let’s Encryptの証明書の更新

 Let’s Encryptの証明書の有効期限は90日となっており、運用にあたっては1、2か月に一度のペースで証明書の更新作業を行う必要がある。証明書の更新は証明書の取得で利用したcertbot-autoコマンドでrenewオプションを使えばよい。

$ /var/opt/letsencrypt/certbot-auto renew

 このコマンドによって、証明書取得時に/etc/letsencrypt/renewal/以下に設置されたドメインごとの設定ファイルが参照され、デフォルトでは有効期限切れ30日未満のドメインに対して更新作業が行われる。残り有効期限が長い証明書については更新作業がスキップされるようになっている。通常は有効期限を変更する必要はないだろう。

 もし、更新実行の期間を調整したい場合は、/etc/letsencrypt/renewal/以下にある<ドメイン名>.confというファイル名の設定ファイルで、以下のような設定を追加(コメントアウトを削除)して日数を変更しておく。

# renew_before_expiry = 30 days

 実運用時には自動的に証明書が更新(「certbot-auto renew」コマンドを実行)されるようにしておくことになるだろう。デフォルトの30日未満で更新するという設定なら30日未満で2回以上実行されるようにcronに設定しておく。たとえば、毎週日曜日午前2時22分に実行するcronの設定例(root)は以下のようになる。

22 2 * * 0 /var/opt/letsencrypt/certbot-auto renew >> /var/log/letsencrypt.log

 また、鍵の更新(certbot-autoの出力や取得した鍵ファイルのタイムスタンプなど)を検知してNginxを再起動させるスクリプトも用意しておいたほうが良いだろう。

更新がない場合のcertbot-autoの出力末尾
  /etc/letsencrypt/live/example.org/fullchain.pem (skipped)
  /etc/letsencrypt/live/example.com/fullchain.pem (skipped)
No renewals were attempted.

更新があった場合のcertbot-autoの出力末尾
  /etc/letsencrypt/live/example.org/fullchain.pem (skipped)
Congratulations, all renewals succeeded. The following certs have been renewed:
  /etc/letsencrypt/live/example.com/fullchain.pem (success)

 以上で、Let’s Encryptを利用した基本的なWebサーバーの設定は完了である。

フロントサーバーで証明書取得ツールを動作させない構成

 Let’s Encryptの証明書取得ツールは、動作のためにソフトウェアインストールを行うほか、今後のアップグレードなどで想定しない動作をする可能性もある。掌握できていないツールをフロントのWebサーバーで動作させるのは怖いので、証明書の取得は別のサーバー上で行い、改めて証明書をWebサーバーにコピーしたいという運用はあるだろう。また、とくにNginxを利用している場合、フロントのWebサーバーはキャッシュ(リバースプロキシ)として機能しており、バックエンドのAPPサーバーとの組み合わせで利用したいという場合もある。たとえば下図のようなNginxの構成である。

図8 Nginxの多段構成にて、証明書をコピーする例
図8 Nginxの多段構成にて、証明書をコピーする例

 Let’s Encryptの利用でのポイントは/.well-knownを透過的に設定しておくことになる。具体的には以下のような数行を加えることによって実装は可能である。まずフロントサーバー側(/etc/nginx/conf.d/front.conf)は以下のlocation設定を追加する。

server {
	:
	:
	location ~ ^/.well-known {
		proxy_set_header Host letsencrypt;
		proxy_no_cache 1;
		proxy_cache_bypass 1;
		proxy_pass http://<鍵取得用のサーバーのIPアドレス>;
	}
	:
	:
}

 フロントとバックエンドとの通信はHTTPのままとし、proxy_passでバックエンドサーバーのIPアドレスを指定する。proxy_no_cacheとproxy_cache_bypassを1とすることにより認証用ファイルなどがキャッシュされることなく、このパスのアクセスのみ透過的にファイルが参照されるようになる。proxy_set_headerの部分は名前解決できるドメイン名である必要はなく、任意の文字列でかまわない。証明書取得後は前述のSSL関連の設定を整備していく形になる。

 続いて、バックエンド側は既存のサーバーとは独立した名前のバーチャルホストとして、以下のような記述の/etc/nginx/conf.d/letsencrypt.confを用意するだけである。

server {
  server_name letsencrypt;
  root /var/www/letsencrypt;
  access_log /var/log/nginx/letsencrypt/access.log combined;
  error_log /var/log/nginx/letsencrypt/error.log warn;
}

 server_nameとして、フロントサーバー側のproxy_set_headerと同じ文字列を指定する。なお、この設定を反映させる際は、独立のバーチャルホストとしているので、ドキュメントルートとログのディレクトリを作成してからNginxを起動(再起動)する。

$ sudo mkdir /var/www/letsencrypt /var/log/nginx/letsencrypt
$ sudo /etc/init.d/nginx restart

 このように、すでにNginxのプロキシ+APPサーバー構成で運用している場合は、フロント側に6行の追加、バックエンドに6行ほどのバーチャルホスト設定の追加だけで構成できる。なお、ここでは独立のバーチャルホストで認証を行うことにしたので、証明書取得の際は、ドキュメントルートの指定オプション「-w」に「/var/www/letsencrypt」を指定する。

$ ./certbot-auto certonly --webroot --agree-tos -w /var/www/letsencrypt \
-m <メールアドレス> \
-d <ドメイン名>

HTTP/2+常時SSLの時代へ

 SSLサーバー証明書を取り巻く状況は変わり、以前と比べてかなり利用しやすい状況にあることは把握していただけたと思う。Let’s Encryptの利用も全体が見えればそれほど面倒でもない。現状はセキュリティを強化する証明書の推奨設定が整理され切っていないが、解説ドキュメントが必要ないぐらいにデフォルト設定が整うのもすぐだろう。そしてインターネット上のサイトのほとんどが常時SSLのHTTP/2となっていくのは間違いなく、かなり早いスピードで進むことが予想できる。まずは身近に管理しているサイトから常時SSL化HTTP/2化を進めてみてはいかがだろうか。