用户命名空间

特性状态: Kubernetes v1.30 [beta]

此页面解释了用户命名空间如何在 Kubernetes Pod 中使用。用户命名空间将容器内运行的用户与主机中的用户隔离。

在容器中以 root 用户运行的进程可以在主机中以不同的(非 root)用户运行;换句话说,该进程在用户命名空间内对操作拥有完全权限,但在命名空间外则没有权限。

您可以使用此功能来减少受损容器对主机或其他位于同一节点上的 Pod 造成的损害。存在几种评级为 HIGHCRITICAL 的安全漏洞,在启用用户命名空间时无法利用。预计用户命名空间也将缓解未来的一些漏洞。

开始之前

这是一个仅限 Linux 的功能,需要在 Linux 中为用于文件系统的 idmap 挂载提供支持。这意味着

  • 在节点上,用于 /var/lib/kubelet/pods/ 的文件系统,或您为此配置的自定义目录,需要 idmap 挂载支持。
  • Pod 的卷中使用的所有文件系统都必须支持 idmap 挂载。

实际上,这意味着您至少需要 Linux 6.3,因为 tmpfs 从该版本开始支持 idmap 挂载。这通常是必需的,因为几个 Kubernetes 功能使用 tmpfs(默认挂载的服务帐户令牌使用 tmpfs,Secrets 使用 tmpfs 等)。

支持 Linux 6.3 中 idmap 挂载的一些常用文件系统包括:btrfs、ext4、xfs、fat、tmpfs、overlayfs。

此外,容器运行时及其底层的 OCI 运行时必须支持用户命名空间。以下 OCI 运行时提供支持

  • crun 版本 1.9 或更高版本(推荐版本 1.13+)。
  • runc 版本 1.2 或更高版本

要与 Kubernetes 一起使用用户命名空间,您还需要使用 CRI 容器运行时才能在 Kubernetes Pod 中使用此功能

  • containerd:版本 2.0(及更高版本)支持容器的用户命名空间。
  • CRI-O:版本 1.25(及更高版本)支持容器的用户命名空间。

cri-dockerd 中用户命名空间支持的状态可以在 GitHub 上的 issue 中查看。

引言

用户命名空间是 Linux 功能,允许将容器中的用户映射到主机中的不同用户。此外,授予 Pod 在用户命名空间中的权限仅在命名空间内有效,在命名空间外无效。

Pod 可以通过将 pod.spec.hostUsers 字段设置为 false 来选择使用用户命名空间。

kubelet 将选择 Pod 映射到的主机 UID/GID,并将以保证同一节点上的两个 Pod 不使用相同映射的方式进行选择。

pod.spec 中的 runAsUserrunAsGroupfsGroup 等字段始终引用容器内的用户。这些用户将用于卷挂载(在 pod.spec.volumes 中指定),因此主机 UID/GID 不会对 Pod 可以挂载的卷的读/写操作产生任何影响。换句话说,Pod 挂载的卷中创建/读取的 inode 与 Pod 未使用用户命名空间时相同。

这样,Pod 可以轻松地启用和禁用用户命名空间(而不影响其卷的文件所有权),还可以通过在容器内设置适当的用户(RunAsUserRunAsGroupfsGroup 等)与未使用用户命名空间的 Pod 共享卷。这适用于 Pod 可以挂载的任何卷,包括 hostPath(如果允许 Pod 挂载 hostPath 卷)。

默认情况下,启用此功能时有效的 UID/GID 范围是 0-65535。这适用于文件和进程(runAsUserrunAsGroup 等)。

使用超出此范围的 UID/GID 的文件将被视为属于溢出 ID,通常为 65534(在 /proc/sys/kernel/overflowuid/proc/sys/kernel/overflowgid 中配置)。但是,即使以 65534 用户/组运行,也无法修改这些文件。

如果使用配置旋钮扩展 0-65535 范围,则上述限制将适用于扩展范围。

大多数需要以 root 用户身份运行但不访问其他主机命名空间或资源的应用程序,如果激活了用户命名空间,应该可以继续正常运行而无需任何更改。

了解 Pod 的用户命名空间

几个容器运行时及其默认配置(如 Docker Engine、containerd、CRI-O)使用 Linux 命名空间进行隔离。还存在其他技术,可以与这些运行时一起使用(例如,Kata Containers 使用 VM 而不是 Linux 命名空间)。此页面适用于使用 Linux 命名空间进行隔离的容器运行时。

创建 Pod 时,默认情况下,将使用几个新的命名空间进行隔离:一个网络命名空间来隔离容器的网络,一个 PID 命名空间来隔离进程的视图,等等。如果使用用户命名空间,这将隔离容器中的用户与节点中的用户。

这意味着容器可以以 root 用户身份运行,并映射到主机上的非 root 用户。在容器内部,进程会认为它正在以 root 用户身份运行(因此像 aptyum 等工具可以正常工作),而在现实中,该进程在主机上没有权限。例如,您可以从主机执行 ps aux 来验证这一点。ps 显示的用户与在容器内部执行 id 命令时看到的用户不同。

这种抽象限制了如果容器设法逃逸到主机上会发生什么。由于容器在主机上以非特权用户身份运行,因此它对主机可以执行的操作受到限制。

此外,由于每个 Pod 上的用户都将映射到主机上的不同的非重叠用户,因此它们对其他 Pod 可以执行的操作也受到限制。

授予 Pod 的权限也仅限于 Pod 用户命名空间,并且在命名空间外大多无效,有些甚至完全无效。以下是两个示例

  • 如果授予使用用户命名空间的 Pod CAP_SYS_MODULE,则它将不起作用,Pod 无法加载内核模块。
  • CAP_SYS_ADMIN 仅限于 pod 的用户命名空间,在命名空间外部无效。

如果不使用用户命名空间,以 root 用户身份运行的容器,在容器突破的情况下,将在节点上拥有 root 权限。如果向容器授予了某些 capability,这些 capability 在主机上也是有效的。当我们使用用户命名空间时,以上情况都不成立。

如果您想了解更多关于使用用户命名空间时发生变化的详细信息,请参阅 man 7 user_namespaces

设置节点以支持用户命名空间

默认情况下,kubelet 为 pod 分配的 UID/GID 高于 0-65535 的范围,基于主机的文件和进程使用此范围内的 UID/GID 的假设,这对于大多数 Linux 发行版来说是标准的。这种方法可以防止主机和 pod 的 UID/GID 之间发生重叠。

避免重叠对于减轻漏洞的影响非常重要,例如 CVE-2021-25741,在该漏洞中,pod 可能会潜在地读取主机上的任意文件。如果 pod 和主机的 UID/GID 不重叠,pod 能够执行的操作将受到限制:pod 的 UID/GID 将不会匹配主机的文件所有者/组。

kubelet 可以为 pod 的用户 ID 和组 ID 使用自定义范围。要配置自定义范围,节点需要具备

  • 一个名为 kubelet 的用户在系统中(您不能在此处使用任何其他用户名)
  • 已安装的二进制文件 getsubidsshadow-utils 的一部分)并且在 kubelet 二进制文件的 PATH 中。
  • kubelet 用户配置了子 UID/GID(请参阅 man 5 subuidman 5 subgid)。

此设置仅收集 UID/GID 范围配置,不会更改执行 kubelet 的用户。

您必须遵守为分配给 kubelet 用户的子 ID 范围的一些约束

  • 启动 Pod UID 范围的子用户 ID 必须是 65536 的倍数,并且也必须大于或等于 65536。换句话说,您不能将 0-65535 范围内的任何 ID 用于 Pod;kubelet 施加此限制以防止意外配置不安全的情况。

  • 子 ID 计数必须是 65536 的倍数

  • 子 ID 计数必须至少为 65536 x <maxPods>,其中 <maxPods> 是节点上可以运行的最大 pod 数量。

  • 您必须为用户 ID 和组 ID 分配相同的范围。其他用户具有与组 ID 范围不对应的用户 ID 范围并不重要。

  • 分配的任何范围不应与任何其他分配重叠。

  • 子配置必须只有一行。换句话说,您不能拥有多个范围。

例如,您可以将 /etc/subuid/etc/subgid 定义为对 kubelet 用户具有以下条目

# The format is
#   name:firstID:count of IDs
# where
# - firstID is 65536 (the minimum value possible)
# - count of IDs is 110 * 65536
#   (110 is the default limit for number of pods on the node)

kubelet:65536:7208960

每个 Pod 的 ID 计数

从 Kubernetes v1.33 开始,每个 Pod 的 ID 计数可以在 KubeletConfiguration 中设置。

apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
userNamespaces:
  idsPerPod: 1048576

idsPerPod (uint32) 的值必须是 65536 的倍数。默认值为 65536。此值仅适用于在 kubelet 使用此 KubeletConfiguration 启动后创建的容器。正在运行的容器不受此配置的影响。

在 Kubernetes v1.33 之前的版本中,每个 Pod 的 ID 计数被硬编码为 65536。

与 Pod 安全准入检查的集成

功能状态: Kubernetes v1.29 [alpha]

对于启用用户命名空间的 Linux Pod,Kubernetes 以受控的方式放宽了 Pod 安全标准 的应用。

如果您创建一个使用用户命名空间的 Pod,即使在强制执行 BaselineRestricted pod 安全标准的环境中,以下字段也不会受到约束。这种行为不会带来安全问题,因为在具有用户命名空间的 Pod 内部的 root 实际上指的是容器内的用户,该用户永远不会映射到主机上的特权用户。以下是对于这些情况下 Pod 检查的字段列表

  • spec.securityContext.runAsNonRoot
  • spec.containers[*].securityContext.runAsNonRoot
  • spec.initContainers[*].securityContext.runAsNonRoot
  • spec.ephemeralContainers[*].securityContext.runAsNonRoot
  • spec.securityContext.runAsUser
  • spec.containers[*].securityContext.runAsUser
  • spec.initContainers[*].securityContext.runAsUser
  • spec.ephemeralContainers[*].securityContext.runAsUser

此外,如果 pod 处于具有 Baseline pod 安全标准的上下文中,则对以下字段的验证也将类似地放宽

  • spec.containers[*].securityContext.procMount
  • spec.initContainers[*].securityContext.procMount
  • spec.ephemeralContainers[*].securityContext.procMount

使用 Restricted pod 安全标准时,pod 仍然只能使用默认或空的 ProcMount。

限制

当为 pod 使用用户命名空间时,禁止使用其他主机命名空间。特别是,如果您设置 hostUsers: false,则不允许设置以下任何一项

  • hostNetwork: true
  • hostIPC: true
  • hostPID: true

任何容器都不能使用 volumeDevices(原始块卷,如 /dev/sda)。这包括 pod 规范中的所有容器数组

  • containers
  • initContainers
  • ephemeralContainers

指标和可观察性

kubelet 导出两个特定于用户命名空间的 prometheus 指标

  • started_user_namespaced_pods_total:一个计数器,用于跟踪尝试创建的用户命名空间 pod 的数量。
  • started_user_namespaced_pods_errors_total:一个计数器,用于跟踪创建用户命名空间 pod 时发生的错误数量。

接下来

此页面上的项目引用了提供 Kubernetes 所需功能的第三方产品或项目。Kubernetes 项目作者不对这些第三方产品或项目负责。有关更多详细信息,请参阅 CNCF 网站指南

在提出添加额外的第三方链接的更改之前,您应该阅读 内容指南

最后修改时间:2025 年 10 月 22 日下午 11:33 PST:KEP-127:更新 beta3 (e55c83bbe4)