引言

Docker采用C/S架构,包括客户端和服务端。Docker守护进程(Daemon)作为服务端接受来自客户端的请求,并处理这些请求( 创建、 运行、 分发容器)。Docker从 0.5.2 之后使用本地Unix套接字机制强制代替了原先绑定在127.0.0.1 上的套接字,以加强服务端的防护。用户任然可以使用HTTP提供REST API 访问。建议使用安全机制,确保只有可行的网络或VPN,或证书保护机制(例如受保护的stunnel和ssl认证)下的访问可以进行。 这里简单说明开启 REST API的过程。

安装环境

操作系统:ubuntu 16.04 Docker版本: 18.03.0-ce

开启REST API

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
$ systemctl show --property=FragmentPath docker
FragmentPath=/lib/systemd/system/docker.service 

# 修改 ExecStart=/usr/bin/dockerd -H fd:// 为 ExecStart=/usr/bin/dockerd -H unix:///var/run/docker.sock -H tcp://0.0.0.0:2376
$ sudo vi /lib/systemd/system/docker.service
$ sudo systemctl daemon-reload
$ sudo systemctl restart docker 
$ docker -H tcp://127.0.0.1:2376 version
Client:
 Version:	18.03.0-ce
 API version:	1.37
 Go version:	go1.9.4
 Git commit:	0520e24
 Built:	Wed Mar 21 23:10:01 2018
 OS/Arch:	linux/amd64
 Experimental:	false
 Orchestrator:	swarm

Server:
 Engine:
  Version:	18.03.0-ce
  API version:	1.37 (minimum version 1.12)
  Go version:	go1.9.4
  Git commit:	0520e24
  Built:	Wed Mar 21 23:08:31 2018
  OS/Arch:	linux/amd64
  Experimental:	false

由上面的结果可以看出, REST API 已经开启,但是没有使用加密,docker 不安全

前面的方式可以完成任务但是对原有脚本修改较大。可以使用下面的方式: 修改 /etc/default/docker , 将 DOCKER_OPTS 修改为 "-H=unix:///var/run/docker.sock -H=0.0.0.0:2375"。 然后执行 sudo service docker restart 即可。

在Ubuntu16.04 LTS 下 DOCKER_OPTS有可能不生效,需要做一下修改:

  1. 修改文件 /lib/systemd/system/docker.serviceExecStart=/usr/bin/dockerd 前增加一行(-代表ignore error)。
1
EnvironmentFile=-/etc/default/docker
  1. 修改文件 /lib/systemd/system/docker.service ,将 ExecStart=/usr/bin/docker daemon -H fd:// 修改为ExecStart=/usr/bin/docker daemon -H fd:// $DOCKER_OPTS

  2. 执行systemctl daemon-reload 重载默认配置

开启REST API 使用SSL加密

创建SSL证书,这里可以参考官方文档Protect the Docker daemon socket。 在参考文档中有已经写好的脚本,更加方便。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
#/bin/bash
# @author: anyesu

if [ $# != 1 ] ; then 
echo "USAGE: $0 [HOST_IP]" 
exit 1; 
fi 

#============================================#
#   下面为证书密钥及相关信息配置,注意修改   #
#============================================#
PASSWORD="8#QBD2$!EmED&QxK"
COUNTRY=CN
PROVINCE=yourprovince
CITY=yourcity
ORGANIZATION=yourorganization
GROUP=yourgroup
NAME=yourname
HOST=$1
SUBJ="/C=$COUNTRY/ST=$PROVINCE/L=$CITY/O=$ORGANIZATION/OU=$GROUP/CN=$HOST"

echo "your host is: $1"

# 1.生成根证书RSA私钥,PASSWORD作为私钥文件的密码
openssl genrsa -passout pass:$PASSWORD -aes256 -out ca-key.pem 4096

# 2.用根证书RSA私钥生成自签名的根证书
openssl req -passin pass:$PASSWORD -new -x509 -days 365 -key ca-key.pem -sha256 -out ca.pem -subj $SUBJ

#============================================#
#          用根证书签发server端证书          #
#============================================#

# 3.生成服务端私钥
openssl genrsa -out server-key.pem 4096

# 4.生成服务端证书请求文件
openssl req -new -sha256 -key server-key.pem -out server.csr -subj "/CN=$HOST"

# 5.使tls连接能通过ip地址方式,绑定IP
echo subjectAltName = IP:127.0.0.1,IP:$HOST > extfile.cnf

# 6.使用根证书签发服务端证书
openssl x509 -passin pass:$PASSWORD -req -days 365 -sha256 -in server.csr -CA ca.pem -CAkey ca-key.pem -CAcreateserial -out server-cert.pem -extfile extfile.cnf


#============================================#
#          用根证书签发client端证书          #
#============================================#

# 7.生成客户端私钥
openssl genrsa -out key.pem 4096

# 8.生成客户端证书请求文件
openssl req -subj '/CN=client' -new -key key.pem -out client.csr

# 9.客户端证书配置文件
echo extendedKeyUsage = clientAuth > extfile.cnf

# 10.使用根证书签发客户端证书
openssl x509 -passin pass:$PASSWORD -req -days 365 -sha256 -in client.csr -CA ca.pem -CAkey ca-key.pem -CAcreateserial -out cert.pem -extfile extfile.cnf

#============================================#
#                    清理                    #
#============================================#
# 删除中间文件
rm -f client.csr server.csr ca.srl extfile.cnf

# 转移目录
mkdir client server
cp {ca,cert,key}.pem client
cp {ca,server-cert,server-key}.pem server
rm {cert,key,server-cert,server-key}.pem

# 设置私钥权限为只读
chmod -f 0400 ca-key.pem server/server-key.pem client/key.pem

执行服务端配置

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
 chmod +x tlscert.sh
HOST_IP=127.0.0.1
./tlscert.sh $HOST_IP
# 客户端需要的证书保存在client目录下, 服务端需要的证书保存在server目录下
sudo cp server/* /etc/docker
# 修改配置
sudo vi /etc/default/docker
# 改为 DOCKER_OPTS="--selinux-enabled --tlsverify --tlscacert=/etc/docker/ca.pem --tlscert=/etc/docker/server-cert.pem --tlskey=/etc/docker/server-key.pem -H=unix:///var/run/docker.sock -H=0.0.0.0:2375"

# 重启docker
sudo service docker restart

在Ubuntu16.04 LTS 下 DOCKER_OPTS有可能不生效,参见上面的修改或参考文档

客户端访问方式

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
# 客户端加tls参数访问
docker --tlsverify --tlscacert=client/ca.pem --tlscert=client/cert.pem --tlskey=client/key.pem -H tcp://127.0.0.1:2375 version

# Docker API方式访问
curl https://127.0.0.1:2375/images/json --cert client/cert.pem --key client/key.pem --cacert client/ca.pem

# 简化客户端调用参数配置
sudo cp client/* ~/.docker
# 方式一
docker --tlsverify -H tcp://127.0.0.1:2375 version


# 方式二
# 追加环境变量
echo -e "export DOCKER_HOST=tcp://$HOST_IP:2375 DOCKER_TLS_VERIFY=1" >> ~/.bashrc

sudo docker version

参考资料

  1. Docker Daemon连接方式详解
  2. Docker 配置文件配置无效 /etc/default/docker