本文发布已超过一年。较早的文章可能包含过时的内容。请检查页面中的信息自发布以来是否已不再准确。
Kubernetes 1.30:适用于带用户命名空间的 Pod 的 Beta 支持
Linux 提供了不同的命名空间 (namespaces) 来隔离进程。例如,典型的 Kubernetes Pod 运行在网络命名空间中以隔离网络身份,运行在 PID 命名空间中以隔离进程。
一个被遗漏的 Linux 命名空间是 用户命名空间 (user namespace)。这个命名空间允许我们将容器内部使用的用户和组标识符 (UIDs 和 GIDs) 与主机上的标识符隔离开来。
这是一个强大的抽象,允许我们以“root”身份运行容器:我们在容器内部是 root,可以在 Pod 内部执行 root 可以做的所有事情,但我们与主机的交互仅限于非特权用户能做的事情。这对于限制容器逃逸的影响非常有用。
容器逃逸是指容器内的进程利用容器运行时或内核中的未修补漏洞逃逸到主机上,并可以访问/修改主机或其他容器上的文件。如果我们使用用户命名空间运行 Pod,则容器对主机其余部分的特权会降低,并且其可以访问的容器外部文件也受到限制。
在 Kubernetes v1.25 中,我们仅为无状态 Pod 引入了用户命名空间支持。Kubernetes 1.28 解除了该限制,现在随着 Kubernetes 1.30 的发布,该功能已进入 Beta 阶段!
什么是用户命名空间?
注意:Linux 用户命名空间是与 Kubernetes 命名空间不同的概念。前者是 Linux 内核特性;后者是 Kubernetes 特性。
用户命名空间是 Linux 的一项特性,它将容器的 UIDs 和 GIDs 与主机的 UIDs 和 GIDs 隔离开来。容器中的标识符可以映射到主机上的标识符,且不同容器所使用的主机 UID/GIDs 永远不会重叠。此外,标识符可以映射到主机上非特权、不重叠的 UIDs 和 GIDs。这带来了两个主要好处:
防止横向移动:由于不同容器的 UIDs 和 GIDs 映射到主机上不同的 UIDs 和 GIDs,即使容器逃逸出容器边界,它们之间也很难互相攻击。例如,假设容器 A 在主机上运行的 UIDs 和 GIDs 与容器 B 不同。在这种情况下,它对容器 B 文件和进程的操作将受到限制:只能读/写文件对“其他人”开放的内容,因为它永远不会获得文件所有者或组权限(主机上不同容器的 UIDs/GIDs 保证不同)。
增强主机隔离性:由于 UIDs 和 GIDs 映射到主机上的非特权用户,如果容器逃逸出容器边界,即使它在容器内部以 root 身份运行,它在主机上也没有特权。这极大地保护了它可以读/写的主机文件、可以向哪个进程发送信号等。此外,授予的能力仅在用户命名空间内部有效,在主机上无效,这限制了容器逃逸可能造成的影响。
用户命名空间 ID 分配
如果不使用用户命名空间,容器在发生逃逸时以 root 身份运行时,将在节点上拥有 root 特权。如果授予容器某些能力,这些能力在主机上也是有效的。使用用户命名空间时,所有这些都不会发生(当然,除非有 Bug 🙂)。
1.30 版本中的变化
在 Kubernetes 1.30 中,除了将用户命名空间提升到 Beta 阶段外,为此特性工作的贡献者们还
- 引入了 kubelet 使用自定义范围进行 UIDs/GIDs 映射的方式
- 增加了一种机制,让 Kubernetes 可以强制要求运行时支持用户命名所需的所有特性。如果不支持,尝试创建带有用户命名空间的 Pod 时,Kubernetes 将显示明确的错误。在 1.30 之前,如果容器运行时不支持用户命名空间,仍然可以创建不带用户命名空间的 Pod。
- 添加了更多测试,包括 cri-tools 仓库中的测试。
您可以查阅关于用户命名空的文档,了解如何配置自定义映射范围。
演示
几个月前,CVE-2024-21626 被披露。该漏洞评分为 8.6 (高危)。它允许攻击者逃逸容器,并可以读/写节点上以及同一节点上托管的其他 Pod 中的任何路径。
Rodrigo 创建了一个演示,该演示利用了 CVE 2024-21626,并展示了该漏洞(在不使用用户命名空间时有效)在使用用户命名空间时是如何被缓解的。
请注意,使用用户命名空间时,攻击者在主机文件系统上可以执行“其他人”权限位允许的操作。因此,该 CVE 并未完全阻止,但影响已大大降低。
节点系统要求
使用此特性对 Linux 内核版本和容器运行时有要求。
在 Linux 上,您需要 Linux 6.3 或更高版本。这是因为该特性依赖于一个名为 idmap mounts 的内核特性,而对 tmpfs 使用 idmap mounts 的支持已合并到 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,请考虑此限制。
上述 containerd 1.7 的限制都不适用于 CRI-O。
如何参与贡献?
您可以通过以下方式联系 SIG Node:
您也可以直接联系我们:
- GitHub:@rata @giuseppe @saschagrunert
- Slack:@rata @giuseppe @sascha