Kubernetes v1.30 [beta]本页解释了在 Kubernetes Pod 中如何使用用户命名空间。用户命名空间将容器内运行的用户与主机中的用户隔离开来。
在容器中以 Root 身份运行的进程可以在主机中以不同的(非 Root)用户身份运行;换句话说,该进程在用户命名空间内的操作具有完全的权限,但在命名空间外的操作是无特权的。
你可以使用这个功能来减少被破坏的容器对主机或同一节点中的其他 Pod 的破坏。有几个安全漏洞被评为 高(HIGH) 或 重要(CRITICAL),当用户命名空间处于激活状态时,这些漏洞是无法被利用的。预计用户命名空间也会减轻一些未来的漏洞。
这是一个只对 Linux 有效的功能特性,且需要 Linux 支持在所用文件系统上挂载 idmap。这意味着:
/var/lib/kubelet/pods/ 的文件系统,或你为此配置的自定义目录,需要支持 idmap 挂载。在实践中,这意味着你最低需要 Linux 6.3,因为 tmpfs 在该版本中开始支持 idmap 挂载。这通常是需要的,因为有几个 Kubernetes 功能特性使用 tmpfs(默认情况下挂载的服务账号令牌使用 tmpfs、Secret 使用 tmpfs 等等)。
Linux 6.3 中支持 idmap 挂载的一些比较流行的文件系统是:btrfs、ext4、xfs、fat、tmpfs、overlayfs。
此外,容器运行时及其底层 OCI 运行时必须支持用户命名空间。以下 OCI 运行时提供支持:
一些 OCI 运行时不包含在 Linux Pod 中使用用户命名空间所需的支持。如果你使用托管 Kubernetes,或者使用软件包下载并安装 Kubernetes 集群,则集群中的节点可能使用不包含支持此特性的运行时。
此外,需要在容器运行时提供支持,才能在 Kubernetes Pod 中使用这一功能:
你可以在 GitHub 上的 [Issue][CRI-dockerd-issue] 中查看 cri-dockerd中用户命名空间支持的状态。
用户命名空间是一个 Linux 功能,允许将容器中的用户映射到主机中的不同用户。此外,在某用户命名空间中授予 Pod 的权能只在该命名空间中有效,在该命名空间之外无效。
一个 Pod 可以通过将 pod.spec.hostUsers 字段设置为 false 来选择使用用户命名空间。
kubelet 将挑选 Pod 所映射的主机 UID/GID,并以此保证同一节点上没有两个 Pod 使用相同的方式进行映射。
pod.spec 中的 runAsUser、runAsGroup、fsGroup 等字段总是指的是容器内的用户。这些用户将用于卷挂载(在 pod.spec.volumes 中指定),因此,主机上的 UID/GID 不会影响 Pod 挂载卷的读写操作。换句话说,由 Pod 挂载卷中创建或读取的 inode,将与 Pod 未使用用户命名空间时相同。
通过这种方式,Pod 可以轻松启用或禁用用户命名空间(不会影响其卷中文件的所有权),并且可以通过在容器内部设置适当的用户(runAsUser、runAsGroup、fsGroup 等),即可与没有用户命名空间的 Pod 共享卷。这一点适用于 Pod 可挂载的任何卷,包括 hostPath(前提是允许 Pod 挂载 hostPath 卷)。
默认情况下,当启用该功能时,有效的 UID/GID 在 0-65535 范围内。这适用于文件和进程(runAsUser、runAsGroup 等)。
使用这个范围之外的 UID/GID 的文件将被视为属于溢出 ID,通常是 65534(配置在 /proc/sys/kernel/overflowuid和/proc/sys/kernel/overflowgid)。然而,即使以 65534 用户/组的身份运行,也不可能修改这些文件。
如果用配置旋钮将 0-65535 范围扩展,则上述限制适用于扩展的范围。
大多数需要以 Root 身份运行但不访问其他主机命名空间或资源的应用程序,在用户命名空间被启用时,应该可以继续正常运行,不需要做任何改变。
一些容器运行时的默认配置(如 Docker Engine、containerd、CRI-O)使用 Linux 命名空间进行隔离。其他技术也存在,也可以与这些运行时(例如,Kata Containers 使用虚拟机而不是 Linux 命名空间)结合使用。本页适用于使用 Linux 命名空间进行隔离的容器运行时。
在创建 Pod 时,默认情况下会使用几个新的命名空间进行隔离:一个网络命名空间来隔离容器网络,一个 PID 命名空间来隔离进程视图等等。如果使用了一个用户命名空间,这将把容器中的用户与节点中的用户隔离开来。
这意味着容器可以以 Root 身份运行,并将该身份映射到主机上的一个非 Root 用户。在容器内,进程会认为它是以 Root 身份运行的(因此像 apt、yum 等工具可以正常工作),而实际上该进程在主机上没有权限。你可以验证这一点,例如,如果你从主机上执行 ps aux 来检查容器进程是以哪个用户运行的。ps 显示的用户与你在容器内执行 id 命令时看到的用户是不一样的。
这种抽象限制了可能发生的情况,例如,容器设法逃逸到主机上时的后果。鉴于容器是作为主机上的一个非特权用户运行的,它能对主机做的事情是有限的。
此外,由于每个 Pod 上的用户将被映射到主机中不同的非重叠用户,他们对其他 Pod 可以执行的操作也是有限的。
授予一个 Pod 的权能也被限制在 Pod 的用户命名空间内,并且在这一命名空间之外大多无效,有些甚至完全无效。这里有两个例子:
CAP_SYS_MODULE 若被授予一个使用用户命名空间的 Pod 则没有任何效果,这个 Pod 不能加载内核模块。CAP_SYS_ADMIN 只限于 Pod 所在的用户命名空间,在该命名空间之外无效。在不使用用户命名空间的情况下,以 Root 账号运行的容器,在容器逃逸时,在节点上有 Root 权限。而且如果某些权能被授予了某容器,这些权能在宿主机上也是有效的。当我们使用用户命名空间时,这些都不再成立。
如果你想知道关于使用用户命名空间时的更多变化细节,请参见 man 7 user_namespaces。
默认情况下,kubelet 会分配 0-65535 范围以上的 Pod UID/GID,这是基于主机的文件和进程使用此范围内的 UID/GID 的假设,也是大多数 Linux 发行版的标准。此方法可防止主机的 UID/GID 与 Pod 的 UID/GID 之间出现重叠。
避免重叠对于减轻 CVE-2021-25741 等漏洞的影响非常重要,其中 Pod 可能会读取主机中的任意文件。如果 Pod 和主机的 UID/GID 不重叠,则 Pod 的功能将受到限制:Pod UID/GID 将与主机的文件所有者/组不匹配。
kubelet 可以对 Pod 的用户 ID 和组 ID 使用自定义范围。要配置自定义范围,节点需要具有:
kubelet(此处不能使用任何其他用户名)。getsubids(shadow-utils 的一部分)并位于kubelet 二进制文件的 PATH 中。kubelet 用户的从属 UID/GID 配置(请参阅 man 5 subuid 和man 5 subgid)此设置仅收集 UID/GID 范围配置,不会更改执行 kubelet 的用户。
对于分配给 kubelet 用户的从属 ID 范围, 你必须遵循一些限制:
从属 ID 计数必须是 65536 的倍数。
从属 ID 计数必须至少为 65536 x <maxPods>,其中 <maxPods>
是节点上可以运行的最大 Pod 数量。
你必须为用户 ID 和组 ID 分配相同的范围。如果其他用户的用户 ID 范围与组ID 范围不一致也没关系。
所分配的范围不得与任何其他分配重叠。
从属配置必须只有一行。换句话说,你不能有多个范围。
例如,你可以定义 /etc/subuid 和 /etc/subgid 来为 kubelet
用户定义以下条目:
# 格式为:
# name:firstID:count of IDs
# 其中:
# - firstID 是 65536 (可能的最小值)
# - ID 的数量是 110 * 65536(110 是节点上 Pod 数量的默认限制)
kubelet:65536:7208960
从 Kubernetes v1.33 开始,每个 Pod 的 ID 计数可以在
KubeletConfiguration 中设置。
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
userNamespaces:
idsPerPod: 1048576
idsPerPod 的值(uint32)必须是 65536 的倍数。默认值是 65536。此值仅适用于使用此 KubeletConfiguration 启动 kubelet 后创建的容器。正在运行的容器不受此配置的影响。
在 Kubernetes v1.33 之前,每个 Pod 的 ID 计数被硬编码为 65536。
Kubernetes v1.29 [alpha]对于启用了用户命名空间的 Linux Pod,Kubernetes 会以受控方式放宽Pod 安全性标准的应用。
如果你创建了使用用户命名空间的 Pod,以下的字段不会被限制,即使在执行了 Baseline 或 Restricted Pod 安全性标准的上下文中。这种行为不会带来安全问题,因为带有用户命名空间的 Pod 内的 root 实际上指的是容器内的用户,绝不会映射到主机上的特权用户。以下是在这种情况下不进行检查的 Pod 字段列表:
spec.securityContext.runAsNonRootspec.containers[*].securityContext.runAsNonRootspec.initContainers[*].securityContext.runAsNonRootspec.ephemeralContainers[*].securityContext.runAsNonRootspec.securityContext.runAsUserspec.containers[*].securityContext.runAsUserspec.initContainers[*].securityContext.runAsUser此外,如果 Pod 处于符合 Baseline Pod 安全标准的上下文中,则对以下字段的合法性检查也将类似地放宽:
spec.containers[*].securityContext.procMountspec.initContainers[*].securityContext.procMountspec.ephemeralContainers[*].securityContext.procMount如果使用 Restricted Pod 安全标准,Pod 仍然只能使用默认的或空的 procMount。
当 Pod 使用用户命名空间时,不允许 Pod 使用其他主机命名空间。特别是,如果你设置了 hostUsers: false,那么你就不可以设置如下属性:
hostNetwork: truehostIPC: truehostPID: true任何容器都不能使用 volumeDevices(原始块设备卷,例如 /dev/sda)。这包括 Pod 规约中的所有容器数组:
containersinitContainersephemeralContainerskubelet 会导出两项与用户命名空间相关的 Prometheus 指标:
started_user_namespaced_pods_total:这个计数器跟踪尝试创建的、作用域为用户命名空间的 Pod 数量。started_user_namespaced_pods_errors_total:这个计数器跟踪创建作用域为用户命名空间的 Pod 时发生的错误次数。