出租窝

内网 DNS 平台(4):内网 HTTPS 突围——证书问题的系统解法

背景

短域和正式域都建好之后,用户的第一批反馈里很快出现一类:浏览器打开 https://api.zhangsan.user.xx,证书报错。

这是内网私有域名的宿命问题:公共 CA 不可能给你签证书。这一篇把这个问题从原理到解法讲清楚——它是个支线话题,但坑的密度超出我预期,值得一篇。

公共 CA 的两道墙

“用 Let’s Encrypt 签一个不就行了?"——不行,而且不行有两层原因,一层比一层硬。

第一层:技术墙。申请证书时 CA 要验证你拥有这个域名。即使是 DNS-01 验证(在 TXT 记录里放 challenge),也要求 CA 从公网能查到这条 TXT。而 xx 是私有 TLD,公网上根本不存在——CA 的解析器查不到,验证无从谈起。

第二层:政策墙,更硬。CA/Browser Forum 从 2015 年起就禁止公共 CA 为 Internal Name(内网名称、私有 TLD、IP 地址)签发证书。这是合规红线,写在全球 CA 的基线要求(Baseline Requirements)里——就算某个 CA 技术上能验证,它也不敢签,签了会被浏览器踢出信任库。

两层墙的结论一致:私有域名的证书,公共 CA 这条路是死的。

自救:自建 ACME CA

墙堵死了正门,但 ACME 给了我一个启发:它是开放协议,不是 Let’s Encrypt 的专利。既然协议公开,就可以在内网自建一个 ACME CA——我用的是 smallstep 的 step-ca。

step-ca 部署很轻,而且 ACME 客户端生态零改造:certbot、acme.sh、Traefik、Caddy 全都天然支持自定义 ACME 目录,把目录指向内网 step-ca 就能自动签发。签发这个问题,一晚上就解决了。

然后我撞到了真正的问题。

私有 CA 的真正成本:信任分发

签出来的证书,浏览器不认、curl 报错、Java 应用直接抛异常。原因很简单:step-ca 的根证书不在任何客户端的信任库里。

step-ca 只解决签发,完全不解决信任。

而"让内网所有客户端信任一个根证书”,是个比签发大一个数量级的工程。它的碎坑在于信任库根本不是一个东西,而是一堆各自为政的实现:

运行时信任库位置
操作系统/etc/ssl/certs相对标准,好办
Javacacerts(JRE 自带)不读系统信任库,容器镜像里更难办
Pythoncertifi(pip 包)不读系统信任库,升级会被覆盖
Node.js编译进二进制需要 NODE_EXTRA_CA_CERTS 环境变量
Go读系统信任库但 scratch 镜像里什么都没有
Firefox独立信任库不读系统信任库

每个 JDK 容器、每个 Python 镜像、每个 Firefox 用户,都是一次单独的分发动作。

更清醒的认识来自一个现成案例:Let’s Encrypt 自己铺信任铺了六年。2015 年成立时没有设备信任它,它借道 IdenTrust 的交叉签名才被接受;2021 年借道的根(DST Root X3)到期独立,又直接导致老 Android 设备大面积瘫痪——即使六年之后,它仍然要为历史遗留付账。

信任库是"时间 × 渠道"的产物。 Let’s Encrypt 有全球浏览器厂商配合尚且如此,一个内网私有 CA 想把根证书铺满所有运行时,成本和收益完全不成比例。

务实突围:泛域名证书双轨

自建 CA 的信任分发走不通,就得回到公共 CA——但公共 CA 只给公网域名签。解法因此变成一句话:

给公网域名签泛证书,让内网解析把它指向内网 IP。

具体做法:给 *.devops.example.com 在公网 CA 签一张泛域名证书(公司有公网 DNS,DNS-01 验证没问题),然后在内网权威 DNS 里把 payment-api.devops.example.com 解析到内网 IP。浏览器访问时,域名是真的、证书是真的,天然信任——HTTPS 问题在没有触碰任何信任库的情况下消失了。

这个方案还有两个意外的好处:

但有三个坑必须说清楚:

  1. 私钥扩散:所有服务共用一张泛证书,私钥要分发到每个用它的服务上。任何一处泄露,全部服务受影响。私钥的分发和轮换要有纪律。
  2. 续期必须自动化:泛证书全公司共用,过期是大面积故障。ACME 自动续期是唯一选项,而且要监控续期本身。
  3. 泛证书只盖一级子域*.devops.example.com 能盖 api.devops.example.com,盖不了 api.test.devops.example.com(那是两级)。这条限制会反过来决定你的命名设计——层级不能随心所欲地深,要服务名字的深度服从证书的覆盖范围。

最终格局

方案落定后,两个域名轨的 HTTPS 策略分道扬镳:

这个分工又和空间模型自洽了:正式的、需要被好好访问的,本来就该走正式域;短域承载的临时试验,容忍证书的粗糙。

最后

证书这一篇的三个结论:

ACME 是协议,不是 Let’s Encrypt 的专利——自建 CA 解决签发很容易。

私有 CA 的真正成本是信任分发,信任库是时间和渠道的产物,内网铺不起。

泛证书只盖一级子域——这条技术限制会反过来参与你的命名设计。

域名有了,HTTPS 通了。下一个问题是这些域名指向的 IP 本身:谁在用哪个段、这个 IP 原来是谁的——这是 IPAM 的故事,下一篇。