内部服务的 TLS 证书:Split-Horizon DNS 与 ACME 实现
内部服务的 TLS 证书:Split-Horizon DNS 与 ACME 实现
内部 TLS 的挑战
为内部服务配置 TLS 通常面临一个二选一的问题:使用自签名证书或使用公共证书。自签名证书可以避免泄露内部网络细节,但需要每个客户端设备手动信任自定义的根 CA,这在管理上非常繁琐。公共证书默认受到信任,但通常要求服务可以通过公共互联网进行验证。
为内部服务实现 Split-Horizon DNS
Split-horizon DNS 允许单个域名根据请求者的位置解析到不同的 IP 地址。对于内部服务,这意味着像 grafana.tuxnet.dev 这样的域名对于外部解析器解析为公共 IP(以满足 CA 验证),而对于通过 VPN 连接的客户端则解析为私有内部 IP。
技术架构
要实现此工作流,需要以下组件:
- 带有 DNS 解析器的 VPN:可以使用像 NetBird 这样的工具来管理 Custom Zones,确保 VPN 连接的客户端接收到内部 IP,而服务器本身使用公共 DNS 以便于证书签发。
- ACME 客户端:可以使用
acme.sh来自动从 Let's Encrypt 或 ZeroSSL 签发证书。 - 反向代理/WAF:Nginx 可以作为入口点,专门绑定到 VPN 网络接口,以确保只有来自 VPN 的流量可以访问内部服务,从而有效地起到 Web Access Firewall (WAF) 的作用。
证书签发与更新
使用 acme.sh 的 standalone 模式配合 http-01 挑战,允许客户端仅在签发过程中绑定到 80 端口,这意味着 Nginx 不需要永久占用 80 端口。
为了实现自动更新,可以配置一个 cron job 来运行 acme.sh --cron,随后将证书同步到 Nginx 目录并重新加载服务。为了避免以 root 身份运行 ACME 客户端,可以对 socat 应用 setcap CAP_NET_BIND_SERVICE=+ep,允许非特权用户绑定到 80 端口。
使用 SANs 和 CNAMEs 进行扩展
与其为每个内部服务签发单独的证书,不如使用 Subject Alternative Names (SANs)。通过创建一个包含多个 DNS 名称(例如 internal.tuxnet.dev、grafana.tuxnet.dev 和 analytics.tuxnet.dev)的单一证书,一个证书文件就可以在多个 Nginx server blocks 中重复使用。
其他方案与权衡
虽然 split-horizon DNS 解决了客户端信任问题,但许多工程师对此持有异议,并建议使用其他方法来避免 "split-brain" DNS 行为和内部主机名的泄露。
DNS-01 验证
许多从业者推荐使用 DNS-01 挑战而非 HTTP-01。DNS-01 验证允许 CA 通过公共 DNS 中的 TXT 记录来验证域名所有权,这意味着内部服务永远不需要能够被公共路由,也不需要关联公共 IP 地址。这完全消除了对 split-horizon DNS 的需求。
通配符证书
通配符证书 (*.domain.com) 常用于避免将特定的内部子域名泄露到 Certificate Transparency (CT) 日志中。虽然有人认为如果私钥泄露,通配符证书会带来更高的安全风险,但也有人认为对于由单个负载均衡器处理所有终端连接的内部基础设施来说,它们更具实用性。
内部 CA 与 mTLS
对于高安全性环境,通常更倾向于使用内部 CA(例如 step-ca)。这可以确保内部主机名永远不会出现在公共 CT 日志中。此外,实现双向 TLS (mTLS) 提供了比标准 TLS 更强的身份保证,因为它要求客户端向服务器出示证书,从而将安全性从网络层 (VPN) 转移到了身份层。
对比总结
| 方法 | 客户端信任 | DNS 复杂度 | 隐私 (CT 日志) | 网络暴露 |
| :--- | :--- |body (Note: The user requested a single JSON object, not an array, but the schema requires an array if multiple JSONs are requested. Since only one translation is requested, I will return a single object.) }