在前一篇文章中,我介绍了我们已经开放其源码的新的基于 Docker 的平台: Menagerie 。Menagerie 在 Docker 容器内实现了批量有效负载调度和跟踪。这篇文章将重点介绍架构和开发流程,所以现在是时候转入部署和生产主题了。我们将从安全性开始介绍,这是我们最关心的问题。这篇文章主要以前一篇文章为基础,但对打算保障基于 Docker 的系统的安全性的新读者也很有用。
保护任何系统的第一个阶段都是确认薄弱环节并创建修复计划。对于 Menagerie,我们执行了以下操作:
介绍了 CIS 最佳实践(已在它们的 基准测试文档 中详细介绍)。为了方便测试,Docker 中的热心人员提供了 docker-bench-security 项目 ,该项目输出一个可操作项列表。
检查了我们代码中的数据流并识别潜在的漏洞,重点关注恶意或容易出错的用户输入。
检查各种 Docker 版本,从中挑选安全特性。我们的假设是系统运行着任意的各种各样的有效负载。鉴于此,我们希望尽可能多地限制各种执行,避免与主机有任何交互。在下面会看到我们选择的操作的详细信息。
我们在 Vagrant 环境中执行了 docker-bench-security 测试 ,然后获得了一个可操作项列表,我们检查了这个列表,并选择修复在离开特定试验台后仍会继续存在的问题。推荐在任何部署环境中运行此测试,以获取可能该部署环境特有的具体问题。这是我们根据测试结果来修复的一些问题:
禁用 inter-container-communication(Docker 守护进程的 --icc=false 标志):只允许显式连接。
为容器设置只读 rootfs(为 docker run 设置 --read-only 标志)。
使用用户命名空间重新映射根用户;参见下文的详细说明。
将 Apparmor 配置文件添加到基础架构和引擎容器中;参见下文的详细说明。
vagrant 环境包含第三方基础架构容器:mysql、rabbitmq 和 docker-registry。这些可能单独部署在生产环境中,而且保护它们不在本文的介绍范围内。简单地讲,我们提供了足够的材料来帮助您操作。
这当然不属于 Docker 问题,但它是一个重要部分。在 Menagerie 中,数据流从一次 web-API 上传开始,随后是写入文件存储、数据库和消息队列,最后是由后端工作线程来读取和执行。我们检查了通过传递、写入、文件系统访问和执行而流动的数据,尽力确保外部输入已根据需要进行了净化,并尽力
使用外部输入。您可以进一步了解 净化外部输入的重要性 。
正如我提到的,因为此系统在任意输入上运行多样化的引擎,所以我们希望确保即使悄悄渗入了有害的东西,系统也足够牢固,能够避免重大损害。这包括:
拦截对 API 的未授权外部访问。
避开对主机文件系统和其他资源的访问。
因为过度使用资源而导致的 DoS。
等等。
我将详细介绍每项加固工作。
部署在生产环境中时,推荐将服务端口设为私有,并在它的前面放置一个安全的上游 Web 服务器(比如 Nginx)。应配置这个上游服务器来处理 SSL 通信,使用客户端证书,限制速率,并使用能够想到的其他所有安全特性。
尽管这不是一个安全特性,但它通过消除将主机卷挂载到容器中,可以获得安全性提升。写入容器和 Docker 卷内的所有数据都会保留在那里,在它们处理的数据和配置之间保持了很好的分离。1.9 版提供了 Docker 卷。
Menagerie 通过后端工作线程来执行各种引擎容器。由于此执行是间接的,所以我们希望系统管理员能够对执行进行限制。这可以通过在引擎配置中指定额外的运行标志来实现;这些标志被传递给 Docker create/run 命令。这是来自我为 apktool 提供的配置的示例:
"name": "apktool", "workers": 2, "image": "localhost:5000apktool:stable", "runflags": [ "--read-only", "-–net=/”none/"", "--security-opt=/"apparmor:apk/"" ], "cmd": "/engine/run.sh", "sizelimit": 50000000, "inputfilename": "sample.apk", "timeout": 240 }
上述代码禁止了网络连接,将容器 rootfs 设为只读,并应用了我们创建的 apparmor 配置文件。另请注意,我们有一个 ‘sizelimit’ 属性,可以在前端级别上限制特定引擎的上传大小。 run 命令文档 中提供了执行标志的更多信息。
Apparmor 是一个 Linux 安全性模块,它实现了 MAC 来加固应用程序。它允许为应用程序创建执行配置文件,规定想要的文件系统访问、进程创建、网络访问等权限。通过为基础容器和引擎创建 apparmor 配置文件,我们将它们锁定到某个特定的行为中,并限制了利用 post-exploit 进行恶意活动的自由。
创建配置文件的过程是在投诉(仅报告)模式下围绕执行而构建的。这不属于本文的讨论范围,但我建议您查阅 我们编写的维基 ,了解如何将配置文件添加到新引擎容器中。
请注意,Docker 1.10 还添加了对 seccomp 配置文件 的支持。这允许将应用程序/容器锁定到特定的系统调用中。这就像我们还没有机会利用的一个不错的工具。
尽管支持在非根 UID 下运行容器,但需要显式地指定并在处理代码上施加限制;在计划嵌入任意引擎时,无法始终确保它们能在此环境中工作。Docker 1.10 中公开的一个不错的内核特性是在独立的用户命名空间下运行。 这篇博客 对此进行了很好的解释。基本上讲,在执行的容器中,内部 UID 被映射到这个特定命名空间所独有的一个外部 UID 范围。具体地讲,在内部,容器仍可以在根用户下运行,但实际上被映射到外部的无特权用户 UID。
使用用户命名空间引入了一些挑战并暴露了一些问题,我们已将它们报告给 Docker 人员。希望上手使用此特性的人需要注意以下几点:
目前不支持将 –-read-only 标志与用户命名空间结合使用。
docker cp 命令在 1.10 中存在问题(已在 1.10.2 中修复):复制的文件具有错误的用户映射。
Docker 构建文件在使用 COPY/ADD 时将会显式创建内部目录,但没有正确的用户映射。这会导致该目录不可用,可通过提前创建目标目录来避免此问题(将来会修复这个问题)。
希望我提供的有关加固流程的见解可为您的 Docker 系统提供帮助,当然,还希望能够激发您进一步了解 Menagerie 的欲望。我们已竭尽全力让 Menagerie 尽可能安全,从代码层面一直到使用 Docker 所提供的最佳配置选项。这项工作仍在继续,我们期待听到您的建议和贡献。