k8s 容器安全上下文

发布时间 2023-08-08 18:02:48作者: 小吉猫

容器安全上下文介绍

kubernetes为安全运行pod及容器运行设计了安全上下文机制,该机制允许用户和管理员定义pod或容器的特权与访问控制,已配置容器与主机以及主机之上的其它容器间的隔离级别。安全上下文就是一组用来决定容器时如何创建和运行的约束条件,这些条件代表创建和运行容器时使用的运行时参数。需要提升容器权限时,用户通常只应授予容器执行其工作所需的访问权限,以最小权限法则来抑制容器对基础架构及其它容器产生的负面影响。

kubernetes支持用户在pod及容器级别配置安全上下文,并允许管理员通过pod安全策略在集群全局级别限制用户在创建和运行pod时可设定安全上下文。
安全上下文(Security Context)定义 Pod 或 Container 的特权与访问控制设置。 安全上下文包括但不限于:

  自主访问控制(DAC):传统UNIX的访问控制机制,它允许对象的所有者基于UUID和GID设定对象的访问权限。
  Linux功能:Linux为突破系统上传统的两级用户(root和普通用户)授权模型,而将内核管理权限打散成多个不同维度或级别的权限子集,每个子集称为一种功能或能力,例如CAP_NET_ADMIN、CAP_SYS_TIME、CAP_SYS_PTRACE和CAP_SYS_ADMIN等,从而允许进程仅具有一部分内核管理功能就能完成必要的管理任务。
  seccomp:全称为secure computing mode,是linux内核的安全模型,用于为默认可发起的任何系统调用进程施加控制机制,人为地禁止它能够发起的系统调用,有效降低了程序被劫持的危害级别。
  AppArmor:全称为Application Armor,意为应用盔甲,是linux内核的一个安全模块,通过加载带内核的配置文件来定义对程序的约束与控制。
  SELinux:全称为Security-Enhanced Linux,意为安全加强的Linux,是linux内核的一个安全模块,提供了包括强制访问控制在内的访问控制安全策略机制。
  Privileged模式:即特权模式容器,该模式下容器中的root用户拥有所有的内核功能,即具有真正的管理员权限,它能看到主机上的所有设备,能够挂载文件系统,甚至可以在容器中运行容器;容器默认运行于非特权模式。
  AllowPrivilegeEscalation:控制是否允许特权升级,及进程是否能够获取比父进程更多的特权;运行于特权模式或具有CAP_SYS_ADMIN能力的容器默认允许特权升级。
这些安全上下文相关的特性多数嵌套定义在pod或容器的securityContext字段中,而且有些特性对应的嵌套字段还不止一个。而seccomp和AppArmor的安全上下文则需要以资源注解的方式进行定义,而且技能由管理员在集群级别进行pod安全策略配置。

配置格式

kubernetes默认以非特权模式创建并允许容器,同时禁用了其他与管理功能相关的内核能力,但未额外设定其他上下文参数。
apiVersion: v1
kind: Pod
metadata:
  name: securitycontext-capabilities-demo
  namespace: default
spec:
  securityContext:            #pod级别的安全上下文,对内部多有容器均有效
    runAsUer <integer>        #以指定的用户身份运行容器进程,默认由镜像中的USER指定
    runAsGroup <integer>      #以指定的用户组运行容器进程,默认使用的组随容器运行时设定
    supplementalGroups <[] integer>  #为容器中1号进程的用户添加的附加组
    fsGroup <integer>         #为容器中的1号进程附加一个专用组,其功能类似于sgid
    runAsNonRoot <boolean>    #是否以非root身份运行
    seLinuxOptions <object>   #SELINUX的相关配置
    sysctl <[] object>        #应用到当前pod名称空间级别的sysctl参数设置列表
    windowsOptions <object>   #windows容器专用配置
  containers:
  - name: ...
    image: ...
    securityContext:      #容器级别的安全上下文,仅在当前容器生效
      runAsUer <integer>        #以指定的用户身份运行容器进程,默认由镜像中的USER指定
      runAsGroup <integer>      #以指定的用户组运行容器进程,默认使用的组随容器运行时设定
      runAsNonRoot <boolean>    #是否以非root身份运行
      allowPrivilegeEscalation <boolean> #是否允许特权升级
      capabilities <object>     #为当前容器添加或删除内核能力
        add <[] string>         #添加由列表定义的各项内核能力
        drop <[] string>        #移除由列表定义的各项内核能力
      privileged <boolean>      #是否允许为特权容器
      procMount <string>        #设置容器procMount类型,默认为DefaultProcMount
      readOnlyRootFilesystem <boolean>  #是否将根文件系统设置为只读模式
      seLinuxOptions <object>   #selinux相关配置
      windowsOptions <object>   #windows相关配置

管理容器的内核功能

传统linux仅实现了特权和非特权两类进程,前者是指以0号UID身份运行的进程,而后者则是从属非0号UID用户的进程。linux内核从2.2版本开始将附加在超级用户的权限分割为多个独立单元,这些单元是线程级别的,它们可配置在每个线程之上为其赋予特定的管理能力。linux内核常用的功能包括但不限于如下这些:

  CAP_CHOWN:改变文件的UID和GID。
  CAP_MKNOD:借助系统调用mknod()创建设备文件。
  CAP_NET_ADMIN:网络管理相关的操作,可用于管理网络接口、netfilter上的iptables规则、路由表、透明代理、TOS、清空驱动统计数据、设计混杂模式和启动多播功能等。
  CAP_NET_BIND_SERVICE:绑定小于1024的特权端口,但该功能在重新映射用户后可能会失效。
  CAP_NET_RAW:使用RAW或PACKET类型的套接字,并可绑定任何地址进行透明代理。
  CAP_SYS_ADMIN:支持内核上的很大一部分管理功能。
  CAP_SYS_BOOT:重启系统
  CAP_SYS_CHROOT:使用chroot()进行根文件系统切换,并能够调用setns()修改Mount名称空间
  CAP_SYS_MODULE:转载内核模块
  CAP_SYS_TIME:设定系统时钟和硬件时钟
  CAP_SYSLOG:调用syslog()执行日志相关的特权操作。
系统管理员可以通过get命令获取程序文件上的内核功能,并可使用setcap为程序文件设定内核功能或取消其已有的内核功能。而kubernetes上运行的进程设定内核功能则需要在pod容器的安全上下文中嵌套capabilities字段,添加和移除内核能力还需要分别在下一级嵌套中使用add或drop字段。这两个字段可接收以内核能力名称为列表项,但引用个内核能力名称时序移除CAP_前缀,例如可使用NET_ADMIN等。
apiVersion: v1
kind: Pod
metadata:
  name: securitycontext-capabilities-demo
  namespace: default
spec:
  containers:
  - name: demo
    image: 192.168.174.120/baseimages/tomcat-app1:v1.0
    imagePullPolicy: IfNotPresent
    securityContext:
      capabilities:
        add: ['NET_ADMIN']
        drop: ['CHOWN']

特权模式容器

kubernetes集群中,kube-proxy中的容器就运行在特权模式。
# kubectl get pods calico-node-pbfcs -n kube-system -o yaml
apiVersion: v1
kind: Pod
metadata:
    ...
  - image: 192.168.174.120/baseimages/calico-pod2daemon-flexvol:v3.19.2
    imagePullPolicy: IfNotPresent
    name: flexvol-driver
    resources: {}
    securityContext:
      privileged: true   #运行在特权模式
    ....

在pod中使用sysctl

kubernetes允许在pod中独立安全地设置支持名称空间级别的内核参数,它们默认处于启动状态,而节点级别内核参数则被认为是不安全地,它们默认处于禁用状态。kernel.shm_rmid_force、net.ipv4.ip_local_range和net.ipv4.tcp_sysncookies这3个内核参数被kubernetes视为安全参数,它们在pod安全上下文的sysctl参数内嵌套使用,而余下的绝大多数的内核参数都是非安全参数,需要管理员手动在每个节点上通过kubelet选项逐个启用后才能配置到pod上。例如在各工作节点上编辑/etc/default/kubelet文件,添加以下内容允许在pod上使用指定的两个非安全地内核参数,并重启kubelet服务使之生效。
KUBELET_EXTRA_ARGS='--allowed-unsafe-sysctls=net.core.somaxconn,net.ipv4.ip_unprivileged_port_start'

配置样例

apiVersion: v1
kind: Pod
metadata:
  name: securitycontext-sysctls-demo
  namespace: default
spec:
  securityContext:
    sysctls:
    - name: kernel.shm_rmid_forced
      value: "0"
    - name: net.ipv4.ip_unprivileged_port_start
      value: "0"
  containers:
  - name: demo
    image: 192.168.174.120/baseimages/tomcat-app1:v1.0
    imagePullPolicy: IfNotPresent
    securityContext:
      runAsUser: 1001
      runAsGroup: 1001

为 Pod 设置安全性上下文

要为 Pod 设置安全性设置,可在 Pod 规约中包含 securityContext 字段。securityContext 字段值是一个 PodSecurityContext 对象。你为 Pod 所设置的安全性配置会应用到 Pod 中所有 Container 上。 下面是一个 Pod 的配置文件,该 Pod 定义了 securityContext 和一个 emptyDir 卷:
apiVersion: v1
kind: Pod
metadata:
  name: security-context-demo
spec:
  securityContext:
    runAsUser: 1000
    runAsGroup: 3000
    fsGroup: 2000
  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 字段指定所有容器中的进程都以主组 ID 3000 来运行。 如果忽略此字段,则容器的主组 ID 将是 root(0)。 当 runAsGroup 被设置时,所有创建的文件也会划归用户 1000 和组 3000。 由于 fsGroup 被设置,容器中所有进程也会是附组 ID 2000 的一部分。 卷 /data/demo 及在该卷中创建的任何文件的属主都会是组 ID 2000。

为 Pod 配置卷访问权限和属主变更策略

默认情况下,Kubernetes 在挂载一个卷时,会递归地更改每个卷中的内容的属主和访问权限, 使之与 Pod 的 securityContext 中指定的 fsGroup 匹配。 对于较大的数据卷,检查和变更属主与访问权限可能会花费很长时间,降低 Pod 启动速度。 你可以在 securityContext 中使用 fsGroupChangePolicy 字段来控制 Kubernetes 检查和管理卷属主和访问权限的方式。
fsGroupChangePolicy - fsGroupChangePolicy 定义在卷被暴露给 Pod 内部之前对其 内容的属主和访问许可进行变更的行为。此字段仅适用于那些支持使用 fsGroup 来 控制属主与访问权限的卷类型。此字段的取值可以是:

  OnRootMismatch:只有根目录的属主与访问权限与卷所期望的权限不一致时, 才改变其中内容的属主和访问权限。这一设置有助于缩短更改卷的属主与访问 权限所需要的时间。
  Always:在挂载卷时总是更改卷中内容的属主和访问权限。

例如:

securityContext:
  runAsUser: 1000
  runAsGroup: 3000
  fsGroup: 2000
  fsGroupChangePolicy: "OnRootMismatch"
说明: 此字段对于 secret、 configMap 和 emptydir 这类临时性存储无效。

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

特性状态: Kubernetes v1.26 [stable]
如果你部署了一个容器存储接口 (CSI) 驱动,而该驱动支持 VOLUME_MOUNT_GROUP NodeServiceCapability, 在 securityContext 中指定 fsGroup 来设置文件所有权和权限的过程将由 CSI 驱动而不是 Kubernetes 来执行。在这种情况下,由于 Kubernetes 不执行任何所有权和权限更改, fsGroupChangePolicy 不会生效,并且按照 CSI 的规定,CSI 驱动应该使用所指定的 fsGroup 来挂载卷,从而生成了一个对 fsGroup 可读/可写的卷.

为 Container 设置安全性上下文

若要为 Container 设置安全性配置,可以在 Container 清单中包含 securityContext 字段。securityContext 字段的取值是一个 SecurityContext 对象。你为 Container 设置的安全性配置仅适用于该容器本身,并且所指定的设置在与 Pod 层面设置的内容发生重叠时,会重写 Pod 层面的设置。Container 层面的设置不会影响到 Pod 的卷。

下面是一个 Pod 的配置文件,其中包含一个 Container。Pod 和 Container 都有 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/node-hello:1.0
    securityContext:
      runAsUser: 2000
      allowPrivilegeEscalation: false
进程以用户 2000 运行。该值是在 Container 的 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

为容器设置 Seccomp 配置

若要为容器设置 Seccomp 配置(Profile),可在你的 Pod 或 Container 清单的 securityContext 节中包含 seccompProfile 字段。该字段是一个 SeccompProfile 对象,包含 type 和 localhostProfile 属性。 type 的合法选项包括 RuntimeDefault、Unconfined 和 Localhost。 localhostProfile 只能在 type: Localhost 配置下才可以设置。 该字段标明节点上预先设定的配置的路径,路径是相对于 kubelet 所配置的 Seccomp 配置路径(使用 --root-dir 设置)而言的。

下面是一个例子,设置容器使用节点上容器运行时的默认配置作为 Seccomp 配置:
...
securityContext:
  seccompProfile:
    type: RuntimeDefault
下面是另一个例子,将 Seccomp 的样板设置为位于 <kubelet-根目录>/seccomp/my-profiles/profile-allow.json 的一个预先配置的文件。
...
securityContext:
  seccompProfile:
    type: Localhost
    localhostProfile: my-profiles/profile-allow.json

为 Container 赋予 SELinux 标签

若要给 Container 设置 SELinux 标签,可以在 Pod 或 Container 清单的 securityContext 节包含 seLinuxOptions 字段。 seLinuxOptions 字段的取值是一个 SELinuxOptions 对象。
下面是一个应用 SELinux 标签的例子:
...
securityContext:
  seLinuxOptions:
    level: "s0:c123,c456"
说明: 要指定 SELinux,需要在宿主操作系统中装载 SELinux 安全性模块。

高效重打 SELinux 卷标签

特性状态: Kubernetes v1.27 [beta]
默认情况下,容器运行时递归地将 SELinux 标签赋予所有 Pod 卷上的所有文件。 为了加快该过程,Kubernetes 使用挂载可选项 -o context=<label> 可以立即改变卷的 SELinux 标签。
要使用这项加速功能,必须满足下列条件:

  必须启用 ReadWriteOncePod 和 SELinuxMountReadWriteOncePod 特性门控。
  Pod 必须以 accessModes: ["ReadWriteOncePod"] 模式使用 PersistentVolumeClaim。
  Pod(或其中使用 PersistentVolumeClaim 的所有容器)必须设置 seLinuxOptions。
  对应的 PersistentVolume 必须是:
    使用传统树内(In-Tree) iscsi、rbd 或 fs 卷类型的卷。
    或者是使用 {< glossary_tooltip text="CSI" term_id="csi" >}} 驱动程序的卷 CSI 驱动程序必须能够通过在 CSIDriver 实例中设置 spec.seLinuxMount: true 以支持 -o context 挂载。
对于所有其他卷类型,重打 SELinux 标签的方式有所不同: 容器运行时为卷中的所有节点(文件和目录)递归地修改 SELinux 标签。 卷中的文件和目录越多,重打标签需要耗费的时间就越长。

特殊说明

Pod 的安全上下文适用于 Pod 中的容器,也适用于 Pod 所挂载的卷(如果有的话)。 尤其是,fsGroup 和 seLinuxOptions 按下面的方式应用到挂载卷上:

  fsGroup:支持属主管理的卷会被修改,将其属主变更为 fsGroup 所指定的 GID, 并且对该 GID 可写。

  seLinuxOptions:支持 SELinux 标签的卷会被重新打标签,以便可被 seLinuxOptions 下所设置的标签访问。通常你只需要设置 level 部分。 该部分设置的是赋予 Pod 中所有容器及卷的 多类别安全性(Multi-Category Security,MCS)标签。
警告: 在为 Pod 设置 MCS 标签之后,所有带有相同标签的 Pod 可以访问该卷。 如果你需要跨 Pod 的保护,你必须为每个 Pod 赋予独特的 MCS 标签。

参考文档

https://kubernetes.io/docs/tasks/configure-pod-container/security-context/