用户命名空间:对运行有状态 Pod 的支持进入 Alpha 阶段!

作者: Rodrigo Campos Catelin (Microsoft), Giuseppe Scrivano (Red Hat), Sascha Grunert (Red Hat)

译者: Xin Li (DaoCloud)

Kubernetes v1.25 引入用户命名空间(User Namespace)特性,仅支持无状态(Stateless)Pod。 Kubernetes 1.28 在 1.27 的基础上中进行了一些改进后,取消了这一限制。

此特性的精妙之处在于:

  • 使用起来很简单(只需在 Pod 规约(spec)中设置一个 bool)
  • 大多数应用程序不需要任何更改
  • 通过大幅度加强容器的隔离性以及应对评级为高(HIGH)和关键(CRITICAL)的 CVE 来提高安全性。

这篇文章介绍了用户命名空间的基础知识,并展示了:

  • 最近的 Kubernetes v1.28 版本中出现的变化
  • 一个评级为高(HIGH)的漏洞的演示(Demo),该漏洞无法在用户命名空间中被利用
  • 使用此特性的运行时要求
  • 关于用户命名空间的未来版本中可以期待的内容

用户命名空间是什么?

用户命名空间是 Linux 的一项特性,它将容器的用户和组标识符(UID 和 GID)与宿主机上的标识符隔离开来。 容器中的标识符可以映射到宿主机上的标识符,其中用于不同容器的主机 UID/GID 从不重叠。 更重要的是,标识符可以映射到宿主机上的非特权、非重叠的 UID 和 GID。这基本上意味着两件事:

  • 由于不同容器的 UID 和 GID 映射到宿主机上不同的 UID 和 GID,因此即使它们逃逸出了容器的边界,也很难相互攻击。 例如,如果容器 A 在宿主机上使用与容器 B 不同的 UID 和 GID 运行,则它可以对容器 B 的文件和进程执行的操作受到限制:只能读/写允许其他人使用的文件, 因为它永远不会拥有所有者或组的权限(宿主机上的 UID/GID 保证对于不同的容器是不同的)。
  • 由于 UID 和 GID 映射到宿主机上的非特权用户,如果容器逃逸出了容器边界, 即使它在容器内以 root 身份运行,它在宿主机上也没有特权。 这极大地保护了它可以读/写哪些宿主机文件、可以向哪个进程发送信号等。

此外,所授予的权能(Capability)仅在用户命名空间内有效,而在宿主机上无效。

在不使用用户命名空间的情况下,以 root 身份运行的容器在发生逃逸的情况下会获得节点上的 root 权限。如果某些权能被授予容器,那么这些权能在主机上也有效。 当使用用户命名空间时,这些情况都会被避免(当然,除非存在漏洞 🙂)。

1.28 版本的变化

正如之前提到的,从 1.28 版本开始,Kubernetes 支持有状态的 Pod 的用户命名空间。 这意味着具有用户命名空间的 Pod 可以使用任何类型的卷,不再仅限于以前的部分卷类型。

从 1.28 版本开始,用于激活此特性的特性门控已被重命名,不再是 UserNamespacesStatelessPodsSupport, 而应该使用 UserNamespacesSupport。此特性经历了许多更改, 对节点主机的要求也发生了变化。因此,Kubernetes 1.28 版本将该特性标志重命名以反映这一变化。

演示

Rodrigo 创建了一个利用 CVE 2022-0492 的演示, 用以展现如何在没有用户命名空间的情况下利用该漏洞。 他还展示了在容器使用了此特性的 Pod 中无法利用此漏洞的情况。

此漏洞被评为高危,允许一个没有特殊特权的容器读/写宿主机上的任何路径,并在宿主机上以 root 身份启动进程。

如今,容器中的大多数应用程序都以 root 身份运行,或者以半可预测的非 root 用户身份运行(用户 ID 65534 是一个比较流行的选择)。 当你运行某个 Pod,而其中带有使用用户名命名空间(userns)的容器时,Kubernetes 以非特权用户身份运行这些容器,无需在你的应用程序中进行任何更改。

这意味着两个以用户 65534 身份运行的容器实际上会被映射到宿主机上的不同用户, 从而限制了它们在发生逃逸的情况下能够对彼此执行的操作,如果它们以 root 身份运行, 宿主机上的特权也会降低到非特权用户的权限。

节点系统要求

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

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

如果你使用 CRI-O 与 crun,这一特性在 CRI-O 1.28.1 和 crun 1.9 或更高版本中受支持。 如果你使用 CRI-O 与 runc,目前仍不受支持。

containerd 对此的支持目前设定的目标是 containerd 2.0;不管你是否与 crun 或 runc 一起使用,或许都不重要。

请注意,containerd 1.7 添加了对用户命名空间的实验性支持,正如在 Kubernetes 1.25 和 1.26 中实现的那样。1.27 版本中进行的重新设计不受 containerd 1.7 支持, 因此它在用户命名空间支持方面仅适用于 Kubernetes 1.25 和 1.26。

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

这些 Containerd 限制均不适用于 [CRI-O 1.28][CRIO 版本]。

接下来?

展望 Kubernetes 1.29,计划是与 SIG Auth 合作,将用户命名空间集成到 Pod 安全标准(PSS)和 Pod 安全准入中。 目前的计划是在使用用户命名空间时放宽 Pod 安全标准(PSS)策略中的检查。这意味着如果使用用户命名空间,那么字段 spec[.*].securityContextrunAsUserrunAsNonRootallowPrivilegeEscalation和capabilities 将不会触发违规,此行为可能会通过使用 API Server 特性门控来控制,比如 UserNamespacesPodSecurityStandards 或其他类似的。

我该如何参与?

你可以通过以下方式与 SIG Node 联系:

你还可以直接联系我们:

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