Docker Private Registry是私有的Docker Image存储池。其Registry v2 源码是公开的。
互联网上有很多关于使用安全的TLS搭建Docker Private Registry失败的问题,有一些work around的建议是使用 --insecure-registry
选项,也就是通过不使用安全的HTTPS-TLS方式来暂时绕过这个问题。
使用 --insecure-registry
的work aroud:
sudo vi /etc/default/docker
DOCKER_OPTS="$DOCKER_OPTS --insecure-registry=www.example.com:8080"
然后执行: sudo service docker restart
而搭建Private Registry的目的往往是因为要建立自有的Docker Image池,否则干嘛不使用公开的Docker Hub上那么多成熟的并且很多是由官方维护的Docker Images呢?出于此目的,对于Private Registry的安全机制就要格外关注,决不能简单使用上述的work around来绕过。
安全有两个方面:
1. Registry自身的权威性。这是基本的安全,若一个Registry很重要,那么需要从该Registry获取Image的Docker客户端就必须能够通过证书机制验证它的权威性。
2. Docker客户端与Registry交互的安全性。如果一个Registry暴露在互联网上,但是只对某个组织开放存取Images,那么仅验证Registry权威性是不够的,还需要有密码或双向SSL(Dual-way SSL)机制保证只有该组织内获得授权的Docker客户端(用来制造和上传Docker Image的机器)能够push,或者组织内需要下载该Registry内Images的Docker客户端能够pull。
接下来就这两个方面的安全,阐述如何搭建Docker Private Registry。
如果不了解公开秘钥加密体系原理,请Google相关内容,或参考 OpenSSL 与 SSL 数字证书概念贴 和SSL/TLS原理详解。这里仅阐述互联网如何应用该原理打造一套可信任体系的。
首先早期互联网基础架构人先建立了Root CA,用来对证书进行签名,随着互联网规模越来越大,仅几个root CA已经不足以应付需求,于是采用了类似DNS逐层授权的模式,建立多个Intermediate CA,这些CA本质上都可以为站点证书进行签名。
当CA用自己的私钥为证书进行了签名之后,得让客户端方便地获取CA的公钥。现实操作中,各个操作系统、浏览器等客户端采取将可信Root CA以及部分Intermediate CA公钥内置的办法,当安装这些软件的时候,这些公钥就已经就绪了,还有一些客户端采取的办法是从操作系统中获取CA公钥。CA公钥也以证书的形式提供。(证书是对密钥进行签名/Sign和散列/Hash之后的结果)
在人们使用客户端访问一个带有SSL TLS证书站点的时候,为了证明这些站点的证书就是真正的,客户端就用内置的CA证书对这些经过CA私钥签名过的证书进行校验。
因此,当站点安装了可信证书但客户端却报安全错误的时候,需要检查CA的证书是否已经下载到本地,可否被客户端正确读取。
根据Docker官方文档,获取安全证书有两个办法:一是从互联网上的认证CA处获取,二是自己建CA自己给证书签名。
1. 我们先准备一台Linux机器,叫做dockie.mydomain.com,有公网IP,准备用作Docker Private Registry服务器。确保该服务器80,443端口可以从互联网上访问到。
公网IP是为了申请Let's Encrypt证书。在获得证书之后,这个公网IP其实不是必须的。因此可以在某些拥有公网IP的机器上获取证书,再将证书转移到使用私网IP的Registry服务器上。若采用这样的方式,Docker客户端需要将私网IP和dockie.mydomain.com对应写入hosts文件或将该解析写到私网DNS服务器里。但证书到期renew的时候还需要同样的公网域名(公网IP可以不同)。
$ git clone https://github.com/letsencrypt/letsencrypt.git $ cd letsencrypt $ sudo ./letsencrypt-auto根据该向导,选用
standalone
模式,最后获取到的证书文件放在 /etc/letsencrypt/archive/dockie.mydomain.com/
。首先备份这些证书到自己电脑上去。 $ sudo mkdir /opt/data $ sudo mkdir /opt/certs
$ sudo cp /etc/letsencrypt/archive/dockie.mydomain.com/cert*.pem /opt/certs/dockie.mydomain.com.crt $ sudo cp /etc/letsencrypt/archive/dockie.mydomain.com/*key*.pem /opt/certs/dockie.mydomain.com.key
$ sudo apt-get install docker
- RedHat/CentOS:
`$ sudo yum install docker`
此时执行`$ sudo docker ps`和`$ sudo docker images`命令可以看到本机里没有任何image和运行的container。
$ sudo docker pull registry:2
$ sudo docker image
可以看到本服务器已经有了image,执行 $ sudo docker ps
可确认该image并未运行。 - 使用Docker命令方法:
```
$ sudo docker run -d -p 5000:5000 --restart=always --name registry /
-v /opt/data:/var/lib/registry -v /opt/certs:/certs /
-e REGISTRY_HTTP_TLS_CERTIFICATE=/certs/dockie.mydomain.com.crt /
-e REGISTRY_HTTP_TLS_KEY=/certs/dockie.mydomain.com.key registry:2
```
使用Yaml编排方法:
建立一个 docker-compose.yml
文件,其内容如下:
registry: restart: always image: registry:2 ports: - 5000:5000 environment: REGISTRY_HTTP_TLS_CERTIFICATE: /certs/dockie.mydomain.com.crt REGISTRY_HTTP_TLS_KEY: /certs/dockie.mydomain.com.key volumes: - /path/data:/var/lib/registry - /path/certs:/certs然后再执行
$ sudo docker-compose up -d
此时服务器上安装并运行一个Private Registry的工作就完成了。通过执行 $ sudo docker ps
可以查看运行Registry Image的容器。接下来介绍在Docker客户端上如何与该Private Registry通信。
Docker客户端可以是服务器本身,也可以是另一个安装了Docker Engine的计算机(Linux/Windows/Mac OS),以另一个计算机为例。
在Docker客户端上,确认执行 ping dockie.mydomain.com
可以获得正确IP, telnet dockie.mydomain.com 5000
能够获得响应,屏幕显示如下:
Trying xx.xx.xx.xx(服务器IP)... Connected to dockie.mydomain.com. Escape character is '^]'.查看站点证书,执行命令:
openssl s_client -showcerts -verify 32 -connect dockie.mydomain.com:5000
使用curl来测试TLS是否工作正常。执行命令:
$ curl -i -k -v https://dockie.mydomain.com:5000
若正常,则有以下返回提示:
```
* Rebuilt URL to: https://dockie.mydomain.com:5000/
* Trying xx.xx.xx.xx(服务器IP)...
* Connected to dockie.mydomain.com (xx.xx.xx.xx) port 5000 (#0)
* Initializing NSS with certpath: sql:/etc/pki/nssdb
* skipping SSL peer certificate verification
* SSL connection using TLS_RSA_WITH_AES_128_CBC_SHA
* Server certificate:
* subject: CN=dockie.mydomain.com
* start date: May 05 00:48:00 2016 GMT
* expire date: Aug 03 00:48:00 2016 GMT
* common name: dockie.mydomain.com
* issuer: CN=Let's Encrypt Authority X3,O=Let's Encrypt,C=US
GET / HTTP/1.1
User-Agent: curl/7.40.0
Host: dockie.mydomain.com:5000
Accept: /
< HTTP/1.1 200 OK
HTTP/1.1 200 OK
< Cache-Control: no-cache
Cache-Control: no-cache
< Date: Sat, 07 May 2016 06:28:17 GMT
Date: Sat, 07 May 2016 06:28:17 GMT
< Content-Length: 0
Content-Length: 0
< Content-Type: text/plain; charset=utf-8
Content-Type: text/plain; charset=utf-8
```
若没有正常响应,先检查Docker客户端与服务器是否使用同样的时区并用ntp进行过校准。分别在两台机器上执行 $ date
查看当前时区与时间。若不同,则Google搜索相关文档并参考校准。
需要说明的是:即使curl测试通过了,Docker客户端仍然有可能不工作。这其实是因为Docker对Let's Encrypt证书支持并不到位,所以必须手工安装root CA和Intermediate CA证书。
某些版本Docker使用自己的目录/etc/docker/certs.d/存储CA证书,某些版本使用操作系统的CA证书路径。这造成了文档方面极大的混乱。
在使用操作系统CA证书的时候,Docker会按照以下方法进行: 按照源码crypto/x509/root_unix.go,Go语言 (Docker所使用的语言) 将会在以下文件中检查CA证书。
"/etc/ssl/certs/ca-certificates.crt", // Debian/Ubuntu/Gentoo etc.
"/etc/pki/tls/certs/ca-bundle.crt", // Fedora/RHEL
"/etc/ssl/ca-bundle.pem", // OpenSUSE
"/etc/ssl/cert.pem", // OpenBSD
"/usr/local/share/certs/ca-root-nss.crt", // FreeBSD/DragonFly
"/etc/pki/tls/cacert.pem", // OpenELEC
"/etc/certs/ca-certificates.crt", // Solaris 11.2+
建议在安装证书的时候,首先采用安装到操作系统路径,若Docker还是报unknow authority错误再安装到Docker自己定义的路径中。具体安装方法见下文。
如果响应中有 unknow authority
,那么需要安装Let's Encrypt的授权机构证书。在 Let's Encrypt Certificates 页面可以找到对其授权的root CA证书以及其本身的证书,将pem格式证书下载到Docker客户端机器上。具体选择哪个,可使用Chrome浏览器访问 https://dockie.mydomain.com:5000 ,点击链接旁变绿色的小锁子,再点“详细信息”,如下图:
![Chrome查看证书步骤1]( http://7xllsv.com1.z0.glb.clou ... 1.png "点绿色小锁子再点“详细信息”")
然后在右侧控制台点击“View Certificates”按钮,如下图:
![Chrome查看证书步骤2]( http://7xllsv.com1.z0.glb.clou ... 2.png "点“View Certificates”")
最后在弹出的窗口中点击“证书路径”,如下图:
![Chrome查看证书步骤3]( http://7xllsv.com1.z0.glb.clou ... 3.png "点击“证书路径”")
此时即可查看该证书对应的上级CA证书都是哪些类型。接下来下载pem格式证书,本例中,下载的证书分别为:ISRG Root X1和Let’s Encrypt Authority X3 (IdenTrust cross-signed)。前者是root CA证书,后者是Intermediate CA证书。如下图:
![下载CA证书]( http://7xllsv.com1.z0.glb.clou ... a.png "下载PEM格式证书")
接下来就很关键, 必须将两个CA证书都安装 ,否则执行 $ sudo docker push
的时候就可能还会有错误提示:
x509: certificate signed by unknown authority
下载证书:
$ sudo wget https://letsencrypt.org/certs/isrgrootx1.pem $ sudo wget https://letsencrypt.org/certs/lets-encrypt-x3-cross-signed.pem
将证书安装到Docker客户端机器的操作系统中。
$ sudo mkdir /usr/local/share/ca-certificates/dockie.mydomain.com $ sudo cat lets-encrypt-x3-cross-signed.pem >> /usr/local/share/ca-certificates/dockie.mydomain.com/ca.crt $ sudo cat isrgrootx1.pem >> /usr/local/share/ca-certificates/dockie.mydomain.com/ca.crt $ sudo update-ca-certificates
$ sudo yum install ca-certificates $ sudo update-ca-trust force-enable $ sudo cat lets-encrypt-x3-cross-signed.pem >> /etc/pki/ca-trust/source/anchors/dockie.mydomain.com.crt $ sudo cat isrgrootx1.pem >> /etc/pki/ca-trust/source/anchors/dockie.mydomain.com.crt $ sudo update-ca-trust extract
$ sudo cat lets-encrypt-x3-cross-signed.pem >> /etc/pki/tls/certs/ca-bundle.crt $ sudo cat isrgrootx1.pem >> /etc/pki/tls/certs/ca-bundle.crt
接下来,就可以愉快地在Docker客户端上向Docker Private Registry push 一个Image:
首先先下载一个Ubuntu:$ sudo docker pull ubuntu
$ sudo docker tag ubuntu dockie.mydomain.com:5000/ubuntu
sudo docker push dockie.mydomain.com:5000/ubuntu
大功告成!
小技巧:若无法方便地将Base Images从公共Registry中pull回来,可以先在一台可以pull的服务器上pull image,然后使用命令 sudo docker save -o 镜像名.tar 镜像ID
予以导出成文件,将文件通过scp或ftp传输到Private Registry上,再使用命令 sudo docker load < 镜像名.tar
进行导入。
docker export 导出的是Container,docker save导出的是Image,若导入docker export导出的文件,需要使用docker import命令。
~~验证证书 (不必要,这步反而不work)参考文档~~
~~ $ openssl s_client -connect dockie.mydomain.com:5000 -CApath /etc/ssl/cert
~~
~~The first thing to look for is the certificate chain near the top of the output. This should show the CA as the issuer (next to i:). This tells you that the server is presenting a certificate signed by the CA you're installing.
Second, look for the verify return code at the end to be set to 0 (ok).~~
自己建立CA最常见的用法是用于测试,通常是在一台服务器上,既做CA也做站点,然后把CA证书拷贝到客户端(很多时候客户端也在同一服务器)。所以这种方式和使用Let's Encrypt证书有异曲同工之处,都需要让客户端正确使用CA证书。具体CA的建立方法以及站点证书的获取可参考 基于OpenSSL自建CA和颁发SSL证书 。获得站点证书和CA证书后,参考以上设置Let's Encrypt证书的方法进行设置。
如本文开头所述,客户端与Docker Private Registry进行安全交互可以使用密码或双向SSL。双路SSL的基本原理就是把客户端访问可信站点的方法路径反方向也实施一次,这样Registry就可以对有授权的客户端开放服务。由于此方法比较复杂,除非极度机密的通信采用该模式,一般通信均不用。
这里仅介绍使用密码的方式。
参考 Docker官方文档 ,将 docker-compose.yml
文件修改为:
registry: restart: always image: registry:2 ports: - 5000:5000 environment: REGISTRY_HTTP_TLS_CERTIFICATE: /certs/dockie.mydomain.com.crt REGISTRY_HTTP_TLS_KEY: /certs/dockie.mydomain.com.key REGISTRY_AUTH: htpasswd REGISTRY_AUTH_HTPASSWD_PATH: /auth/htpasswd REGISTRY_AUTH_HTPASSWD_REALM: Registry Realm volumes: - /opt/data:/var/lib/registry - /opt/certs:/certs - /opt/auth:/auth
使用Apache的htpasswd,生成用户名和密码,放在/opt/auth目录下即可。参考 Digital Ocean的介绍文章