原文:ssl自签证书不受信?一招帮你解决
线上 ASR 服务突然回调失败,日志里全是 "certificate signed by unknown authority"。容器内环境有限,没有 sudo,没有 yum 源,你该怎么救?
一、问题现场
项目上反馈新业务部署到客户环境后,测试业务功能始终有问题。这个服务运行在 openEuler 24.03 容器中,负责语音识别完成后向平台回调转写结果。
应用日志中不断刷出这样的错误:
{"file":"ai-meeting-asr-server/dispatcher/callback_dispacher.go:45", "level":"ERROR", "msg":"[callback]create req error:Post \"https://api.internal.com/meeting/...\": tls: failed to verify certificate: x509: certificate signed by unknown authority"}错误信息很明确:TLS 握手失败,客户端不信任服务端的证书。但诡异的是,同一套证书之前一直跑得好好的,而且手动用 curl 测试同一地址完全正常。
到底哪里出了问题?
二、用 curl 还原现场
排查 SSL 问题的第一步,永远是用curl -v精确复现。进入容器后执行:
curl -v https://api.internal.com/meeting/api/v1/callback/transcripts/...输出的 TLS 握手过程清晰地展示了问题:
* SSL certificate problem: unable to get local issuer certificate * Closing connection 0 curl: (60) SSL certificate problem: unable to get local issuer certificate关键信息:unable to get local issuer certificate。这意味着 curl 在本地信任库中找不到签发服务端证书的 CA 根证书。
为什么会这样?因为容器使用的是自签发的内部 CA 证书,而 openEuler 基础镜像的 CA 信任库中没有导入这张根证书。
三、为什么必须拿 CA 根证书,而不是服务器证书?
很多同学到这里会有疑问:我直接把域名的证书下载下来不行吗?为什么非要用 openssl 去拿 CA 根证书?
要理解这个问题,先搞清楚 SSL 证书验证的信任链:
CA 根证书(Root CA) │ ▼ 签发 中间证书(Intermediate CA,可能有多级) │ ▼ 签发 服务器证书(Server Certificate) ← 你访问的网站持有的客户端验证服务端身份时,不是直接看服务器证书本身,而是沿着这条链逐级往上追溯,直到找到一个自己信任的根证书。如果追溯到顶都没找到信任的根,就会报unknown authority。
换句话说:
- 服务器证书= 这台服务器的"身份证"
- CA 根证书= 签发这张身份证的"公安局"
你拿到"身份证"(服务器证书)没用,客户端不认识"公安局"(CA 根证书),照样不信你。你得把"公安局"的备案信息(CA 根证书)加到本地信任库里,验证才能通过。
💡一句话总结:我们需要导入的是签发者的证书(CA 根证书),而不是被签发者的证书(服务器证书)。把服务器证书加到信任库,就像把张三的身份证加到警察系统里——没有任何意义。
四、用 openssl s_client 提取 CA 根证书
理解了原理,操作就很简单了。用openssl s_client连接目标服务器,获取完整证书链:
openssl s_client -connect api.internal.com:443 -showcerts-showcerts会输出完整的证书链信息,类似:
--- Certificate chain 0 s:/CN=api.internal.com ← 服务器证书 i:/CN=My Internal CA ← 签发者(中间/根证书) 1 s:/CN=My Internal CA i:/CN=My Internal CA ← 自己签发自己 = 根证书 ✓ ---找到Issuer 和 Subject 相同的那张证书(即自己签发自己的根证书),将其内容保存为文件:
openssl s_client -connect api.internal.com:443 -showcerts 2>/dev/null | \ sed -n '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' > ca.crt💡小贴士:如果证书链有多级(根 → 中间 → 服务器),你需要提取最顶层的根证书。验证方法:查看证书的 Issuer 和 Subject 是否一致,一致的就是根证书。
五、解决方案
方案一:程序级修复(推荐,永久生效)
如果你的服务有 Dockerfile,这是最干净的方案——在构建镜像时就把证书加进去:
FROM openeuler/openeuler:24.03 # 安装 ca-certificates 工具 RUN yum install -y ca-certificates # 将自签 CA 证书复制到系统信任目录 COPY ca.crt /etc/pki/ca-trust/source/anchors/ # 更新系统证书信任库 RUN update-ca-trust原理说明:
/etc/pki/ca-trust/source/anchors/是 openEuler/CentOS 系列的自定义证书导入目录- 执行
update-ca-trust后,系统会将anchors/下的所有证书合并到/etc/pki/tls/certs/ca-bundle.crt - 之后容器内所有依赖系统 CA 库的程序(curl、wget、Go、Python requests 等)都能自动信任该证书
重新构建镜像并部署,问题彻底解决。
方案二:运行时追加(临时方案,已运行的容器)
如果容器已经在跑,短时间内不方便重新构建镜像,可以用临时方案救急:
# 将 CA 证书追加到系统 CA 信任包 cat ca.crt >> /etc/pki/tls/certs/ca-bundle.crt # 重启容器内的应用服务 kill -HUP $(pidof your-app) # 发送热重载信号 # 或直接重启容器 docker restart <container_name>⚠️注意:临时方案的缺点是——容器重启后ca-bundle.crt会恢复原状,需要重新追加。所以这只是救急手段,长期方案请走方案一。方案三:跳过证书验证(仅限测试环境)
curl -k https://api.internal.com/... # Go 程序中跳过验证(不推荐生产使用) # tls.Config{InsecureSkipVerify: true}生产环境绝对不要用这个方案!跳过证书验证等于放弃了 TLS 的身份校验,中间人攻击将毫无阻拦。
六、方案对比与选型
| 方案 | 适用场景 | 是否持久化 | 推荐度 |
|---|---|---|---|
| Dockerfile + update-ca-trust | 新构建镜像 / 有计划重建 | ✅ 永久生效 | ⭐⭐⭐⭐⭐ |
| 追加到 ca-bundle.crt | 已运行容器紧急修复 | ❌ 重启失效 | ⭐⭐⭐ |
| 跳过验证 (-k) | 仅限测试环境调试 | — | ⭐(禁用于生产) |
七、排查思路总结
遇到容器内 SSL 证书报错,按照这个流程走:
- curl -v 复现→ 确认具体错误信息(unable to get local issuer / certificate expired / self-signed 等)
- 理解信任链→ 我们缺的是 CA 根证书,不是服务器证书
- openssl s_client -showcerts→ 查看证书链,定位并提取根证书
- 选择修复方案→ 能重建镜像走 Dockerfile,不能就临时追加到 ca-bundle.crt
- 验证修复→ 再次 curl -v 确认 SSL 握手成功
八、写在最后
SSL 证书问题在容器化环境中非常常见,尤其是使用自签发证书的内部服务。很多同学遇到这类问题的第一反应是"加个InsecureSkipVerify = true"——能跑,但隐患巨大。
正确的做法是:把 CA 证书打进镜像,从源头解决信任问题。成本很低,效果永久,安全性也有保障。
记住:在生产环境里,绕过安全不是解决问题,只是把问题藏起来。
📌本文配套排查命令已整理成速查表,回复「SSL」即可获取。
— 运维之美 · 守护每一次稳定运行