# 环境

  • 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 加密和证书认证。
本篇文章中补充说明了全部的流程,同时将步骤用单一的脚本实现方便后续的批量自动化创建证书和开放对应的端口。

# 服务器配置证书

在远程服务器上执行下述步骤:

  1. 变量定义
    定义服务器的 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。

  2. 创建存放证书的目录

    mkdir ~/docker-certs

    ~/docker-certs 可以替换为你想要存放的证书位置

  3. 创建服务端证书

    openssl genrsa -aes256 -passout pass:$PASS -out ca-key.pem 4096
  4. 生成 ca.pem

    openssl req -new -x509 -passin pass:$PASS -days 3650 -key ca-key.pem -sha256 -out ca.pem -subj "/CN=$CN/O=$ORG"
  5. 创建服务端 server-key.pem

    openssl genrsa -out server-key.pem 4096
  6. 创建中间 csr

    openssl req -passin pass:$PASS -subj "/CN=$HOST" -sha256 -new -key server-key.pem -out server.csr
  7. 创建可信赖的服务端 IP 以及域名(仅通过该 IP 或者域名连接时服务器才可以接受,防止中间人代理)

    echo subjectAltName = DNS:$HOST,IP:$IP,IP:127.0.0.1 >> extfile.cnf
    echo extendedKeyUsage = serverAuth >> extfile.cnf
  8. 生成 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.pemserver-cert.pemserver-key.pem ,其余中间文件都可以删除。

# 客户端证书

  1. 生成客户端 key.pem

    openssl genrsa -out key.pem 4096
  2. 生成中间 csr 文件

    openssl req -subj '/CN=client' -new -key key.pem -out client.csr
  3. 生成客户端的 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.pemcert.pemkey.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 服务端的配置文件

  1. Ubuntu 自带有 systemd 管理服务,因此主要修改 docker 的 systemd 配置,其路径在 /lib/systemd/system/docker.service

    sudo vim /lib/systemd/system/docker.service
    
  2. 修改配置文件中 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

    1. 重启服务以生效
    sudo systemctl daemon-reload
    sudo systemctl restart docker
    1. 查看服务是否正常运行
    sudo systemctl status docker

# 客户端 Docker 证书配置

在上述步骤中已经将 Docker 开放到公网的端口 2376 上,下述步骤将配置 Docker 客户端来连上远程 Docker 服务。

  1. 下载服务端客户端到本地💻上:

    scp -r {remoteUserName}@{remoteIP}:~/docker-certs/{ca.pem,cert.pem,key.pem} ./

    上述命令使用 scp 将远程客户端文件下载到本地当前目录 ./ 中,可以通过 PWD 命令查看当前目录位置。其中 {remoteUserName}{remoteIP} 是 SSH 的账户和 SSH 服务器 IP。

  2. 使用 docker context 创建对应的远程上下文

    docker context create {name} --docker host=tcp://$HOST:2376

    其中 {name} 为上下文名称

  3. 配置客户端证书
    使用 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
    
  4. 切换到对应的上下文

    docker context use {name}
  5. 检查服务端版本是否正确

    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
更新于 阅读次数