准备工作

  • 在服务器内创建一个文件夹,进入该文件夹,后续所有密钥文件全部存入该文件夹

    示例:

    mkdir -p /mnt/docker/tsl
    cd /mnt/docker/tsl
    
  • 默认情况下,Docker 通过非联网的 Unix 套接字运行。它还可以选择使用 HTTP 套接字进行通信。这里有三个角色,Docker 服务端Docker 客户端CA 签名的证书

  • Docker 服务端:对应运行 Docker 守护程序的主机

    • 通过开放端口 (一般为 2376) 支持远程连接
    • 通过指定 tlsverify 标志并将 Docker 的 tlscacert 标志指向 可信的 CA 证书来启用 TLS 以实现安全访问 (仅允许由该 CA 签名的证书进行身份验证的客户端连接)
  • Docker 客户端:即默认情况下的 Docker 主机

    • 当使用证书连接时,仅能连接到具有该 CA 签名的证书的服务器
  • CA 签名的证书

    • 作用:服务端和客户端证书都只对应一份信任列表,信任列表里是服务端的信息 (如 ip 或域名等等), 服务端持有服务端证书,仅接受持有客户端证书的主机访问 (这里可以再加上其他的限制,详情见下文)

注意:

  • Docker 服务端也可以不开启 TLS 验证,不过这样子很不安全,生产环境下应当尽量避免。如果只是试验性的,可以指定关闭 TLS 验证,但只对特定主机开放,方法见后文 /etc/docker/daemon.json 配置相关
  • 如果 Docker 服务端没有开启 TLS 验证,则 Docker 客户端不需要使用证书连接。但如果客户端不使用证书连接开启了 TLS 验证的服务端,则会报错,如下:
 Get http://远程主机ip:2376/v1.38/version: net/http: HTTP/1.x 
 transport connection broken: malformed HTTP response 
 "\x15\x03\x01\x00\x02\x02".
 * Are you trying to connect to a TLS-enabled daemon without 
 TLS?
  • 如果 Docker 客户端连接时使用的证书内不含目的主机的信息,则会提示对方主机不在证书信任列表内,访问失败

生成密钥对

使用 OpenSSL 创建 CA 和服务端密钥 key

注意:将以下示例中的所有 $HOST 实例替换为 Docker 守护程序主机的域名 (DNS name)(博主使用的是域名,如果是使用 ip 则将此处替换为自己的 ip 地址,有影响的地方我会再做提醒)

以下步骤在 Docker 服务端进行:

1. 生成 CA 私钥 ca-key.pem

说明:ca-key.pem 是一个临时文件,最后可以删除。

  • 指令:
openssl genrsa -aes256 -out ca-key.pem 4096

如下示例,需要设置密码并验证

$ openssl genrsa -aes256 -out ca-key.pem 4096
Generating RSA private key, 4096 bit long modulus
............................................................................................................................................................................................++
........++
e is 65537 (0x10001)
Enter pass phrase for ca-key.pem:
Verifying - Enter pass phrase for ca-key.pem:

2. 使用 CA 私钥生成自签名 CA 证书 ca.pem

说明:生成证书时,通过 - days 365 设置证书的有效期。单位为天,默认情况下为 30 天。有了 CA 证书后,就可以创建服务器密钥和证书签名请求(CSR)了

  • 指令:
openssl req -new -x509 -days 365 -key ca-key.pem -sha256 -out ca.pem

如下示例,需要验证密码并输入信息,注意 "Common Name" 设置为服务器所在主机的域名 (我只填了 Country Name 为 CN,Province Name 为 Guangdong, 后面直接回车也没关系,反正也只是自己用的证书)

$ openssl req -new -x509 -days 365 -key ca-key.pem -sha256 -out ca.pem
Enter pass phrase for ca-key.pem:
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]:CN
State or Province Name (full name) [Some-State]:Guangdong
    Locality Name (eg, city) []:ShenZhen
Organization Name (eg, company) [Internet Widgits Pty Ltd]:Docker Inc
Organizational Unit Name (eg, section) []:Sales
       Common Name (e.g. server FQDN or YOUR name) []:$HOST(冒号后改为自己的域名或ip)
Email Address []:

3. 生成服务器私钥 server-key.pem 和证书签名请求 server-csr

说明:CSR:Certificate Signing Request, 证书签名请求,server-csr 是一个临时文件,生成 server-cert.pem 以后,可以删除。这里分两小步:

  • 第一步生成服务器私钥 server-key.pem
  • 第二步使用服务器私钥另加 CN 信息生成证书签名请求 server-csr.pem
  • 指令 1:
openssl genrsa -out server-key.pem 4096
  • 指令 2:
openssl req -subj "/CN=$HOST(等于号后面改为自己的域名或ip)" -sha256 -new -key server-key.pem -out server.csr

如下示例 (注意这里的 CN 信息对应的是服务器所在主机域名,如果没有的话也没关系,可以通过下一步的 extfile.cnf 配置 IP 地址连接):

$ openssl genrsa -out server-key.pem 4096
Generating RSA private key, 4096 bit long modulus
.....................................................................++
.................................................................................................++
e is 65537 (0x10001)

$ openssl req -subj "/CN=$HOST(等于号后面改为自己的域名或ip)" -sha256 -new -key server-key.pem -out server.csr

4. 编写 extfile.cnf (重要)

说明:这个文件用于指定下一步生成签名证书的一些属性配置,这里我们主要用到两个属性,如果要其他要求 (如限制指定 ip 范围的客户端才能连接) 的可以看 OpenSSL x509v3_config 文档

  • subjectAltName

    主题备选名称,是有点像上一步生成 server.csr 时所用的选项 - subj "/CN=$HOST" 的东西,这个更像一个说明补充,这里可以填信任的 DNS 域名和主机 IP 等等。 另外,需要特别注意,这里对应生成的是一份信任列表,这里所说的信任是对服务端的信任,所以填的是服务端的信息 (如域名,IP), 我之前看到有文章说这里的列表是客户端的列表,只有在列表中的客户端才能访问服务器,这种说法是错误,在使用证书连接到服务器时,会报错说服务器 IP 不在信任列表中 ( 如远程主机 ip 为 ip3, 证书的信任列表为 ip1 和 ip2 时,若使用该证书访问远程主机,则会报错 x509: certificate is valid for ip1, ip2, not ip3)

  • extendedKeyUsage

    扩展密钥用法,此扩展包含一个用法列表,用于指示证书公钥可用于的目的

  • 指令 1:
echo extendedKeyUsage = serverAuth >> extfile.cnf
  • 指令 2:
echo subjectAltName = DNS:$HOST(冒号后面改为自己的域名),IP:10.10.10.20,IP:127.0.0.1,IP:公网ip(若是拥有域名则可不使用公网ip连接,此处可省略) >> extfile.cnf

指令解析:

  • 将 Docker 守护程序密钥的扩展使用属性设置为仅用于服务器身份验证
  • 将域名 (DNS Name)$HOST 和 IP 为 10.10.10.20 (私有地址,用于局域网登录) 和 127.0.0.1 (本地地址,用于本机登录) 和公网 ip (用于远程登录)

注意:事实上只配一个公网 IP 就行了,其他的视实际需求而定,本机的话一般不那么麻烦,通过 docker 本身的配置即可使用本机控制,这里配 127.0.0.1 主要是为了测试是否能连通,如果只配了私有地址则只有本局域网可访问,安全性更高

5. 使用 CA 证书生成服务器签名证书 server-cert.pem

注意,上面总共生成了两大模块的文件和一个 extfile.cnf 配置文件,这三部分之间是彼此独立的,到了这一步,也是最后一步才真正做了整合. 这里要用到的有,(1) CA 私钥 ca-key.pem 和 CA 签名文件 ca.pem,(2) 证书签名请求 server-csr (3) 配置文件 extfile.cnf

  • 指令:
openssl x509 -req -days 365 -sha256 -in server.csr -CA ca.pem -CAkey ca-key.pem -CAcreateserial -out server-cert.pem -extfile extfile.cnf

如下示例:

$ openssl x509 -req -days 365 -sha256 -in server.csr -CA ca.pem -CAkey ca-key.pem \
  -CAcreateserial -out server-cert.pem -extfile extfile.cnf
Signature ok
subject=/CN=your.host.com
Getting CA Private Key
Enter pass phrase for ca-key.pem:

至此,Docker 服务端密钥创建完毕。

利用 CA 创建客户端密钥 key

注意:生成密钥需要使用 CA 私钥和签名文件,为简化流程,避免 CA 文件在服务端和客户端之间的传输,以下步骤仍在 Docker 服务端进行

创建客户端密钥的过程和服务端类似,CA 相关已经创建好了,extfile.cnf 配置文件也简单很多,具体如下

1. 生成客户端私钥 key.pem 和证书签名请求 client-csr

  • 指令:
openssl genrsa -out key.pem 4096
openssl req -subj '/CN=client' -new -key key.pem -out client.csr

如下示例:

$ openssl genrsa -out key.pem 4096
Generating RSA private key, 4096 bit long modulus
.........................................................++
................++
e is 65537 (0x10001)

$ openssl req -subj '/CN=client' -new -key key.pem -out client.csr

2. 编写扩展配置文件 extfile.cnf

注意,我们依旧是在刚才创建服务器私钥的文件夹下,应该还有原来的 extfile.cnf 文件,为避免覆写,可以先执行重命名

  • 指令 1:
mv extfile.cnf extfile.cnf.old
  • 指令 2: 创建扩展配置文件并使密钥适用于客户端身份验证
echo extendedKeyUsage = clientAuth >> extfile.cnf

3. 生成签名文件 cert.pem

  • 指令:
openssl x509 -req -days 365 -sha256 -in client.csr -CA ca.pem -CAkey ca-key.pem -CAcreateserial -out cert.pem -extfile extfile.cnf

如下示例:

$ openssl x509 -req -days 365 -sha256 -in client.csr -CA ca.pem -CAkey ca-key.pem \
  -CAcreateserial -out cert.pem -extfile extfile.cnf
Signature ok
subject=/CN=client
Getting CA Private Key
Enter pass phrase for ca-key.pem:

至此,Docker 客户端密钥 key 创建完毕

修改文件权限

  • 指令 1:删除两个证书签名请求文件
rm -v client.csr server.csr
  • 指令 2:修改密钥文件权限为只由所有者读取
chmod -v 0400 ca-key.pem key.pem server-key.pem
  • 指令 3:修改证书文件权限为只读
chmod -v 0444 ca.pem server-cert.pem cert.pem

启动 Docker 守护进程

下面的两种方法的介绍,只可二选一!

方式 1. 指令启动

  • 指令:
dockerd  --tlsverify=true \
 --tlscacert=/mnt/docker/tsl/ca.pem  \
 --tlscert=/mnt/docker/tsl/server-cert.pem  \
 --tlskey=/mnt/docker/tsl/server-key.pem  \
 --host tcp://0.0.0.0:2376  \
 --host unix:///var/run/docker.sock

方式 2. daemon.json 配置启动

  • 配置 /etc/docker/daemon.json 文件如下,注意,镜像地址与本文无关,可不配置,配置只为加速镜像拉取。
{
 "tlsverify": true,
  "tlscacert": "/mnt/docker/tsl/ca.pem",
  "tlscert": "/mnt/docker/tsl/server-cert.pem",
  "tlskey": "/mnt/docker/tsl/server-key.pem",
  "hosts": ["tcp://0.0.0.0:2376","unix:///var/run/docker.sock"],
  "registry-mirrors": ["https://5ehijrnq.mirror.aliyuncs.com"]
}
  • 然后通过 systemctl 正常启动,指令如下
systemctl restart docker

启动失败的处理办法

  • 如果在修改 /etc/docker/daemon.json 后遇到启动失败的问题,如
Job for docker.service failed because the control process exited with error code. See "systemctl status docker.service" and "journalctl -xe" for details.
  • 首先检查 json 文件的编写有没有错误,比方说是不是加多了一个 “,” 符号,如果确定 json 没有错,那通常是 json 文件的启动项和 dockerd 指令方法启动时附带的参数冲突了,这个时候建议不要使用 dockerd 指令启动,如果你只是简单地使用 systemctl start docker 启动,那这个时候通常只剩下一种可能性 --------systemd 的 docker.service 文件里面附带了与你 daemon.json 文件重复的配置项。这个时候处理方法如下:

    • 打开配置文件

      vim /usr/lib/systemd/system/docker.service
      
    • 找到其中 Service 下的内容,将其中的 -H fd:// --containerd=/run/containerd/containerd.sock 后的内容注释掉。如下所示:

      ···
      [Service]
      Type=notify
      # the default is not to use systemd for cgroups because the delegate issues still
      # exists and systemd currently does not support the cgroup feature set required
      # for containers run by docker
      ExecStart=/usr/bin/dockerd
          # -H fd:// --containerd=/run/containerd/containerd.sock(注释这一行)
      ExecReload=/bin/kill -s HUP $MAINPID
      TimeoutSec=0
      RestartSec=2
      Restart=always
      ···
      
    • 重新加载配置

    systemctl daemon-reload
    
    • 重新启动
    systemctl restart docker
    

验证远程控制

  • 客户端需要的 TLS 文件有 CA 证书 ca.pem, 客户端证书 cert.pem, 客户端密钥 key.pem 连接的指令格式如下,以 docker version 为例,其中 $HOST 为远程主机的域名或 ip
docker --tlsverify --tlscacert=ca.pem --tlscert=cert.pem --tlskey=key.pem -H=$HOST:2376 version

注意:此指令需要在客户端 TSL 的放置目录执行,即 ca.pemcert.pemkey.pem 三个文件在哪个目录,就在哪个目录执行此条指令。

  • 若看到 docker 的版本信息即为 TSL 配置成功
Client: Docker Engine - Community
 Version:           19.03.5
 API version:       1.40
 Go version:        go1.12.12
 Git commit:        633a0ea
 Built:             Wed Nov 13 07:25:41 2019
 OS/Arch:           linux/amd64
 Experimental:      false

Server: Docker Engine - Community
 Engine:
  Version:          19.03.5
  API version:      1.40 (minimum version 1.12)
  Go version:       go1.12.12
  Git commit:       633a0ea
  Built:            Wed Nov 13 07:24:18 2019
  OS/Arch:          linux/amd64
  Experimental:     false
 containerd:
  Version:          1.2.10
  GitCommit:        b34a5c8af56e510852c35414db4c1f4fa6172339
 runc:
  Version:          1.0.0-rc8+dev
  GitCommit:        3e425f80a8c931f88e6d94a8c831b9d5aa481657
 docker-init:
  Version:          0.18.0
  GitCommit:        fec3683

本地连接

  • 此处以 idea 为例,如图所示

    idea的docker安全连接配置-191c26240bd74efcaad579dfd5de4c00