Auto renew SSL Certificate with Certbot(Let’s Encrypt)

このブログのSSL証明書はLet’s Encryptという認証局のものを使っています。Let’s Encryptの証明書を使い始めてしばらく経ちましたがいいかんじです。

Let’s Encryptについて

Let’s Encryptの証明書は無料で提供されており、certbotという証明書の取得や更新をコマンドで行うことの出来るツールが提供されているのが特徴です。神ってことです。

証明書の有効期限は90日(3ヶ月)と短いですが、コマンド一発で証明書を更新できるのでデメリットではありません。

また、証明書を細かくアップデートしていけるということは常に最新の暗号技術を取り入れた証明書を(Let’s Encryptが追随してくれているので)使えるということであり、度々世間を騒がせている弱い暗号方式を使った証明書によって起きる脆弱性に対しても運用上強いはずです。

certbotをインストールする

Let’s Encryptの証明書はcertbotを通じて取得、更新を行うので、まずはcertbotを導入します。

certbotのサイトの案内に従い使用しているWebサーバとOSを選択していきます。 Webサーバまで選ばせているのは、certbotコマンドが対応しているWebサーバであればWebサーバの設定まで行ってくれるのでその案内のためです。

h2oは選択肢にはないのでNon of the aboveを選択しました。選択するとディストリビューションに応じたインストール方法を案内するページに遷移します。

CentOS7の場合はepelリポジトリからインストールできるので非常に楽です。

sudo yum install epel-release  
sudo yum install --enablerepo=epel certbot  

CentOS6は若干面倒です(特に構成管理に含めようとすると...)。

証明書を取得する

certbotで証明書を取得するコマンドは以下です(webrootプラグインを使った方法)。

  • certonly: 証明書ファイルを取得
  • --webroot: webrootプラグインを使用
  • --webroot-path: Let’s Encryptが認証のためにアクセスすることのできるpathを指定(そのドメインのドキュメントルート以下のpathを指定すればok)
  • --domains: 証明書を取得したいドメイン名
  • --email: そのドメインの所有者のメールアドレス
  • --agree-tos: yesコマンド的なやつ。このオプションがないと対話モードで同意する必要がある
sudo certbot certonly --webroot --webroot-path <証明書を取得したいドメインでアクセスできるpath> --domains <ドメイン名> --email <メールアドレス> --agree-tos  

certbotコマンドを実行すると、webroot-path以下に.well-knownディレクトリが作成され、そこに一時的な認証トークンが設置されます。その場所へLet’s Encryptがアクセスしてきて、アクセスできればそのドメインの所有者であることが証明されるという流れです。

なのでドメインのドキュメントルートがWebアプリケーションの場合は、Let’s Encrypt用にディレクトリを作成してあげる必要があります(何もしないとappにリダイレクトされて404になるので)。

h2oでの設定イメージ。

hosts:  
  "blog.lorentzca.me:80":
    listen:
      port: 80
    paths:
      "/":
        proxy.reverse.url: "http://127.0.0.1:2368/"
      "/.well-known":
        file.dir: /var/www/ghost/.well-known

コマンドが成功すると、以下のようなディレクトリツリーが作成されます。

/etc/letsencrypt/
├── accounts
│   ├── acme-staging.api.letsencrypt.org
│   │   └── directory
│   │       └── xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
│   └── acme-v01.api.letsencrypt.org
│       └── directory
│           └── yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
├── archive
│   ├── ドメイン名
├── csr
├── keys
├── live
│   ├── ドメイン名
└── renewal

証明書はarchive/ドメイン名以下にファイルが置かれ、世代管理されます。live/ドメイン名以下に最新の世代の証明書のファイルがシンボリックリンクされています。

Webサーバに設定する際には、live/ドメイン名ディレクトリ以下のprivkey.pemfullchain.pem(証明書&中間証明書)を指定すればOKです。

証明書を取得し終わったら(SSLにリダイレクトさせる場合は)Webサーバの設定をかえる必要があります(443ポートでLet’s Encrypt認証用pathを受けられるようにする)。

hosts:  
  "blog.lorentzca.me:443":
    listen:
      port: 443
      ssl:
        certificate-file: "/etc/letsencrypt/live/blog.lorentzca.me/fullchain.pem"
        key-file:         "/etc/letsencrypt/live/blog.lorentzca.me/privkey.pem"
    paths:
      "/":
        proxy.reverse.url: "http://127.0.0.1:2368/"
      "/.well-known":
        file.dir: /var/www/ghost/.well-known

証明書を更新する

更新はかんたんで、以下のコマンドを実行するだけです。複数の証明書がある場合でも、以下のコマンドでOKです。

  • renew: SSL証明書の更新を実行
  • --post-hook: SSL証明書が置き換わった場合に実行する任意のコマンド
sudo certbot renew --post-hook "好きなコマンド"  

--post-hookオプションには、systemctl reload nginxのようにWebサーバが証明書を読み込み直せるようなコマンドを指定してあげます。

renewコマンドは、実行されると証明書の期限を確認し、1ヶ月を切っていたら実際に証明書を更新します。なのでcronなどで毎日実行しても大丈夫です。

証明書をsystemd.timerを使い定期的に行う

cronなどで上記のrenewコマンドを定期的に叩けば、人間の手を介さずSSL証明書を更新することができます。神...!!! ✨

cronでもいいですが私はせっかくの(?)systemdなので、systemd.timerを使って定期実行しています。

以下の2ファイルを/etc/systemd/system/以下に設置します(h2oがWebサーバ)。

  • serviceで定期実行したいコマンドを定義
  • timerで定期実行したいコマンドを定義したserviceを指定し、指定した時間に実行

certbot.service

[Unit]
Description=Certbot renew service  
After=h2o.target

[Service]
Type=oneshot  
ExecStart=/usr/bin/certbot renew --post-hook "systemctl restart h2o"

[Install]
WantedBy=default.target  

certbot.timer

[Unit]
Description=Renew Certification every day

[Timer]
OnBootSec=1min  
OnCalendar=*-*-* 04:00:00  
Unit=certbot.service

[Install]
WantedBy=timers.target  

設置後はsystemdに読み込ませてあげます。

sudo systemctl daemon-reload  

timerは以下のように確認することができます。

$ sudo systemctl list-timers
NEXT                         LEFT          LAST                         PASSED  UNIT                         ACTIVATES  
水 2017-03-08 02:09:56 JST  5h 47min left 火 2017-03-07 02:09:56 JST  18h ago systemd-tmpfiles-clean.timer systemd-tmpfiles-clean.service
水 2017-03-08 04:00:00 JST  7h left       火 2017-03-07 04:00:01 JST  16h ago certbot.timer                certbot.service

2 timers listed.  
Pass --all to see loaded but inactive timers, too.  

自動更新された様子

去年の9月に自動更新された様子を激写。journalctl -x -u certbotで確認しました。

 9月 19 04:00:14 ponpokopon.me systemd[1]: Starting Certbot renew service...
-- Subject: Unit certbot.service has begun start-up
-- Defined-By: systemd
-- Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
--
-- Unit certbot.service has begun starting up.
 9月 19 04:00:27 ponpokopon.me certbot[17305]: -------------------------------------------------------------------------------
 9月 19 04:00:27 ponpokopon.me certbot[17305]: Processing /etc/letsencrypt/renewal/ponpokopon.me.conf
 9月 19 04:00:27 ponpokopon.me certbot[17305]: -------------------------------------------------------------------------------
 9月 19 04:00:27 ponpokopon.me certbot[17305]: -------------------------------------------------------------------------------
 9月 19 04:00:27 ponpokopon.me certbot[17305]: new certificate deployed without reload, fullchain is
 9月 19 04:00:27 ponpokopon.me certbot[17305]: /etc/letsencrypt/live/ponpokopon.me/fullchain.pem
 9月 19 04:00:27 ponpokopon.me certbot[17305]: -------------------------------------------------------------------------------
 9月 19 04:00:27 ponpokopon.me certbot[17305]: -------------------------------------------------------------------------------
 9月 19 04:00:27 ponpokopon.me certbot[17305]: Processing /etc/letsencrypt/renewal/ghost.ponpokopon.me.conf
 9月 19 04:00:27 ponpokopon.me certbot[17305]: -------------------------------------------------------------------------------
 9月 19 04:00:27 ponpokopon.me certbot[17305]: -------------------------------------------------------------------------------
 9月 19 04:00:27 ponpokopon.me certbot[17305]: new certificate deployed without reload, fullchain is
 9月 19 04:00:27 ponpokopon.me certbot[17305]: /etc/letsencrypt/live/ghost.ponpokopon.me/fullchain.pem
 9月 19 04:00:27 ponpokopon.me certbot[17305]: -------------------------------------------------------------------------------
 9月 19 04:00:27 ponpokopon.me certbot[17305]: Congratulations, all renewals succeeded. The following certs have been renewed:
 9月 19 04:00:27 ponpokopon.me certbot[17305]: /etc/letsencrypt/live/ponpokopon.me/fullchain.pem (success)
 9月 19 04:00:27 ponpokopon.me certbot[17305]: /etc/letsencrypt/live/ghost.ponpokopon.me/fullchain.pem (success)
 9月 19 04:00:27 ponpokopon.me systemd[1]: Started Certbot renew service.
-- Subject: Unit certbot.service has finished start-up
-- Defined-By: systemd
-- Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
--
-- Unit certbot.service has finished starting up.
--
-- The start-up result is done.

感想

もうとにかく楽で素晴らしい。普通の証明書を更新する場合はだいたい以下の手順が必要です。

  1. CAのサイトへ行って対象ドメインの証明書更新を開始する
  2. CSRを貼り付けたり、認証方法の確認をしたりする
  3. 設定を確認後、入金する
  4. しばらくして承認メールが来るので承認ボタンを押す
  5. メールで証明書が納品される
  6. 納品された証明書をサーバに設置しWebサーバをreload

これがコマンド一発ってやばい!!

certbotにはwebrootプラグイン以外にもstandaloneやnginxプラグインがあります。また認証フローはACMEプロトコルというプロトコルが使われているようです。

機会があればそのあたりについても調べてみようと思っています。

参考

公式サイト。

日本語ページ。

Let’s Encryptを支えるACMEプロトコルについて。

systemd.timerについて。