快乐的春饼

使用阿里云 SDK 自动更新 DNS 记录

背景

HTTPS 启动指南中,我提到了 ACME 协议的 DNS-01 验证方式,因为各家 DNS 服务商提供的 API 不同,所以没有通用的方式做自动 DNS-01 验证。但好消息是,使用 Certbot 的验证钩子和清理钩子,可以编写自动化证书验证和清理脚本。本站使用了阿里云的免费版 ESA(原 DCDN),通过 NS 方式接入,我需要查找阿里云和 ESA 的相关文档,来编写验证和清理脚本。

安全第一

想要通过 API 请求修改 DNS 记录,就必须在请求中加上身份证明,通常被称作 API 凭据。如果要长期通过 DNS-01 方式自动续签 TLS 证书,就需要在服务器上长期存储 API 凭据,用以修改 DNS 记录。这可能存在安全隐患。假设 API 凭据的权限过大,能任意增删改查 DNS 记录,一旦服务器被攻击导致 API 凭据泄露,会造成十分危险的后果,比如恶意攻击者可以将你的主域名解析到它们的服务器上,然后申请一张完全合法的证书,就可以以你的名义做他们想做的事。

Let’s Encrypt 和 Certbot 的文档都提到了在服务器上存储 API 凭据的危险性。原因是,部分 DNS 商提供的权限粒度过粗,导致用户没办法细致地只授予 DNS-01 验证所需的权限。如果 DNS 商提供的 API 可以做到细粒度的授权,我们就应该用最小权限授权,否则就是主动开放后门。

ESA

阿里云的权限设置还是比较精细的。对于 DNS-01 验证来说,我们只需要一个 API:UpdateRecord。它只能通过 RecordId 来修改记录内容。给 API 凭据限制权限,让它只能使用 UpdateRecord,然后手动获取 DNS-01 验证所需的 RecordId(可以通过 API 调试功能调用 ListRecords),填入续签脚本中。就算有恶意攻击者拿到了 API 凭据,也只能用来修改这些指定的以 _acme-challenge 开头的记录。当然,攻击者能据此向 Let’s Encrypt 证明他控制了该子域名,从而为该子域名申请证书,并且私钥在他手中。不过,他仍没办法把该子域名的 DNS 解析到其他地方,他申请的证书实际上使用不了。

在验证脚本中,我们可以把记录的值更新为 Certbot 验证码。等待一段时间,确保 DNS 记录生效后,在清理脚本中,我们可以把记录的值更新为 0 或其他不重要的值(TXT 记录不能为空字符串)。

我之前想到的办法是使用两个 API,一个是 CreateRecord,另一个是 DeleteRecord。验证脚本中先创建记录,清理脚本中再删除。DeleteRecord 也只能通过 RecordId 来删除记录,没有列举记录的权限时,其实只能拿到 CreateRecord 后返回的 RecordId,并删除对应记录。虽然这种方式也不能修改现有记录,但是恶意攻击者拿到 API 凭据后可以添加很多垃圾记录或申请很多证书,不确定的风险更多。

我的实际情况

我的 ESA 在账号 A 上,ECS 在账号 B 上。阿里云可以跨账号授权。

首先在账号 A 上创建一个 RAM 角色,信任主体类型为云账号,信任主体名称设置为其他云账号,也就是账号 B。为该 RAM 角色新增一个自定义权限策略,该策略只包含 UpdateRecord。然后给账号 B 也添加一个 RAM 角色,信任主体类型为云服务,然后指定 ECS,这样确保指定的 ECS 才能使用该角色,进一步降低了 API 凭据泄露的风险。给账号 B 的 RAM 角色新增授权,策略为 AliyunSTSAssumeRoleAccess,这意味着账号 B 的 RAM 角色可以模拟账号 A 的 RAM 角色,从而调用账号 A 的 UpdateRecord。

脚本编写

Certbot 给脚本提供了一些环境变量,其中必须用到的是CERTBOT_DOMAINCERTBOT_VALIDATION,分别是要验证的域名和 TXT 记录的值。这个 CERTBOT_DOMAIN是 Certbot 参数中的原始域名,不包含_acme-challenge前缀。我们需要自行判断和添加。对于清理脚本,Certbot 还提供了一个CERTBOT_AUTH_OUTPUT环境变量,内容是验证脚本的 STDOUT 输出。

相关脚本在这里

使用

可以像这样给www.example.com申请证书:

sudo certbot certonly \
	--preferred-challenges=dns --manual \
	--manual-auth-hook /opt/aliyun/acme_authenticator.py \
	--manual-cleanup-hook /opt/aliyun/acme_cleanup.py \
	--deploy-hook acme_deploy.sh \
	-d www.nispring.cn

也可以像这样给*.example.com更换验证方式:

sudo certbot renew \
	--cert-name example.com \
	--preferred-challenges=dns --manual \
	--manual-auth-hook /opt/aliyun/acme_authenticator.py \
	--manual-cleanup-hook /opt/aliyun/acme_cleanup.py \
	--deploy-hook acme_deploy.sh \
	--force-renewal

总结

虽然 DNS-01 不太通用,但是 Certbot 提供了手动验证方式,以及钩子和相关环境变量,这使得我们能自行编写任意逻辑。Git 也有类似的钩子,在执行了某个操作之后可以运行一段可执行程序。

阿里云的 API 文档很详细,虽然也有一些美中不足的地方,碰到了一些问题,稍稍地绕了些弯路,但无伤大雅。

阿里云集成的 AI 助理很不错,在碰到问题的时候,询问它比询问那些通用的 AI 能得到更具体的解答,而且回答的时候有文档链接,确保回答内容不太容易胡编乱造。

阿里云的 API 调试 功能十分强大。有些参数在浏览器界面里设置不了,必须通过 API 才能设置,使用 API 调试功能,就可以免去手写一些账号验证的代码。最让我惊讶的是,它们的 SDK 都是通过 Darabonba 自动生成的,定义统一的 API,然后就可以生成不同语言的 SDK,还有代码示例。代码示例尤其有用,省去了很多查询文档的时间。非常值得学习。

参考