本文发表于一年多前。旧文章可能包含过时内容。请检查页面中的信息自发布以来是否已变得不正确。
Kubernetes 1.31:细粒度的 SupplementalGroups 控制
这篇博文讨论了 Kubernetes 1.31 中的一个新功能,该功能旨在改进 Pod 内容器中补充组的处理。
动机:容器镜像中 /etc/group
定义的隐式组成员关系
虽然这种行为可能不受许多 Kubernetes 集群用户/管理员的欢迎,但 Kubernetes 默认会**合并**来自 Pod 的组信息与容器镜像中 /etc/group
中定义的信息。
让我们看一个例子,下面的 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
命令的结果是什么?
# Create the Pod:
$ kubectl apply -f https://k8s.io/blog/2024-08-22-Fine-grained-SupplementalGroups-control/implicit-groups.yaml
# Verify that the Pod's Container is running:
$ kubectl get pod implicit-groups
# Check the id command
$ kubectl exec implicit-groups -- id
那么,输出应类似于此
uid=1000 gid=3000 groups=3000,4000,50000
补充组(groups
字段)中的组 ID 50000
是从哪里来的,即使在 Pod 的清单中根本没有定义 50000
?答案是容器镜像中的 /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
。
因此,容器镜像中为容器主用户在 /etc/group
中定义的组成员关系被**隐式**地合并到了来自 Pod 的信息中。请注意,这是当前的 CRI 实现从 Docker 继承的设计决策,社区直到现在才真正重新考虑过它。
这有什么问题?
从容器镜像中 /etc/group
**隐式**合并的组信息可能会引起一些问题,特别是在访问卷时(详情请参阅 kubernetes/kubernetes#112879),因为在 Linux 中文件权限是由 uid/gid 控制的。更糟糕的是,来自 /etc/group
的隐式 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
# Create the Pod:
$ kubectl apply -f https://k8s.io/blog/2024-08-22-Fine-grained-SupplementalGroups-control/strict-supplementalgroups-policy.yaml
# Verify that the Pod's Container is running:
$ kubectl get pod strict-supplementalgroups-policy
# Check the process identity:
kubectl exec -it strict-supplementalgroups-policy -- id
输出应类似于此
uid=1000 gid=3000 groups=3000,4000
你可以看到 Strict
策略可以从 groups
中排除组 50000
!
因此,确保 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 或更高版本
你可以在节点的 .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 社区推动。欢迎加入我们,与社区建立联系,分享你对上述功能及其他方面的想法和反馈。我们期待你的回音!