安全上下文

在生产环境中,往往需要对 Pod 的安全性进行设置,用户可以在 Pod 中通过安全上下文选项配置其安全性相关的特性。可以通过 pod.spec.securityContext 对 Pod 中的所有容器进行设置,或者通过 pod.spec.containers.securityContext 对 Pod 中指定的容器单独进行设置。

安全上下文主要包括以下可配置的选项:

  • 指定容器中默认运行进程的用户(用户 ID);

  • 阻止容器使用 root 用户运行(容器默认使用的用户通常在构建容器时指定,所以为了安全可能需要阻止容器以 root 用户运行);

  • 使用特权模式运行容器,使其对宿主机节点的内核具有完全的访问权限;

  • 通过添加或禁用内核功能,配置细粒度的内核访问权限;

  • 设置 SELinux 选项,加强对容器的限制;

  • 阻止进程写入容器的根文件系统;

🚩 容器运行时使用的用户是在构建镜像时指定的,即在 Dockerfile 的 USER 字段中进行指定 ,默认都是 root。默认情况下的用户以及组情况如下:

/ # id
uid=0(root) gid=0(root) groups=0(root),1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel),11(floppy),20(dialout),26(tape),27(video)

用户 ID (uid) 为 0,即 root;用户组 ID (gid) 为 0,即 root;groups 表示其还属于多个其他组,如 0、1、2 等等;

指定容器中运行进程的默认用户

可以在 pod.spec.securityContext.runAsUser 中指定运行的用户 ID(若想单独为一个容器指定,则在 pod.spec.containers.securityContext.runAsUser 中指定即可 )。

🌰 创建一个 Pod,其中的名为 main 的容器中的默认运行用户 ID 修改为 405,即 guest 用户:

apiVersion: v1
kind: Pod
metadata:
  name: pod-as-user-guest
spec:
  containers:
  - name: main
    image: alpine
    command: ["/bin/sleep", "999999"]
    securityContext:
      runAsUser: 405    # 指定默认的用户 ID,405 表示 guest 用户

查看创建的 Pod 中的容器的 id 命令:

>>> exec -it pod-as-user-guest -- sh
/ $ id
uid=405(guest) gid=100(users)

可以发现其 uid 被修改为 405,gid 也被分配为 uid 默认属于的组 ID

不同用户共享存储卷

Linux 中可以使用 ls -la 查看文件或目录的权限,其定义规范如下:

×××(所有者)×××(组用户)×××(其他用户)

在 Pod 中,若有两个容器,且每个容器都是以不同的用户运行的,因此若想要两个用户均能对同一个存储卷进行操作,则可以 将这两个用户放入同一个组,再 修改组用户对卷设备的权限,这样就可以实现不同的用户均能对同一个卷设备中进行读写。

🕹 通过设置 pod.spec.securityContext.fsGrouppod.spec.securityContext.supplementalGroups 可以将 Pod 中的容器中运行的用户划分进指定的用户组中,其中前者用于指定卷设备所属的组用户,两者都定义了用户关联的额外的用户组。

🌰 创建一个包含两个容器的 Pod,一个容器中是 1111 用户运行,另一个容器中则是 2222 用户运行,将这两个容器加入用户组 555666777 中,其中用户组 555fsGroup 指定的,即卷设备的所属组 ID 为 555

apiVersion: v1
kind: Pod
metadata:
  name: pod-with-shared-volume-fsgroup
spec:
  securityContext:
    fsGroup: 555    # 设置卷设备的所属组 ID,以及容器中用户关联的额外组
    supplementalGroups: [666, 777]    # 设置容器中用户关联的额外组
  containers:
  - name: first
    image: alpine
    command: ["/bin/sleep", "999999"]
    securityContext:
      runAsUser: 1111        # 第一个容器以 1111 用户运行
    volumeMounts:    # 将卷设备挂入
    - name: shared-volume
      mountPath: /volume
      readOnly: false
  - name: second
    image: alpine
    command: ["/bin/sleep", "999999"]
    securityContext:
      runAsUser: 2222        # 第二个容器以 2222 用户运行
    volumeMounts:    # 将卷设备挂入
    - name: shared-volume
      mountPath: /volume
      readOnly: false
  volumes:    # 创建的用于共享的卷设备
  - name: shared-volume
    emptyDir:

创建完 Pod 后,观察 1111 用户的所属组信息,可以看到其所属组为 0, 555, 666, 777:

uid=1111 gid=0(root) groups=555,666,777

观察挂载的卷设备的信息如下,该卷设备属于用户 root,属于组 555,因此只要属于 555 组的用户均可以向其中读写:

drwxrwsrwx    2 root     555           4096 Mar 17 08:17 .

1111 用户向其中创建一个名为 foo 的文件,并查看其权限如下:

-rw-r--r--    1 1111     555              5 Mar 17 08:18 /volume/foo

该文件属于用户 1111 以及 用户组 555,且从其权限 rw-r--r-- 可以知道,除了用户 1111 可以对其读写外,同属于用户组 555 的用户 2222 只可以对该文件进行读,而没有其他权限

阻止容器以 root 用户运行

可以在 pod.spec.securityContext.runAsNonRoot=true 中指定 Pod 中的容器必须以非 root 用户运行(若想单独为一个容器指定,则在 pod.spec.containers.securityContext.runAsNonRoot 中指定即可 ),在指定了该特性以后,若容器是以 root 用户运行的,则该 Pod 会一直无法进入 Ready 状态。

🌰 创建一个 Pod,使用镜像 alpine,该镜像默认是使用的 root 用户,在 Pod 中定义阻止容器以 root 用户运行,创建该 Pod:

apiVersion: v1
kind: Pod
metadata:
  name: pod-run-as-non-root
spec:
  containers:
  - name: main
    image: alpine
    command: ["/bin/sleep", "999999"]
    securityContext:
      runAsNonRoot: true        # 容器不允许以 root 运行

通过 kubectl get pod 可以看到没有 READY,其状态显示为 CreateContainerConfigError

>>> get pod
NAME                             READY   STATUS                       RESTARTS   AGE
pod-run-as-non-root              0/1     CreateContainerConfigError   0          2m44s

通过 kubectl describe 可以看到其失败的详细原因为 Error: container has runAsNonRoot and image will run as root

  Warning  Failed     9s (x3 over 64s)     kubelet, minikube  Error: container has runAsNonRoot and image will run as root

使用特权模式运行 Pod

可以在 pod.spec.securityContext.privileged=true 中指定 Pod 中的容器以特权模式运行(若想单独为一个容器指定,则在 pod.spec.containers.securityContext.privileged=true 中指定即可 )。

🌰 创建一个 Pod,开启特权模式:

apiVersion: v1
kind: Pod
metadata:
  name: pod-privileged
spec:
  containers:
  - name: main
    image: alpine
    command: ["/bin/sleep", "999999"]
    securityContext:
      privileged: true        # 开启容器的特权模式

为容器添加、禁用内核功能

相比于直接令容器以特权模式运行,赋予其所有权限的方法,更加安全的做法是只给它真正需要的内核功能权限,从而实现更精细粒度的权限控制。

🎧 Linux 中的内核功能的名称通常以 CAP_ 开头,而在 Pod 的定义中指定内核功能时,需要 省略 CAP_ 开头。例如,修改系统文件所有者的内核功能名为 CAP_CHOWN,在 Pod 的定义中指定时则为 CHOWN

添加内核功能

可以在 pod.spec.securityContext.capabilities.add 中指定为 Pod 中所有容器添加的内核功能(若想单独为一个容器指定,则在 pod.spec.containers.securityContext.capabilities.add 中指定即可 )。

🌰 默认不开启特权模式的情况下,容器是不允许修改系统时间的(因为修改容器的修改时间会影响其所在 Node 的系统时间),而提供该功能的内核功能名称为 CAP_SYS_TIME。下面创建一个 Pod,为其中的容器添加该内核功能:

apiVersion: v1
kind: Pod
metadata:
  name: pod-add-settime-capability
spec:
  containers:
  - name: main
    image: alpine
    command: ["/bin/sleep", "999999"]
    securityContext:
      capabilities:
        add:    # 添加内核功能
        - SYS_TIME    # 为 Pod 添加 CAP_SYS_TIME 内核功能

添加了该功能后,该 Pod 中的容器就可以通过 date 命令修改时间了

禁用内核功能

相对于上面的添加功能,将 add 改为 drop 即可,即可以在 pod.spec.securityContext.capabilities.drop 中指定为 Pod 中所有容器禁用的内核功能(若想单独为一个容器指定,则在 pod.spec.containers.securityContext.capabilities.drop 中指定即可 )。

🌰 默认情况下的容器可以使用 chown 命令修改容器中的文件的持有者,这是 CAP_CHOWN 内核功能提供的。下面创建一个 Pod,将该功能禁用:

apiVersion: v1
kind: Pod
metadata:
  name: pod-drop-chown-capability
spec:
  containers:
  - name: main
    image: alpine
    command: ["/bin/sleep", "999999"]
    securityContext:
      capabilities:
        drop:    # 禁用内核功能
        - CHOWN        # 为 Pod 禁用 CAP_CHOWN 内核功能

禁用了该功能后,该 Pod 中的容器将无法使用 chown 命令修改文件的持有者

阻止对容器根文件系统的写入

可以在 pod.spec.securityContext.readOnlyRootFileSystem 中指定为 Pod 中所有容器添加的内核功能(若想单独为一个容器指定,则在 pod.spec.containers.securityContext.readOnlyRootFileSystem 中指定即可 )。

当指定了该特性之后,意味着 Pod 中的容器将只能对其根文件系统进行读取,而不能进行写入,因此往往需要为其提供 卷设备 以储存业务所需数据,注意该设备必须是可写的,否则通常情况下容器将无法正常满足业务场景的使用。

🌰 创建一个容器,并且阻止其对根文件系统的写入:

apiVersion: v1
kind: Pod
metadata:
  name: pod-with-readonly-filesystem
spec:
  containers:
  - name: main
    image: alpine
    command: ["/bin/sleep", "999999"]
    securityContext:
      readOnlyRootFilesystem: true    # 阻止容器对根文件系统的写入
    volumeMounts:    # 将卷设备挂载至容器中
    - name: my-volume
      mountPath: /volume
      readOnly: false    # 将卷设备设置为可写的
  volumes:    # 创建一个用于存储业务数据的卷设备
  - name: my-volume
    emptyDir:

创建的容器将只能对挂载的卷设备进行写入操作,而对根文件系统则只能够读取

Last updated

Was this helpful?