[记录]使用Certbot与Cloudflare插件申请通配符证书

·  阅读 1049

这篇文章是记录性质的,专注于我的切身实践的过程,不会有太多扩展,也不会太深入。重点讲述我的实践步骤、遇到的问题和解决方法。如果你的情况与我相似,遇到了类似的问题,希望这篇文章对你有帮助。

必备知识

通配符证书

通配符证书(wildcard certificate)是可以与多个子域名一起使用的公钥证书。与传统证书相比,通配符证书可以比每个子域的证书更便宜、更方便。

证书是针对域名颁发的,与具体的主机无关,已经申请的证书可以迁移到任何主机。因此颁发证书只需验证你对域名的所有权。

ACME

自动证书管理环境(Automatic Certificate Management Environment, ACME)是一种通信协议,用于自动化证书颁发机构与其用户的 Web 服务器之间的交互,允许以非常低的成本自动部署公钥基础设施。

ACME使用“客户端/服务器”架构,使用HTTPS传输数据。想要通过ACME协议申请证书,证书颁发机构必须支持ACME,实现ACME服务器,而我们需要一个ACME客户端。

Let's Encrypt

Let's Encrypt是由Internet Security Research Group(ISRG)运营的非营利性证书颁发机构,它免费为传输层安全(TLS)加密提供X.509证书。它是世界上最大的证书颁发机构,被超过 2.65 亿个网站使用,目标是所有网站都使用HTTPS。

Let's Encrypt支持ACME协议,并支持通配符证书。

Certbot

Certbot是一个ACME客户端,也是ACME客户端的参考实现,使用Python编写。Certbot默认使用Let's Encrypt作为证书颁发机构。

验证方式

上文说到,证书颁发机构需要验证你对域名的所有权,然后才能给你证书。虽然ACME客户端可能有五花八门的验证选项,但是归结起来只有两种类型:HTTP质询DNS质询

HTTP质询

这是当今最常见的验证方式。与ACME服务器沟通后,ACME客户端将一个特定的文件放在主机Web服务器的一个特定路径上。ACME服务器会通过申请的域名与此路径访问这个文件,如果一切正确,则验证完毕。HTTP质询只能使用80端口

可以看出,HTTP质询需要你事先添加DNS记录,使你申请证书的域名指向运行ACME客户端的主机,并且该主机必须是可公网访问的。其次,需要一个已经在运行的Web服务器,ACME客户端需要足够的权限在Web服务器下放置文件。

HTTP质询不支持通配符证书。

DNS质询

DNS质询与Web服务器无关,而是通过检查DNS记录来验证对域名的所有权。ACME服务器只需要检查域名下特定的TXT记录即可完成验证。

为了让ACME客户端自动添加TXT记录,DNS托管商必须提供编辑DNS记录的API,并且由DNS托管商提供身份认证的方案。此外,ACME客户端还要有支持这些API的插件。

DNS质询看起来麻烦,但更具普适性,而且功能强大。DNS质询不依赖于具体主机,并且支持通配符证书

安装ACME客户端

我选择Certbot作为ACME客户端。我的操作系统是Arch Linux,不同分发版包管理器和软件包名可能不同,请自行判断。

sudo pacman -Sy certbot
复制代码

安装Cloudflare API的插件。

sudo pacman -Sy certbot-dns-cloudflare
复制代码

获取Cloudflare API Token

登录Cloudflare后点击右上角用户头像,在下拉列表中点击My Profile。在新页面便可看到API Token的标签。

点击Create Token后,有很多现成的模板可以使用,第一个就是Edit zone DNS

API token templates

使用模板后,一切保持默认就好,除了Zone Resources,需要选择Token适用的域名。

Zone Resources

创建Token后,页面会显示一个很长的字符串,这就是Token,点击后面的Copy保存下来。

需要注意的是,这个Token只会显示这一次。之后便无法再查看这个Token,只能更新,更新后旧Token就会失效。

在需要运行Certbot的主机上创建一个文件(比如/etc/letsencrypt/cloudflare.ini),保存Token。文件内容如下:

# Cloudflare API token used by Certbot
dns_cloudflare_api_token = _yourToken_
复制代码

#开头的注释可忽略,_yourToken_ 换成你的Token。

修改这个文件的所有者和权限,提高域名安全性。建议root:root600

申请证书

子命令

certbot有许多的子命令,默认使用run(即certbotcertbot run相同),获取证书并安装证书。安装证书即配置Web服务器使用证书,需要搭配相关的插件。我们不配置Web服务器,不需要安装证书。

子命令certonly只申请证书,是我们需要的子命令。

调用Cloudflare插件

使用两个选项调用Cloudflare插件,--dns-cloudflare--dns-cloudflare-credentials _tokenFile_,把 _tokenFile_ 换成之前创建的保存Token的文件。

使用ECDSA密钥

1.10版本开始,Certbot支持椭圆曲线数字签名算法(Elliptic Curve Digital Signature Algorithm, ECDSA),默认算法是RSA。ECDSA使用更短的密钥就可实现和RSA相同的安全级别,意味着更高的效率。选项--key-type ecdsa使用ECDSA密钥。

指定域名

选项-d--domain用来指定证书的域名,一个-d选项只能跟一个参数,如果需要一个证书用于多个域名,需要多个-d选项或者将域名用逗号分隔。我们的目的是申请通配符证书,所以将域名写成通配符样式。通配符证书不包括裸域,所以需要再加上裸域。比如-d "example.com,*.example.com",加上双引号防止Shell的通配符扩展。在证书管理时,第一个域名会作为证书的名字。通配符证书只能匹配一级子域名。


最终的certbot命令大概是这样:

sudo certbot certonly \
  --dns-cloudflare \
  --dns-cloudflare-credentials /etc/letsencrypt/cloudflare.ini \
  --key-type ecdsa \
  --domain "example.com,*.example.com"
复制代码

开始申请

命令运行以后,会交互式地问一些问题。

首先要求输入邮箱,可以跳过。建议输入邮箱,可以接收到证书过期或安全问题的通知。

接下来让你同意服务条款,输入y按回车就行。

最后问你是否订阅电子前沿基金会的邮件,我选n

申请证书前会注册一个ACME账号,并且把账号识别信息保存在本地。

等待几秒后证书就申请好了,然后会显示证书的存储位置。

管理证书

子命令certificates显示所有证书的信息;revoke吊销证书;delete删除证书。

这些管理证书的子命令都不再需要额外的验证,因为账号信息已经保存在了本地。

$ sudo certbot certificates
Saving debug log to /var/log/letsencrypt/letsencrypt.log

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Found the following certs:
  Certificate Name: example.com
    Serial Number: 1234567890abcdef
    Key Type: ECDSA
    Domains: example.com *.example.com
    Expiry Date: 2022-05-29 12:48:06+00:00 (VALID: 89 days)
    Certificate Path: /etc/letsencrypt/live/example.com/fullchain.pem
    Private Key Path: /etc/letsencrypt/live/example.com/privkey.pem
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

复制代码

一般来说,Certbot的所有信息都存储在/etc/letsencrypt

archive目录包含所有已经申请的证书及私钥,包括已经过期的证书。而live目录只包含最新的证书及私钥,通过符号链接指向archive目录对应的文件。

$ sudo ls -l /etc/letsencrypt/live/example.com
total 4
lrwxrwxrwx 1 root root  34 Feb 28 21:48 cert.pem -> ../../archive/example.com/cert1.pem
lrwxrwxrwx 1 root root  35 Feb 28 21:48 chain.pem -> ../../archive/example.com/chain1.pem
lrwxrwxrwx 1 root root  39 Feb 28 21:48 fullchain.pem -> ../../archive/example.com/fullchain1.pem
lrwxrwxrwx 1 root root  37 Feb 28 21:48 privkey.pem -> ../../archive/example.com/privkey1.pem
-rw-r--r-- 1 root root 692 Feb 28 21:48 README
复制代码

fullchain.pem是大多数软件需要的证书文件,实际上它是cert.pemchain.pem的拼接。

privkey.pem如其名,是私钥文件。

修改权限

由于历史原因,archivelive的权限都是700,只有root用户才能访问证书。

可以手动更改权限,并且后续更新不会覆盖权限。

sudo chmod 755 /etc/letsencrypt/{live,archive}
复制代码

现在我们进入证书的目录,注意到privkey.pem的权限与众不同。

$ ls -l /etc/letsencrypt/archive/example.com
total 20K
-rw-r--r-- 1 root root 1.6K Feb 28 21:48 cert1.pem
-rw-r--r-- 1 root root 3.7K Feb 28 21:48 chain1.pem
-rw-r--r-- 1 root root 5.2K Feb 28 21:48 fullchain1.pem
-rw------- 1 root root  241 Feb 28 21:48 privkey1.pem
复制代码

在对称加密体系中,公钥(即证书)不需要保密,所以任何用户都可以读;私钥必须保密,所以只有root用户才能读。但这需要所有使用该证书和私钥的程序都以root权限运行,显然不利于系统安全。

privkey.pem所有组组权限所做的更改将在续订的证书中保留。

所以我们可以创建一个组,把需要使用证书的用户加入这个组中,并且更改privkey.pem的所有组和组权限。如果只创建组而不创建用户,会导致后续创建用户的uid和gid不匹配,所以我建议同时创建用户。

sudo useradd cert --system --home-dir=/ --shell=/usr/bin/nologin
sudo chgrp cert /etc/letsencrypt/live/example.com/privkey.pem
sudo chmod g+r /etc/letsencrypt/live/example.com/privkey.pem
复制代码

更新证书

所谓更新证书,实际上是用相同账号申请一个效果相同的新证书。没有到期的旧证书依然可以使用。

Let's Encrypt颁发的证书有效期固定为90天,需要经常更新证书。

子命令renew用来更新证书。虽然更新证书是一个完整的证书申请过程,但是在之前申请证书时,Certbot把相关信息都保存在了renewal目录,我们直接运行certbot renew即可。

$ cat /etc/letsencrypt/renewal/example.com.conf
# renew_before_expiry = 30 days
version = 1.23.0
archive_dir = /etc/letsencrypt/archive/example.com
cert = /etc/letsencrypt/live/example.com/cert.pem
privkey = /etc/letsencrypt/live/example.com/privkey.pem
chain = /etc/letsencrypt/live/example.com/chain.pem
fullchain = /etc/letsencrypt/live/example.com/fullchain.pem

# Options used in the renewal process
[renewalparams]
account = 1234567890abcde
key_type = ecdsa
authenticator = dns-cloudflare
dns_cloudflare_credentials = /etc/letsencrypt/cloudflare.ini
server = https://acme-v02.api.letsencrypt.org/directory
复制代码

renew子命令会检查所有证书的到期时间,只有在30天内到期的证书才会被真正更新。这个特性非常有利于自动化,我们不需要关心每个证书的到期时间,只要经常运行certbot renew,就可以自动更新即将到期的证书。

如果想立刻看到更新证书的效果,也可以跳过到期检查,强制更新证书,使用--force-renewal选项。需要注意,证书颁发机构对申请证书的频率有限制,如果频繁强制更新证书,会导致账号被暂时冻结。

renew成功更新证书后,会把新的证书和私钥文件放在archive对应的目录下,并且修改live目录下的符号链接,使其始终指向最新的证书和私钥。

$ ls /etc/letsencrypt/archive/example.com /etc/letsencrypt/live/example.com
/etc/letsencrypt/archive/example.com:
total 40K
-rw-r--r-- 1 root root 1.6K Feb 28 21:48 cert1.pem
-rw-r--r-- 1 root root 1.6K Mar  1 09:54 cert2.pem
-rw-r--r-- 1 root root 3.7K Feb 28 21:48 chain1.pem
-rw-r--r-- 1 root root 3.7K Mar  1 09:54 chain2.pem
-rw-r--r-- 1 root root 5.2K Feb 28 21:48 fullchain1.pem
-rw-r--r-- 1 root root 5.2K Mar  1 09:54 fullchain2.pem
-rw-r----- 1 root cert  241 Feb 28 21:48 privkey1.pem
-rw-r----- 1 root cert  241 Mar  1 09:54 privkey2.pem

/etc/letsencrypt/live/example.com:
total 4.0K
lrwxrwxrwx 1 root root  34 Mar  1 09:54 cert.pem -> ../../archive/example.com/cert2.pem
lrwxrwxrwx 1 root root  35 Mar  1 09:54 chain.pem -> ../../archive/example.com/chain2.pem
lrwxrwxrwx 1 root root  39 Mar  1 09:54 fullchain.pem -> ../../archive/example.com/fullchain2.pem
lrwxrwxrwx 1 root root  37 Mar  1 09:54 privkey.pem -> ../../archive/example.com/privkey2.pem
-rw-r--r-- 1 root root 692 Feb 28 21:48 README
复制代码

可以看到,privkey2.pem继承了privkey1.pem的所有组和组权限。

钩子

Certbot支持三种钩子,可以通过选项附加在certbot renew的末尾。

  • --pre-hook:更新证书之前调用
  • --post-hook:更新证书之后调用
  • --deploy-hook:成功更新证书之后调用

当有即将到期的证书时,--pre-hook--post-hook总会调用。--deploy-hook只有新证书已经申请下来才会调用,比较常用。

比如在证书更新后自动重载Caddy服务器:certbot renew --deploy-hook "systemctl reload caddy.service"

命令里塞命令不太方便,更优雅的钩子用法是把可执行文件放在对应的钩子目录。/etc/letsencrypt/renewal-hooks目录下有三个子目录prepostdeploy,分别对应上面的三种钩子。

自动化更新

Certbot的设计就是用来自动化的,根据获得软件包的方式,你的Certbot可能已经带有自动化的脚本。

遗憾的是,Arch Linux并没有打包自动化脚本,需要自己创建。

自动化有两套方案,CrontabSystemd Timer。Crontab很简单,不啰嗦,我只说Systemd Timer。

先创建/etc/systemd/system/certbot.service

[Unit]
Description=Let's Encrypt renewal

[Service]
Type=oneshot
ExecStart=/usr/bin/certbot renew --quiet --agree-tos
复制代码

再创建/etc/systemd/system/certbot.timer

[Unit]
Description=Automatic renewal of Let's Encrypt's certificates

[Timer]
OnCalendar=03:00:00
RandomizedDelaySec=1h
Persistent=true

[Install]
WantedBy=timers.target
复制代码

启用自动更新:

sudo systemctl enable --now certbot.timer
复制代码
分类:
后端
标签:
收藏成功!
已添加到「」, 点击更改