为 Pod 或容器配置安全上下文

安全上下文定义了 Pod 或容器的权限和访问控制设置。安全上下文设置包括但不限于

  • 自由裁量访问控制:访问对象(如文件)的权限基于 用户 ID (UID) 和组 ID (GID).

  • 安全增强型 Linux (SELinux):对象被分配了安全标签。

  • 以特权或非特权身份运行。

  • Linux 功能:赋予进程一些权限,但不是 root 用户的所有权限。

  • AppArmor:使用程序配置文件来限制各个程序的功能。

  • Seccomp:过滤进程的系统调用。

  • allowPrivilegeEscalation:控制进程是否可以获得比其父进程更多的权限。此布尔值直接控制是否在容器进程上设置 no_new_privs 标志。allowPrivilegeEscalation 在以下情况下始终为真

    • 以特权身份运行容器,或
    • 具有 CAP_SYS_ADMIN
  • readOnlyRootFilesystem:以只读方式挂载容器的根文件系统。

以上要点并非安全上下文设置的完整集合 - 请参阅 SecurityContext 以了解完整的列表。

开始之前

您需要有一个 Kubernetes 集群,并且 kubectl 命令行工具必须配置为与您的集群通信。建议您在一个至少有两个节点的集群上运行本教程,这些节点不充当控制平面主机。如果您还没有集群,您可以使用 minikube 创建一个,或者您可以使用以下 Kubernetes 游乐场之一

要检查版本,请输入 kubectl version

设置 Pod 的安全上下文

要为 Pod 指定安全设置,请在 Pod 规范中包含 securityContext 字段。securityContext 字段是一个 PodSecurityContext 对象。您为 Pod 指定的安全设置适用于 Pod 中的所有容器。以下是一个具有 securityContextemptyDir 卷的 Pod 的配置文件

apiVersion: v1
kind: Pod
metadata:
  name: security-context-demo
spec:
  securityContext:
    runAsUser: 1000
    runAsGroup: 3000
    fsGroup: 2000
    supplementalGroups: [4000]
  volumes:
  - name: sec-ctx-vol
    emptyDir: {}
  containers:
  - name: sec-ctx-demo
    image: busybox:1.28
    command: [ "sh", "-c", "sleep 1h" ]
    volumeMounts:
    - name: sec-ctx-vol
      mountPath: /data/demo
    securityContext:
      allowPrivilegeEscalation: false

在配置文件中,runAsUser 字段指定对于 Pod 中的任何容器,所有进程都以用户 ID 1000 运行。runAsGroup 字段指定 Pod 中任何容器内的所有进程的主组 ID 为 3000。如果省略此字段,容器的主组 ID 将为 root(0)。当指定 runAsGroup 时,创建的任何文件也将归用户 1000 和组 3000 所有。由于 fsGroup 字段被指定,容器的所有进程也属于补充组 ID 2000。卷 /data/demo 及其上创建的任何文件的拥有者将是组 ID 2000。此外,当指定 supplementalGroups 字段时,容器的所有进程也属于指定的组。如果省略此字段,则表示为空。

创建 Pod

kubectl apply -f https://k8s.io/examples/pods/security/security-context.yaml

验证 Pod 的容器是否正在运行

kubectl get pod security-context-demo

获取到正在运行的容器的 shell

kubectl exec -it security-context-demo -- sh

在您的 shell 中,列出正在运行的进程

ps

输出显示进程以用户 1000 运行,这是 runAsUser 的值

PID   USER     TIME  COMMAND
    1 1000      0:00 sleep 1h
    6 1000      0:00 sh
...

在您的 shell 中,导航到 /data,并列出其中的一个目录

cd /data
ls -l

输出显示 /data/demo 目录的组 ID 为 2000,这是 fsGroup 的值。

drwxrwsrwx 2 root 2000 4096 Jun  6 20:08 demo

在您的 shell 中,导航到 /data/demo,并创建一个文件

cd demo
echo hello > testfile

列出 /data/demo 目录中的文件

ls -l

输出显示 testfile 的组 ID 为 2000,这是 fsGroup 的值。

-rw-r--r-- 1 1000 2000 6 Jun  6 20:08 testfile

运行以下命令

id

输出类似于以下内容

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

从输出中,您可以看到 gid 为 3000,与 runAsGroup 字段相同。如果省略 runAsGroup,则 gid 将保持为 0(root),并且该进程将能够与由 root(0) 组拥有的文件以及对 root (0) 组具有所需组权限的组进行交互。您还可以看到 groups 包含由 fsGroupsupplementalGroups 指定的组 ID,以及 gid

退出 shell

exit

容器镜像中的 /etc/group 中定义的隐式组成员资格

默认情况下,kubernetes 将 Pod 中的组信息与容器镜像中 /etc/group 中定义的信息合并。

apiVersion: v1
kind: Pod
metadata:
  name: security-context-demo
spec:
  securityContext:
    runAsUser: 1000
    runAsGroup: 3000
    supplementalGroups: [4000]
  containers:
  - name: sec-ctx-demo
    image: registry.k8s.io/e2e-test-images/agnhost:2.45
    command: [ "sh", "-c", "sleep 1h" ]
    securityContext:
      allowPrivilegeEscalation: false

此 Pod 安全上下文包含 runAsUserrunAsGroupsupplementalGroups。但是,您可以看到实际附加到容器进程的补充组将包括来自容器镜像中 /etc/group 的组 ID。

创建 Pod

kubectl apply -f https://k8s.io/examples/pods/security/security-context-5.yaml

验证 Pod 的容器是否正在运行

kubectl get pod security-context-demo

获取到正在运行的容器的 shell

kubectl exec -it security-context-demo -- sh

检查进程标识

$ id

输出类似于以下内容

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

您可以看到 groups 包含组 ID 50000。这是因为用户 (uid=1000)(在镜像中定义)属于组 (gid=50000)(在容器镜像内的 /etc/group 中定义)。

检查容器镜像中的 /etc/group

$ cat /etc/group

您可以看到 uid 1000 属于组 50000

...
user-defined-in-image:x:1000:
group-defined-in-image:x:50000:user-defined-in-image

退出 shell

exit

为 Pod 配置细粒度 SupplementalGroups 控制

功能状态: Kubernetes v1.31 [alpha]

此功能可以通过为 kubelet 和 kube-apiserver 设置 SupplementalGroupsPolicy 功能开关,并为 Pod 设置 .spec.securityContext.supplementalGroupsPolicy 字段来启用。

supplementalGroupsPolicy 字段定义了用于计算 Pod 中容器进程的补充组的策略。此字段有两个有效值

  • Merge:将合并容器主用户的 /etc/group 中定义的组成员资格。这是未指定时的默认策略。

  • Strict:仅将 fsGroupsupplementalGroupsrunAsGroup 字段中的组 ID 附加为容器进程的补充组。这意味着不会合并来自 /etc/group 的容器主用户的任何组成员资格。

启用此功能后,它还会在 .status.containerStatuses[].user.linux 字段中公开附加到第一个容器进程的进程标识。这将有助于检测是否附加了隐式组 ID。

apiVersion: v1
kind: Pod
metadata:
  name: security-context-demo
spec:
  securityContext:
    runAsUser: 1000
    runAsGroup: 3000
    supplementalGroups: [4000]
    supplementalGroupsPolicy: Strict
  containers:
  - name: sec-ctx-demo
    image: registry.k8s.io/e2e-test-images/agnhost:2.45
    command: [ "sh", "-c", "sleep 1h" ]
    securityContext:
      allowPrivilegeEscalation: false

此 pod 清单定义了 supplementalGroupsPolicy=Strict。您可以看到,不会将 /etc/group 中定义的任何组成员资格合并到容器进程的补充组中。

创建 Pod

kubectl apply -f https://k8s.io/examples/pods/security/security-context-6.yaml

验证 Pod 的容器是否正在运行

kubectl get pod security-context-demo

检查进程标识

kubectl exec -it security-context-demo -- id

输出类似于以下内容

uid=1000 gid=3000 groups=3000,4000

查看 Pod 的状态

kubectl get pod security-context-demo -o yaml

您可以看到 status.containerStatuses[].user.linux 字段公开了附加到第一个容器进程的进程标识。

...
status:
  containerStatuses:
  - name: sec-ctx-demo
    user:
      linux:
        gid: 3000
        supplementalGroups:
        - 3000
        - 4000
        uid: 1000
...

实现

以下容器运行时已知支持细粒度 SupplementalGroups 控制。

CRI 级

您可以在节点状态中查看是否支持此功能。

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

为 Pod 配置卷权限和所有权更改策略

功能状态: Kubernetes v1.23 [stable]

默认情况下,Kubernetes 会递归地更改每个卷内容的所有权和权限,以匹配 Pod 的 securityContext 中指定的 fsGroup,前提是该卷已挂载。对于大型卷,检查和更改所有权和权限可能需要很长时间,从而减慢 Pod 启动速度。可以使用 securityContext 内的 fsGroupChangePolicy 字段来控制 Kubernetes 检查和管理卷的所有权和权限的方式。

fsGroupChangePolicy - fsGroupChangePolicy 定义在将卷暴露到 Pod 内部之前更改其所有权和权限的行为。此字段仅适用于支持 fsGroup 控制的所有权和权限的卷类型。此字段有两个可能的值

  • OnRootMismatch:仅在根目录的权限和所有权与卷的预期权限不匹配时才更改权限和所有权。这有助于缩短更改卷所有权和权限所需的时间。
  • Always:始终在挂载卷时更改卷的权限和所有权。

例如

securityContext:
  runAsUser: 1000
  runAsGroup: 3000
  fsGroup: 2000
  fsGroupChangePolicy: "OnRootMismatch"

将卷权限和所有权更改委托给 CSI 驱动程序

功能状态: Kubernetes v1.26 [稳定]

如果您部署了支持 VOLUME_MOUNT_GROUP NodeServiceCapability容器存储接口 (CSI) 驱动程序,则根据 securityContext 中指定的 fsGroup 设置文件所有权和权限的过程将由 CSI 驱动程序执行,而不是 Kubernetes。在这种情况下,由于 Kubernetes 不会执行任何所有权和权限更改,因此 fsGroupChangePolicy 不会生效,并且如 CSI 所指定,驱动程序应使用提供的 fsGroup 挂载卷,从而导致卷可由 fsGroup 读取/写入。

设置容器的安全上下文

要为容器指定安全设置,请在容器清单中包含 securityContext 字段。securityContext 字段是一个 SecurityContext 对象。您为容器指定的安全设置仅适用于单个容器,并且在存在重叠时会覆盖在 Pod 级别进行的设置。容器设置不会影响 Pod 的卷。

以下是一个具有一个容器的 Pod 的配置文件。Pod 和容器都有一个 securityContext 字段

apiVersion: v1
kind: Pod
metadata:
  name: security-context-demo-2
spec:
  securityContext:
    runAsUser: 1000
  containers:
  - name: sec-ctx-demo-2
    image: gcr.io/google-samples/hello-app:2.0
    securityContext:
      runAsUser: 2000
      allowPrivilegeEscalation: false

创建 Pod

kubectl apply -f https://k8s.io/examples/pods/security/security-context-2.yaml

验证 Pod 的容器是否正在运行

kubectl get pod security-context-demo-2

进入正在运行的容器的 shell

kubectl exec -it security-context-demo-2 -- sh

在您的 shell 中,列出正在运行的进程

ps aux

输出显示进程以用户 2000 运行。这是为容器指定的 runAsUser 的值。它会覆盖为 Pod 指定的值 1000。

USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
2000         1  0.0  0.0   4336   764 ?        Ss   20:36   0:00 /bin/sh -c node server.js
2000         8  0.1  0.5 772124 22604 ?        Sl   20:36   0:00 node server.js
...

退出 shell

exit

为容器设置功能

使用 Linux 功能,您可以向进程授予某些特权,而无需授予 root 用户的所有特权。要添加或删除容器的 Linux 功能,请在容器清单的 securityContext 部分中包含 capabilities 字段。

首先,看看不包含 capabilities 字段会发生什么。以下配置文件不会添加或删除任何容器功能

apiVersion: v1
kind: Pod
metadata:
  name: security-context-demo-3
spec:
  containers:
  - name: sec-ctx-3
    image: gcr.io/google-samples/hello-app:2.0

创建 Pod

kubectl apply -f https://k8s.io/examples/pods/security/security-context-3.yaml

验证 Pod 的容器是否正在运行

kubectl get pod security-context-demo-3

进入正在运行的容器的 shell

kubectl exec -it security-context-demo-3 -- sh

在您的 shell 中,列出正在运行的进程

ps aux

输出显示容器的进程 ID (PID)

USER  PID %CPU %MEM    VSZ   RSS TTY   STAT START   TIME COMMAND
root    1  0.0  0.0   4336   796 ?     Ss   18:17   0:00 /bin/sh -c node server.js
root    5  0.1  0.5 772124 22700 ?     Sl   18:17   0:00 node server.js

在您的 shell 中,查看进程 1 的状态

cd /proc/1
cat status

输出显示进程的功能位图

...
CapPrm:	00000000a80425fb
CapEff:	00000000a80425fb
...

记下功能位图,然后退出 shell

exit

接下来,运行一个与前面容器相同的容器,只是它设置了额外的功能。

以下是一个运行一个容器的 Pod 的配置文件。该配置添加了 CAP_NET_ADMINCAP_SYS_TIME 功能

apiVersion: v1
kind: Pod
metadata:
  name: security-context-demo-4
spec:
  containers:
  - name: sec-ctx-4
    image: gcr.io/google-samples/hello-app:2.0
    securityContext:
      capabilities:
        add: ["NET_ADMIN", "SYS_TIME"]

创建 Pod

kubectl apply -f https://k8s.io/examples/pods/security/security-context-4.yaml

进入正在运行的容器的 shell

kubectl exec -it security-context-demo-4 -- sh

在您的 shell 中,查看进程 1 的功能

cd /proc/1
cat status

输出显示进程的功能位图

...
CapPrm:	00000000aa0435fb
CapEff:	00000000aa0435fb
...

比较两个容器的功能

00000000a80425fb
00000000aa0435fb

在第一个容器的功能位图中,位 12 和 25 是清除的。在第二个容器中,位 12 和 25 是设置的。位 12 是 CAP_NET_ADMIN,位 25 是 CAP_SYS_TIME。有关功能常数定义,请参见 capability.h

设置容器的 Seccomp 配置文件

要设置容器的 Seccomp 配置文件,请在 Pod 或容器清单的 securityContext 部分中包含 seccompProfile 字段。seccompProfile 字段是一个 SeccompProfile 对象,由 typelocalhostProfile 组成。type 的有效选项包括 RuntimeDefaultUnconfinedLocalhost。仅当 type: Localhost 时才必须设置 localhostProfile。它指示节点上预配置配置文件的路径,相对于 kubelet 配置的 Seccomp 配置文件位置(使用 --root-dir 标志配置)。

以下示例将 Seccomp 配置文件设置为节点的容器运行时默认配置文件

...
securityContext:
  seccompProfile:
    type: RuntimeDefault

以下示例将 Seccomp 配置文件设置为 <kubelet-root-dir>/seccomp/my-profiles/profile-allow.json 的预配置文件

...
securityContext:
  seccompProfile:
    type: Localhost
    localhostProfile: my-profiles/profile-allow.json

设置容器的 AppArmor 配置文件

要设置容器的 AppArmor 配置文件,请在容器的 securityContext 部分中包含 appArmorProfile 字段。appArmorProfile 字段是一个 AppArmorProfile 对象,由 typelocalhostProfile 组成。type 的有效选项包括 RuntimeDefault(默认)、UnconfinedLocalhost。仅当 typeLocalhost 时才必须设置 localhostProfile。它指示节点上预配置配置文件的名称。配置文件需要加载到适合 Pod 的所有节点上,因为您不知道 Pod 将在何处调度。在 使用配置文件设置节点 中讨论了设置自定义配置文件的方法。

注意:如果 containers[*].securityContext.appArmorProfile.type 显式设置为 RuntimeDefault,则如果节点上未启用 AppArmor,则不会承认 Pod。但是,如果未指定 containers[*].securityContext.appArmorProfile.type,则默认值(也是 RuntimeDefault)仅在节点启用 AppArmor 时才会应用。如果节点禁用 AppArmor,则会承认 Pod,但容器不会受到 RuntimeDefault 配置文件的限制。

以下示例将 AppArmor 配置文件设置为节点的容器运行时默认配置文件

...
containers:
- name: container-1
  securityContext:
    appArmorProfile:
      type: RuntimeDefault

以下示例将 AppArmor 配置文件设置为名为 k8s-apparmor-example-deny-write 的预配置配置文件

...
containers:
- name: container-1
  securityContext:
    appArmorProfile:
      type: Localhost
      localhostProfile: k8s-apparmor-example-deny-write

有关更多详细信息,请参阅 使用 AppArmor 限制容器对资源的访问

向容器分配 SELinux 标签

要向容器分配 SELinux 标签,请在 Pod 或容器清单的 securityContext 部分中包含 seLinuxOptions 字段。seLinuxOptions 字段是一个 SELinuxOptions 对象。以下示例应用 SELinux 级别

...
securityContext:
  seLinuxOptions:
    level: "s0:c123,c456"

高效的 SELinux 卷重新标记

功能状态: Kubernetes v1.28 [beta]

默认情况下,容器运行时会递归地将 SELinux 标签分配给所有 Pod 卷上的所有文件。为了加快此过程,Kubernetes 可以使用挂载选项 -o context=<label> 即时更改卷的 SELinux 标签。

要从这种加速中获益,必须满足以下所有条件

  • 功能网关 ReadWriteOncePodSELinuxMountReadWriteOncePod 必须启用。
  • Pod 必须使用具有适用 accessModes功能网关 的持久卷声明。
    • 卷的 accessModes: ["ReadWriteOncePod"],并且功能网关 SELinuxMountReadWriteOncePod 已启用。
    • 或者,卷可以使用任何其他访问模式,并且功能网关 SELinuxMountReadWriteOncePodSELinuxMount 必须都启用。
  • Pod(或使用持久卷声明的所有容器)必须设置 seLinuxOptions
  • 相应的持久卷必须是
    • 使用旧版树内 iscsirbdfc 卷类型的卷。
    • 或者使用 CSI 驱动程序的卷。CSI 驱动程序必须通过在其 CSIDriver 实例中设置 spec.seLinuxMount: true 来宣布它支持使用 -o context 进行挂载。

对于任何其他卷类型,SELinux 重新标记以另一种方式发生:容器运行时会递归地更改卷中所有 inode(文件和目录)的 SELinux 标签。卷中的文件和目录越多,重新标记所需的时间就越长。

管理对 /proc 文件系统的访问

功能状态: Kubernetes v1.12 [alpha]

对于遵循 OCI 运行时规范的运行时,容器默认以一种模式运行,在这种模式下,有多条路径同时被屏蔽并设置为只读。这样做的结果是容器在容器的挂载命名空间内存在这些路径,并且它们的功能类似于容器是一个隔离的主机,但容器进程无法写入它们。屏蔽和只读路径的列表如下

  • 屏蔽的路径

    • /proc/asound
    • /proc/acpi
    • /proc/kcore
    • /proc/keys
    • /proc/latency_stats
    • /proc/timer_list
    • /proc/timer_stats
    • /proc/sched_debug
    • /proc/scsi
    • /sys/firmware
    • /sys/devices/virtual/powercap
  • 只读路径

    • /proc/bus
    • /proc/fs
    • /proc/irq
    • /proc/sys
    • /proc/sysrq-trigger

对于某些 Pod,您可能希望绕过对路径的默认屏蔽。最常见的需要这样做的情况是,如果您尝试在 Kubernetes 容器(在 Pod 内)内运行容器。

securityContext 字段 procMount 允许用户请求容器的 /procUnmasked,或者被容器进程以读写方式挂载。这也适用于 /sys/firmware,它不在 /proc 中。

...
securityContext:
  procMount: Unmasked

讨论

Pod 的安全上下文适用于 Pod 的容器,以及在适用情况下适用于 Pod 的卷。具体来说,fsGroupseLinuxOptions 应用于卷的方式如下

  • fsGroup:支持所有权管理的卷将被修改为由 fsGroup 中指定的 GID 拥有和写入。有关更多详细信息,请参阅 所有权管理设计文档

  • seLinuxOptions:支持 SELinux 标记的卷将被重新标记,以使其可以通过 seLinuxOptions 下指定的标签访问。通常,您只需要设置 level 部分。这将为 Pod 中的所有容器以及卷设置 多类别安全 (MCS) 标签。

清理

删除 Pod

kubectl delete pod security-context-demo
kubectl delete pod security-context-demo-2
kubectl delete pod security-context-demo-3
kubectl delete pod security-context-demo-4

下一步

本页面上的项目引用第三方产品或项目,这些产品或项目提供 Kubernetes 所需的功能。Kubernetes 项目作者不对这些第三方产品或项目负责。有关更多详细信息,请参阅 CNCF 网站指南

在提出添加额外第三方链接的更改之前,您应该阅读 内容指南

上次修改时间:2024 年 7 月 26 日上午 10:07 PST:应用代码审查建议的改进 (3fb2925b56)