ACME Server 实践之 ACME DNS

前言⌗
在 《Traefik 结合 Step-CA 实现自动 HTTPS》 一文中,我们已经实现了基于 tlsChallenge
和 httpChallenge
的证书申请方式。但是这两种方式都无法支持通配证书的申请。
要申请通配证书,必须通过 dnsChallenge 来实现,而 dnsChallenge 的大致流程如下:
也就是说,这里面最核心的就是 DNS API,通过 DNS API 向 DNS 中添加 TXT 记录,该记录的值由 CA 生成,返回给 ACME 客户端,由客户端通过 API 的形式写入 DNS,然后 CA 再通过 DNS 查询去验证这个域名是否属于申请者!
ACME DNS⌗
通常云 DNS 服务商都有提供对应的 ACME 支持,但是部分传统的 DNS 服务商可能不支持 ACME 协议,所以就有了 ACME DNS!有兴趣的可以阅读一下这篇文章 A Technical Deep Dive: Securing the Automation of ACME DNS Challenge Validation。
项目仓库:joohoi/acme-dns
ACME DNS 的配置如下:
- 45 行: 我为 ACME DNS 的 API 也开启了 HTTPS,因为他本身也集成了支持 ACME 协议的客户端。
如果 ACME DNS API 开启 HTTPS 需要注意的问题:
ACME DNS 仅支持
letsencrypt
和letsencryptstaging
,这两个 API 的 Endpoint,是硬编码到程序中的,所以如果要使用本地的 Step-CA 来替换letsencrypt
或letsencryptstaging
,还需要在运行容器时做一些设置!并且因为 Step-CA 的 HTTPS 是不被信任的,所以需要在容器中添加 Step-CA 的根证书,具体方法可以参考《在本地 Docker 环境中信任自签名 CA 证书》 这篇文章。在 Step-CA 的容器的环境变量
DOCKER_STEPCA_INIT_DNS_NAMES
中添加上letsencrypt
和letsencryptstaging
的域名,这样当 ACME DNS 将请求发送到 Step-CA 时,才能正常通信!
下面是我按照官方的 Self-hosted 文档编写的 docker-compose.yaml
:
- 16~18 行:强行将
letsencrypt
和letsencryptstaging
的域名指向到内网的 Step-CA 容器 IP,这一步很重要!
ACME 服务启动后,查看 API 服务是否从 Step-CA 获取到了证书,如果一切正常,则通过下面的方法测试 API 是否正常:
API 正常后,我们还需要配置 Traefik 的 docker-compose.yaml
文件:
- 14 行:使用
acme-dns
作为 dnsChallenge 的 Provider - 27~28 行:配置
acme-dns
所需的环境变量
完成上述配置后,重新创建 Traefik 容器,不出意外的话,会在 certs
目录下创建 lego-acme-dns-accounts.json
文件,结构如下:
因为我们想为 *.svc.dev
申请通配证书,这时候需要我们在 ACME DNS 的配置文件的 general.records
数组中配置一个 _acme-challenge.svc.dev
的 CNAME 记录:
然后重启 ACME DNS,等待 Traefik 和 Step-CA 执行整个 dnsChallenge 流程……
原理分析⌗
ACME DNS 这种方案,本身就属于是曲线救国,其关键就在于 CNAME,因为权威服务器不支持 DNS API,所以只能把 _acme_challenge.tld.
的查询请求通过 CNAME 委托给 ACME DNS 来处理和响应查询。其大致流程如下:
- Traefik 根据路由中定义的 FQDN,检查
certs/acme.json
中是否存在该 FQDN 的证书 - 如果证书不存在,则检测
certs/lego-acme-dns-accounts.json
文件中是否存在该 FQDN 的账户 - 如果不存在 ACME DNS 的 Account,则调用
/register
API 创建,然后将结果写入certs/lego-acme-dns-accounts.json
- 向 Step-CA 发送申请,获得用于验证的 Token 后,通过 ACME DNS 的
/update
API 创建一条008a8c8a-d5a8-4ea6-964e-651f09220763.dns.svc.dev
的 TXT 记录用于验证 - Step-CA 向 DNS 发起查询请求,但是因为
008a8c8a-d5a8-4ea6-964e-651f09220763.dns.svc.dev
对于 CA 来说是无感的,他只会去验证查询_acme-challenge.svc.dev
这个 FQDN 是否有对应的 TXT 记录
所以在第 5 步之前,我们要手动在 ACME DNS 中添加 CNAME 记录,这也是让我感觉比较割裂的地方,如果我需要申请多个二级域名的 FQDN 通配证书,那么每个都需要我手动添加。
意外情况⌗
不出意外的话是要出意外了,虽然是前人走过的路,但是还是有一个大坑,至于是什么问题,可以看我提的 Issue,截止我发文,还为收到作者的解答!
那么接下来只能靠自己了……
CDNS 的诞生⌗
ACME DNS 看起来很美好,但是实际体验下来后,与我的预期相差甚远!我阅读源码后,修改了部分代码,让 Step-CA 能够正常验证 TXT,但这依然无法实现完全的自动签发证书!
其原因就是前面说到的,每个 FQDN 都需要添加一条 CNAME 解析记录,而这个没办法自动化来完成!于是乎我就诞生了一个想法,那就是自己开发一个专门用于内网 dnsChallenge
的 DNS。
项目地址:betterde/cdns,为什么要叫 CDNS 呢,因为是解决 dnsChallenge
问题的,所以其中的 C
也就是 Challenge 的简写。
通过这个项目,可以实现某个 TLD 下面的所有通配证书的申请和验证!具体配置和最终效果,我会在下一期文章中分享出来,敬请期待……
总结⌗
ACME 协议看似很完美,但是要想在内网中实现完全的自动化,还是要走很多弯路的。不过作为基础设施,一旦搭建完成后,后面对于开发来说,开发体验将是极其舒适的!至少在我的工作流中是这样,我不用再为每个项目手动生成通配证书,也不用在 Traefik 的配置文件中添加证书了!
I hope this is helpful, Happy hacking…