jessie の certbot が 0.9.3 に上がって、 /etc/cron.d/certbot よりも systemd の certbot.timer が優先して動くように変わったので、 reload に post-hook を使うように変えたり、 ログの差分メールの仕組みを変えたりしました。

今までの方法

今までは /etc/cron.daily/local-letsencryptcertbot renew を呼び出して、ログを保存して diff を出力して cron からメールを送信させて、 reload は更新の有無にかかわらず実行していました。

#!/bin/sh
LOGFILE=/var/log/certbot-renew.log
if [ -f "$LOGFILE" ]; then
    savelog -c 90 -q "$LOGFILE"
fi
if ! certbot renew > "$LOGFILE" 2>&1 ; then
    echo Automated renewal failed:
    cat "$LOGFILE"
    exit 1
fi
if [ -f "$LOGFILE".0 ]; then
    diff -u "$LOGFILE".0 "$LOGFILE"
fi
apachectl graceful
service postfix reload

certbot パッケージでインストールされた自動更新の仕組み

/etc/cron.d/certbot/run/systemd/system をチェックして普通は動かないようになっていました。

 % cat /etc/cron.d/certbot
 # /etc/cron.d/certbot: crontab entries for the certbot package
 #
 # Upstream recommends attempting renewal twice a day
 #
 # Eventually, this will be an opportunity to validate certificates
 # haven't been revoked, etc.  Renewal will only occur if expiration
 # is within 30 days.
 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

systemd の方では、timer から動かすため、有効化されていない certbot.service と、 service を動かすための certbot.timer が入っていました。

% cat /lib/systemd/system/certbot.service
[Unit]
Description=Certbot
Documentation=file:///usr/share/doc/python-certbot-doc/html/index.html
Documentation=https://letsencrypt.readthedocs.io/en/latest/
[Service]
Type=oneshot
ExecStart=/usr/bin/certbot -q renew
PrivateTmp=true
% cat /lib/systemd/system/certbot.timer
[Unit]
Description=Run certbot twice daily

[Timer]
OnCalendar=*-*-* 00,12:00:00
RandomizedDelaySec=3600
Persistent=true

[Install]
WantedBy=timers.target

OnCalendar で毎日 00:00:00 と 12:00:00 に動くようになっていました。 RandomizedDelaySec でランダムな遅延の設定をしようとしているようですが、 jessie の systemd は対応していないようで、 systemd[1]: [/lib/systemd/system/certbot.timer:6] Unknown lvalue 'RandomizedDelaySec' in section 'Timer' というログが出ていました。 (#843607 - certbot: Unknown lvalue ‘RandomizedDelaySec’ in section ‘Timer’ として報告済みです。)

ログの diff のメール送信方法変更

設定を追加するには /etc/systemd/system/certbot.service.d にファイルをおけば良いので、 以下の内容の /etc/systemd/system/certbot.service.d/diffmail.conf を作成しました。

certbot.serviceExecStart=/usr/bin/certbot -q renew の後に実行したかったので、 ExecStopPost を使ってみました。

[Service]
ExecStopPost=/bin/bash -c "diff -u <(cut -d: -f4- /var/log/letsencrypt/letsencrypt.log.1 | egrep -v '^DEBUG') <(cut -d: -f4- /var/log/letsencrypt/letsencrypt.log | egrep -v '^DEBUG') | ifne mail -s 'Change certbot log' root"

前回のログ (letsencrypt.log.1) と今回のログ (letsencrypt.log) から、必ず差分になってしまう時刻を cut で削って、さらに DEBUG ログの中にも現在日時で変化する部分があったので除外するようにしてから差分をとっています。

そして moreutilsifne を使って差分があるときだけメール送信をするようにしています。

post-hook への変更

systemd の ExecStart を書き換えるのは、メンテナンスしにくいとか、手動で certbot renew を実行したときに使われないなど、あまりよくないかと思い、 /etc/letsencrypt/cli.ini で設定することにしました。

letsencrypt の証明書を apache のみで使っているサーバーでは post-hook にリロードするコマンドを直接設定しました。

ついでに rsa-key-size も 2048 から 4096 に変更するようにしました。

% cat /etc/letsencrypt/cli.ini
rsa-key-size = 4096
post-hook = apachectl graceful

複数コマンドの post-hook

post-hook = apachectl graceful; service postfix reload >/dev/null のような書き方は certbot: error: Unexpected line 1 in /etc/letsencrypt/cli.ini: post-hook = apachectl graceful; service postfix reload >/dev/null というエラーになってうまくいかなかったので、 /etc/letsencrypt/post-hook に実行ファイルを作って、それを post-hook に指定することにしました。

 % sudoedit /etc/letsencrypt/cli.ini
 % sudoedit /etc/letsencrypt/post-hook
 % sudo chmod +x /etc/letsencrypt/post-hook
 % cat /etc/letsencrypt/cli.ini
 rsa-key-size = 4096
 post-hook = /etc/letsencrypt/post-hook
 % cat /etc/letsencrypt/post-hook
 #!/bin/sh
 apachectl graceful
 service postfix reload >/dev/null

テスト実行

sudo certbot renew で試しに実行してみたところ、以下のような感じで更新の必要がないときは post-hook は実行されないことが確認できました。

% sudo certbot renew
Saving debug log to /var/log/letsencrypt/letsencrypt.log

-------------------------------------------------------------------------------
Processing /etc/letsencrypt/renewal/www.example.org.conf
-------------------------------------------------------------------------------
Cert not yet due for renewal

The following certs are not due for renewal yet:
  /etc/letsencrypt/live/www.example.org/fullchain.pem (skipped)
No renewals were attempted.
No renewals attempted, so not running post-hook

まとめ

certbot renew で証明書が更新されたときに実行したいことは post-hook に、 証明書の更新とは関係なく毎回実行したいことは systemd の ExecStopPost を使えば良いことがわかりました。

certbot の hook には post-hook 以外に pre-hookrenew-hook もあるので、 用途によってはそちらも使えそうです。 (webroot プラグインを使っているので使っていませんが、 standalone プラグインを使っているのなら pre-hookstop して post-hookstart するとか、 renew-hook で更新されたドメインに応じて必要な時だけ postfix reload するとか)

Disqus Comments

Kazuhiro NISHIYAMA

Ruby のコミッターとかやってます。 フルスタックエンジニア(って何?)かもしれません。 About znzに主なアカウントをまとめました。

znz znz


Published