Let's Encrypt の certbot renew を
Docker の HAProxy で実行した覚書

2018-03-06

Let's Encrypt の証明書の有効期限 (3ヶ月) が切れそうになると、おおよそ以下のようなメールが送られてくる。


From: Let's Encrypt Expiry Bot <expiry@letsencrypt.org>
Subject: Let's Encrypt certificate expiration notice for domain "www.ドメイン.名" (and 1 more)

Hello,

Your certificate (or certificates) for the names listed below will expire in
20 days (on 18 Mar 18 05:55 +0000). Please make sure to renew your certificate before then, 
or visitors to your website will encounter errors.

www.ドメイン.名
ドメイン.名

〜

証明書の更新方法について、 初回に certbot を走らせて証明書を取得したとき は、最後に以下のようなメッセージが出ていた。 (抜粋)

root@d10b45736849:/# certbot certonly --standalone --preferred-challenges http -d ドメイン.名 -d www.ドメイン.名
〜
IMPORTANT NOTES:
 - Congratulations! Your certificate and chain have been saved at
   /etc/letsencrypt/live/ドメイン.名/fullchain.pem. Your cert will expire
   on 2018-03-18. 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"
〜

さらに抜き出すと、

Your cert will expire on 2018-03-18.
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"
→ 証明書を更新するには、単に certbot をもう一度走らせるか、あるいは "certbot renew" を走らせる。

前者の、単に certbot を初回と同じようにもう一度走らせる方法では、その間 web サーバ (HAProxy) を止めておかないといけない。 初回は不都合を承知の上でそのようにして行ったが、今は web サーバを止めずに行いたい。

そこで後者だが、 How To Secure HAProxy with Let's Encrypt on Ubuntu 14.04 に、HAProxy を動かしたまま certbot renew を走らせる手法が書いてあったので、これを参考にさせてもらう。

● 設定 (1回だけでいい作業)

haproxy.cfg

以下の「# let's encrpyt certbot renew」の所の設定を含めておくようにする。

〜
frontend http-in
〜
	# https (SSL/TLS)
	bind *:443 ssl crt /usr/local/etc/haproxy/server.pem

	# let's encrpyt certbot renew
	acl letsencrypt-acl path_beg /.well-known/acme-challenge/
	use_backend letsencrypt-backend if letsencrypt-acl
〜
# let's encrpyt certbot renew
backend letsencrypt-backend
	server letsencrypt 127.0.0.1:54321
〜

Dockerfile

certbot がインストールされるようにしておく。 (今回のようなことを見越して既にそうなっていた)

FROM haproxy:1.8
〜
RUN apt-get update && apt-get install -y 〜 certbot \
    〜
〜

docker-compose.yaml

てもとの環境では swarm mode と docker-compose.yaml を使用している。
以下のように、/etc/letsencrypt を volume mount しておくようにする。 (今回のようなことを見越して既にそうなっていた (今は中にファイルは無し))

version: "3.2"
services:
〜
  haproxy:
〜
    volumes:
      # 〜
      - type: volume
        source: letsencrypt
        target: /etc/letsencrypt
〜

volume の中身

てもとの環境では、初回に certbot を実行した test という名のサーバーに以下のように、初回に certbot を実行したときの /etc/letsencrypt の volume mount の内容が置かれたままになっていた。

[root@test ~]# cd /var/lib/docker/volumes/tmp_letsencrypt/_data/

[root@test _data]# ls -ld `find . ! -type d -print`
-rw-r--r--. 1 root root   72 Dec 15 17:08 ./accounts/acme-v01.api.letsencrypt.org/directory/885f876108ac4db8cc148276dfd184ae/meta.json
-r--------. 1 root root 1632 Dec 15 17:08 ./accounts/acme-v01.api.letsencrypt.org/directory/885f876108ac4db8cc148276dfd184ae/private_key.json
-rw-r--r--. 1 root root  759 Dec 15 17:08 ./accounts/acme-v01.api.letsencrypt.org/directory/885f876108ac4db8cc148276dfd184ae/regr.json
-rw-r--r--. 1 root root 1797 Dec 18 15:55 ./archive/ドメイン.名/cert1.pem
-rw-r--r--. 1 root root 1647 Dec 18 15:55 ./archive/ドメイン.名/chain1.pem
-rw-r--r--. 1 root root 3444 Dec 18 15:55 ./archive/ドメイン.名/fullchain1.pem
-rw-r--r--. 1 root root 1704 Dec 18 15:55 ./archive/ドメイン.名/privkey1.pem
-rw-r--r--. 1 root root  960 Dec 18 15:55 ./csr/0000_csr-certbot.pem
-rw-------. 1 root root 1704 Dec 18 15:55 ./keys/0000_key-certbot.pem
lrwxrwxrwx. 1 root root   32 Dec 18 15:55 ./live/ドメイン.名/cert.pem -> ../../archive/ドメイン.名/cert1.pem
lrwxrwxrwx. 1 root root   33 Dec 18 15:55 ./live/ドメイン.名/chain.pem -> ../../archive/ドメイン.名/chain1.pem
lrwxrwxrwx. 1 root root   37 Dec 18 15:55 ./live/ドメイン.名/fullchain.pem -> ../../archive/ドメイン.名/fullchain1.pem
lrwxrwxrwx. 1 root root   35 Dec 18 15:55 ./live/ドメイン.名/privkey.pem -> ../../archive/ドメイン.名/privkey1.pem
-rw-r--r--. 1 root root  543 Dec 18 15:55 ./live/ドメイン.名/README
-rw-r--r--. 1 root root  468 Dec 18 15:55 ./renewal/ドメイン.名.conf

てもとの環境では、今回は haproxy を実行するサーバーが test から foobarbaz に変わっていたので、foobarbaz 上で実行する haproxy コンテナの volume として見えるように、ファイルをコピーした。

[root@test _data]# tar cf - . | xz -9 > ~nsmrtks/letsencrypt.tar.xz
 
[nsmrtks@foobarbaz ~]$ scp -p test:letsencrypt.tar.xz .
〜
[nsmrtks@foobarbaz ~]$ sudo -i
〜
[root@foobarbaz ~]# cd /var/lib/docker/volumes/foo_letsencrypt/_data/

[root@foobarbaz _data]# ls -a
.  ..

[root@foobarbaz _data]# xzcat ~nsmrtks/letsencrypt.tar.xz | tar xpf -

[root@foobarbaz _data]# ls -ld `find . ! -type d -print`
-rw-r--r--. 1 root root   72 Dec 15 08:08 ./accounts/acme-v01.api.letsencrypt.org/directory/885f876108ac4db8cc148276dfd184ae/meta.json
-r--------. 1 root root 1632 Dec 15 08:08 ./accounts/acme-v01.api.letsencrypt.org/directory/885f876108ac4db8cc148276dfd184ae/private_key.json
-rw-r--r--. 1 root root  759 Dec 15 08:08 ./accounts/acme-v01.api.letsencrypt.org/directory/885f876108ac4db8cc148276dfd184ae/regr.json
-rw-r--r--. 1 root root 1797 Dec 18 06:55 ./archive/ドメイン.名/cert1.pem
-rw-r--r--. 1 root root 1647 Dec 18 06:55 ./archive/ドメイン.名/chain1.pem
-rw-r--r--. 1 root root 3444 Dec 18 06:55 ./archive/ドメイン.名/fullchain1.pem
-rw-r--r--. 1 root root 1704 Dec 18 06:55 ./archive/ドメイン.名/privkey1.pem
-rw-r--r--. 1 root root  960 Dec 18 06:55 ./csr/0000_csr-certbot.pem
-rw-------. 1 root root 1704 Dec 18 06:55 ./keys/0000_key-certbot.pem
lrwxrwxrwx. 1 root root   32 Dec 18 06:55 ./live/ドメイン.名/cert.pem -> ../../archive/ドメイン.名/cert1.pem
lrwxrwxrwx. 1 root root   33 Dec 18 06:55 ./live/ドメイン.名/chain.pem -> ../../archive/ドメイン.名/chain1.pem
lrwxrwxrwx. 1 root root   37 Dec 18 06:55 ./live/ドメイン.名/fullchain.pem -> ../../archive/ドメイン.名/fullchain1.pem
lrwxrwxrwx. 1 root root   35 Dec 18 06:55 ./live/ドメイン.名/privkey.pem -> ../../archive/ドメイン.名/privkey1.pem
-rw-r--r--. 1 root root  543 Dec 18 06:55 ./live/ドメイン.名/README
-rw-r--r--. 1 root root  468 Dec 18 06:55 ./renewal/ドメイン.名.conf

[root@foobarbaz _data]# date
Tue Feb 27 07:01:41 UTC 2018
(展開したファイルのタイムスタンプが一見ずれて見えるのは、タイムゾーンの設定が違うため。)

certbot renew の設定ファイル (renewal/ドメイン.名.conf)

以下のようになっていた。

# renew_before_expiry = 30 days
version = 0.10.2
archive_dir = /etc/letsencrypt/archive/ドメイン.名
cert = /etc/letsencrypt/live/ドメイン.名/cert.pem
privkey = /etc/letsencrypt/live/ドメイン.名/privkey.pem
chain = /etc/letsencrypt/live/ドメイン.名/chain.pem
fullchain = /etc/letsencrypt/live/ドメイン.名/fullchain.pem

# Options used in the renewal process
[renewalparams]
authenticator = standalone
installer = None
account = 0123456789abcdef0123456789abcdef
pref_challs = http-01,

以下のようにして編集する。

[root@foobarbaz _data]# vi renewal/ドメイン.名.conf 
http01_port = 54321
という行を追加する

( How To Secure HAProxy with Let's Encrypt on Ubuntu 14.04 には 「change the http01_port line」 とあるがそのような行が無いため最後に追加した)

● 毎回必要な作業

1. 80番ポートへのアクセスが制限されていればいったん制限を外す

2. certbot renew

haproxy コンテナ内で certbot renew するために、docker exec でシェルを起動する。

[nsmrtks@foobarbaz ~]$ docker ps
〜

[nsmrtks@foobarbaz ~]$ docker exec -it foo_haproxy.1.rjlt3vdiq3br4uvd0rjt613gn bash

そして、まずは certbot renew --dry-run してみる。

root@d121940033e6:/# certbot renew --dry-run
Saving debug log to /var/log/letsencrypt/letsencrypt.log

-------------------------------------------------------------------------------
Processing /etc/letsencrypt/renewal/ドメイン.名.conf
-------------------------------------------------------------------------------
Cert is due for renewal, auto-renewing...
Renewing an existing certificate
Performing the following challenges:
http-01 challenge for ドメイン.名
http-01 challenge for www.ドメイン.名
Waiting for verification...
Cleaning up challenges
Generating key (2048 bits): /etc/letsencrypt/keys/0001_key-certbot.pem
Creating CSR: /etc/letsencrypt/csr/0001_csr-certbot.pem
** DRY RUN: simulating 'certbot renew' close to cert expiry
**          (The test certificates below have not been saved.)

Congratulations, all renewals succeeded. The following certs have been renewed:
  /etc/letsencrypt/live/ドメイン.名/fullchain.pem (success)
** DRY RUN: simulating 'certbot renew' close to cert expiry
**          (The test certificates above have not been saved.)

IMPORTANT NOTES:
 - Your account credentials have been saved in your Certbot
   configuration directory at /etc/letsencrypt. You should make a
   secure backup of this folder now. This configuration directory will
   also contain certificates and private keys obtained by Certbot so
   making regular backups of this folder is ideal.

問題なさそうなら、certbot renew する。

root@d121940033e6:/# certbot renew
Saving debug log to /var/log/letsencrypt/letsencrypt.log

-------------------------------------------------------------------------------
Processing /etc/letsencrypt/renewal/ドメイン.名.conf
-------------------------------------------------------------------------------
Cert is due for renewal, auto-renewing...
Renewing an existing certificate
Performing the following challenges:
http-01 challenge for ドメイン.名
http-01 challenge for www.ドメイン.名
Waiting for verification...
Cleaning up challenges
Generating key (2048 bits): /etc/letsencrypt/keys/0002_key-certbot.pem
Creating CSR: /etc/letsencrypt/csr/0002_csr-certbot.pem

-------------------------------------------------------------------------------
new certificate deployed without reload, fullchain is
/etc/letsencrypt/live/ドメイン.名/fullchain.pem
-------------------------------------------------------------------------------

Congratulations, all renewals succeeded. The following certs have been renewed:
  /etc/letsencrypt/live/ドメイン.名/fullchain.pem (success)

3. 80番ポートのアクセス制限を元に戻す

4. fullchain.pem と privkey.pem を取り出す・結合する

初回に certbot を走らせて証明書を取得したとき と同様。

[nsmrtks@foobarbaz ~]$ sudo -i
〜
[root@foobarbaz ~]# cd /var/lib/docker/volumes/foo_letsencrypt/_data/live/ドメイン.名/

[root@foobarbaz ドメイン.名]# cat fullchain.pem privkey.pem > ~nsmrtks/server.pem

[root@foobarbaz ドメイン.名]# exit

5. 証明書を入れ替えて反映する

以上


index