# 环境
- Docker Client 24.0.2 本地客户端(MacOS 14)
- Docker Server 24.0.4 远程服务端(Ubuntu 22.0.4)
# 前提
在日常开发环境中,可以通过开放远程 Docker 服务端 tcp://0.0.0.0:2375
让 Docker 客户端可以通过 TCP 连接访问该远程 Docker 服务,然而在公网的条件下,直接开放 2375 端口是不安全的。为了通过安全连接使用 Docker Socket,官方提供了 TLS 加密和证书认证。
本篇文章中补充说明了全部的流程,同时将步骤用单一的脚本实现方便后续的批量自动化创建证书和开放对应的端口。
# 服务器配置证书
在远程服务器上执行下述步骤:
变量定义
定义服务器的 HOST 变量export HOST=example.com
export PASS=password
export CN=example
export ORG="Example Inc."
export IP=100.64.0.1
这里的
example.com
替换为你的远程服务器对应的域名,password
为你设置的自定义 PEM PASS。创建存放证书的目录
mkdir ~/docker-certs
~/docker-certs
可以替换为你想要存放的证书位置创建服务端证书
openssl genrsa -aes256 -passout pass:$PASS -out ca-key.pem 4096
生成
ca.pem
openssl req -new -x509 -passin pass:$PASS -days 3650 -key ca-key.pem -sha256 -out ca.pem -subj "/CN=$CN/O=$ORG"
创建服务端
server-key.pem
openssl genrsa -out server-key.pem 4096
创建中间
csr
openssl req -passin pass:$PASS -subj "/CN=$HOST" -sha256 -new -key server-key.pem -out server.csr
创建可信赖的服务端 IP 以及域名(仅通过该 IP 或者域名连接时服务器才可以接受,防止中间人代理)
echo subjectAltName = DNS:$HOST,IP:$IP,IP:127.0.0.1 >> extfile.cnf
echo extendedKeyUsage = serverAuth >> extfile.cnf
生成
server-cert.pem
openssl x509 -passin pass:$PASS -req -days 3650 -sha256 -in server.csr -CA ca.pem -CAkey ca-key.pem -CAcreateserial -out server-cert.pem -extfile extfile.cnf
将会得到以下提示:
Certificate request self-signature ok
subject=CN = example.com
上述步骤生成了所需要的服务端证书文件
ca.pem
、server-cert.pem
、server-key.pem
,其余中间文件都可以删除。
# 客户端证书
生成客户端
key.pem
openssl genrsa -out key.pem 4096
生成中间
csr
文件openssl req -subj '/CN=client' -new -key key.pem -out client.csr
生成客户端的
cert.pem
文件echo extendedKeyUsage = clientAuth > extfile-client.cnf
openssl x509 -passin pass:$PASS -req -days 3650 -sha256 -in client.csr -CA ca.pem -CAkey ca-key.pem -CAcreateserial -out cert.pem -extfile extfile-client.cnf
得到提示结果:
Certificate request self-signature ok
subject=CN = client
得到客户端证书文件
ca.pem
,cert.pem
,key.pem
# 删除中间文件
rm -v client.csr server.csr extfile.cnf extfile-client.cnf |
# 设置证书权限 (可选)
这一步主要是防止文件被服务器上的其他用户访问
chmod -v 0400 ca-key.pem key.pem server-key.pem | |
chmod -v 0444 ca.pem server-cert.pem cert.pem |
其中数字 4
二进制 100
代表可读不可写不可执行。
# 修改 Docker 服务端的配置文件
Ubuntu 自带有
systemd
管理服务,因此主要修改 docker 的 systemd 配置,其路径在/lib/systemd/system/docker.service
sudo vim /lib/systemd/system/docker.service
修改配置文件中
ExecStart
这一条[Unit] Description=Docker Application Container Engine Documentation=https://docs.docker.com After=network-online.target docker.socket firewalld.service containerd.service time-set.target Wants=network-online.target containerd.service Requires=docker.socket [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 --tlsverify --tlscacert=/home/ubuntu/docker-certs/ca.pem --tlscert=/home/ubuntu/docker-certs/server-cert.pem --tlskey=/home/ubuntu/docker-certs/server-key.pem -H=0.0.0.0:2376 ExecReload=/bin/kill -s HUP $MAINPID TimeoutStartSec=0 RestartSec=2 Restart=always # Note that StartLimit* options were moved from "Service" to "Unit" in systemd 229. # Both the old, and new location are accepted by systemd 229 and up, so using the old location # to make them work for either version of systemd. StartLimitBurst=3 # Note that StartLimitInterval was renamed to StartLimitIntervalSec in systemd 230. # Both the old, and new name are accepted by systemd 230 and up, so using the old name to make # this option work for either version of systemd. StartLimitInterval=60s # Having non-zero Limit*s causes performance problems due to accounting overhead # in the kernel. We recommend using cgroups to do container-local accounting. LimitNOFILE=infinity LimitNPROC=infinity LimitCORE=infinity # Comment TasksMax if your systemd version does not support it. # Only systemd 226 and above support this option. TasksMax=infinity # set delegate yes so that systemd does not reset the cgroups of docker containers Delegate=yes # kill only the docker process, not all processes in the cgroup KillMode=process OOMScoreAdjust=-500 [Install] WantedBy=multi-user.target
主要增加
--tlsverify --tlscacert=/home/ubuntu/docker-certs/ca.pem --tlscert=/home/ubuntu/docker-certs/server-cert.pem --tlskey=/home/ubuntu/docker-certs/server-key.pem -H=0.0.0.0:2376
- 重启服务以生效
sudo systemctl daemon-reload
sudo systemctl restart docker
- 查看服务是否正常运行
sudo systemctl status docker
# 客户端 Docker 证书配置
在上述步骤中已经将 Docker 开放到公网的端口 2376 上,下述步骤将配置 Docker 客户端来连上远程 Docker 服务。
下载服务端客户端到本地💻上:
scp -r {remoteUserName}@{remoteIP}:~/docker-certs/{ca.pem,cert.pem,key.pem} ./
上述命令使用
scp
将远程客户端文件下载到本地当前目录./
中,可以通过PWD
命令查看当前目录位置。其中{remoteUserName}
和{remoteIP}
是 SSH 的账户和 SSH 服务器 IP。使用
docker context
创建对应的远程上下文docker context create {name} --docker host=tcp://$HOST:2376
其中
{name}
为上下文名称配置客户端证书
使用docker inspect {name}
来获取配置文件的位置。docker inspect {name}
这个命令会返回一个 JSON 格式的结果,其中
{name}
上下文对应的Storage
中的TLSPath
即为证书目录。根据这个目录直接创建对应的文件夹📁mkdir -p /Users/{accountName}/.docker/contexts/tls/4e96961b0f053ff965906be405bbb8b53f3d716136cb9c78b9b9875fd7ef84d0/docker
类似于这样的路径,其中路径中 HASH 值每个上下文唯一,
{accountName}
为账户名,如果是 Window 的则是C:\Users\{accountName}\...
复制步骤 1. 中下载的三个文件到该目录下cp -r ./{ca.pem,cert.pem,key.pem} /Users/{accountName}/.docker/contexts/tls/4e96961b0f053ff965906be405bbb8b53f3d716136cb9c78b9b9875fd7ef84d0/docker
切换到对应的上下文
docker context use {name}
检查服务端版本是否正确
docker version
此时可以在本地调用 docker
的命令来操控远程的服务端。
# 证书创建一键式脚本
命名为 gencert.sh
, 并赋予执行权限 chmod +x gencert.sh
#!/bin/bash | |
while [[ $# -gt 0 ]]; do | |
case "$1" in | |
--host) | |
HOST="$2" | |
shift 2 | |
;; | |
--pass) | |
PASS="$2" | |
shift 2 | |
;; | |
--cn) | |
CN="$2" | |
shift 2 | |
;; | |
--org) | |
ORG="$2" | |
shift 2 | |
;; | |
--ip) | |
IP="$2" | |
shift 2 | |
;; | |
*) | |
echo "Unknown option: $1" | |
exit 1 | |
;; | |
esac | |
done | |
# 检查是否所有参数都已经设置 | |
if [[ -z $HOST || -z $PASS || -z $CN || -z $ORG || -z $IP ]]; then | |
echo "USAGE: gencert.sh --host host --pass pass --cn cn --org org --ip ip" | |
exit 1 | |
fi | |
mkdir -p /home/docker/docker-certs | |
cd /home/docker/docker-certs | |
openssl genrsa -aes256 -passout pass:$PASS -out ca-key.pem 4096 | |
openssl req -new -x509 -passin pass:$PASS -days 3650 -key ca-key.pem -sha256 -out ca.pem -subj "/CN=$CN/O=$ORG" | |
openssl genrsa -out server-key.pem 4096 | |
openssl req -passin pass:$PASS -subj "/CN=$HOST" -sha256 -new -key server-key.pem -out server.csr | |
echo subjectAltName = DNS:$HOST,IP:$IP,IP:127.0.0.1 >> extfile.cnf | |
echo extendedKeyUsage = serverAuth >> extfile.cnf | |
openssl x509 -passin pass:$PASS -req -days 3650 -sha256 -in server.csr -CA ca.pem -CAkey ca-key.pem -CAcreateserial -out server-cert.pem -extfile extfile.cnf | |
openssl genrsa -out key.pem 4096 | |
openssl req -subj '/CN=client' -new -key key.pem -out client.csr | |
echo extendedKeyUsage = clientAuth > extfile-client.cnf | |
openssl x509 -passin pass:$PASS -req -days 3650 -sha256 -in client.csr -CA ca.pem -CAkey ca-key.pem -CAcreateserial -out cert.pem -extfile extfile-client.cnf | |
rm -v client.csr server.csr extfile.cnf extfile-client.cnf | |
chmod -v 0400 ca-key.pem key.pem server-key.pem | |
chmod -v 0444 ca.pem server-cert.pem cert.pem | |
sudo sed -i 's/ExecStart=\/usr\/bin\/dockerd -H fd:\/\/ --containerd=\/run\/containerd\/containerd.sock/ExecStart=\/usr\/bin\/dockerd -H fd:\/\/ --containerd=\/run\/containerd\/containerd.sock --tlsverify --tlscacert=\/home\/docker\/docker-certs\/ca.pem --tlscert=\/home\/docker\/docker-certs\/server-cert.pem --tlskey=\/home\/docker\/docker-certs\/server-key.pem -H=0.0.0.0:2376/g' /lib/systemd/system/docker.service | |
sudo systemctl daemon-reload | |
sudo systemctl restart docker | |
sudo systemctl status docker | |
echo "done!" |
# 参考文档等
- https://docs.docker.com/engine/security/protect-access/
- ChatGPT