鱼喃

听!布鲁布鲁,大鱼又在那叨叨了

泛域名证书自动申请续期方案(基于docker)

作为一个程序员,懒是生产力,能自动的东西,为什么还要手动

感谢Let's Encrypt提供的免费证书,让个人站长也能用上https。之前只提供了单域名证书,需要对于每一个子域名进行独立的申请工作,但是年初开放了泛域名证书的支持,一个顶级域名下的所有子域名可以共享一个证书,大大简化了证书管理的麻烦。

之前使用的是使用Docker容器签发和自动续期Let’s Encrypt证书一文中的方法,是针对单域名的,现在感觉略有一点繁琐和过时了,趁有时间重写一篇。

本文最后的预期效果:手工完成首次初始设置之后,之后自动完成域名证书的续期、服务器的证书刷新工作。跟上一篇一样,所有操作都可以在docker容器内进行,不需要安装新的软件包。

申请泛域名证书

Let's Encrypt 直接提供的api还是略显繁琐的,所以出现了很多封装和简化的程序,之前用的是较为官方的certbot,后来发现Neilpang/acme.sh用起来更为舒服一点,就切换过来了。(acme.sh是国人写的,GitHub上star比前者少一些)

acme.sh提供了很多种申请方式,从原理上来看,包括文件验证和dns记录验证。因为我们的目的是申请一个泛域名证书,所以使用了dns验证模式,这种模式还有一个优点就是可以在自己的机器上申请证书然后分发到不同的服务器上去。

使用也比较简单,安装之后,使用命令开始申请证书,

1
2
3
4
5
acme.sh \
--issue \
--dns \
-d '*.example.com' \
--yes-I-know-dns-manual-mode-enough-go-ahead-please

然后根据提示添加dns解析记录,再次运行以上命令即可完成证书申请。

虽然这样的操作三个月才需要执行一次,但是如果服务器多了,还是有一点麻烦的。

自动设置dns记录

好在 acme.sh 支持通过服务商的api自动完成dns解析的修改操作,这下就不需要登陆上去修改等等麻烦的操作了。

目前acme支持 CloudFlareDNSPodAmazon Route53Aliyun等等,基本上主流的都支持,完整的列表可以在DNS API找到。

以下以CloudFlare为例。

首先我们需要四个配置参数:Account IDTokenEmailAPI Key

Account ID 在进入任意一个域名的Overview页,拉到页面右下角即可找到;

f644ec0c10fba484d8b80be13a2dbe82.png

Token 可以在API Tokens页面,点击Create Token创建一个。按照最小权限原则给这个token授权,让它只能修改指定域名的dns解析记录;

3470677be65428d5b7a487273ebb64b8.png

(arm系统上似乎表现略有差异,需要补上 Account Settings:ReadZone:Read 这两个权限)

API Key 就是同一个页面的API Keys板块的Global API Key,点击view然后输入账号密码即会显示;

Email 就是注册邮箱;

然后使用命令就可以自动完成dns记录修改和证书申请了。

1
2
3
4
5
6
7
8
CF_Token="ZK4aspUKNNp20nhEv96awtmS54nTkeVKX-1NGq1E1" \
CF_Account_ID="DfooRWEAWKCNUmDyYJXvQCJDDYv9nvUru" \
CF_Key="RheH3wMCg29yF9oCucbSuMBV7iRyawiUV65Mxc" \
CF_Email="[email protected]" \
acme.sh --issue \
--dns dns_cf \
-d 'example.com' \
-d '*.example.com'

使用alias模式来保护原始域名解析

尽管可以让acme.sh直接修改dns,以及限制了token的范围,但是毕竟是非官方项目,在隐私上还是有点顾虑的(此处没有任何怀疑作者的意思)。

好在官方支持通过cname的形式来验证,也就是说我们可以用一个不太在乎的域名来替代验证,从而避免对原始域名的直接操作。具体的操作需要将原始域名的_acme-challenge记录cname到新域名的_acme-challenge,例如_acme-challenge.example-alias.com。还有一点很好的就是,这个域名可以同时给多个域名使用,在管理大量域名的时候比较方便。这里我是新注册了一个新的廉价xyz域名,十年的价格才5刀,相当便宜了。

1da663240394d47d199afb807f6b4239.png

设置好之后,只需要给acme.sh授权新的域名dns操作权限即可,其他的域名只需要设置cname。

1
2
3
4
5
6
7
8
9
CF_Token="ZK4aspUKNNp20nhEv96awtmS54nTkeVKX-1NGq1E1" \
CF_Account_ID="DfooRWEAWKCNUmDyYJXvQCJDDYv9nvUru" \
CF_Key="RheH3wMCg29yF9oCucbSuMBV7iRyawiUV65Mxc" \
CF_Email="[email protected]" \
acme.sh --issue \
--dns dns_cf \
--challenge-alias example-alias.com \
-d 'example.com' \
-d '*.example.com'

参考DNS alias mode | acme.sh

定期更新证书

以上只是完成了域名证书的自动申请工作,接下来还要设置自动续期,续期很简单,就是利用cron job每两个月执行一次续期操作。

1
2
3
4
5
6
7
8
9
10
CF_Token="ZK4aspUKNNp20nhEv96awtmS54nTkeVKX-1NGq1E1" \
CF_Account_ID="DfooRWEAWKCNUmDyYJXvQCJDDYv9nvUru" \
CF_Key="RheH3wMCg29yF9oCucbSuMBV7iRyawiUV65Mxc" \
CF_Email="[email protected]" \
acme.sh --renew \
--force \
--dns dns_cf \
--challenge-alias example-alias.com \
-d 'example.com' \
-d '*.example.com'

给脚本添加可执行权限

1
chmod +x renew_cert.sh

这里利用系统自带的 crontab 功能来定期执行续期脚本。

1
crontab -e

添加以下行

1
0 0 1 * * /crontab/renew_cert.sh

保存退出,crontab 就会自动更新生效。以上命令的含义是每个月1号零点自动执行 /crontab/renew_cert.sh 这个脚本。

刷新服务器证书

根据部署方式来决定如何刷新证书,如果是在宿主机上直接安装的就直接reload nginx/apache服务器即可。

1
service nginx force-reload

如果像我一样使用docker来部署nginx的话,直接重启容器就行。

1
docker ps -a | grep nginx | grep Up | awk '{print $1}' | xargs docker kill

如果你的容器没有配置自动重启的话,需要换成先关闭再创建

分发证书到其他服务器

如果只有一台服务器就不需要分发了,不过大鱼有好几台服务器,需要将新的证书分发到其他服务器上。目前的策略是证书托管在私有仓库里,然后各个服务器定期拉取,如果发现有更新就刷新证书。(这里也可以采用webhook的方式,不过没有找到合适的程序)

1
2
3
4
5
6
7
8
9
10
11
current=$( < "/acme.sh/.git/refs/heads/master")

cd /acme.sh && git pull && cd -

latest=$( < "/acme.sh/.git/refs/heads/master")

# restart nginx if updated
if [ "$current" != "$latest" ] ; then
docker pull nginx
docker ps -a | grep nginx | grep Up | awk '{print $1}' | xargs docker kill
fi

完整脚本

对上述进行汇总,再将acme.sh装进docker的笼子里,可以得到以下脚本。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#!/bin/bash

docker run -it \
--name acme \
--rm \
--env CF_Token="ZK4aspUKNNp20nhEv96awtmS54nTkeVKX-1NGq1E1" \
--env CF_Account_ID="DfooRWEAWKCNUmDyYJXvQCJDDYv9nvUru" \
--env CF_Key="RheH3wMCg29yF9oCucbSuMBV7iRyawiUV65Mxc" \
--env CF_Email="[email protected]" \
-v "/acme.sh/":/acme.sh \
neilpang/acme.sh \
--renew --force \
--dns dns_cf \
--challenge-alias example-alias.com \
-d 'example.com' \
-d '*.example.com'

# restart nginx
docker ps -a | grep nginx | grep Up | awk '{print $1}' | xargs docker kill

别忘了挂载宿主机文件夹

至此,就可以欢快的让它自己去运行啦,彻底从手动续期证书这件小事里脱离出来。当然了,别忘记给域名续费,赎回费用挺高的,要是开了自动续期那就当我没说。