编程语言

x509: certificate signed by unknown authority(Go)怎么解决

作者: · 最后核实

报错信息x509: certificate signed by unknown authority

x509: certificate signed by unknown authority 是 Go 在告诉你:服务器的证书链最终没有落到程序信任的任何根证书上。 常见原因有三类:服务器缺中间证书;机器或容器里没有 CA 证书包;服务器用了私有 CA,或者流量经过了解密 HTTPS 的代理,而对应的根证书没装。

快速修复

服务器缺中间证书就修服务器,否则把缺失的根证书加进系统证书包或 RootCAs。先看服务器发了什么、由谁签发:

openssl s_client -connect example.com:443 -servername example.com -showcerts </dev/null | grep -E 'depth|s:|i:|Verify return'
  • 只有一张站点证书,Verify return code: 21: 服务器证书链不完整,去服务器上修,见缺少中间证书。
  • 证书链完整、根证书是公网 CA,程序仍然报错: 客户端没有 CA 证书包或者太旧,安装 ca-certificates。
  • 证书链最终是私有根或代理根: 在客户端信任这张根证书:
export SSL_CERT_FILE=/etc/ssl/certs/company-bundle.pem   # Linux,只对当前进程生效

更推荐把根证书加入系统证书库(命令见下文),这样所有 Go 工具都能用上。

原因

这个报错有 4 种原因:容器里没有 CA 证书包、服务器没发中间证书、私有 CA 或代理根证书没装、本机证书包太旧。

1. 容器里没有 CA 证书包

scratch、部分 distroless 和精简镜像不带 CA 证书,Go 程序的所有 HTTPS 请求都会报这个错。调用微信、支付宝、阿里云 OpenAPI 时最常遇到。

  • 确认: 进入容器执行 ls /etc/ssl/certs/,目录为空或不存在。
  • 修复(多阶段构建):
FROM golang:1 AS build
WORKDIR /src
COPY . .
RUN CGO_ENABLED=0 go build -o /app .

FROM scratch
COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=build /app /app
ENTRYPOINT ["/app"]

也可以在代码里 import _ "golang.org/x/crypto/x509roots/fallback",把一份基于 Mozilla 的根证书编进二进制,只在系统没有根证书时启用。

2. 服务器没发中间证书

Go 不会自动下载缺失的中间证书。只发站点证书的服务器在 Chrome 里正常,在 Go 里报错。

3. 私有 CA、自签名证书或代理根证书

自建的 Harbor 镜像仓库、Vault、Consul、Kubernetes API Server 以及公司代理,用的根证书都不在公网证书包里。

  • 代码里修复,保留系统根证书,再追加你的根证书:
pool, err := x509.SystemCertPool()
if err != nil {
    pool = x509.NewCertPool()
}
pem, err := os.ReadFile("/etc/myapp/company-root.pem")
if err != nil {
    log.Fatal(err)
}
if !pool.AppendCertsFromPEM(pem) {
    log.Fatal("no certificates parsed from company-root.pem")
}
client := &http.Client{Transport: &http.Transport{
    TLSClientConfig: &tls.Config{RootCAs: pool},
}}

4. CA 换了新根,本机证书包太旧

CA 切换到新根证书后,如果你的证书包早于这次切换,链到新根的证书就验证失败。更新 ca-certificates 包即可。

各环境修法

Go 在 Linux 上读系统证书包,所以修法是把根证书加进系统证书包,或用 SSL_CERT_FILE 指定证书包;镜像里先装 ca-certificates。

Debian / Ubuntu:

sudo apt-get install -y ca-certificates
sudo cp company-root.crt /usr/local/share/ca-certificates/
sudo update-ca-certificates

CentOS / RHEL / Rocky / Alibaba Cloud Linux: 复制到 /etc/pki/ca-trust/source/anchors/,执行 sudo update-ca-trust。

Alpine: apk add --no-cache ca-certificates,再把根证书放到 /usr/local/share/ca-certificates/ 并执行 update-ca-certificates。国内构建慢时可以先把 apk 源换成阿里云、清华等镜像。

macOS 和 Windows: Go 使用系统验证接口,把根证书加入“系统”钥匙串并设为“始终信任”,或导入 Windows 的“受信任的根证书颁发机构”。

具体工具: Docker 拉镜像时验证证书的是守护进程而不是命令行,kubectl 用的是 kubeconfig 里的 CA,分别见 Docker x509 报错和 kubectl x509 报错。

Go 模块下载: 在解密 HTTPS 的公司代理后面执行 go mod download 也会这样报错。在系统层面信任代理根证书,或者使用 goproxy.cn 等国内模块代理(它们用公网证书),不要设置 GOINSECURE。

不要这样做

跳过校验能消除报错,但证书链和域名校验也一起关掉了。

  • InsecureSkipVerify: true。 同时关闭证书链和域名校验,gosec 等静态检查工具会专门报出来。
  • RootCAs 里只放私有根证书,而程序还要访问公网服务。应从 x509.SystemCertPool() 开始再追加。
  • 用 GOINSECURE 绕过模块下载报错。 它会对匹配的模块路径跳过 HTTPS 证书校验,下载内容被篡改时只能指望校验和数据库发现。
  • 从网盘、论坛下载来路不明的 ca-certificates.crt 放进镜像。 用发行版的软件包或 Go 官方的 fallback 包。

常见问题

Go 从哪里读取受信任的根证书?

在 Linux 等 Unix 系统上,Go 读取 /etc/ssl/certs/ca-certificates.crt、/etc/pki/tls/certs/ca-bundle.crt 等常见位置,SSL_CERT_FILE 和 SSL_CERT_DIR 环境变量可以覆盖。在 Windows 和 macOS 上,Go 调用操作系统自己的验证接口,所以 Windows 证书库和 macOS 钥匙串里的根证书都会被信任。

本地运行正常,放进 scratch 或 distroless 镜像就报错,为什么?

FROM scratch 镜像里只有你的二进制文件,没有 CA 证书包可读。在多阶段构建里从 build 阶段复制 /etc/ssl/certs/ca-certificates.crt,或者在代码里引入 golang.org/x/crypto/x509roots/fallback 包,把一份兜底根证书编进二进制。

可以用 InsecureSkipVerify 吗?

上线的代码里不行。InsecureSkipVerify: true 同时跳过证书链和域名校验,同一网络路径上的任何机器都能冒充服务器。需要信任私有 CA 时,把那张 CA 放进 RootCAs。

报错前面多了 tls: failed to verify certificate,是另一个错误吗?

不是。较新的 Go 版本会在同一个 x509 错误前加上这段前缀,原因和修法相同。

资料来源

  1. Go package crypto/x509
  2. Docker: Verify repository client with certificates
  3. openssl-s_client (OpenSSL 3.0)