Kubernetes 1.31:细粒度的 SupplementalGroups 控制

本博客讨论了 Kubernetes 1.31 中的一项新特性,目的是改善处理 Pod 中容器内的附加组。

动机:容器镜像中 /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 命令的结果是什么?

# 创建 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 50000groups 字段)是从哪里来的呢? 答案是容器镜像中的 /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:仅将 fsGroupsupplementalGroupsrunAsGroup 字段中指定的组 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 策略可以将组 50000groups 中排除出去!

因此,确保(通过某些策略机制强制执行的)supplementalGroupsPolicy: Strict 有助于防止 Pod 中的隐式附加组。

Pod 状态中挂接的进程身份

此特性还通过 .status.containerStatuses[].user.linux 字段公开挂接到容器的第一个容器进程的进程身份。这将有助于查看隐式组 ID 是否被挂接。

...
status:
  containerStatuses:
  - name: ctr
    user:
      linux:
        gid: 3000
        supplementalGroups:
        - 3000
        - 4000
        uid: 1000
...

特性可用性

要启用 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 策略,以保持向后兼容性。

我如何了解更多?

如何参与?

此特性由 SIG Node 社区推动。请加入我们,与社区保持联系, 分享你对上述特性及其他方面的想法和反馈。我们期待听到你的声音!