Docker Registry使用自签名证书后总提示“tls: bad certificate”的排除手法和解决方案

假设的复现环境:

A服务器:ip为192.168.1.1,Docker Registry域名和端口为docker-registry.orztip.internal:9933

B服务器:ip为192.168.1.2

以上服务器的docker使用rootless模式运行,运行docker rootless的用户名为demo。

(备注:以下内容也适用于排查docker使用常规root模式运行的问题)

docker配置目录下已经放置自签名证书ca文件,即文件在“【docker配置目录】/certs.d/docker-registry.orztip.internal:9933/ca.crt”。

现象:

A服务器使用自签名证书搭建了一个Docker Registry,然后B服务器向A服务器使用docker pull拉取镜像时总出现连接不上、或者其他错误(比如mirror docker hub时A服务器并没有拉取镜像缓存下来)。

此时A服务器查看docker logs时会有如下报错:

registry-mirror_1  | 2023/01/25 15:17:22 http: TLS handshake error from 192.168.1.2:59725: remote error: tls: bad certificate

排查:

以下均在B服务器上操作。

开启docker调试模式和详细日志,见:《如何开启docker的调试模式和详细日志输出》:https://www.orztip.com/?p=761&article_title=docker-debug-mode-and-verbose-log

重启docker后,再次尝试运行docker pull动作,然后到/var/log/syslog或者/var/log/messages查找日志,此时会看到如下日志:

Jan 26 00:12:38 B服务器 dockerd-rootless.sh[3291]: time="2023-01-26T00:12:38.689846619+08:00" level=debug msg="hostDir: /home/demo/.config/docker/certs.d/docker-registry.orztip.internal:9933"
Jan 26 00:12:38 B服务器 dockerd-rootless.sh[3291]: time="2023-01-26T00:12:38.689930920+08:00" level=debug msg="crt: /home/demo/.config/docker/certs.d/docker-registry.orztip.internal:9933/ca.crt"
Jan 26 00:12:38 B服务器 dockerd-rootless.sh[3291]: time="2023-01-26T00:12:38.689998990+08:00" level=debug msg="Trying to pull ubuntu from https://docker-registry.orztip.internal:9933/ v2"
Jan 26 00:12:38 B服务器 dockerd-rootless.sh[3291]: time="2023-01-26T00:12:38.695741621+08:00" level=warning msg="Error getting v2 registry: Get \"https://docker-registry.orztip.internal:9933/v2/\": x509: certificate relies on legacy Common Name field, use SANs instead"
Jan 26 00:12:38 B服务器 dockerd-rootless.sh[3291]: time="2023-01-26T00:12:38.695776571+08:00" level=info msg="Attempting next endpoint for pull after error: Get \"https://docker-registry.orztip.internal:9933/v2/\": x509: certificate relies on legacy Common Name field, use SANs instead"
Jan 26 00:12:38 B服务器 dockerd-rootless.sh[3291]: time="2023-01-26T00:12:38.695807201+08:00" level=debug msg="Trying to pull ubuntu from https://registry-1.docker.io v2"
Jan 26 00:12:42 B服务器 dockerd-rootless.sh[3291]: time="2023-01-26T00:12:42.218347552+08:00" level=debug msg="Fetching manifest from remote" digest="sha256:0e0402cd13f68137edb0266e1d2c682f217814420f2d43d300ed8f65479b14fb" error="context canceled" remote="docker.io/library/ubuntu:20.04"

故障最终原因:

日志中存在“ x509: certificate relies on legacy Common Name field, use SANs instead”。

顺着排查才知道,原来Go 1.15开始废弃X.509 CommonName【1】【2】,那么使用Go语言编写的docker自然也不认字段CN(Common Name),必须使用X.509的扩展字段SANs(Subject Alternate Names)。

解决:

重新生成带扩展字段SANs(Subject Alternate Names)的自签名证书。

参见docker registry的文档,使用openssl的命令行如下(注意Common Name仍然要手动输入,内容和subjectAltName一样的域名):

openssl req \
  -newkey rsa:4096 -nodes -sha256 -keyout domain.key \
  -addext "subjectAltName = DNS:docker-registry.orztip.internal" \
  -x509 -days 3650 -out domain.crt

输出示例:


demo@A服务器: $ openssl req \
  -newkey rsa:4096 -nodes -sha256 -keyout domain.key \
  -addext "subjectAltName = DNS:docker-registry.orztip.internal" \
  -x509 -days 3650 -out domain.crt


Generating a RSA private key
.......................................................++++
..............................................................................++++
writing new private key to 'domain.key'
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:
State or Province Name (full name) [Some-State]:
Locality Name (eg, city) []:
Organization Name (eg, company) [Internet Widgits Pty Ltd]:
Organizational Unit Name (eg, section) []:
Common Name (e.g. server FQDN or YOUR name) []:docker-registry.orztip.internal
Email Address []:

验证证书是否带SANs扩展字段,CN字段是否和SANs扩展字段中的域名是否一致:

openssl x509 -in  domain.crt  -text -noout

示例:


demo@A服务器$ openssl x509 -in  domain.crt -text -noout
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            [略]
        Signature Algorithm: sha256WithRSAEncryption
        Issuer: C = AU, ST = Some-State, O = Internet Widgits Pty Ltd, CN = docker-registry.orztip.internal
        Validity
            Not Before: Jan 26 09:47:30 2023 GMT
            Not After : Jan 23 09:47:30 2033 GMT
        Subject: C = AU, ST = Some-State, O = Internet Widgits Pty Ltd, CN = docker-registry.orztip.internal
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                RSA Public-Key: (4096 bit)
                Modulus:
                                [略]
                Exponent: 65537 (0x10001)
        X509v3 extensions:
            X509v3 Subject Key Identifier: 
                [略]
            X509v3 Authority Key Identifier: 
                keyid:[略]
            X509v3 Basic Constraints: critical
                CA:TRUE
            X509v3 Subject Alternative Name: 
                DNS:docker-registry.orztip.internal
    Signature Algorithm: sha256WithRSAEncryption
         [略]

然后重新使用domain.crt和domain.key部署A服务器上的docker registry,并且重启docker。

同时使用domain.crt文件覆盖掉B服务器上部署的ca文件,即B服务器上的“【docker配置目录】/certs.d/docker-registry.orztip.internal:9933/ca.crt”。

最后验证一下,应该就可以正常运行了。

参考文章:

【1】https://go.dev/doc/go1.15#commonname

【2】https://github.com/johanbrandhorst/certify/issues/122

【3】https://docs.docker.com/registry/insecure/

本页永久链接:https://www.orztip.com/?p=768&article_title=docker-registry-self-sign-cert-error-tls-bad-certificate