用户命名空间
特性状态: Kubernetes v1.30 [beta]
本页解释了在 Kubernetes Pod 中如何使用用户命名空间。 用户命名空间将容器内运行的用户与主机中的用户隔离开来。
在容器中以 root 身份运行的进程可以在主机中以不同的(非 root)用户身份运行; 换句话说,该进程在用户命名空间内的操作具有完全的权限, 但在命名空间外的操作是无特权的。
你可以使用这个功能来减少被破坏的容器对主机或同一节点中的其他 Pod 的破坏。 有几个安全漏洞被评为 高 或 重要, 当用户命名空间处于激活状态时,这些漏洞是无法被利用的。 预计用户命名空间也会减轻一些未来的漏洞。
准备开始
说明: 本部分链接到提供 Kubernetes 所需功能的第三方项目。Kubernetes 项目作者不负责这些项目。此页面遵循CNCF 网站指南,按字母顺序列出项目。要将项目添加到此列表中,请在提交更改之前阅读内容指南。
这是一个只对 Linux 有效的功能特性,且需要 Linux 支持在所用文件系统上挂载 idmap。 这意味着:
- 在节点上,你用于
/var/lib/kubelet/pods/
的文件系统,或你为此配置的自定义目录, 需要支持 idmap 挂载。 - Pod 卷中使用的所有文件系统都必须支持 idmap 挂载。
在实践中,这意味着你最低需要 Linux 6.3,因为 tmpfs 在该版本中开始支持 idmap 挂载。 这通常是需要的,因为有几个 Kubernetes 功能特性使用 tmpfs (默认情况下挂载的服务账号令牌使用 tmpfs、Secret 使用 tmpfs 等等)。
Linux 6.3 中支持 idmap 挂载的一些比较流行的文件系统是:btrfs、ext4、xfs、fat、 tmpfs、overlayfs。
此外,容器运行时及其底层 OCI 运行时必须支持用户命名空间。以下 OCI 运行时提供支持:
- crun 1.9 或更高版本(推荐 1.13+ 版本)。
说明:
许多 OCI 运行时不包含在 Linux Pod 中使用用户命名空间所需的支持。 如果你使用托管 Kubernetes,或者使用软件包下载并安装 Kubernetes 集群, 则集群中的节点可能使用不包含支持此特性的运行时。 例如,最广泛使用的 OCI 运行时是 runc
,而 runc 的 1.1.z
版本不支持 Kubernetes 实现用户命名空间所需的所有特性。
如果有比 1.1 更新的 runc 版本可供使用,请检查其文档和发行说明以了解兼容性 (特别寻找 idmap 挂载支持,因为这一特性是缺失的)。
此外,需要在容器运行时提供支持, 才能在 Kubernetes Pod 中使用这一功能:
- CRI-O:1.25(及更高)版本支持配置容器的用户命名空间。
containerd v1.7 与 Kubernetes v1.27 至 v1.31 版本中的用户命名空间不兼容。 Kubernetes v1.25 和 v1.26 使用了早期的实现,在用户命名空间方面与 containerd v1.7 兼容。 如果你使用的 Kubernetes 版本不是 1.31,请查看该版本 Kubernetes 的文档以获取更准确的信息。 如果有比 v1.7 更新的 containerd 版本可供使用,请检查 containerd 文档以获取兼容性信息。
你可以在 GitHub 上的 [issue][CRI-dockerd-issue] 中查看 cri-dockerd 中用户命名空间支持的状态。
介绍
用户命名空间是一个 Linux 功能,允许将容器中的用户映射到主机中的不同用户。 此外,在某用户命名空间中授予 Pod 的权能只在该命名空间中有效,在该命名空间之外无效。
一个 Pod 可以通过将 pod.spec.hostUsers
字段设置为 false
来选择使用用户命名空间。
kubelet 将挑选 Pod 所映射的主机 UID/GID, 并以此保证同一节点上没有两个 Pod 使用相同的方式进行映射。
pod.spec
中的 runAsUser
、runAsGroup
、fsGroup
等字段总是指的是容器内的用户。 启用该功能时,有效的 UID/GID 在 0-65535 范围内。这以限制适用于文件和进程(runAsUser
、runAsGroup
等)。
使用这个范围之外的 UID/GID 的文件将被视为属于溢出 ID, 通常是 65534(配置在 /proc/sys/kernel/overflowuid和/proc/sys/kernel/overflowgid
)。 然而,即使以 65534 用户/组的身份运行,也不可能修改这些文件。
大多数需要以 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 的用户命名空间内, 并且在这一命名空间之外大多无效,有些甚至完全无效。这里有两个例子:
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 范围, 你必须遵循一些限制:
启动 Pod 的 UID 范围的从属用户 ID 必须是 65536 的倍数,并且还必须大于或等于 65536。 换句话说,Pod 不能使用 0-65535 范围内的任何 ID;kubelet 施加此限制是为了使创建意外不安全的配置变得困难。
从属 ID 计数必须是 65536 的倍数
从属 ID 计数必须至少为
65536 x <maxPods>
,其中<maxPods>
是节点上可以运行的最大 Pod 数量。你必须为用户 ID 和组 ID 分配相同的范围。如果其他用户的用户 ID 范围与组 ID 范围不一致也没关系。
所分配的范围不得与任何其他分配重叠。
从属配置必须只有一行。换句话说,你不能有多个范围。
例如,你可以定义 /etc/subuid
和 /etc/subgid
来为 kubelet
用户定义以下条目:
# 格式为:
# name:firstID:count of IDs
# 在哪里:
# - firstID 是 65536 (可能的最小值)
# - IDs 的数量是 110(默认数量限制)* 65536
kubelet:65536:7208960
与 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
限制
当 Pod 使用用户命名空间时,不允许 Pod 使用其他主机命名空间。 特别是,如果你设置了 hostUsers: false
,那么你就不可以设置如下属性:
hostNetwork: true
hostIPC: true
hostPID: true