内网配置免费泛域名证书
在 PVE 配置了一个独有的网络,然后全部通过一个网关 VM 暴露到局域网。
为了防止浏览器在访问时告警,遂配置免费的泛域名(通配符域名)证书。
证书自动签发服务(ACME)
证书自动签发服务(ACME)协议是这一切的起源。免费的证书提供商如 Let’s Encrypt (LE) 基于这个协议来自动化签发能有效使用 90 日的免费证书。
※ 与传统证书签发商不同,LE 并不验证也不需要提供网站所有者的个人资料,只关心「你是否控制该域名的机器(HTTP 验证)」或「你是否拥有这个域名(DNS 验证)」。因此启用了 HTTPS 的网站并不代表它是安全的,它只能证明从你的设备到这个网站的连接是加密的1。
ACME 客户端
最轻量的客户端当属 acme.sh 了,只要有 shell 解释器就能跑2。当然,因为其使用的 shell 脚本,很多高阶逻辑都不太好实现… 所以错误信息可能会比较难以理解。
LE 推荐的客户端是来自 EFF 的 Certbot,但是因为其官方文档不管我选什么环境都想让我换用繁琐的 Snap 而让我不再使用也不推荐它3。
安装 acme.sh
笔者所使用的环境为 Rocky Linux 9 + Nginx,其它系统除了依赖的那几个包名之外应该没啥区别。
如果文章有不清楚的地方,请参考官方文档「How to install」。
首先,确保软件需要的基本依赖都有安装到系统:
dnf install cronie curl openssl tar
然后就是软件部分,安装非常简单,将下面这一行的邮箱 [email protected]
提换成你自己的然后执行即可:
curl https://get.acme.sh | sh -s email=[email protected]
默认情况下会安装到你的家目录($HOME/.acme.sh
),如 root 用户会安装到 /root/.acme.sh/
下。
※ 你需要确保你的环境能够正常访问 GitHub 来拉取文件。
安装过程的输出日志
注意,时间戳已被清理。
[00:00] Installing from online archive.
[00:00] Downloading https://github.com/acmesh-official/acme.sh/archive/master.tar.gz
[00:00] Extracting master.tar.gz
[00:00] It is recommended to install socat first.
[00:00] We use socat for standalone server if you use standalone mode.
[00:00] If you don't use standalone mode, just ignore this warning.
[00:00] Installing to /root/.acme.sh
[00:00] Installed to /root/.acme.sh/acme.sh
[00:00] Installing alias to '/root/.bashrc'
[00:00] OK, Close and reopen your terminal to start using acme.sh
[00:00] Installing alias to '/root/.cshrc'
[00:00] Installing alias to '/root/.tcshrc'
[00:00] Installing cron job
no crontab for root
no crontab for root
[00:00] Good, bash is found, so change the shebang to use bash as preferred.
[00:00] OK
[00:00] Install success!
然后重新加载初始化文件来应用 acme.sh
指令别名:
. ~/.bashrc
配置证书提供商
自 acme.sh 3.0 开始,默认使用 ZeroSSL 的免费服务来签发证书。唯一的问题是这家的免费证书不能签泛域名证书,因此需要调整到 Let’s Encrypt。
acme.sh --set-default-ca --server letsencrypt
※ 截止当前时间 2023 年 5 月 28 日,acme.sh 共支持 5 个证书提供商。其中只有 Let’s Encrypt 和谷歌有注明允许建立免费的泛域名证书。
申请证书
你需要根据你域名 DNS 服务商来决定如何让 acme.sh 客户端进行 DNS 认证。
如果你使用的服务商不在本文中,你需要参考对应的百科来寻找说明:第一页、第二页。
※ 注册域名后可以使用第三方 DNS 服务来进行管理。具体信息你需要自行查找。
Cloudflare
应该算是最早提供用于自动化验证的 DNS API 了吧?官网是 cloudflare.com。
申请 API Key/Token 后拿来用就可以了。注意「API Key」是全局的,而「API Token」是需要你手动指定权限范围的。
# 使用「全局 API Key」时的写法
export CF_Email="[email protected]"
export CF_Key="3499c2729730a7f807efb8676a92dcb6f8a3f8f"
# 使用「API Token」时的写法
export CF_Account_ID="fda168d8c2d531358b020323310212e7"
export CF_Token="aml4dW5KaXh1bmpJeHVuamlYdW5qaXhVbmppeHVO"
※ 注意不要同时提供两对令牌。可能会造成未知的错误或后果。
环境变量设定后,就可以申请证书了:
WILD_DOMAIN="home.example.com"
acme.sh --issue -d "$WILD_DOMAIN" -d "*.$WILD_DOMAIN" --dns dns_cf
指定 Zone ID
如果你希望限制 acme.sh 以防它意外访问其它域名(Zone),你也可以手动指定该值。
export CF_Zone_ID="20776c1797822117687715554f77ba60"
Porkbun
一个平价域名注册机构,官网是 porkbun.com。
你需要准备两件事:
得到两串字符串后,配置环境:
export PORKBUN_API_KEY='pk1_your_api_key'
export PORKBUN_SECRET_API_KEY='sk1_your_secret_key'
然后就可以申请证书了:
WILD_DOMAIN="home.example.com"
acme.sh --issue -d "$WILD_DOMAIN" -d "*.$WILD_DOMAIN" --dns dns_porkbun
在 Nginx 使用证书
因为我使用的网关服务软件是 nginx,因此让它在安装证书到 /etc/nginx/certs.d/
目录下,并在续签成功时自动安装并应用新的证书:
acme.sh --install-cert -d "$WILD_DOMAIN" \
--key-file "/etc/nginx/certs.d/wildcard.$WILD_DOMAIN.key" \
--fullchain-file "/etc/nginx/certs.d/wildcard.$WILD_DOMAIN.crt" \
--reloadcmd "/usr/sbin/nginx -t && /usr/sbin/nginx -s reload"
最后则是配置 nginx 规则。我使用 MDN 的 SSL 配置生成器生成了一个范本,然后在此之上稍作修改;其中 my_service.home.jixun.uk
是我最终访问时的域名。
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name my_service.home.jixun.uk;
ssl_certificate /etc/nginx/certs.d/wildcard.home.jixun.uk.crt;
ssl_certificate_key /etc/nginx/certs.d/wildcard.home.jixun.uk.key;
ssl_session_timeout 1d;
ssl_session_cache shared:MozSSL:10m; # about 40000 sessions
ssl_session_tickets off;
ssl_dhparam dhparam;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
location / {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_buffering off;
proxy_pass http://10.20.30.40:5000;
}
}
关于证书续签
acme.sh 在安装时就建立了对应的 cron 任务来自动续签。默认是每日 0 时会进行续签检测,如果需要就自动进行认证。
你可以通过执行 crontab -l
来检查,应当看到类似如下的输出:
[root@gateway ~]# crontab -l
27 0 * * * "/root/.acme.sh"/acme.sh --cron --home "/root/.acme.sh" > /dev/null
这里的 27
是分钟(随机生成),而小时则是固定的 0 时。如果需要更换,执行 crontab -e
进入交互式编辑器更改,然后保存即可。
结语
DNS 插件非常方便,不需要单独暴露 80
端口来验证,适合内网的使用情景。
唯一想吐槽的就是 acme.sh 的错误信息了 - 默认情况下几乎不会输出有用的错误信息,而加上 --debug
后又出来太多信息…
然后就是请求 Porkbun API 时好像并不会验证 HTTP 状态码?出错后还是会尝试继续执行,最后被 Let’s Encrypt 验证拒绝才报错。