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的证书是否已经下载到本地,可否被客户端正确读取。


# 权威Registry #


根据 ​​Docker官方文档​

,获取安全证书有两个办法:一是从互联网上的认证CA处获取,二是自己建CA自己给证书签名。


  • 从认证CA处获得签名证书,大多数是需要付出一定费用的,近些年也有认证CA提供免费证书,例如​​Let’s Encrypt​​​。需要值得注意的是,费用低廉或免费的认证CA往往是intermediate CA,很多客户端(操作系统、浏览器等)并不默认信任它们,用户需要根据相应客户端CA安装机制来进行安装。下文使用​​Let’s Encrypt​​的例子您将清楚地看到这个步骤。
  • 自建CA并签名证书的方式所带来的问题是CA本身的维护以及客户端方面的维护。要保证自建CA的安全需要有比较扎实的基础安全知识,维护它的运转需要有对签名流程进行干预的控制能力,或自动、或手工。客户端方面,同样需要对所有客户端按照其CA安装机制来进行额外安装。若将自建CA维护到与认证CA同等的安全性和便利性,所付出的代价将超过付费证书。因此这种方式主要用于试验性环境。


采用​​Let's Encrypt​​搭建Private Registry

  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可以不同)。

  1. 在该服务器上获取证书:
    $ git clone https://github.com/letsencrypt/letsencrypt.git $ cd letsencrypt $ sudo ./letsencrypt-auto
    根据该向导,选用​​​standalone​​​模式,最后获取到的证书文件放在​​/etc/letsencrypt/archive/dockie.mydomain.com/​​。首先备份这些证书到自己电脑上去。
  2. 在服务器上准备证书目录和Image存储目录
    $ sudo mkdir /opt/data $ sudo mkdir /opt/certs
  3. 将获取的证书拷贝到证书目录
    $ 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
  4. 安装Docker Engine
  • Debian/Ubuntu: ​​$ sudo apt-get install docker​
  • RedHat/CentOS: ​​$ sudo yum install docker​​​ 此时执行​​$ sudo docker ps​​和​​$ sudo docker images​​命令可以看到本机里没有任何image和运行的container。
  1. Docker的Registry(当前版本为2.0)已经被制作成容器并放在Docker Hub中,简单地将其pull下来:
    ​​​$ sudo docker pull registry:2​​​ 待pull执行完毕再执行​​$ sudo docker image​​可以看到本服务器已经有了image,执行​​$ sudo docker ps​​可确认该image并未运行。
  2. 运行Registry
  • 使用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][2]的授权机构证书。在[Let's Encrypt Certificates][3]页面可以找到对其授权的root CA证书以及其本身的证书,将pem格式证书下载到Docker客户端机器上。具体选择哪个,可使用Chrome浏览器访问 ​​https://dockie.mydomain.com:5000​

,点击链接旁变绿色的小锁子,再点“详细信息”,如下图:


​​



然后在右侧控制台点击“View Certificates”按钮,如下图:


​​



最后在弹出的窗口中点击“证书路径”,如下图:


​​



此时即可查看该证书对应的上级CA证书都是哪些类型。接下来下载pem格式证书,本例中,下载的证书分别为:ISRG Root X1和Let’s Encrypt Authority X3 (IdenTrust cross-signed)。前者是root CA证书,后者是Intermediate CA证书。如下图:


​​



接下来就很关键,

必须将两个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客户端机器的操作系统中。


  • Debian/Ubuntu
    $ 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
  • RedHat/CentOS 6.x
    $ 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
  • RedHat/CentOS 5.x
    $ 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
  • Windows/Mac OS
    参考[Adding trusted root certificates to the server][5]


接下来,就可以愉快地在Docker客户端上向Docker Private Registry push 一个Image:


首先先下载一个Ubuntu:

​$ sudo docker pull ubuntu​


然后打tag:

​$ sudo docker tag ubuntu dockie.mydomain.com:5000/ubuntu​


最后传给Private Registry:

​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 &lt; 镜像名.tar​​​进行导入。


docker export 导出的是Container,docker save导出的是Image,若导入docker export导出的文件,需要使用docker import命令。

采用自建CA搭建Private Registry


自己建立CA最常见的用法是用于测试,通常是在一台服务器上,既做CA也做站点,然后把CA证书拷贝到客户端(很多时候客户端也在同一服务器)。所以这种方式和使用Let's Encrypt证书有异曲同工之处,都需要让客户端正确使用CA证书。具体CA的建立方法以及站点证书的获取可参考 ​​基于OpenSSL自建CA和颁发SSL证书​

。获得站点证书和CA证书后,参考以上设置Let's Encrypt证书的方法进行设置。



# Client与Registry的安全交互 #


如本文开头所述,客户端与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的介绍文章​