
TLDR:修改内部BIND9 DNS服务器IP地址后cert-manager续签证书时仍旧请求老的IP地址的问题排查以及对Homelab本地DNS服务器设计的思考。
问题现象
某天发现运行在k8s集群内站点的证书有效期告警了,检查cert-manager没有成功续签的原因,还以为是最近轮换了Cloudflare Api Token的原因,直到发现Challenge提示Waiting for DNS-01 challenge propagation: dial tcp 10.1.0.248:53: connect: connection refused。
这引发了两个有趣的疑问
- 为什么我修改了本地DNS服务器的IP并且更新了k8s节点的dns服务器配置
- 为什么acme会去本地的DNS服务器而不是公共权威DNS验证记录
处理经过
如拓补图所示,首先来看pod,默认使用的是集群的coredns。
root@nginx-red-76dc44b459-24p74:/# cat /etc/resolv.conf
search tenant-red.svc.cluster.local svc.cluster.local cluster.local hmhomelab
nameserver 10.96.0.10
options ndots:5
而coredns的默认配置使用节点自身操作系统的/etc/resolv.conf中的DNS服务器作为forwarder转发DNS请求,在ubuntu下使用netplan管理,这里已经配置了DNS服务器新的IP地址。
.:53 {
errors
health {
lameduck 5s
}
ready
kubernetes cluster.local in-addr.arpa ip6.arpa {
pods insecure
fallthrough in-addr.arpa ip6.arpa
ttl 30
}
prometheus :9153
forward . /etc/resolv.conf {
max_concurrent 1000
}
cache 30
loop
reload
loadbalance
}
一筹莫展,偶然间在cert-manager的pod log中看到了ns.hmhomelab.top相关字眼,才意识到问题的源头。
家里使用的Bind9作为内网DNS服务器,和dnsmasq和adguard这类DNS代理服务器不同,Bind9通常作为权威DNS服务器配置使用。
root@bind9:/data/bind9/config# cat hmhomelab-top.zone
$ORIGIN .
$TTL 86400 ; 1 day
hmhomelab.top IN SOA ns.hmhomelab.top. info.hmhomelab.top. (
20240719 ; serial
43200 ; refresh (12 hours)
900 ; retry (15 minutes)
1814400 ; expire (3 weeks)
7200 ; minimum (2 hours)
)
NS ns.hmhomelab.top.
$ORIGIN hmhomelab.top.
$TTL 300 ; 5 minutes
hostxxx A 10.1.x.x
hostxxx CNAME hostnamexxx.xxx.xxx
在bind9管理的hmhomelab.top zone的根记录中,告诉了客户端hmhomelab.top的权威DNS服务器是ns.hmhomelab.top,因此客户端在查询_acme-challenge.host.hmhomelab.top记录时,会向ns.hmhomelab.top查询。而当初在修改bind9服务器ip地址的时候,忘了去更新ns.hmhomelab.top的A记录,怪不得cert-manager的pod仍旧会去尝试请求老的ip地址。
值得一提的是,客户端在向TLD查询hmhomelab.top的nameserver的同时,TLD除了回复hmhomelab.top的的ns记录外同时还会带一个ns.hmhomelab.top的A记录告诉客户端nameserver的IP地址。这种机制称作glue records,用来解决首次发现的问题,客户端继而才可以向ns.hmhomelab.top查询hmhomelab.top.域下的记录。
更新完ns记录,acme又卡在了等待验证_acme-challenge.hmhomelab.top的TXT记录上,检查了cloudflare上hmhomelab.top的公网权威DNS上已经生成了该TXT记录,但是cert-manager日志仍旧显示DNS返回NXDOMAIN。验证后发现是本地的Bind9返回的,因为bind9认为自己已经是权威dns服务器,不会再向上级DNS递归或者转发请求,所以查找不到本Zone的记录就返回了NXDOMAIN。这也是独立部署DNS会遇到的问题,要不选择互相同步记录,要不选择本地DNS将部分记录NS指向公网权威DNS,这里选择了后者,在Bind9上添加了_acme-challenge的NS记录指向公网权威DNS。
至此cert-manager成功向letsencrypt续签了SSL证书。
acme申请证书过程
其实还有一个方法,根据官方文档,可以在cert-manager controller的Deployment extraArgs中增加–dns01-recursive-nameservers-only –dns01-recursive-nameservers=8.8.8.8:53,1.1.1.1:53来使用指定的公共dns服务器来自检TXT记录。更加简单直接。
这是cert-manager使用acme签发letsencrypt证书的过程,
1、声明Certificate之后新申请或者续签证书,cert-manager会先创建。 2、根据CertificateRequest创建Order资源并且生成对应Challenge资源,产生DNS-01验证。 3、根据绑定的issuer通过DNS Provider添加TXT记录。 4、cert-manager首先自检TXT记录。自检不通过则不会通知CA进行challenge,就是这个机制导致了本次续签失败。 5、TXT记录生效后,通知ACME CA进行Challenge 验证。 6、ACME CA验证记录,签发证书。 7、cert-manager获取证书,存储为Secret。 8、清理TXT记录,清理Challenge资源。
Homelab内部DNS选择
顺着这次cert-manager续签的故障重新审视了Homelab内部DNS设计方案。
为什么是Bind9
从最初的pihole,smartdns到bind9,至于为什么选择bind9作为主dns,当初只是为了解决前者添加主机记录不便的痛点,bind9可以方便地通过RFC2136标准动态更新DNS记录,同样支持的还有Windows DNS、PowerDNS等。
其次可以部署Secondary DNS来同步Primary DNS。只要是支持AXFR(RFC 5936)/IXFR(RFC 1995)的DNS服务器,都可以异构从Primary DNS同步数据。不像nebula-sync通过api方式来同步pihole实现dns高可用必须都是pihole实例。
除此之外还能学习一下标准的DNS服务器,不像dnsmasq、adguard、pihole或者路由器自带的dns这类DNS代理服务器,递归服务器完整实现了域名分层管理,递归查询的功能。而代理DNS只是转发DNS请求并且缓存记录,对于本机存在的主机记录会模仿权威DNS的行为给出权威应答,对于本机查找不到的记录,统一向forwarder代理查询。至于bind9,可以只递归或者只代理,如果都启用的话会优先代理其次递归查询。
其实对于家庭网络DNS,主要的角色还是负责客户端的域名解析请求,如果没有内部dns解析需求,大可以直接公共DNS服务器。针对少量的内部A、AAAA、CNAME记录需求,使用代理DNS服务器也能满足,配置更加简洁。反倒是Bind9配置成权威DNS服务器的话,还要处理和公网权威DNS记录同步的事情,不然就会遇到找不到记录也不代理查询的问题。
DNS污染和DNS安全
为了避免中间人攻击篡改记录值,DNSSEC使用非对称加密确保数据的完整性。递归DNS向权威DNS获取记录时使用公钥验证签名确保未被篡改,听起来是不是就解决了DNS污染了?事实上基于某些原因大部分根DNS和权威DNS都是无法直接访问到的,因此只能在访问得到的地方部署一个forwarder通过DOH/DOT代理请求。
此前使用smartdns配置DoT上游进行查询,从Bind9 v9.19.12开始,forwarder便提供了对DoH/DoT上游的支持。
另外,部分CDN厂商使用GeoDNS根据客户端的DNS请求源IP位置响应不同的记录值,为了避免forwarder和客户端地域不一致导致CDN Edge南辕北辙,部分递归DNS支持EDNS Client Subnet,允许携带客户端IP subnet进行DNS查询,从而更准确地返回结果。
分流和加速
由于Bind9不支持基于目标域名进行forwarder分流,因此选择在Bind9之上套了一层smartdns来做分流、加速、缓存。传统的DNS服务器在forwarder列表中是依次查询的,只有当上一个无法响应的时候才会请求下一个,而smartdns支持并发查询甚至预查询,能够提升查询速度。