Docker Engine 安全

在审查 Docker 安全性时,需要考虑四个主要方面

  • 内核本身的固有安全性及其对命名空间和 cgroups 的支持
  • Docker 守护进程自身的攻击面
  • 容器配置配置文件中的漏洞,无论是默认配置还是用户自定义配置。
  • 内核的“加固”安全特性以及它们与容器的交互方式。

内核命名空间

Docker 容器与 LXC 容器非常相似,它们具有相似的安全特性。当你使用 docker run 启动一个容器时,Docker 会在后台为该容器创建一组命名空间和控制组。

命名空间提供了第一个也是最直接的隔离形式。在容器内运行的进程无法看到,更不能影响,在另一个容器或主机系统中运行的进程。

每个容器也有自己的网络堆栈,这意味着一个容器不会获得对另一个容器的套接字或接口的特权访问。当然,如果主机系统进行了相应的设置,容器可以通过各自的网络接口相互交互——就像它们可以与外部主机交互一样。当你为容器指定公共端口或使用 链接 时,容器之间允许进行 IP 流量传输。它们可以相互 ping,发送/接收 UDP 数据包,并建立 TCP 连接,但必要时可以限制这些操作。从网络架构的角度来看,给定 Docker 主机上的所有容器都位于 bridge 接口上。这意味着它们就像通过一个通用以太网交换机连接的物理机器;仅此而已,没有更多。

提供内核命名空间和私有网络的代码成熟度如何?内核命名空间是在 内核版本 2.6.15 和 2.6.26 之间引入的。这意味着自 2008 年 7 月(2.6.26 版本发布日期)以来,命名空间代码已在大量生产系统中得到实践和审查。还不止如此:命名空间代码的设计和灵感甚至更早。命名空间实际上是为了以一种能够合并到主流内核中的方式,重新实现 OpenVZ 的特性。而 OpenVZ 最初发布于 2005 年,因此设计和实现都相当成熟。

控制组 (cgroups)

控制组 (Control Groups) 是 Linux 容器的另一个关键组件。它们实现资源记账和限制。它们提供了许多有用的指标,但更重要的是,它们还有助于确保每个容器获得公平的内存、CPU、磁盘 I/O 分配;并且单个容器不会通过耗尽其中一项资源而导致系统崩溃。

因此,虽然它们在阻止一个容器访问或影响另一个容器的数据和进程方面不起作用,但对于抵御一些拒绝服务攻击至关重要。在公共和私有 PaaS 等多租户平台上,它们尤为重要,即使某些应用程序开始出现异常行为,也能保证一致的正常运行时间(和性能)。

控制组 (Control Groups) 也已存在一段时间:代码始于 2006 年,并最初合并到内核 2.6.24 中。

Docker 守护进程攻击面

使用 Docker 运行容器(和应用程序)意味着运行 Docker 守护进程。除非你选择 无根模式,否则此守护进程需要 root 权限,因此你应该了解一些重要细节。

首先,只应允许受信任的用户控制你的 Docker 守护进程。这是 Docker 一些强大功能的直接结果。具体来说,Docker 允许你在 Docker 主机和 guest 容器之间共享目录;并且允许你在不限制容器访问权限的情况下执行此操作。这意味着你可以启动一个容器,其中 /host 目录就是主机上的 / 目录;并且容器可以不受任何限制地修改主机文件系统。这类似于虚拟化系统如何允许文件系统资源共享。没有什么能阻止你与虚拟机共享你的 root 文件系统(甚至你的 root 块设备)。

这具有很强的安全隐患:例如,如果你从 Web 服务器调用 Docker API 来配置容器,你应该比平时更加仔细地检查参数,以确保恶意用户无法传递精心制作的参数导致 Docker 创建任意容器。

因此,Docker 0.5.2 更改了 REST API 端点(Docker CLI 用于与 Docker 守护进程通信),现在使用 Unix 套接字而不是绑定在 127.0.0.1 上的 TCP 套接字(如果在本地机器上直接运行 Docker 而不是在 VM 中,后者容易受到跨站请求伪造攻击)。然后,你可以使用传统的 Unix 权限检查来限制对控制套接字的访问。

如果你明确决定这样做,也可以通过 HTTP 暴露 REST API。但是,如果你这样做,请注意上述安全隐患。请注意,即使你设置了防火墙来限制网络中其他主机对 REST API 端点的访问,该端点仍然可以从容器中访问,并且很容易导致权限提升。因此,必须使用 HTTPS 和证书 来保护 API 端点。禁止在没有 TLS 的情况下通过 HTTP 暴露守护进程 API,这种配置会导致守护进程在启动时早期失败,请参见 未认证的 TCP 连接。还建议确保它只能从受信任的网络或 VPN 访问。

如果你更喜欢通过 SSH 而不是 TLS,你也可以使用 DOCKER_HOST=ssh://USER@HOSTssh -L /path/to/docker.sock:/var/run/docker.sock

守护进程也可能容易受到其他输入的影响,例如使用 docker load 从磁盘加载镜像,或使用 docker pull 从网络加载镜像。自 Docker 1.3.2 起,镜像现在在 Linux/Unix 平台上的 chroot 子进程中提取,这是更广泛的权限分离努力的第一步。自 Docker 1.10.0 起,所有镜像都按其内容的加密校验和存储和访问,这限制了攻击者与现有镜像发生碰撞的可能性。

最后,如果你在服务器上运行 Docker,建议仅在该服务器上运行 Docker,并将所有其他服务移至由 Docker 控制的容器内。当然,保留你喜欢的管理工具(可能至少包括一个 SSH 服务器)以及现有的监控/监督进程(如 NRPE 和 collectd)是可以的。

Linux 内核 Capabilities

默认情况下,Docker 以受限的 Capabilities 集启动容器。这是什么意思?

Capabilities 将二元的“root/非 root”区分转变为细粒度的访问控制系统。只需要绑定 1024 以下端口的进程(如 Web 服务器)无需以 root 身份运行:只需授予它们 net_bind_service capability 即可。还有许多其他 capability,几乎涵盖了通常需要 root 权限的所有特定领域。这对容器安全意义重大。

典型的服务器以 root 身份运行多个进程,包括 SSH 守护进程、cron 守护进程、日志守护进程、内核模块、网络配置工具等。容器则不同,因为这些任务几乎都由容器周围的基础设施处理。

  • SSH 访问通常由运行在 Docker 主机上的单个服务器管理
  • cron 在必要时应作为用户进程运行,专门为需要其调度服务的应用程序量身定制,而不是作为平台范围内的设施。
  • 日志管理通常也交由 Docker 处理,或交由 Loggly 或 Splunk 等第三方服务处理。
  • 硬件管理无关紧要,这意味着你永远不需要在容器内运行 udevd 或等效的守护进程。
  • 网络管理发生在容器外部,尽可能强制实现关注点分离,这意味着容器通常不需要执行 ifconfigroute 或 ip 命令(当然,除了容器专门设计用于充当路由器或防火墙的情况外)。

这意味着在大多数情况下,容器根本不需要“真正”的 root 权限* 因此,容器可以以缩减的 capability 集运行;这意味着容器内的“root”拥有的权限比真实的“root”少得多。例如,可以

  • 拒绝所有“mount”操作
  • 拒绝访问 raw sockets(以防止数据包欺骗)
  • 拒绝访问某些文件系统操作,例如创建新的设备节点、更改文件所有者或更改属性(包括不可变标志)。
  • 拒绝模块加载

这意味着即使入侵者设法在容器内升级到 root 权限,也很难造成严重损害或升级到主机。

这不影响常规 Web 应用,但显著减少了恶意用户的攻击向量。默认情况下,Docker 会删除除 所需能力 外的所有能力,这是一种允许列表而非拒绝列表的方法。您可以在 Linux 手册页 中查看可用能力的完整列表。

运行 Docker 容器的一个主要风险是,赋予容器的默认能力集和挂载点可能无法提供完整的隔离,无论是独立使用,还是与内核漏洞结合使用。

Docker 支持添加和删除能力,允许使用非默认配置文件。这可以通过移除能力来使 Docker 更安全,或通过添加能力使其安全性降低。对用户而言,最佳实践是移除除进程明确要求的所有能力。

Docker 内容信任签名验证

Docker Engine 可以配置为仅运行签名镜像。Docker 内容信任签名验证功能直接内置于 dockerd 二进制文件中。
这在 Dockerd 配置文件中进行配置。

要启用此功能,可以在 daemon.json 中配置信任锁定(trustpinning),这样只有使用用户指定的根密钥签名的仓库才能被拉取和运行。

此功能为管理员提供了比之前通过 CLI 执行和验证镜像签名时更深入的洞察。

有关配置 Docker 内容信任签名验证的更多信息,请访问 Docker 中的内容信任

其他内核安全特性

能力只是现代 Linux 内核提供的众多安全功能之一。也可以在 Docker 中利用现有的、知名的系统,如 TOMOYO、AppArmor、SELinux、GRSEC 等。

虽然 Docker 当前仅启用能力,但它不干扰其他系统。这意味着有许多不同的方法可以加固 Docker 主机。这里有一些例子。

  • 您可以运行带有 GRSEC 和 PAX 的内核。这在编译时和运行时都添加了许多安全检查;它还凭借地址随机化等技术挫败了许多漏洞利用。它不需要 Docker 特定配置,因为这些安全功能是系统范围的,独立于容器。
  • 如果您的发行版附带 Docker 容器的安全模型模板,您可以直接使用它们。例如,我们提供了一个适用于 AppArmor 的模板,而 Red Hat 提供了用于 Docker 的 SELinux 策略。这些模板提供了一个额外的安全网(尽管它与能力有很大重叠)。
  • 您可以使用您喜欢的访问控制机制定义自己的策略。

正如您可以使用第三方工具来增强 Docker 容器(包括特殊的网络拓扑或共享文件系统)一样,也存在无需修改 Docker 本身即可加固 Docker 容器的工具。

从 Docker 1.10 开始,用户命名空间(User Namespaces)直接由 docker daemon 支持。此功能允许将容器内的 root 用户映射到容器外的非 uid-0 用户,这有助于减轻容器逃逸的风险。此功能可用但默认未启用。

有关此功能的更多信息,请参阅命令行参考中的 daemon 命令。有关 Docker 中用户命名空间实现的更多信息,请参阅此博客文章

结论

默认情况下,Docker 容器相当安全;特别是如果您在容器内部以非特权用户身份运行进程。

通过启用 AppArmor、SELinux、GRSEC 或其他适当的加固系统,您可以添加额外的安全层。

如果您想到了使 Docker 更安全的方法,我们欢迎在 Docker 社区论坛上提交功能请求、拉取请求或评论。

页面选项