Go crypto/x509Go
错误信息
x509: certificate signed by unknown authority
Go crypto/x509 — returned by Certificate.Verify and TLS dials
含义
Go 尝试从服务器的证书构建一条通向其信任的根证书的证书链,但失败了:没有任何路径终止于所配置根证书池中的证书。证书本身可能完全有效——只是 Go 没有理由信任它的颁发者。这是 Go 部署中最常遇到的 TLS 错误。
原因
服务器没有发送中间证书
常见信任库中的根证书为中间证书签名,中间证书为叶子证书签名。如果服务器只配置了叶子证书(用的是 cert.pem 而不是 fullchain.pem),Go 就无法连通“叶子证书 → 根证书”。浏览器常常借助缓存的中间证书掩盖这个问题;Go 不会这样做。
内部或私有 CA 不在根证书池中
常见系统信任库按设计本来就不认识公司 CA、开发用的自签名证书和服务网格签发的证书。必须显式地把它们告知客户端。
不含 CA 证书的精简容器镜像
常见scratch 和 slim 镜像不带 /etc/ssl/certs。这时每次 TLS 连接都会报出这个错误,无论目标的证书多么公开、多么有效。
拦截代理对流量重新签名
偶见公司用于 TLS 检查的中间设备会把证书替换为由公司 CA 签名的证书。没有安装该 CA 的机器会把每个外部网站都视为不受信任。
修复方法
- 1.
把 CA 添加到客户端的根证书池
扩展系统证书池,而不是替换它,这样在使用内部 CA 的同时,公共网站也能继续正常访问。
go roots, err := x509.SystemCertPool() if err != nil { roots = x509.NewCertPool() } pem, _ := os.ReadFile("internal-ca.pem") roots.AppendCertsFromPEM(pem) client := &http.Client{Transport: &http.Transport{ TLSClientConfig: &tls.Config{RootCAs: roots}, }} - 2.
修复服务器:提供完整证书链
检查服务器实际发送的内容;如果只出现一张证书,就让服务器改用完整证书链文件(例如 Let's Encrypt 的 fullchain.pem)。
bash openssl s_client -connect api.example.com:443 -showcerts </dev/null \ | grep -c "BEGIN CERTIFICATE" # 1 = leaf only (broken), 2+ = chain present - 3.
在镜像中附带 CA 证书
对于精简镜像,在构建时把 CA 证书包复制进去。切勿用 InsecureSkipVerify 来“解决”这个问题——那会彻底禁用验证。
docker FROM alpine AS certs RUN apk add --no-cache ca-certificates FROM scratch COPY --from=certs /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/