Let's Encryptの証明書が更新されているかどうかをZabbixで監視する
自動更新のcronジョブが成功しているといつから思っていた?
登場以降TLSの普及に大きく貢献している Let's Encryptだが、証明書の有効期間が3ヶ月間と短く、手動で更新して運用するのは現実でないため自動更新の仕組みを仕掛けて利用するのが一般的である。
certbot(Let's Encryptのフロントエンドツール)で取得したSSL証明書を更新するのは簡単で、```certbot renew``` というコマンドを実行すれば良い。これで証明書の残り有効期限が1ヶ月を切っているかどうかを自動的に判断して、切っていれば更新を行ってくれる。このコマンドを実行するだけのシェルスクリプトを cron.dailyなり cron.weeklyに置いておけば自動更新の設定は完了だ(実際には[各サービスをリロードする処理も別の場所に記述する](#renewal-hooks)必要がある)。
このように基本的には簡単なことではあるのだが、それはWebサーバの状況がずっと変わらなければの話で、実際には「Let's Encryptの証明書が期限切れになってしまった」という事態はよく起こる。これは ```certbot renew``` コマンドによる自動更新に失敗しているからで、
- 「WebサーバにBASIC認証をかけたのでドメイン認証に失敗した」
- 「.htaccessでIPアドレス制限を設定したのでドメイン認証に失敗した」
- 「.htaccessに雑なRewriteRuleを追加したのでドメイン認証に失敗した」
- 「そういえばドキュメントルートを移動したのでドメイン認証に失敗した」
- 「certbotのバージョンが古くなってもう使えなくなっていた」(長い目で見ればありうる)
などといった原因がありがちだ。
certbotの初回実行時にメールアドレスを登録していれば Let's Encrypt側から証明書の期限切れについてメールが届くので異常に気付くことができるかもしれないが、個人用途ならともかく業務では「そのドメインに紐づけてしまって良いメールアドレス」がその時に決定できるかどうかという微妙な問題があったりするため ```--register-unsafely-without-email --agree-tos``` オプションを certbotに与えることでメールアドレスを登録せずに証明書を取得しているケースも多いのではないだろうか(それが良いとは言わないが現実問題としてそういう状況はある)。
仕方がないのでここでは Zabbixを使って証明書の「新しさ」を常に監視する方法について述べる。
## 監視のコンセプト
対象ホストが既にZabbixエージェントによって監視されていることを前提とし、下記のロジックで監視を行う。
- Zabbixエージェントにより__証明書ファイルの最終更新時刻からの経過時間__を適当な頻度で監視する
- 65日くらい過ぎてしまっていたら ```certbot renew``` に失敗しているものと判断して Zabbixサーバからアラートを上げる
証明書ファイルを監視すると述べたが、certbotは root権限で動作し、証明書ファイルなどは root権限でしか触れない場所に生成されるため(e.g. /etc/letsencrypt/live/YOURDOMAIN.com/cert.pem )、実際には Zabbixエージェントから直接監視することはできない。代わりに __/etc/letsencrypt/renewal/YOURDOMAIN.com.conf__ を監視する。このファイルは次の証明書更新に備えて設定などを保存しておくファイルのようだ。
## エージェントにユーザー定義アイテムを追加
Zabbixエージェントにはファイルの最終更新時刻を監視するためのアイテムが標準では用意されていないため、[UserParameter](https://www.zabbix.com/documentation/2.2/jp/manual/config/items/userparameters) として下記のようなものを監視対象ホストの /etc/zabbix/zabbix-agentd.conf に追加する。
```text
UserParameter=file.age[*],[ -e "$1" ] && echo $(($(date +%s)-$(stat -c %Y "$1"))) || echo 9223372036854775807
```
Zabbixサーバからこのパラメータを file.age という名前で参照することで任意のファイルについて最終更新時刻からの経過秒数を取得できる。なお、ファイルが存在しない場合については無限を表現するために極端に大きな値(9223372036854775807)を返すようになっている。
/etc/zabbix/zabbix-agentd.conf を書き換えたら Zabbixエージェントを再起動する。
```shell
systemctl restart zabbix-agentd
```
## 監視の開始
Zabbixの Web UIにログインし、対象ホストを選択して下記の操作を行う。
### 監視アイテムの作成
「アイテムの作成」から、

アイテム名、キー、単位、監視間隔を入力してアイテムを作成する。
キーとして指定するのはエージェントに追加した UserParameter名で、ここを ```file.age [/foo/bar]``` とすればファイル /foot/bar の最終更新からの経過秒数を取得できることになる。また、単位として uptime を指定しておくと Zabbix上で数値を表示するさいに秒数ではなく時間表記が用いられて便利だ。監視間隔については例では1h(1時間)としているが1d(1日)でも大抵十分だろう。

### トリガーの作成
Zabbixでは監視対象を設定しただけでは異常に対しアラートを上げてはくれない。監視対象がどういう数値を示していたら異常と判断してアラートを上げるかを定義するのがトリガーだ。「トリガーの作成」から、

名前、条件式を入力してトリガーを作成する。

条件式は ```{ホスト:file.age[/etc/letsencrypt/renewal/YOURDOMAIN.com.conf].last()}>5616000``` のようにする。これは全部手入力しなくても「追加」ボタンを押して入力補助に頼ることもできる。(5,616,000秒=65日)
これで証明書が最後の更新から65日を超えた場合にアラートが発生するようになる。
## 将来バージョンでの注意
[certbotのドキュメント](https://github.com/certbot/certbot/blob/2622a700e0a83e0de0994c970929b624b98dad40/certbot/docs/compatibility.rst) によれば、一部のディレクトリ以外の利用のされかたについては将来変更されることがあると述べられているため、/etc/letsencrypt/renewal/YOURDOMAIN.com.conf ファイルを監視する方法が将来のバージョンにわたってずっと使えるとは限らないことに注意する必要がある。もしそこを気にするなら、後述の renewal-hooks スクリプトの成功時に適当なマーカーファイルを touchするようにしておいてそちらを監視するという方法を用いても良いだろう。むしろWebサーバのリロードを含めて成功しているかどうかを監視できるという意味ではその方が良いかもしれない。
## おまけ(renewal-hooks) <a id="renewal-hooks"></a>
certbot [0.19.0(2017年10月4日)](https://github.com/certbot/certbot/blob/f88105a9529b47b91bb29dbe0e766e63b6911204/certbot/CHANGELOG.md#0190---2017-10-04)より、証明書の自動更新が行われた際に実行する処理を所定ディレクトリ ```/etc/letsencrypt/renewal-hooks/deploy``` 以下にシェルスクリプトとして設置しておくことが出来るようになっている(それまでは certbotコマンドの引数に更新成功時に実行するコマンドを指定する方法しかなかった)。シェルスクリプトには下記の環境変数が引き渡されるようである。
- ```$RENEWED_LINEAGE```
- ```$RENEWED_DOMAINS```