4. DNS-01 挑战详解:DNS-01 原理、手动 DNS 配置、自动 DNS API 集成(以阿里云 DNS 为例)
聊完了 HTTP-01 和 TLS-ALPN-01,咱们来啃最硬核的 DNS-01 挑战。说实话,这是三种验证方式里我最偏爱的一个。为什么?因为它能签发通配符证书,而且完全不需要暴露 80 或 443 端口。我在生产环境里部署边缘网关时,几乎清一色都用它。
4.1 DNS-01 挑战原理
说白了,DNS-01 就是让 CA 去检查你的域名 DNS 记录里,有没有一条特定的 TXT 记录。这条记录的内容是 CA 给你的一个验证令牌。你需要在你的 DNS 服务商那里手动或自动添加这条记录。
流程其实很简单:
- 你向 CA(比如 Let's Encrypt)申请证书。
- CA 给你一个随机字符串,也就是验证令牌。
- 你把这个令牌以 TXT 记录的形式,添加到你的域名下。记录名通常是
_acme-challenge.yourdomain.com。 - CA 去查询这个 TXT 记录。如果匹配,就证明你拥有这个域名的控制权。
- CA 签发证书。
核心优势: 不需要监听任何端口,也不需要停止正在运行的服务。这对边缘网关这种 7x24 小时在线的设备来说,简直是福音。
我刚开始接触时,总觉得这步很玄乎。后来自己手动配了一次,才彻底搞明白。嗯,这里要注意:DNS 记录生效有延迟。TTL 设置得越长,等待时间就越久。我一般会把 TTL 设成 60 秒,既保证稳定性,又不会等太久。
4.2 手动 DNS 配置
手动配置适合测试环境,或者你只有一两个域名要管理。你想想看,如果网关数量少,手动配一下也不是不行。但要是你有几十上百个网关,手动配能把你累死。
手动配置的步骤大致如下:
- 登录你的 DNS 服务商控制台。
- 找到你要验证的域名,添加一条 TXT 记录。
- 记录名填
_acme-challenge,记录值填 CA 给你的令牌。 - TTL 建议设成 60 秒。
- 等待 DNS 生效,然后执行验证。
我在项目中遇到过一个问题:DNS 记录生效后,CA 还是验证失败。排查了半天,发现是 DNS 服务商默认的 TTL 是 600 秒,我改完记录后等了 10 分钟才生效。所以,手动配置时,一定要确认 TTL 值。
小技巧: 可以用 dig _acme-challenge.yourdomain.com TXT 命令来检查记录是否生效。如果返回了你添加的令牌值,就说明可以开始验证了。
4.3 自动 DNS API 集成
手动配置太慢,而且容易出错。自动集成才是生产环境的王道。以阿里云 DNS 为例,我给大家拆解一下怎么用 API 自动添加和删除 TXT 记录。
自动集成的核心思路是:
- 获取 DNS 服务商的 API 密钥。
- 在申请证书时,调用 API 自动添加 TXT 记录。
- 验证通过后,调用 API 自动删除 TXT 记录。
- 整个过程脚本化,完全无人值守。
4.3.1 阿里云 DNS API 集成步骤
阿里云提供了丰富的 DNS API。我习惯用 AliyunDNS 这个 Python 库,当然你也可以直接用 HTTP 请求调用。下面是我常用的一个脚本片段:
import json
import requests
# 阿里云 API 配置
access_key_id = "你的 AccessKey ID"
access_key_secret = "你的 AccessKey Secret"
domain_name = "yourdomain.com"
rr = "_acme-challenge"
record_value = "CA 给你的令牌"
# 添加 TXT 记录
def add_txt_record():
url = "https://dns.aliyuncs.com/"
params = {
"Action": "AddDomainRecord",
"DomainName": domain_name,
"RR": rr,
"Type": "TXT",
"Value": record_value,
"TTL": 60,
"Format": "json",
"AccessKeyId": access_key_id,
"SignatureMethod": "HMAC-SHA1",
"SignatureVersion": "1.0",
"Timestamp": "2025-01-01T00:00:00Z",
"SignatureNonce": "123456"
}
# 实际使用时需要计算签名
response = requests.get(url, params=params)
return response.json()
# 删除 TXT 记录
def delete_txt_record(record_id):
url = "https://dns.aliyuncs.com/"
params = {
"Action": "DeleteDomainRecord",
"RecordId": record_id,
"Format": "json",
"AccessKeyId": access_key_id,
"SignatureMethod": "HMAC-SHA1",
"SignatureVersion": "1.0",
"Timestamp": "2025-01-01T00:00:00Z",
"SignatureNonce": "654321"
}
response = requests.get(url, params=params)
return response.json()
注意,上面的代码里签名部分我简化了。实际生产环境中,你需要用阿里云 SDK 或者自己实现签名算法。我个人建议直接用 aliyun-python-sdk-core 这个库,省心很多。
警告: AccessKey 和 Secret 一定要妥善保管。我曾经见过有人把密钥硬编码在脚本里,结果不小心上传到 GitHub,整个 DNS 记录被人删光了。建议用环境变量或密钥管理服务来存储。
4.3.2 自动续签流程
自动续签的完整流程,我画了一张图,方便大家理解:
你看,整个流程非常清晰。从开始到结束,完全不需要人工干预。我部署的网关集群,现在都是靠这套流程自动续签,从来没出过问题。
4.4 常见问题与避坑
自动集成虽然方便,但坑也不少。我把自己踩过的坑整理了一下:
| 问题 | 原因 | 解决方案 |
|---|---|---|
| API 调用失败 | AccessKey 权限不足 | 检查 RAM 权限,确保有 DNS 管理权限 |
| 验证超时 | DNS 生效延迟 | 降低 TTL,或增加等待时间 |
| 记录残留 | 删除记录失败 | 添加重试机制,确保记录被清理 |
| 签名错误 | 时间戳或签名算法不对 | 使用 SDK 自动处理签名 |
避坑指南: 我曾经因为忘记删除 TXT 记录,导致下次续签时 CA 检测到多条记录,直接拒绝验证。后来我在脚本里加了强制删除逻辑,不管验证成功还是失败,都先删掉旧记录再添加新记录。这个习惯我一直保留到现在。