编程语言
x509: certificate relies on legacy Common Name field(Go)
x509: certificate relies on legacy Common Name field, use SANs insteadx509: certificate relies on legacy Common Name field, use SANs instead 的意思是:服务器证书只在使用者 CN 里写了主机名,没有 SAN(使用者可选名称)扩展,而 Go 已经不再接受这种证书。 必须重新签发带 SAN 的证书,当前的 Go 版本在客户端没有任何开关可以绕过。
快速修复
唯一的修法是重新签发带 SAN 的证书。先确认证书确实没有 SAN:
openssl s_client -connect registry.internal.example.com:443 -servername registry.internal.example.com </dev/null 2>/dev/null \
| openssl x509 -noout -subject -ext subjectAltName
如果只看到 subject=CN = registry.internal.example.com,没有 X509v3 Subject Alternative Name 段,就需要重新签发。用内部 CA 签发的做法:
# 1. 生成带 SAN 的私钥和 CSR
openssl req -new -newkey rsa:2048 -nodes -keyout server.key -out server.csr \
-subj "/CN=registry.internal.example.com" \
-addext "subjectAltName=DNS:registry.internal.example.com,IP:203.0.113.10"
# 2. 签发时把 SAN 写进证书
cat > san.ext <<'EOF'
subjectAltName = DNS:registry.internal.example.com, IP:203.0.113.10
extendedKeyUsage = serverAuth
EOF
openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial \
-days 365 -sha256 -extfile san.ext -out server.crt
注意 openssl x509 -req 默认不会把 CSR 里的扩展复制到证书里,网上很多教程生成的证书没有 SAN 就是这个原因;上面的 -extfile 解决了这一点。
原因
没有 SAN 的证书有 4 种来源,最常见的是多年前按教程生成、不带任何扩展的内部证书。
1. 老旧的内部证书或自签名证书
典型的是用 openssl req -x509 -subj "/CN=host" 生成的证书。
2. 内部 CA 签发时丢掉了 SAN
一些自己写的签发脚本和老版本 CA 软件会签发 CSR,但忽略 CSR 里的扩展。
- 确认: CSR 里有 SAN(
openssl req -in server.csr -noout -text),签出来的证书却没有。 - 修复: 让 CA 复制或添加
subjectAltName(OpenSSL 用-extfile,或在 CA 配置里写copy_extensions = copy)。
3. 设备自带的默认证书
存储阵列、服务器 BMC 管理口、老防火墙、打印机等设备出厂证书往往只有 CN。用内部 CA 签发带 SAN 的证书替换掉。
4. 客户端升级了,服务器没变
服务器什么都没改,只是客户端用新版 Go 重新编译了。所以这个报错往往在升级 Docker、kubectl、Terraform 之后突然出现。
各环境修法
唯一的修法是用内部 CA 重签证书,把客户端用到的每个域名和 IP 都写进 SAN。
自建 Docker 仓库(Harbor、registry): 国内很多团队的 Harbor 用的是几年前按教程生成的自签名证书,升级 Docker 后就报这个错。重新签发带 SAN 的仓库证书并重启 Harbor;客户端的 CA 放在 /etc/docker/certs.d/<仓库地址>/ca.crt,详见 Docker x509 报错。
Kubernetes 准入 Webhook: API Server 通过 Service 域名调用 Webhook,例如 my-webhook.my-namespace.svc,这个名字必须在证书 SAN 里。用 cert-manager 时写进 Certificate 的 spec.dnsNames。
etcd 和 kubeadm 集群: kubeadm 生成的证书本身带 SAN。照老教程手工生成的 etcd 证书可能没有,需要把节点主机名和 IP 写进 SAN 重新生成。
阿里云 ACK、腾讯云 TKE 等托管集群: 平台自动签发的组件证书都带 SAN,出问题的一般是自己部署的 Webhook、镜像仓库或中间件。逐个检查这些服务的证书,按上面的方法重新签发,并把新的 CA 同步到 Webhook 配置的 caBundle 里。
Java 等其他客户端: Java 对域名在某些情况下仍会回退到 CN,所以同一张证书可能 Java 正常、Go 报错。修好证书后两边都能用,另见 No subject alternative names present。
不要这样做
下面 3 种做法都修不好证书,只会推迟问题或关掉校验。
- 为了保留回退行为锁死旧版 Go,或旧版 Docker、kubectl。 这等于为了一张有问题的证书放弃所有安全更新。
InsecureSkipVerify: true或--insecure-skip-tls-verify。 这会去掉全部校验,而不只是 CN 检查。- 设置
GODEBUG=x509ignoreCN=0就以为好了。 Go 1.17 及以后这个变量会被直接忽略,白白浪费排查时间。
常见问题
设置 GODEBUG=x509ignoreCN=0 还能用吗?
不能。这个开关只是 Go 1.15、1.16 的临时过渡手段,Go 1.17 的发布说明明确写了已删除。用 Go 1.17 及以后版本编译的程序会直接忽略它。
证书的 CN 明明和域名一致,为什么还不行?
早在 2000 年的 RFC 2818 就要求有 SAN 时优先用 SAN,CA/浏览器论坛也要求所有公网证书必须有 SAN,Go 只是不再支持旧的回退方式。公网 CA 签发的证书都带 SAN,所以这个报错几乎都来自内部或自签名证书。
哪些工具会报这个错?
所有做 TLS 校验的 Go 程序:docker pull 私有仓库、kubectl 和 kubelet、Kubernetes API Server 调用的 Webhook、Terraform provider、Prometheus 抓取、etcd 客户端、Helm 等。
怎么检查证书有没有 SAN?
执行 openssl x509 -in server.crt -noout -ext subjectAltName,没有输出就说明没有 SAN 扩展。也可以把 PEM 内容粘贴到证书解析工具里查看。