跳至内容

Jixun's Blog 填坑还是开坑,这是个好问题。

内网配置免费泛域名证书

在 PVE 配置了一个独有的网络,然后全部通过一个网关 VM 暴露到局域网。

为了防止浏览器在访问时告警,遂配置免费的泛域名(通配符域名)证书。

证书自动签发服务(ACME)#

证书自动签发服务(ACME)(Automatic Certificate Management Environment)协议是这一切的起源。免费的证书提供商如 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

你需要准备两件事:

  1. 申请 API 密钥
  2. 访问域名管理页面,允许 API Key 操作该域:

    Porkbun 域名管理页面启用 API 访问权限

得到两串字符串后,配置环境:

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 验证拒绝才报错。


  1. 即便如此,如果用户所在的设备被攻击也不能保证这个连接无法被第三方截取,例如在用户设备上信任了根证书的中间人流量拦截软件。 ↩︎

  2. 官方文档注明兼容 dash、bash 以及 unix shell;如果系统有 bash 会优先使用它。 ↩︎

  3. 官方文档。几乎所有可选的「操作系统选项」说明都想让你安装「Snap」。 ↩︎

知识共享许可协议 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。

评论区