安全上下文
在生产环境中,往往需要对 Pod 的安全性进行设置,用户可以在 Pod 中通过安全上下文选项配置其安全性相关的特性。可以通过 pod.spec.securityContext
对 Pod 中的所有容器进行设置,或者通过 pod.spec.containers.securityContext
对 Pod 中指定的容器单独进行设置。
安全上下文主要包括以下可配置的选项:
指定容器中默认运行进程的用户(用户 ID);
阻止容器使用 root 用户运行(容器默认使用的用户通常在构建容器时指定,所以为了安全可能需要阻止容器以 root 用户运行);
使用特权模式运行容器,使其对宿主机节点的内核具有完全的访问权限;
通过添加或禁用内核功能,配置细粒度的内核访问权限;
设置 SELinux 选项,加强对容器的限制;
阻止进程写入容器的根文件系统;
指定容器中运行进程的默认用户
可以在 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.fsGroup
和 pod.spec.securityContext.supplementalGroups
可以将 Pod 中的容器中运行的用户划分进指定的用户组中,其中前者用于指定卷设备所属的组用户,两者都定义了用户关联的额外的用户组。
🌰 创建一个包含两个容器的 Pod,一个容器中是 1111
用户运行,另一个容器中则是 2222
用户运行,将这两个容器加入用户组 555
、666
、777
中,其中用户组 555
是 fsGroup
指定的,即卷设备的所属组 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 # 开启容器的特权模式
为容器添加、禁用内核功能
相比于直接令容器以特权模式运行,赋予其所有权限的方法,更加安全的做法是只给它真正需要的内核功能权限,从而实现更精细粒度的权限控制。
添加内核功能
可以在 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?