Go 构建、Composer 安装和 Python 客户端在家里都能用,进公司 VPN 后却全部失败。这个规律说明问题出在路径上,而不是应用本身。链路上的某个环节改变了进程看到的证书链,或者在证书链送到之前就把握手重置了。两类故障在终端里看着相似,处理方式却完全不同。

三种报错,三种机制

x509 certificate signed by unknown authority 这类错误说明证书链已经建立,但它的根不在该进程使用的那份信任存储里。self-signed certificate in certificate chain 通常说明检查代理用自己的证书作答,而这条链你的运行时并不认识。握手过程中出现 SSL EOF 说明连接在送出任何可用证书之前就被关闭,那是中间设备拒绝了 ClientHello,根本不是信任问题。

加一个不安全开关会让三种报错同时消失,而拦截依然存在,同时你也去掉了本可以抓住真实攻击者的那道校验。

找到该进程真正读取的那份包

装在 Windows 存储里的公司根证书只能修好查询系统存储的浏览器和 .NET 应用。对容器里跑的构建没有用,因为容器自带 CA 包;对使用 certifi 的 Python 进程、或使用自带清单的 Node 进程同样没有用。Go 读取的是二进制运行环境的系统池,也就是容器,而不是笔记本。于是同一台机器上,浏览器正常,构建工具却失败。

去问工具,而不是凭猜测。python3 -c "import ssl; print(ssl.get_default_verify_paths())" 会打印 Python 将要查询的文件,node -e "console.log(process.env.NODE_EXTRA_CA_CERTS)" 则显示是否已经设置了覆盖。容器场景下,请在镜像内部做同样的检查,因为构建真正使用的就是这个环境。

在构建实际运行的镜像里安装一个根

向 IT 索取检查根证书的 PEM 文件,并通过另一条通道核对其指纹之后再信任它。Debian 系镜像把文件复制到 /usr/local/share/ca-certificates/ 并运行 update-ca-certificates;RHEL 系镜像把文件放进 /etc/pki/ca-trust/source/anchors/ 之后运行 update-ca-trust。在容器构建中,把这些步骤放在失败命令之前,好让该层缓存在依赖安装之上。

本地 devcontainer 就挂载 PEM 并在启动时执行同样的更新命令,而不是把它烤进一个别人无法复现的个人镜像。自带包的工具有各自的官方覆盖方式:pip 有证书设置,npm 有 cafile 设置,其余多数接受标准环境变量。

这并不等于接受任何证书。你只是把从实施检查的一方取得的某一个特定根加入到一个环境,其他所有链的校验照旧打开。记录加了哪个文件、什么时候到期,因为检查根和其它 CA 一样需要续期。

握手在送出证书之前就断掉时

如果对方什么都没给,改信任存储不会有任何帮助。把同一个请求在隧道开启和关闭时各做一次对比,然后用 openssl s_client -connect host:443 -servername host -showcerts 直接查看证书链。在 VPN 外能完成、在 VPN 内被重置的连接,是中间设备做出的策略决定,解法是为该主机申请例外,而不是再加一张证书。请对方放行包注册表或 API 端点,不做改动。

把隧道拆开让工具绕过 VPN,等于让这部分流量逃过检查,可能违反政策,也不会让工具变得可信。

按工具分别记录结论

一份简短记录,列出二进制、它读取的信任文件、起作用的环境变量和已获批的例外,就能替下一个人省掉整轮排查。记录要按工具来记,而不是按笔记本,因为出问题的通常不是笔记本。

另外,工单里要把两件事分开写:我们安装了一个特定的公司根证书,以及我们关掉了校验。只有前一件是支持动作。

相关文章