优雅地管理 SSL 证书及其关联服务
我的服务器上开着一堆服务,其中有一些需要用到 SSL 证书,例如 Nginx 及前面刚刚部署的邮件服务。我的 SSL 证书是通过 certbot 向 Let’s Encrypt 申请的,证书有效期为 90 天,也就是说每隔 80 多天就得更新一下证书。
动机
在证书更新后,自然需要将所有用到证书的服务 reload 一下,以加载新的证书,通常我会写一条 crontab 作业来干这件事。之前我只有一台服务器 A 跑 Nginx 服务的时候,这样感觉还比较舒服,而现在情况变得更加复杂了:
这两年我都在维护我们实验室主办的学术会议的官网(规模不大,因此把官网部署在我的服务器上完全没问题),所以需要同时维护两个域名的证书。另外,我现在还同时维护着另一台 B 服务器 —— 两台服务器上的服务共享着同一个域名证书,所以需要在 A 服务器上的证书更新时同步到 B 服务器上,并在 B 服务器上 reload 所有用到证书的服务。
于是原先的方法感觉就不太优雅了。正巧这两天 iBug 大佬发布了一篇文章,介绍了一种通过 systemd 来 reload 一些服务的方法:
看上去非常优雅,于是我学习了一下,也给整了起来。
服务器 A 上的操作
首先我将申请证书的脚本从 certbot 换成了更轻量的 acme.sh,安装完成后直接支持通过接入 cloudflare 以及 godaddy 的 dns api 来更新泛域名证书(而 certbot 则需要分别安装两者的插件,感觉稍显笨重)
我将 A 服务器作为更新证书的机器,更新证书后自动同步给 B,在 A 服务器上只有 Nginx 服务需要 reload,因此先创建 /etc/systemd/system/ssl-certificate.target
:
# /etc/systemd/system/ssl-certificate.target
[Unit]
Description=SSL certificates reload helper
PropagatesReloadTo=nginx.service
然后创建一个 path 文件 /etc/systemd/system/ssl-certificate.path
来监听证书文件的变更事件:
# /etc/systemd/system/ssl-certificate.path
[Unit]
Description=SSL certificate reload helper
Wants=%N.target
[Path]
PathChanged=/path/to/cert1/fullchain.cer
PathChanged=/path/to/cert2/fullchain.cer
[Install]
WantedBy=multi-user.target
我有两个证书,所以这里加了两条 PathChanged
字段分别监听证书 1 和证书 2 的变更事件。
最后由于 path 单元只能激活服务而没办法重载服务,所以需要创建一个 “一次性” 的(oneshot)service,每次激活时来 reload 服务:
/etc/systemd/system/ssl-certificate.service
# /etc/systemd/system/ssl-certificate.service
[Unit]
Description=SSL certificate reload helper
StartLimitIntervalSec=5s
StartLimitBurst=2
[Service]
Type=oneshot
ExecStart=/bin/sh -c '/bin/systemctl reload %N.target; /usr/local/bin/rsync_cert.sh'
这里我需要额外执行的工作还有向 B 服务器同步证书 1,因此采用了将两条命令写在一起的方式。同步证书则使用了 rsync
命令。
/usr/local/bin/rsync_cert.sh
#!/bin/bash
rsync -avzL -e 'ssh -i /path/to/priv_key' /path/to/cert1/ user@serverB:/path/to/cert1/
需要先手动执行一下这个脚本将服务器 B 的公钥写入到 ~/.ssh/known_hosts
,后面就能自动化了。
最后启动创建的 path 单元:
systemctl daemon-reload
systemctl enable --now ssl-certificate.path
服务器 B 上的操作
在服务器 B 上,同样可以创建与前面相似的 target
、path
及 service
单元,这样当新的证书同步过去后,也会触发 reload。
由于我的 mailserver 是部署在 docker 里的,好像不太好通过 systemd 来维护,因此同样采用了这种更愚蠢的办法,只是因为命令比较多,就干脆专门写了一个文件:
/etc/systemd/system/ssl-certificate.service
# /etc/systemd/system/ssl-certificate.service
[Unit]
Description=SSL certificate reload helper
StartLimitIntervalSec=5s
StartLimitBurst=2
[Service]
Type=oneshot
ExecStart=/usr/local/bin/reload-services.sh
/usr/local/bin/reload-services.sh
#!/bin/bash
set +e
/bin/systemctl reload ssl-certificate.target
/bin/docker exec mailserver postfix reload
/bin/docker exec mailserver dovecot reload
如此配置,当 SSL 证书更新时,两台服务器上的所有相关服务都可以顺利自动重新加载证书了!