广告位 · 页头(后台-模块管理-新建模块后替换此区域)

如何做好集群内部传输层加密并管好证书生命周期?

集群传输层加密解决的是服务端到服务端、节点到节点之间的明文通信问题。很多团队只在负载均衡或Ingress入口配置HTTPS,流量一进入集群内部就恢复为HTTP,这种做法无法防御内网嗅探、ARP欺骗或 compromised 容器逃逸后的横向抓包。要真正建立零信任网络,不只要把入口流量加密,还必须让内部组件之间的东西向流量走TLS,并且让每一张证书都有清晰的签发、轮换、吊销路径。

如何做好集群内部传输层加密并管好证书生命周期?

一、集群内部传输层加密的两种模型

集群内部流量加密通常分成单向TLS和双向TLS两类。单向TLS只验证服务端身份,客户端确认自己连接的是合法服务;双向TLS也叫mTLS,客户端和服务端都要出示证书,双方互验身份。对于数据库、消息队列、配置中心等敏感组件,建议直接启用mTLS,因为仅靠网络隔离无法确认调用方是否被伪造。Kubernetes默认的Service到Pod流量并不加密,跨节点访问时数据会经过CNI插件和虚拟网卡,任何有节点网络权限的进程都可能抓到明文请求。

mTLS在集群里还有一个额外收益,就是把证书身份和服务账号绑定。比如服务A的Pod只有持有CN为service-a.internal的证书,才能调用服务B,这比单纯依赖IP白名单或网络策略更稳定。服务网格方案如Istio、Linkerd会把证书自动注入到Sidecar,由控制面完成身份签发和轮换;不走服务网格的集群可以用cert-manager配合内部CA,为每个服务生成独立Secret。无论采用哪种落地方式,证书有效期、私钥存放位置和SAN扩展都必须提前规划。

下面命令演示用OpenSSL建立一个内部CA,并为节点签发一张带SAN的短期证书。SAN用于声明该证书可用于哪些DNS名和IP地址,主机名校验失败多数情况下是SAN缺失或不匹配造成的。

# 生成集群内部私有CA
openssl genrsa -out ca.key 4096
openssl req -x509 -new -nodes -key ca.key -sha256 -days 3650 -subj "/CN=Internal Cluster CA" -out ca.crt
# 生成服务节点证书请求
openssl genrsa -out server.key 2048
openssl req -new -key server.key -subj "/CN=node01.internal" -out server.csr
# 写入SAN扩展,兼容多服务名和IP
printf "subjectAltName=DNS:node01.internal,DNS:localhost,IP:10.0.0.11\n" > server.ext
# 使用内部CA签发30天短期证书
openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt -days 30 -sha256 -extfile server.ext

二、证书生命周期管理的关键阶段

证书生命周期不是简单的到期续签,它至少包含签发、分发、监控、轮换、吊销五个阶段。很多人把注意力放在申请证书上,却忽略私钥分发和吊销列表更新。私钥一旦以明文形式写进镜像或配置文件,证书有效期再短也形同虚设。建议将私钥保存在Kubernetes Secret、Vault或云密钥管理服务中,应用启动时通过挂载或SDK读取,不允许写入版本库。

签发阶段需要确定CA层级。生产环境通常使用离线根CA签发一个在线中间CA,再由中间CA为服务签发短期证书。这种做法能在根CA私钥泄露时只吊销中间CA,而不用重建整个信任链。证书有效期可以按服务类型划分:APIServer和etcd等核心组件可以保持1年,普通服务证书建议缩短到7天到30天。有效期越短,证书泄露后的风险窗口越小,但对自动轮换能力要求越高。

下面是一个cert-manager的Certificate资源示例,它申请24小时有效期的服务证书,并提前8小时触发续签。控制器会把新证书写入指定Secret,应用可以通过挂载更新后的文件实现热加载。

apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: internal-service-cert
  namespace: default
spec:
  secretName: internal-service-tls
  duration: 24h
  renewBefore: 8h
  dnsNames:
    - service-a.default.svc
    - service-a
  issuerRef:
    name: cluster-internal-issuer
    kind: ClusterIssuer
    group: cert-manager.io

吊销阶段比签发更容易被忽略。内部CA一旦发现某张证书对应节点被入侵或服务下线,就应该吊销该证书。常用机制包括维护CRL吊销列表或提供OCSP响应服务。对于集群内部服务,如果每次TLS握手都实时请求OCSP会引入延迟,可以在客户端配置短期缓存或者定期拉取CRL。证书轮换时还要保证旧证书不会立即失效,通常新旧证书有一小段重叠期,以兼容正在进行的连接。

三、短期证书与自动轮换的落地实践

短期证书策略的收益很明显:即使证书泄露,攻击者能利用的时间窗口也很短。但它对系统时间同步要求极高。节点如果NTP失效导致时钟偏差超过证书有效期的20%,TLS握手就可能直接失败。落地短期证书之前,需要先确认所有节点、容器和中间CA的时钟源一致。Chrony或systemd-timesyncd等工具应该被纳入基础设施监控,而不仅仅是可选的优化项。

自动轮换不能只停留在控制器层面,应用也要配合。Java、Go、Nginx等运行时有的会在启动时一次性读取证书,并不会自动感知文件变化。常见做法是让Sidecar或initContainer负责轮换,然后向应用发送SIGHUP信号,或者使用支持动态证书加载的组件。例如Nginx在证书文件更新后执行nginx -s reload即可重新加载;一些微服务框架可以直接监听Secret挂载目录的文件变化。轮换失败要能告警,不能等到证书过期才被发现。

可以用下面的脚本批量检查Kubernetes证书目录中的到期时间,适合在节点上定期执行。生产环境更推荐通过Prometheus的cert-exporter或黑盒监控采集所有证书到期天数,并在到期前30天、7天、24小时分级告警。

# 查看单张证书到期时间
openssl x509 -enddate -noout -in /etc/kubernetes/pki/apiserver.crt
# 批量输出所有证书的有效期与主体
for cert in /etc/kubernetes/pki/*.crt; do
    echo "$cert"
    openssl x509 -enddate -noout -in "$cert"
done

另外,轮换策略要与部署策略协同。如果证书在Pod启动后才签发,而Pod启动前就需要建立TLS连接,就会出现先有鸡还是先有蛋的问题。推荐使用启动时等待证书文件出现的initContainer,或者让应用在证书缺失时进入重试循环。对于有状态服务,轮换时还要注意长连接不会无限期使用旧证书,服务端可以设置连接最大生命周期,强制客户端定期重新握手。

四、常见证书配置错误与排查方法

集群里证书问题最常见的表现是服务间突然出现connection refused、certificate verify failed或handshake timeout。第一个要查的是证书是否过期。可以用openssl x509 -enddate直接查看,但注意容器内的时间和宿主机可能不一致,过期判断要以证书本身的notAfter字段为准,而不是容器当前时间。时钟偏差也可能把一张尚未过期的证书判断为已经过期。

第二个常见错误是SAN缺失或主机名不匹配。客户端连接时使用的是内部DNS名,但证书只为IP签发了SAN。排查时可以用下面的openssl s_client命令连接目标端口,输出证书主体和SAN扩展。如果服务端返回的证书链不完整,客户端会报unable to verify the first certificate,此时需要确认服务端是否把中间CA证书也一并下发。

# 验证服务端证书链是否完整
openssl s_client -connect 10.0.0.11:8443 -CAfile ca.crt -verify_return_error
# 只输出证书主体、有效期和SAN扩展
echo | openssl s_client -connect 10.0.0.11:8443 2>/dev/null | openssl x509 -noout -subject -dates -ext subjectAltName

第三个是CA信任范围过大。有的团队把内部CA证书挂载到所有Pod,任何持证服务都能调用核心接口。正确做法是给不同服务签发不同中间CA,并在客户端限定可接受的CA集合。证书生命周期管理最终要落到可观测性上,否则短期证书带来的运维负担会超过安全收益。把签发、轮换、吊销和监控串成一条自动化流水线,集群传输层加密才能从一次性配置变成可持续运行的基线能力。

广告位 · 文章底部(后台-模块管理替换此区域)