Kubernetes v1.33:细粒度 SupplementalGroups 控制功能进入 Beta 阶段

新字段 supplementalGroupsPolicy 在 Kubernetes v1.31 中作为可选的 Alpha 特性引入,并已在 v1.33 中升级为 Beta;相应的功能门控(SupplementalGroupsPolicy)现已默认启用。此功能可以对容器中的附加组(supplemental groups)进行更精确的控制,从而加强安全态势,尤其是在访问卷时。此外,它还提高了容器中 UID/GID 详细信息的透明度,从而提供更好的安全监督。

请注意,此 Beta 版本包含一些行为上的破坏性变更。详情请参阅Beta 版本中引入的行为变更升级注意事项部分。

动机:容器镜像中 /etc/group 定义的隐式组成员关系

尽管大多数 Kubernetes 集群管理员/用户可能没有意识到,但 Kubernetes 默认情况下会*合并*来自 Pod 的组信息与容器镜像中 /etc/group 定义的信息。

让我们看一个例子,下面的 Pod 清单在 Pod 的安全上下文中指定了 runAsUser=1000runAsGroup=3000supplementalGroups=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 命令的结果是什么?输出应类似于:

uid=1000 gid=3000 groups=3000,4000,50000

附加组(groups 字段)中的组 ID 50000 是从哪里来的,尽管 50000 根本没有在 Pod 的清单中定义?答案是容器镜像中的 /etc/group 文件。

检查容器镜像中 /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 中*隐式*合并的组信息存在安全风险。这些隐式 GID 无法被策略引擎检测或验证,因为它们在 Pod 清单中没有任何记录。这可能导致意外的访问控制问题,尤其是在访问卷时(详见 kubernetes/kubernetes#112879),因为文件权限在 Linux 中由 UID/GID 控制。

Pod 中细粒度的附加组控制:supplementaryGroupsPolicy

为了解决上述问题,Pod 的 .spec.securityContext 现在包含了 supplementalGroupsPolicy 字段。

此字段允许你控制 Kubernetes 如何计算 Pod 内容器进程的附加组。可用的策略有:

  • Merge:将合并容器主用户在 /etc/group 中定义的组成员关系。如果未指定,将应用此策略(即为了向后兼容而保持现有行为)。

  • Strict:只有在 fsGroupsupplementalGroupsrunAsGroup 中指定的组 ID 会作为附加组附加到容器进程中。容器主用户在 /etc/group 中定义的组成员关系将被忽略。

让我们看看 Strict 策略是如何工作的。下面的 Pod 清单指定了 supplementalGroupsPolicy: 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

ctr 容器中执行 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
...

Strict 策略需要较新的 CRI 版本

实际上,CRI 运行时(例如 containerd、CRI-O)在计算要附加到容器的附加组 ID 方面扮演着核心角色。因此,SupplementalGroupsPolicy=Strict 需要支持此功能的 CRI 运行时(SupplementalGroupsPolicy: Merge 可以与不支持此功能的 CRI 运行时一起工作,因为该策略是完全向后兼容的)。

以下是一些支持此功能的 CRI 运行时及其所需的版本:

  • containerd:v2.0 或更高版本
  • CRI-O:v1.31 或更高版本

并且,你可以在节点的 .status.features.supplementalGroupsPolicy 字段中查看是否支持该功能。

apiVersion: v1
kind: Node
...
status:
  features:
    supplementalGroupsPolicy: true

Beta 版本中引入的行为变更

在 Alpha 版本中,当一个带有 supplementalGroupsPolicy: Strict 的 Pod 被调度到一个不支持该功能的节点上时(即 .status.features.supplementalGroupsPolicy=false),该 Pod 的附加组策略会静默地回退到 Merge

在 v1.33 中,此功能已进入 Beta 阶段,以更严格地执行策略,即 kubelet 会拒绝其节点无法确保指定策略的 Pod。如果你的 Pod 被拒绝,你将看到带有 reason=SupplementalGroupsPolicyNotSupported 的警告事件,如下所示:

apiVersion: v1
kind: Event
...
type: Warning
reason: SupplementalGroupsPolicyNotSupported
message: "SupplementalGroupsPolicy=Strict is not supported in this node"
involvedObject:
  apiVersion: v1
  kind: Pod
  ...

升级注意事项

如果你已经在使用此功能,特别是 supplementalGroupsPolicy: Strict 策略,我们假设你集群的 CRI 运行时已经支持此功能。在这种情况下,你无需担心上述的 Pod 拒绝问题。

但是,如果你的集群:

  • 使用了 supplementalGroupsPolicy: Strict 策略,但是
  • 其 CRI 运行时尚不支持该功能(即 .status.features.supplementalGroupsPolicy=false),

你需要在升级集群时为行为变更(Pod 拒绝)做好准备。

我们推荐几种方法来避免意外的 Pod 拒绝:

  • 在升级 Kubernetes 的同时或之前升级你集群的 CRI 运行时
  • 为你的节点添加一些标签,描述 CRI 运行时是否支持此功能,并为带有 Strict 策略的 Pod 添加标签选择器以选择此类节点(但是,在这种情况下,你需要监控 Pending 状态的 Pod 数量,而不是 Pod 拒绝)。

参与进来

此功能由 SIG Node 社区推动。请加入我们,与社区联系,分享你关于上述功能及其他方面的想法和反馈。我们期待你的回音!

我如何了解更多信息?