搭建安全的 Docker Private Registry 完全指南

2016-05-08 20:18:27 +08:00
 adrianzhang

Docker Private Registry 是私有的 Docker Image 存储池。其[Registry v2 源码][12]是公开的。

互联网上有很多关于使用安全的 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 数字证书概念贴][6]和[SSL/TLS 原理详解][7]。这里仅阐述互联网如何应用该原理打造一套可信任体系的。

首先早期互联网基础架构人先建立了 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 官方文档][1],获取安全证书有两个办法:一是从互联网上的认证 CA 处获取,二是自己建 CA 自己给证书签名。

采用[Let's Encrypt][2]搭建 Private Registry

  1. 我们先准备一台 Linux 机器,叫做 dockie.mydomain.com ,有公网 IP ,准备用作 Docker Private Registry 服务器。确保该服务器 80 , 443 端口可以从互联网上访问到。

公网 IP 是为了申请[Let's Encrypt][2]证书。在获得证书之后,这个公网 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/。首先备份这些证书到自己电脑上去。

  1. 在服务器上准备证书目录和 Image 存储目录
$ sudo mkdir /opt/data
$ sudo mkdir /opt/certs
  1. 将获取的证书拷贝到证书目录
$ 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
  1. 安装 Docker Engine
    • Debian/Ubuntu: $ sudo apt-get install docker
    • RedHat/CentOS: $ sudo yum install docker 此时执行$ sudo docker ps$ sudo docker images命令可以看到本机里没有任何 image 和运行的 container 。
  2. Docker 的 Registry (当前版本为 2.0 )已经被制作成容器并放在 Docker Hub 中,简单地将其 pull 下来: $ sudo docker pull registry:2 待 pull 执行完毕再执行$ sudo docker image可以看到本服务器已经有了 image ,执行$ sudo docker ps可确认该 image 并未运行。
  3. 运行 Registry
$ 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
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 客户端机器的操作系统中。

$ 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 然后打 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 < 镜像名.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 证书][8]。获得站点证书和 CA 证书后,参考以上设置 Let's Encrypt 证书的方法进行设置。

Client 与 Registry 的安全交互

如本文开头所述,客户端与 Docker Private Registry 进行安全交互可以使用密码或双向 SSL 。双路 SSL 的基本原理就是把客户端访问可信站点的方法路径反方向也实施一次,这样 Registry 就可以对有授权的客户端开放服务。由于此方法比较复杂,除非极度机密的通信采用该模式,一般通信均不用。

这里仅介绍使用密码的方式。

参考[Docker 官方文档][10],将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 的介绍文章][13]

[1]: ( https://docs.docker.com/registry/deploying/) [2]: ( https://letsencrypt.org) [3]: ( https://letsencrypt.org/certificates/) [4]: ( https://community.letsencrypt.org/t/which-browsers-and-operating-systems-support-lets-encrypt/4394) [5]: ( http://kb.kerio.com/product/kerio-connect/server-configuration/ssl-certificates/adding-trusted-root-certificates-to-the-server-1605.html) [6]: ( http://seanlook.com/2015/01/15/openssl-certificate-encryption/) [7]: ( http://seanlook.com/2015/01/07/tls-ssl/) [8]: ( http://seanlook.com/2015/01/18/openssl-self-sign-ca/) [9]: ( http://stackoverflow.com/questions/29286307/x509-certificate-signed-by-unknown-authority-both-with-docker-and-with-github) [10]: ( https://docs.docker.com/registry/deploying/) [11]: ( https://github.com/docker/distribution/blob/master/docs/deploying.md) [12]: ( https://github.com/docker/distribution) [13]: ( https://www.digitalocean.com/community/tutorials/how-to-set-up-a-private-docker-registry-on-ubuntu-14-04)

5783 次点击
所在节点    Docker
1 条回复
metrue
2016-07-10 10:19:15 +08:00
写的好.

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/277174

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX