Kubernetes 1.31:细粒度的 SupplementalGroups 控制
本博客讨论了 Kubernetes 1.31 中的一项新特性,目的是改善处理 Pod 中容器内的附加组。
动机:容器镜像中 /etc/group
中定义的隐式组成员关系
尽管这种行为可能并不受许多 Kubernetes 集群用户/管理员的欢迎,
但 Kubernetes 默认情况下会将 Pod 中的组信息与容器镜像中 /etc/group
中定义的信息进行合并。
让我们看一个例子,以下 Pod 在 Pod 的安全上下文中指定了
runAsUser=1000
、runAsGroup=3000
和 supplementalGroups=4000
。
apiVersion: v1
kind: Pod
metadata:
name: implicit-groups
spec:
securityContext:
runAsUser: 1000
runAsGroup: 3000
supplementalGroups: [4000]
containers:
- name: ctr
image: registry.k8s.io/e2e-test-images/agnhost:2.45
command: [ "sh", "-c", "sleep 1h" ]
securityContext:
allowPrivilegeEscalation: false
在 ctr
容器中执行 id
命令的结果是什么?
# 创建 Pod:
$ kubectl apply -f https://k8s.io/blog/2024-08-22-Fine-grained-SupplementalGroups-control/implicit-groups.yaml
# 验证 Pod 的容器正在运行:
$ kubectl get pod implicit-groups
# 检查 id 命令
$ kubectl exec implicit-groups -- id
输出应类似于:
uid=1000 gid=3000 groups=3000,4000,50000
尽管 50000
根本没有在 Pod 的清单中被定义,但附加组中的组 ID 50000
(groups
字段)是从哪里来的呢?
答案是容器镜像中的 /etc/group
文件。
检查容器镜像中 /etc/group
的内容应如下所示:
$ kubectl exec implicit-groups -- cat /etc/group
...
user-defined-in-image:x:1000:
group-defined-in-image:x:50000:user-defined-in-image
原来如此!容器的主要用户 1000
属于最后一个条目中的组 50000
。
因此,容器镜像中为容器的主要用户定义的组成员关系会被隐式合并到 Pod 的信息中。 请注意,这是当前 CRI 实现从 Docker 继承的设计决策,而社区直到现在才重新考虑这个问题。
这有什么问题?
从容器镜像中的 /etc/group
隐式合并的组信息可能会引起一些担忧,特别是在访问卷时
(有关细节参见 kubernetes/kubernetes#112879),
因为在 Linux 中文件权限是通过 uid/gid 进行控制的。
更糟糕的是,隐式的 gid 无法被任何策略引擎所检测/验证,因为在清单中没有隐式组信息的线索。
这对 Kubernetes 的安全性也可能构成隐患。
Pod 中的细粒度 SupplementalGroups 控制:SupplementaryGroupsPolicy
为了解决上述问题,Kubernetes 1.31 在 Pod 的 .spec.securityContext
中引入了新的字段 supplementalGroupsPolicy
。
此字段提供了一种控制 Pod 中容器进程如何计算附加组的方法。可用的策略如下:
Merge:将容器的主要用户在
/etc/group
中定义的组成员关系进行合并。 如果不指定,则应用此策略(即为了向后兼容性而保持的原有行为)。Strict:仅将
fsGroup
、supplementalGroups
或runAsGroup
字段中指定的组 ID 挂接为容器进程的附加组。这意味着容器的主要用户在/etc/group
中定义的任何组成员关系都不会被合并。
让我们看看 Strict
策略是如何工作的。
apiVersion: v1
kind: Pod
metadata:
name: strict-supplementalgroups-policy
spec:
securityContext:
runAsUser: 1000
runAsGroup: 3000
supplementalGroups: [4000]
supplementalGroupsPolicy: Strict
containers:
- name: ctr
image: registry.k8s.io/e2e-test-images/agnhost:2.45
command: [ "sh", "-c", "sleep 1h" ]
securityContext:
allowPrivilegeEscalation: false
# 创建 Pod:
$ kubectl apply -f https://k8s.io/blog/2024-08-22-Fine-grained-SupplementalGroups-control/strict-supplementalgroups-policy.yaml
# 验证 Pod 的容器正在运行:
$ kubectl get pod strict-supplementalgroups-policy
# 检查进程身份:
kubectl exec -it strict-supplementalgroups-policy -- id
输出应类似于:
uid=1000 gid=3000 groups=3000,4000
你可以看到 Strict
策略可以将组 50000
从 groups
中排除出去!
因此,确保(通过某些策略机制强制执行的)supplementalGroupsPolicy: Strict
有助于防止 Pod 中的隐式附加组。
说明:
实际上,这还不够,因为具有足够权限/能力的容器可以更改其进程身份。 有关细节参见以下章节。
Pod 状态中挂接的进程身份
此特性还通过 .status.containerStatuses[].user.linux
字段公开挂接到容器的第一个容器进程的进程身份。这将有助于查看隐式组 ID 是否被挂接。
...
status:
containerStatuses:
- name: ctr
user:
linux:
gid: 3000
supplementalGroups:
- 3000
- 4000
uid: 1000
...
说明:
请注意,status.containerStatuses[].user.linux
字段中的值是首次挂接到容器中第一个容器进程的进程身份。
如果容器具有足够的权限调用与进程身份相关的系统调用(例如
setuid(2)
、
setgid(2)
或
setgroups(2)
等),
则容器进程可以更改其身份。因此,实际的进程身份将是动态的。
特性可用性
要启用 supplementalGroupsPolicy
字段,必须使用以下组件:
- Kubernetes:v1.31 或更高版本,启用
SupplementalGroupsPolicy
特性门控。 截至 v1.31,此门控标记为 Alpha。 - CRI 运行时:
- containerd:v2.0 或更高版本
- CRI-O:v1.31 或更高版本
你可以在 Node 的 .status.features.supplementalGroupsPolicy
字段中查看此特性是否受支持。
apiVersion: v1
kind: Node
...
status:
features:
supplementalGroupsPolicy: true
接下来
Kubernetes SIG Node 希望并期待此特性将在 Kubernetes 后续版本中进阶至 Beta, 并最终进阶至正式发布(GA),以便用户不再需要手动启用特性门控。
当 supplementalGroupsPolicy
未被指定时,将应用 Merge
策略,以保持向后兼容性。
我如何了解更多?
- 为 Pod 或容器配置安全上下文以获取有关
supplementalGroupsPolicy
的更多细节 - KEP-3619:细粒度 SupplementalGroups 控制
如何参与?
此特性由 SIG Node 社区推动。请加入我们,与社区保持联系, 分享你对上述特性及其他方面的想法和反馈。我们期待听到你的声音!