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 服务商那里手动或自动添加这条记录。

流程其实很简单:

  1. 你向 CA(比如 Let's Encrypt)申请证书。
  2. CA 给你一个随机字符串,也就是验证令牌。
  3. 你把这个令牌以 TXT 记录的形式,添加到你的域名下。记录名通常是 _acme-challenge.yourdomain.com。
  4. CA 去查询这个 TXT 记录。如果匹配,就证明你拥有这个域名的控制权。
  5. CA 签发证书。

核心优势: 不需要监听任何端口,也不需要停止正在运行的服务。这对边缘网关这种 7x24 小时在线的设备来说,简直是福音。

我刚开始接触时,总觉得这步很玄乎。后来自己手动配了一次,才彻底搞明白。嗯,这里要注意:DNS 记录生效有延迟。TTL 设置得越长,等待时间就越久。我一般会把 TTL 设成 60 秒,既保证稳定性,又不会等太久。

4.2 手动 DNS 配置

手动配置适合测试环境,或者你只有一两个域名要管理。你想想看,如果网关数量少,手动配一下也不是不行。但要是你有几十上百个网关,手动配能把你累死。

手动配置的步骤大致如下:

  1. 登录你的 DNS 服务商控制台。
  2. 找到你要验证的域名,添加一条 TXT 记录。
  3. 记录名填 _acme-challenge,记录值填 CA 给你的令牌。
  4. TTL 建议设成 60 秒。
  5. 等待 DNS 生效,然后执行验证。

我在项目中遇到过一个问题:DNS 记录生效后,CA 还是验证失败。排查了半天,发现是 DNS 服务商默认的 TTL 是 600 秒,我改完记录后等了 10 分钟才生效。所以,手动配置时,一定要确认 TTL 值。

小技巧: 可以用 dig _acme-challenge.yourdomain.com TXT 命令来检查记录是否生效。如果返回了你添加的令牌值,就说明可以开始验证了。

4.3 自动 DNS API 集成

手动配置太慢,而且容易出错。自动集成才是生产环境的王道。以阿里云 DNS 为例,我给大家拆解一下怎么用 API 自动添加和删除 TXT 记录。

自动集成的核心思路是:

  1. 获取 DNS 服务商的 API 密钥。
  2. 在申请证书时,调用 API 自动添加 TXT 记录。
  3. 验证通过后,调用 API 自动删除 TXT 记录。
  4. 整个过程脚本化,完全无人值守。

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 自动续签流程

自动续签的完整流程,我画了一张图,方便大家理解:

DNS-01 自动续签流程图 开始续签 向 CA 获取验证令牌 调用阿里云 API 添加 TXT 记录 等待 DNS 生效 CA 验证 TXT 记录 调用 API 删除 TXT 记录 续签完成 关键点: • 全程无需开放端口 • 支持通配符证书 • 自动清理记录,避免残留

你看,整个流程非常清晰。从开始到结束,完全不需要人工干预。我部署的网关集群,现在都是靠这套流程自动续签,从来没出过问题。

4.4 常见问题与避坑

自动集成虽然方便,但坑也不少。我把自己踩过的坑整理了一下:

问题 原因 解决方案
API 调用失败 AccessKey 权限不足 检查 RAM 权限,确保有 DNS 管理权限
验证超时 DNS 生效延迟 降低 TTL,或增加等待时间
记录残留 删除记录失败 添加重试机制,确保记录被清理
签名错误 时间戳或签名算法不对 使用 SDK 自动处理签名

避坑指南: 我曾经因为忘记删除 TXT 记录,导致下次续签时 CA 检测到多条记录,直接拒绝验证。后来我在脚本里加了强制删除逻辑,不管验证成功还是失败,都先删掉旧记录再添加新记录。这个习惯我一直保留到现在。