20190304

切勿将配置或机密信息嵌入Docker镜像中。相反,在构建Docker镜像时,期望使用业务流程运行时将配置和机密的信息提供给容器,这些包含:Kubernetes SecretsDocker Secrets。而外部工具或环境变量(对于非敏感性)用于非敏感数据。但仍需注意,不要无意中在镜像层的隐藏层中包含机密信息。

概述#

容器镜像应该是可重用且安全的。当镜像层包含嵌入式配置或机密信息时,它违反了此规则,并且不可重用或不安全。这些值不属于镜像,它们仅属于正在运行的容器中。当凭据保留在Docker镜像中时,可能会出现未知的安全隐患。

在生产中运行Docker容器应该是具有各种配置和密码信息的镜像的组合。如果你在Docker,Kubernetes,Swarm或其他业务流程层中运行此容器并不重要,编排平台应负责通过将这些部件组装到正在运行的容器中来创建容器。如我上述所提到的,secrets,configmap等。

Docker镜像的类型#

Component image#

Docker镜像通常是可重用的组件,可与不同的基础架构共享,并可在各种环境中运行。这种类型的image的一个例子是PostgresRedisNSQ。这些组件通常需要一些凭证来引导。凭证应由容器提供,而不是内置于镜像中。

官方MySQL镜像就是一个很好的例子。如果你运行MySQL Docker镜像而不为某些配置提供初始值,则容器将退出:

[marksugar@www.linuxea.com ~]# docker run mysql
error: database is uninitialized and password option is not specified 
  You need to specify one of MYSQL_ROOT_PASSWORD, MYSQL_ALLOW_EMPTY_PASSWORD and MYSQL_RANDOM_ROOT_PASSWORD

MySQL镜像可以很好地显示缺少的值并给出建议,查看docker-entrpoint.sh脚本。再试一次,键入密码:

MYSQL_ROOT_PASSWORD=www.linuxea.com
[marksugar@www.linuxea.com ~]# docker run -e MYSQL_ROOT_PASSWORD=www.linuxea.com mysql
Initializing database
2019-03-04T07:53:38.390516Z 0 [Warning] [MY-011070] [Server] 'Disabling symbolic links 
......
Warning: Unable to load '/usr/share/zoneinfo/zone1970.tab' as time zone. Skipping it.

2019-03-04T07:54:19.457699Z 0 [System] [MY-010910] [Server] /usr/sbin/mysqld: Shutdown complete (mysqld 8.0.15)  MySQL Community Server - GPL.

MySQL init process done. Ready for start up.

2019-03-04T07:54:19.847135Z 0 [Warning] [MY-011070] [Server] 'Disabling symbolic links using --skip-symbolic-links (or equivalent) is the default. Consider not using this option as it' is deprecated and will be removed in a future release.
....

2019-03-04T07:54:20.734608Z 0 [System] [MY-010931] [Server] /usr/sbin/mysqld: ready for connections. Version: '8.0.15'  socket: '/var/run/mysqld/mysqld.sock'  port: 3306  MySQL Community Server - GPL.
2019-03-04T07:54:20.756534Z 0 [System] [MY-011323] [Server] X Plugin ready for connections. Socket: '/var/run/mysqld/mysqlx.sock' bind-address: '0.0.0.0' port: 33060

这一次,提供了一些配置选项,它可以工作。MySQL镜像是可重用且安全的,因为它可以配置为在大多数环境中工作,并且没有嵌入到镜像中的密码。

Application image#

镜像可以包含应用程序。应用程序镜像的示例是静态站点或API。这些镜像旨在为用户而非其他组件提供请求。通常,这些应用程序镜像需要认证,例如API密钥或数据库凭证,或者这些需要特定于环境的数据,例如要连接的其他端点。除了dev模式默认值之外,在镜像本身中存储这些认证是一种不好的做法,应该避免使用。认证信息应始终由容器提供,而不是内置于镜像中。

如:ELK的前段是Kibana,Kibana容器需要一些配置, 至少它必须能够找到要连接的Elasticsearch服务器。elastic.co团队认为默认链接端口9200上名为elasticsearch的主机,并理所当然的为此配置了默认密码,因为这在docker-compose(dev)环境中运行良好。但希望没有人使用用户名“elastic”和密码“changeme”在生产中运行Elasticsearch集群!

隐藏层#

最后,重要的是不要在镜像的隐藏层中包含机密的信息。如果Dockerfile配置不正确,则在创建镜像时可能会发生这种情况。当删除Dockerfile中的内容时(秘密,文件,源代码,等),并不总是意味着这个密码没有被包含在最终图像中。它可能存在于父层中,但仍可访问。当用户运行docker pull命令时,将传递所有基础层。

FROM marksugar:nginx
RUN git clone http://USERNAME:PASSWORD@git.linuxea.com -o /usr/local/www
WORKDIR /usr/local/www
RUN go build -o /data/wwwroot/
RUN rm -rf /usr/local/www
ENTRYPOINT [/data/wwwroot]

这个Dockerfile的开发人员希望在Docker构建过程中创建一个特殊的构建环境。在构建时,需要将源代码添加到image中,构建二进制文件然后删除源代码。问题是Dockerfile中的每一行都会生成一个新层,每当拉出最终镜像时候时都会pull该层。创建此Dockerfile的过程也会将源代码泄漏给任何提取此镜像的人。

为什么这些很重要?#

Docker镜像应该功能齐全,一致,可重用(可共享和可发现),小巧,安全,可在企业环境中工作并可调试

可重复使用#

应用程序可移植性是部署容器的最大好处之一。当应用程序组件可移植时,可以更轻松地跨各种云提供程序和环境部署不可变基础结构。具有内置配置和包含机密的信息的容器不可移植,它们可能需要为每个环境提供不同的镜像。

安全#

嵌入密码的镜像不安全。任何有权访问镜像的人都可以拉动和查看密码信息,即使它们处于不可见面图层中。这是未经审计的密码信息查看方式,应该避免。

企业兼容#

一个好的的容器镜像包含运行组件时的所有最佳实践。从容器镜像中删除配置将允许企业用户从容器中包含的最佳实践中受益,并且仍然与其审计和安全策略及其工作流程兼容。内置配置将要求企业使用创建私有镜像并失去原始镜像中已有的最佳实践的大部分好处。

可调试#

具有嵌入式配置的镜像调试起来要困难得多。如果镜像具有嵌入式配置,则通过简单docker run命令从镜像启动可调试容器通常更加困难。

建议#

有许多方法可以在运行时为容器提供机密的信息和配置:

即使构建的image仅在内部使用且仅在单个服务器上使用,也应该使用一种更好的方式创建镜像。切勿在镜像中提供默认的硬密码,也不要添加其他的机密信息,而是在运行容器时为它们提供。而这些方式参考以上5种,而最常见的是环境变量和配置文件。

更多阅读#

有关如何将密码信息传递到容器的更多信息的一些建议是: - linuxea:docker config配置使用 - linuxea:变量实值与文件变量替换 - linuxea:compose中的变量传递与docker-createrepo构建 - linuxea:kubernetes secret简单用法(25) - linuxea:kubernetes 介绍ConfigMap与Secret(23)

学习更多#

学习如何使用Docker CLI命令,Dockerfile命令,使用这些命令可以帮助你更有效地使用Docker应用程序。查看Docker文档和我的其他帖子以了解更多信息。