技巧98 删除在构建中加入的密码
在企业环境中构建镜像的时候,经常需要通过密钥和凭证来获取数据。如果使用Dockerfile来构建应用,那么这些密码大体上会出现在历史中,即使事后进行了删除。
这可能是个安全问题:如果有人获取了镜像,他们可能获取之前层中的密码。
问题
想要从镜像历史中移除文件。
解决方案
使用 docker-squash
从镜像中移除层。
对于历史中的工作,有些简单的办法来处理。比如,你可以像代码清单14-8所示的这样在使用密码的时候就把它给删除了。
代码清单14-8 简单粗暴地直接不把密码留在层里
FROM ubuntu
RUN echo mysecret > secretfile && command_using_secret && rm secretfile
这种方法有几个缺点。它需要把密码写在Dockerfile里,所以在你的源码控制系统里可能是明文。
为了避免此问题,需要把该文件加入源码控制的.gitignore(或者类似)文件中,然后在构建镜像的时候加入镜像。这就把文件加入了单独的层,不是轻易可以从构建出来的镜像中简单移除的。
最终,如果使用环境变量来存储密码,这也可能带来安全风险。这些变量可能简单地设置在一些不安全的持久存储层(比如Jenkins任务)里。在任何情况下,都可能有人给你一个镜像然后让你从上面拉取密码。首先我们通过一个简单的例子来展示该问题,然后我们展示一种从基础层移除的方法。
1.带密码的镜像
代码清单14-9所示的Dockerfile会使用一个叫secret_file的文件作为你放入其中的密码数据的占位符来创建镜像。
代码清单14-9 带有密码的Dockerfile
FROM ubuntu
CMD ls / ⇽--- 为了节省一点时间,我们使用了文件列表命令重载了默认的命令,这样会演示文件是否在历史中
ADD /secret_file secret_file ⇽--- 密码文件加入镜像构建(必须和Dockerfile共同存在于当前目录中)
RUN cat /secret_file ⇽--- 把密码文件当成构建的一部分。在本例中,我们简单地用了cat命令来输出文件,但是这个命令也可能是git clone或者其他更有用的命令
RUN rm /secret_file ⇽--- 移除密码文件
现在可以开始构建了,构建结果可称之为密码构建,如代码清单14-10所示。
代码清单14-10 使用密码构建简单的Docker镜像
$ echo mysecret > secret_file
$ docker build -t secret_build .
Sending build context to Docker daemon 5.12 kB
Sending build context to Docker daemon
Step 0 : FROM ubuntu
---> 08881219da4a
Step 1 : CMD ls /
---> Running in 7864e2311699
---> 5b39a3cba0b0
Removing intermediate container 7864e2311699
Step 2 : ADD /secret_file secret_file
---> a00886ff1240
Removing intermediate container 4f279a2af398
Step 3 : RUN cat /secret_file
---> Running in 601fdf2659dd
My secret
---> 2a4238c53408
Removing intermediate container 601fdf2659dd
Step 4 : RUN rm /secret_file
---> Running in 240a4e57153b
---> b8a62a826ddf
Removing intermediate container 240a4e57153b
Successfully built b8a62a826ddf
该镜像一旦构建完毕,可使用技巧27来展示密码文件,如代码清单14-11所示。
代码清单14-11 给每一步打标签,并且展示带有密码的那一层
$ x=0; for id in $(docker history -q secret_build:latest);
➥ do ((x++)); docker tag $id secret_build:step_$x; done ⇽--- 演示密码文件在镜像的标签之中
$ docker run secret_build:step_3 cat /secret_file' ⇽--- 把每一步按照数字顺序打上标签
mysecret
2.使用docker-squash来移除密码
现在已经看到即使在最终产品中没有密码,密码也可能存在于镜像的历史中。这就是 docker-squash
有用武之地的地方了——它在历史中移除中间的层但是保持了Dockerfile命令(如 CMD
、 PORT
、 ENV
以及其他)和原始的基础层。
代码清单14-12下载、安装并且使用了 docke-squash
来比较了处理前和处理后的镜像。
代码清单14-12 用docker-squash来减少镜像的层
$ wget -qO- https://github.com/jwilder/docker-squash/releases/download
➥ /v0.2.0/docker-squash-linux-amd64-v0.2.0.tar.gz | \
tar -zxvf - && mv docker-squash /usr/local/bin ⇽--- 安装docker-squash
$ docker save secret_build:latest | \ ⇽---
docker-squash -t secret_build_squashed | \
docker load ⇽--- 把镜像保存在docker-squash要处理的TAR文件里,之后载入结果镜像,打标签为“secret_build_squashed”
$ docker history secret_build_squashed ⇽--- 处理后的镜像的历史里没有secret_file的记录
IMAGE CREATED CREATED BY SIZE
ee41518cca25 2 seconds ago /bin/sh -c #(nop) CMD ["/bin/sh" " 0 B
b1c283b3b20a 2 seconds ago /bin/sh -c #(nop) CMD ["/bin/bash 0 B
f443d173e026 2 seconds ago /bin/sh -c #(squash) from 93c22f56 2.647 kB
93c22f563196 2 weeks ago /bin/sh -c #(nop) ADD file:7529d28 128.9 MB
$ docker history secret_build ⇽--- 原始镜像仍然有secret_file
IMAGE CREATED CREATED BY SIZE
b8a62a826ddf 3 seconds ago /bin/sh -c rm /secret_file 0 B
2a4238c53408 3 seconds ago /bin/sh -c cat /secret_file 0 B
a00886ff1240 9 seconds ago /bin/sh -c #(nop) ADD file:69e77f6 10 B
5b39a3cba0b0 9 seconds ago /bin/sh -c #(nop) CMD ["/bin/sh" " 0 B
08881219da4a 2 weeks ago /bin/sh -c #(nop) CMD ["/bin/bash 0 B
6a4ec4bddc58 2 weeks ago /bin/sh -c mkdir -p /run/systemd & 7 B
98697477f76a 2 weeks ago /bin/sh -c sed -i 's/^#\s*\(deb.*u 1.895 kB
495ec797e6ba 2 weeks ago /bin/sh -c rm -rf /var/lib/apt/lis 0 B
e3aa81f716f6 2 weeks ago /bin/sh -c set -xe && echo '#!/bin 745 B
93c22f563196 2 weeks ago /bin/sh -c #(nop) ADD file:7529d28 128.9 MB
$ docker run secret_build_squashed ls /secret_file ⇽--- 展示secret_file不在处理后的镜像里
ls: cannot access '/secret_file': No such file or directory
$ docker run f443d173e026 ls /secret_file ⇽--- 展示secret_file不在处理后的“处理”层里
ls: cannot access '/secret_file': No such file or directory
3.对于“消失的“镜像层的标注
Docker在1.10版本改变了层的属性。从那时候起,下载的镜像在历史中展示为“
仍然可以通过把下载的镜像层进行 docker save
然后解压TAR文件的方式来获取其内容。代码清单14-13给出了一个对已下载Ubuntu镜像执行此操作的例子。
代码清单14-13 下载镜像中“消失的”层
$ docker history ubuntu ⇽--- 使用docker history命令来展示Ubuntu镜像的层历史
IMAGE CREATED CREATED BY SIZE
104bec311bcd 2 weeks ago /bin/sh -c #(nop) CMD ["/bin/bash"] 0 B
<missing> 2 weeks ago /bin/sh -c mkdir -p /run/systemd && ech 7 B
<missing> 2 weeks ago /bin/sh -c sed -i 's/^#\s*\(deb.*univer 1.9 kB
<missing> 2 weeks ago /bin/sh -c rm -rf /var/lib/apt/lists/* 0 B
<missing> 2 weeks ago /bin/sh -c set -xe && echo '#!/bin/sh 745 B
<missing> 2 weeks ago /bin/sh -c #(nop) ADD file:7529d28035b4 129 MB
$ docker save ubuntu | tar -xf - ⇽--- 使用docker save命令来输出TAR文件到镜像层,直接通过管道来进行tar和解压
$ find . | grep tar$ ⇽--- 展示TAR文件中只包含在此层的修改
./042e55060780206b2ceabe277a8beb9b10f48262a876fd21b495af318f2f2352/layer.tar
./1037e0a8442d212d5cc63d1bc706e0e82da0eaafd62a2033959cfc629f874b28/layer.tar
./25f649b30070b739bc2aa3dd877986bee4de30e43d6260b8872836cdf549fcfc/layer.tar
./3094e87864d918dfdb2502e3f5dc61ae40974cd957d5759b80f6df37e0e467e4/layer.tar
./41b8111724ab7cb6246c929857b0983a016f11346dcb25a551a778ef0cd8af20/layer.tar
./4c3b7294fe004590676fa2c27a9a952def0b71553cab4305aeed4d06c3b308ea/layer.tar
./5d1be8e6ec27a897e8b732c40911dcc799b6c043a8437149ab021ff713e1044f/layer.tar
./a594214bea5ead6d6774f7a09dbd7410d652f39cc4eba5c8571d5de3bcbe0057/layer.tar
./b18fcc335f7aeefd87c9d43db2888bf6ea0ac12645b7d2c33300744c770bcec7/layer.tar
./d899797a09bfcc6cb8e8a427bb358af546e7c2b18bf8e2f7b743ec36837b42f2/layer.tar
./ubuntu.tar
$ tar -tvf
➥ ./4c3b7294fe004590676fa2c27a9a952def0b71553cab4305aeed4d06c3b308ea
➥ /layer.tar
drwxr-xr-x 0 0 0 0 15 Dec 17:45 etc/
drwxr-xr-x 0 0 0 0 15 Dec 17:45 etc/apt/
-rw-r--r-- 0 0 0 1895 15 Dec 17:45 etc/apt/sources.list
讨论
尽管在某种程度上和技巧52相似,在最终结果上来说使用专用工具有所不同。在之前的解决方案中,可以看到元数据层(比如CMD)得以保留,但是之前的技巧会完全废弃它们。所以需要手动通过另外的Dockerfile来重建元数据层。
这种行为意味着docker-squash工具可以用来在镜像发布到仓库之前自动进行清理,如果你倾向于不相信用户会正确使用镜像构建中的密码数据——这些镜像应该都可以正常工作。
如上所述,你应该怀疑用户会把密码放在元数据层——环境变量尤其是个威胁,在最终的镜像中可能会得到很好的保留。