用户命名空间
Kubernetes v1.30 [beta]
本页面解释了如何在 Kubernetes pod 中使用用户命名空间。用户命名空间将容器内运行的用户与主机上的用户隔离开来。
在容器中以 root 用户运行的进程可以在主机上以不同的(非 root)用户身份运行;换句话说,该进程在用户命名空间内具有完整的操作权限,但在命名空间外没有特权。
你可以使用此功能减少受到攻击的容器对主机或同一节点上其他 Pod 的损害。有 一些安全漏洞 被评为 HIGH 或 CRITICAL,当用户命名空间处于活跃状态时,这些漏洞无法被利用。预计用户命名空间也将缓解未来的某些漏洞。
准备工作
这是一个仅限 Linux 的功能,并且在 Linux 中需要支持对所使用的文件系统进行 idmap 挂载。这意味着
- 在节点上,用于
/var/lib/kubelet/pods/
的文件系统,或为此配置的自定义目录,需要支持 idmap 挂载。 - Pod 卷中使用的所有文件系统都必须支持 idmap 挂载。
实际上,这意味着你需要至少 Linux 6.3,因为 tmpfs 在该版本开始支持 idmap 挂载。这通常是必需的,因为许多 Kubernetes 功能使用 tmpfs(默认挂载的服务账号 Token 使用 tmpfs,Secrets 使用 tmpfs 等)。
在 Linux 6.3 中支持 idmap 挂载的一些常用文件系统包括:btrfs、ext4、xfs、fat、tmpfs、overlayfs。
此外,容器运行时及其底层 OCI 运行时必须支持用户命名空间。以下 OCI 运行时提供支持
注意
某些 OCI 运行时不包含在 Linux Pod 中使用用户命名空间所需的支持。如果你使用托管的 Kubernetes,或者通过软件包下载并安装,你的集群中的节点可能使用了不包含此支持的运行时。要在 Kubernetes 中使用用户命名空间,你还需要使用符合 CRI 的容器运行时,以便在 Kubernetes Pod 中使用此功能
- containerd:2.0 或更高版本支持容器用户命名空间。
- CRI-O:1.25 或更高版本支持容器用户命名空间。
你可以在 GitHub 上的一个 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 用户运行但未访问其他主机命名空间或资源的应用,如果激活了用户命名空间,应该可以继续正常运行而无需任何更改。
理解 Pod 的用户命名空间
一些具有默认配置的容器运行时(例如 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 用户命名空间,且在命名空间外部大多无效,有些甚至完全无效。以下是两个示例
- 授予使用用户命名空间的 Pod 的
CAP_SYS_MODULE
不会产生任何效果,Pod 无法加载内核模块。 CAP_SYS_ADMIN
仅限于 Pod 的用户命名空间,并在命名空间外部无效。
如果不使用用户命名空间,以 root 用户运行的容器在发生容器逃逸时,将在节点上拥有 root 权限。并且,如果授予容器某些权能,这些权能在主机上也有效。当我们使用用户命名空间时,这些情况都不会发生。
如果你想了解更多关于使用用户命名空间时发生的变化的详细信息,请参阅 man 7 user_namespaces
。
设置节点以支持用户命名空间
默认情况下,kubelet 基于主机文件和进程使用 0-65535 范围内的 UID/GID 的假设(这对于大多数 Linux 发行版是标准做法),为 Pod 分配高于此范围的 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
用户配置了 subordinate UIDs/GIDs(参阅man 5 subuid
和man 5 subgid
)。
此设置仅收集 UID/GID 范围配置,不改变执行 kubelet
的用户。
你必须遵循为 kubelet
用户分配的 subordinate ID 范围的一些约束
用于启动 Pod 的 UID 范围的 subordinate user ID,必须是 65536 的倍数,并且必须大于或等于 65536。换句话说,你不能将 0-65535 范围内的任何 ID 用于 Pod;kubelet 施加此限制是为了使创建意外不安全的配置变得困难。
subordinate ID 数量必须是 65536 的倍数
subordinate ID 数量必须至少为
65536 x <maxPods>
,其中<maxPods>
是可以在节点上运行的最大 Pod 数量。你必须为用户 ID 和组 ID 分配相同的范围。如果其他用户拥有的用户 ID 范围与组 ID 范围不一致,这无关紧要。
分配的任何范围都不能与其他分配重叠。
subordinate 配置必须只有一行。换句话说,不能有多个范围。
例如,你可以将 /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 开始,可以在 KubeletConfiguration
中设置每个 Pod 的 ID 数量。
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 安全标准 的应用。这种行为可以通过特性门控 UserNamespacesPodSecurityStandards
来控制,该门控允许最终用户提前选择加入。如果使用特性门控,管理员必须确保集群内的所有节点都启用了用户命名空间。
如果你启用了相关特性门控并创建了一个使用用户命名空间的 Pod,即使在强制执行 Baseline 或 Restricted 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 使用用户命名空间时,不允许使用其他主机命名空间。特别是,如果你设置了 hostUsers: false
,则不允许设置以下任何项
hostNetwork: true
hostIPC: true
hostPID: true
下一步
本页面上的条目提及了提供 Kubernetes 所需功能的第三方产品或项目。Kubernetes 项目作者对这些第三方产品或项目不负责任。有关更多详细信息,请参阅 CNCF 网站指南。
在提议添加额外第三方链接的变更之前,你应该阅读内容指南。