本文发表于一年多前。旧文章可能包含过时内容。请检查页面中的信息自发布以来是否已变得不正确。

Kubernetes 1.30:对具有用户名字空间的 Pod 的 Beta 支持

Linux 提供了不同的名字空间来隔离进程。例如,一个典型的 Kubernetes Pod 运行在一个网络名字空间中以隔离网络身份,以及一个 PID 名字空间以隔离进程。

一个被遗忘的 Linux 名字空间是用户名字空间。这个名字空间允许我们将容器内使用的用户和组标识符(UID 和 GID)与主机上的标识符隔离开来。

这是一个强大的抽象,允许我们以“root”身份运行容器:我们在容器内部是 root,可以在 Pod 内部做 root 能做的一切事情,但我们与主机的交互仅限于非特权用户可以做的事情。这对于限制容器逃逸的影响非常有用。

容器逃逸是指容器内的进程能够利用容器运行时或内核中某些未修补的漏洞逃逸到主机上,并能访问或修改主机或其他容器上的文件。如果我们使用用户名字空间运行 Pod,容器对主机其余部分的权限会减少,其能访问的容器外文件也会受到限制。

在 Kubernetes v1.25 中,我们仅为无状态 Pod 引入了对用户名字空间的支持。Kubernetes 1.28 取消了这一限制,现在,随着 Kubernetes 1.30 的发布,我们正将其推进到 Beta 阶段!

什么是用户名字空间?

注意:Linux 用户名字空间与 Kubernetes 名字空间是不同的概念。前者是 Linux 内核特性;后者是 Kubernetes 特性。

用户名字空间是 Linux 的一项功能,可将容器的 UID 和 GID 与主机上的 UID 和 GID 隔离开来。容器中的标识符可以映射到主机上的标识符,使得不同容器使用的主机 UID/GID 永远不会重叠。此外,这些标识符可以映射到主机上非特权的、不重叠的 UID 和 GID。这带来了两个关键好处:

  • 防止横向移动:由于不同容器的 UID 和 GID 被映射到主机上不同的 UID 和 GID,即使容器逃脱了容器边界,它们也很难相互攻击。例如,假设容器 A 在主机上运行的 UID 和 GID 与容器 B 不同,那么它对容器 B 的文件和进程的操作将受到限制:只能读/写文件允许“others”访问的内容,因为它永远不会拥有所有者或组权限(主机上的 UID/GID 保证对不同容器是不同的)。

  • 增强主机隔离:由于 UID 和 GID 被映射到主机上的非特权用户,如果容器逃脱了容器边界,即使它在容器内以 root 身份运行,它在主机上也没有特权。这极大地保护了它可以读/写的主机文件,可以向其发送信号的进程等。此外,授予的能力仅在用户名字空间内有效,在主机上无效,从而限制了容器逃逸可能造成的影响。

Image showing IDs 0-65535 are reserved to the host, pods use higher IDs

用户名字空间 ID 分配

不使用用户名字空间的情况下,以 root 身份运行的容器在发生容器逃逸时,在节点上拥有 root 权限。如果容器被授予了某些能力,这些能力在主机上也有效。而使用用户名字空间时,这些情况都不会发生(当然,bug 除外 🙂)。

1.30 中的变化

在 Kubernetes 1.30 中,除了将用户名字空间推向 Beta 阶段外,致力于该功能的贡献者们还

  • 引入了一种方式,让 kubelet 可以为 UID/GID 映射使用自定义范围
  • 增加了一种方式,让 Kubernetes 可以强制要求运行时支持用户名字空间所需的所有功能。如果不支持,当尝试创建使用用户名字空间的 Pod 时,Kubernetes 会显示一个明确的错误。在 1.30 之前,如果容器运行时不支持用户名字空间,Pod 可能会在没有用户名字空间的情况下被创建。
  • 添加了更多测试,包括在 cri-tools 仓库中的测试。

您可以查看关于用户名字空间的文档,了解如何为映射配置自定义范围。

演示

几个月前,CVE-2024-21626 被披露。此漏洞评分为 8.6(高危)。它允许攻击者逃脱容器并读/写节点上以及同一节点上托管的其他 Pod 的任何路径

Rodrigo 创建了一个演示,利用了 CVE-2024-21626,并展示了在没有用户名字空间时可行的漏洞利用,在使用用户名字空间时如何被缓解。

请注意,即使使用用户名字空间,攻击者仍然可以对主机文件系统执行“others”权限位所允许的操作。因此,该 CVE 并没有被完全阻止,但其影响被大大降低了。

节点系统要求

要使用此功能,对 Linux 内核版本和容器运行时有特定要求。

在 Linux 上,你需要 Linux 6.3 或更高版本。这是因为该功能依赖于一个名为 idmap mounts 的内核特性,而对 idmap mounts 与 tmpfs 一起使用的支持是在 Linux 6.3 中合并的。

如果你正在使用带有 crun 的 CRI-O,一如既往,你可以期望 CRI-O 1.30 支持 Kubernetes 1.30。请注意,你还需要 crun 1.9 或更高版本。如果你正在使用带有 runc 的 CRI-O,这仍然不受支持。

Containerd 的支持目前目标是 containerd 2.0,并且同样的 crun 版本要求也适用。如果你正在使用带有 runc 的 containerd,这仍然不受支持。

请注意,containerd 1.7 增加了对用户名字空间的*实验性*支持,这是在 Kubernetes 1.25 和 1.26 中实现的。我们在 Kubernetes 1.27 中进行了重新设计,这需要对容器运行时进行更改。这些更改在 containerd 1.7 中不存在,因此它只适用于 Kubernetes 1.25 和 1.26 中的用户名字空间支持。

containerd 1.7 的另一个限制是,它需要在 Pod 启动期间更改容器镜像内每个文件和目录的所有权。这会产生存储开销,并可能显著影响容器的启动延迟。containerd 2.0 可能会包含一个实现,该实现将消除增加的启动延迟和存储开销。如果你计划在生产环境中使用 containerd 1.7 和用户名字空间,请考虑这一点。

CRI-O 不存在任何这些 containerd 1.7 的限制。

我如何参与?

你可以通过多种方式联系 SIG Node

你也可以直接联系我们:

  • GitHub: @rata @giuseppe @saschagrunert
  • Slack: @rata @giuseppe @sascha