外观
12 - 安全与访问控制
概述
TDNS 内置多层安全防护机制,覆盖访问控制、速率限制、缓存投毒防护、传输安全和 API 认证。大部分防护默认生效,无需配置。
ACL 访问控制
快速上手
toml
# 定义命名 ACL 组
[acl.trusted]
addresses = ["10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"]
[acl.management]
addresses = ["127.0.0.1", "::1"]
# 绑定到访问控制规则
[access-control]
allow-query = ["trusted", "management"] # 谁可以查询
allow-recursion = ["trusted"] # 谁可以递归(必填,启用递归时)
allow-transfer = ["management"] # 谁可以区域传输内置关键字
| 关键字 | 含义 | 匹配范围 |
|---|---|---|
any | 匹配所有地址 | 0.0.0.0/0 + ::/0 |
none | 匹配空(拒绝所有) | 无匹配规则 |
localhost | 本机地址 | 127.0.0.0/8 + ::1/128 |
内联 CIDR
规则中可直接使用 CIDR 或 IP 地址,无需定义 ACL 组:
toml
[access-control]
allow-query = ["any"] # 全部允许
allow-recursion = ["10.0.0.0/8", "::1/128"] # 内联 CIDR
allow-transfer = ["none"] # 全部禁止CIDR 匹配
| 格式 | 示例 | 说明 |
|---|---|---|
| IPv4 CIDR | 10.0.0.0/8 | 匹配 10.0.0.0 ~ 10.255.255.255 |
| IPv4 主机 | 10.0.0.1 | 等同 /32 |
| IPv6 CIDR | 2001:db8::/32 | 匹配 2001:db8::/32 |
| IPv6 主机 | ::1 | 等同 /128 |
IPv4 与 IPv6 不交叉匹配:IPv4 规则不匹配 IPv6 地址,反之亦然。
空规则行为
| 规则为空 | 行为 |
|---|---|
allow-query = [] | 允许所有查询(无限制) |
allow-recursion = [] | 允许所有递归(危险!) |
allow-transfer = [] | 允许所有传输 |
启用递归时强制要求配置
allow-recursion,否则拒绝启动(防开放递归)。
查询处理中的 ACL 检查
查询到达
│
├─ allow-query 检查 → 不通过 → REFUSED
│
├─ 权威区域命中 → 返回(不需要递归 ACL)
│
├─ allow-recursion 检查 → 不通过 → REFUSED
│
└─ 执行递归/转发速率限制 (RRL)
工作原理
RRL(Response Rate Limiting)基于令牌桶算法,限制相同响应(按客户端网络前缀 + 查询名分组)的发送速率,缓解 DNS 放大攻击。
遵循 BIND RRL 设计:客户端按 /24(IPv4)或 /64(IPv6)分组,超限后丢弃大部分响应,仅每 slip 次放行一个截断响应(TC=1),引导合法客户端切换到 TCP 重试。
配置
toml
[security.rrl]
enabled = true # 总开关(默认启用)
responses-per-second = 10 # 每秒最大相同响应数
slip = 2 # 每 2 次超限放行 1 次
window = 5 # 空闲桶存活秒数令牌桶行为
| 状态 | 行为 |
|---|---|
| 令牌 ≥ 1 | 消费 1 个令牌,允许响应(Allow) |
| 令牌 < 1 且 slip > 0 | 每 slip 次放行 1 个截断响应(Slip),其余丢弃(Drop) |
| 令牌 < 1 且 slip = 0 | 全部丢弃(Drop) |
令牌按 responses-per-second 速率恢复,最大容量为 1 秒的令牌数(允许短时突发)。
分组键
键 = (客户端网络前缀, 查询名)- IPv4 客户端 → /24 前缀(前 3 个八位组)
- IPv6 客户端 → /64 前缀(前 8 个八位组)
- 查询名 → 小写归一化,补全 FQDN
相同 /24 或 /64 内的客户端共享令牌桶,防止单 IP 切换绕过限制。
RRL 决策类型
| 决策 | 调用方行为 |
|---|---|
Allow | 正常发送完整响应 |
Slip | 发送截断响应(TC=1),引导客户端 TCP 重试 |
Drop | 静默丢弃响应 |
监控 RRL
bash
# 查看 RRL 丢弃统计
curl -s http://localhost:9090/metrics | grep rrl
# 指标
# tdns_rrl_dropped_total{reason="rate_limited"} 500
# tdns_rrl_dropped_total{reason="slipped"} 50缓存投毒防护
TDNS 对递归和转发返回的上游响应实施多重验证,默认生效,无需配置:
| 验证项 | 机制 | 失败处理 |
|---|---|---|
| TXID 匹配 | 响应 TXID 必须与查询 TXID 一致 | 丢弃,标记 SpoofedResponse |
| 源 IP 匹配 | 响应源 IP 必须与上游 IP 一致 | 丢弃,标记 SpoofedResponse |
| 源端口匹配 | 响应源端口必须与上游端口一致 | 丢弃,标记 SpoofedResponse |
| 响应方向 | 必须是响应消息(QR=1),非查询 | 忽略 |
三项验证任一不通过,响应被丢弃并标记为欺骗响应(SpoofedResponse),自动尝试下一个上游。
查询安全验证
以下检查默认生效:
| 检查项 | 行为 | RFC 参考 |
|---|---|---|
| Opcode 验证 | 仅接受 QUERY (0),其他返回 NOTIMP | RFC 6895 |
| ANY 查询拒绝 | 返回 HINFO 替代 ANY | RFC 8482 |
| CNAME 循环检测 | HashSet 跟踪已访问名称 | — |
| 查询深度限制 | CNAME 链最大追踪深度可配置(默认 16) | — |
| 负缓存容量限制 | 最大 50,000 条目 | — |
| 正缓存容量保护 | 插入时二次容量检查 | — |
API 安全
Bearer Token 认证
toml
[api]
enabled = true
listen = "127.0.0.1:8080" # 单个 IP:Port 字符串
auth-token = "your-secure-random-token" # 必填所有 API 请求必须在 Authorization 头中携带 Bearer Token:
bash
curl -H "Authorization: Bearer your-secure-random-token" \
http://127.0.0.1:8080/api/v1/server未携带或 Token 不匹配的请求返回 401 Unauthorized。
生成强 Token
bash
# 生成 256 位随机 Token
openssl rand -hex 32CORS 安全
REST API 启用 CORS(跨域资源共享)层,默认禁止跨域。需要浏览器前端调用时配置具体来源:
toml
[api]
enabled = true
listen = "127.0.0.1:8080"
auth-token = "your-token"
cors-origins = ["https://admin.example.com"]API 绑定地址
建议将 API 监听地址限制为本机或内网:
toml
[api]
listen = "127.0.0.1:8080" # 仅本机访问(推荐)
# listen = "10.0.0.1:8080" # 内网管理口
# listen = "0.0.0.0:8080" # 不推荐!需配合防火墙TSIG 认证
TSIG(Transaction SIGnature)用于动态更新(DDNS)和区域传输的认证。
1. 生成密钥
bash
# 使用 tsig-keygen(BIND 工具)
tsig-keygen -a HMAC-SHA256 update-key
# 或手动生成
echo -n "your-secret" | base642. 配置
toml
[[keys]]
name = "update-key."
algorithm = "hmac-sha256"
secret = "<base64-encoded-secret>"
[[zones]]
name = "example.com"
file = "/etc/tdns/zones/example.com.zone"
allow-update = ["update-key."] # 允许此密钥执行 DDNS
allow-transfer = ["management"] # 区域传输 ACL3. 使用 nsupdate 发送动态更新
bash
nsupdate -k Kupdate-key.+157+12345.key << EOF
server 127.0.0.1 53
zone example.com
update add www.example.com 300 A 192.0.2.100
show
send
EOF4. 监控 TSIG
bash
curl -s http://localhost:9090/metrics | grep tsig
# tdns_tsig_results_total{result="valid"} # 验证成功
# tdns_tsig_results_total{result="invalid"} # 验证失败TSIG + DDNS 完整使用流程见 07-权威DNS服务。
安全配置检查清单
部署前逐项检查:
- [ ]
allow-recursion已限制为内网地址(非空、非any) - [ ]
allow-query已根据需求限制范围 - [ ]
allow-transfer已限制为管理网段 - [ ] RRL 已启用(
security.rrl.enabled = true,默认启用) - [ ] API
auth-token已设置且足够复杂 - [ ] API
listen非0.0.0.0(除非有防火墙保护) - [ ] DoT/DoH 证书有效且定期续期
- [ ] 缓存投毒防护已生效(默认启用,无需配置)
- [ ] 负缓存容量限制已设置(
max-negative-entries) - [ ] 服务以非 root 用户运行(特权端口通过 capabilities 授权)