为了更精细地控制Pod启动或更新时的安全管理,Kubernetes从1.5版本开始引入PodSecurityPolicy资源对象对Pod安全策略进行管理,到1.18版本时达到Beta阶段。通过对PodSecurityPolicy的设置,管理员可以控制Pod的运行条件,以及可以使用系统的哪些功能。PodSecurityPolicy是集群范围内的资源对象,不属于命名空间范围。
若需要启用PodSecurityPolicy机制,则首先需要设置kube-apiserver服务的启动参数--enable-admission-plugins来开启PodSecurityPolicy准入控制器:

注意,在开启PodSecurityPolicy准入控制器后,系统中还没有任何PodSecurityPolicy策略配置时,Kubernetes默认不允许创建任何Pod,需要管理员创建适合的PodSecurityPolicy策略和相应的RBAC授权策略,Pod才能创建成功。
例如,尝试创建如下Pod:

使用kubectl命令创建时,系统将提示“禁止创建”的报错信息:

接下来创建一个PodSecurityPolicy资源对象,其配置文件psp-non-privileged.yaml的内容如下:

使用kubectl create命令创建该PodSecurityPolicy:

查看PodSecurityPolicy:

再次创建Pod就能成功:

上面的PodSecurityPolicy“psp-non-privileged”设置了privileged:false,表示禁止Pod或者容器以特权模式运行。设置了特权模式Pod的配置文件pod-priv.yaml的内容如下:

创建Pod时,系统将提示“禁止特权模式”的报错信息:

PodSecurityPolicy资源对象通过许多字段来控制Pod运行时安全策略的方方面面。这里对这些字段及其作用按功能分组说明如下。
privileged:是否允许容器以特权模式运行。
(1)hostPID:是否允许容器共享宿主机的进程ID命名空间(PID Namespace)。
(2)hostIPC:是否允许容器共享宿主机的IPC命名空间(IPC Namespace)。
(3)hostNetwork:是否允许Pod使用宿主机的网络命名空间(Network Namespace),使用hostNetwork的Pod将可以访问宿主机的各个网卡。
(4)hostPorts:是否允许Pod使用宿主机的端口号,可以通过hostPortRange字段设置允许使用的端口号范围,以[min,max]设置最小端口号和最大端口号。
(1)Volumes:允许Pod使用的存储卷Volume类型,设置为“*”表示允许使用任意Volume类型,建议至少允许Pod使用的Volume类型有configMap、downwardAPI、emptyDir、persistentVolumeClaim(PVC)、secret和projected。
注意,PodSecurityPolicy并不会对PVC引用的PersistentVolume(PV)进行限制,hostPath类型的PV也不支持只读访问模式,所以管理员应该仅允许授权用户创建PV。
(2)FSGroup:设置允许访问某些Volume的Group ID范围,规则(rule字段)可被设置为MustRunAs、MayRunAs或RunAsAny。
◎ MustRunAs:需要设置Group ID的范围,例如1~65535,要求Pod或Container的securityContext.fsGroup设置的值必须属于该Group ID的范围,如未设置,则系统将自动设置securityContext.fsGroup的值为Group ID范围的最小值。
◎ MayRunAs:需要设置Group ID的范围,例如1~65535,要求Pod或Container的securityContext.fsGroup设置的值必须属于该Group ID的范围,如未设置,则系统将不会自动设置默认值。
◎ RunAsAny:不限制Group ID的范围,Pod或Container的securityContext.fsGroup可被设置为任意Group ID。
(3)AllowedHostPaths:允许Pod使用宿主机的hostPath路径名称,可以通过pathPrefix字段设置路径的前缀,并设置是否仅允许以只读模式挂载,示例如下:

结果为允许Pod访问宿主机上以“/foo”为前缀的路径,包括“/foo”“/foo/”“/foo/bar”等,但不能访问“/fool”“/etc/foo”等路径,也不允许通过“/foo/../”表达式访问/foo的上层目录。
(4)ReadOnlyRootFilesystem:要求容器运行的根文件系统(root filesystem)必须是只读的。
allowedFlexVolumes用于对类型为flexVolume的存储卷设置允许使用的驱动类型,空值表示没有限制。在下例中设置了允许使用lvm和cifs驱动:

(1)RunAsUser:设置运行容器的用户ID(User ID)范围,规则字段(rule)的值可被设置为MustRunAs、MustRunAsNonRoot或RunAsAny。
◎ MustRunAs:需要设置User ID的范围,要求Pod或Container的securityContext.runAsUser设置的值必须属于该User ID的范围,如未设置,则系统将自动设置securityContext.runAsUser的值为User ID范围的最小值。
◎ MustRunAsNonRoot:必须以非root用户运行容器,要求在Pod或Container的securityContext.runAsUser中设置一个非0的用户ID,或在镜像的USER字段设置了用户ID。如果Pod既未设置runAsNonRoot,也未设置runAsUser,则系统将自动设置runAsNonRoot=true,默认要求在镜像中必须设置非0的USER。在该策略生效时,建议同时设置allowPrivilegeEscalation=false,以免不必要的权限提升行为。
◎ RunAsAny:不限制User ID的范围,Pod或Container的securityContext.runAsUser可被设置为任意User ID。
(2)RunAsGroup:设置运行容器的用户组Group ID范围,可以将规则字段(rule)的值设置为MustRunAs、MustRunAsNonRoot或RunAsAny。
◎ MustRunAs:需要设置Group ID的范围,要求Pod或Container的securityContext.runAsGroup设置的值必须属于该Group ID的范围,如未设置,则系统将自动设置securityContext.runAsGroup的值为Group ID范围内的最小值。
◎ MayRunAs:不强制要求Pod或Container设置securityContext.runAsGroup,如果设置了Group ID的范围,则仍然要求Pod或Container的securityContext.runAsGroup设置的值必须属于该Group ID的范围,如未设置,则系统将不会自动设置默认值。
◎ RunAsAny:不限制Group ID的范围,Pod或Container的securityContext.runAsGroup可被设置为任意Group ID。
(3)SupplementalGroups:设置运行容器的用户允许属于的额外Group ID范围,规则字段(rule)的值可被设置为MustRunAs、MayRunAs或RunAsAny。
◎ MustRunAs:需要设置Supplemental Group ID的范围,要求Pod或Container的securityContext.supplementalGroups设置的值必须属于该Supplemental Group ID的范围,如未设置,则系统将自动设置securityContext.supplementalGroups的值为Supplemental Group ID范围内的最小值。
◎ MayRunAs:不强制要求Pod或Container设置securityContext.supplementalGroups,即使设置了Supplemental Group ID的范围,也仍然要求Pod或Container的securityContext.supplementalGroups设置的值必须属于该Supplemental Group ID的范围,如未设置,则系统将不会自动设置默认值。
◎ RunAsAny:不限制Supplemental Group ID的范围,Pod或Container的securityContext.supplementalGroups可被设置为任意Supplemental Group ID。
提升权限字段用于控制是否允许容器内的进程提升权限。提升权限配置直接影响对容器进程的“no_new_privs”标志位的设置,如果设置了该标志位,则将阻止通过setuid程序修改进程User ID的行为,并阻止文件启用额外的功能(例如阻止运行ping命令)。通常在设置了以非root用户运行(MustRunAsNonRoot)时设置是否允许提升权限。
(1)AllowPrivilegeEscalation:该字段用于表示是否允许容器securityContext设置allowPrivilegeEscalation为true,默认值为true。当该字段被设置为false时,容器内的子进程将无法提升权限。
(2)DefaultAllowPrivilegeEscalation:设置AllowPrivilegeEscalation字段的默认值,在无默认值设置的情况下,系统将允许提升权限。当该字段被设置为disallow时,仍可以通过设置AllowPrivilegeEscalation来指定是否允许提升权限。
Linux能力(Capabilities)提供了与传统超级用户关联权限的细粒度管理机制。下面是Linux能力字段可以设置的内容,以不带“CAP_”的名称进行表示,可以设置多个能力。
(1)AllowedCapabilities:设置容器可以使用的Linux能力列表,设置为“*”表示允许使用Linux的所有能力(如NET_ADMIN、SYS_TIME等)。
(2)RequiredDropCapabilities:设置必须从容器中删除的Linux能力列表。这些能力将从默认列表中删除,并且不得再进行添加。通过该字段设置的需删除的能力列表不能被设置在AllowedCapabilities和DefaultAddCapabilities字段中。
(3)DefaultAddCapabilities:设置默认为容器添加的Linux能力列表,例如SYS_TIME等。Docker会为容器提供一组默认的允许使用的能力列表,包括SETPCAP、MKNOD、AUDIT_WRITE、CHOWN等。
通过seLinux字段设置SELinux参数,规则字段(rule)的值可被设置为MustRunAs或RunAsAny。
◎ MustRunAs:要求Pod或Container设置securityContext.seLinuxOptions,系统将对Pod或Container的securityContext.seLinuxOptions值进行校验。
◎ RunAsAny:不校验Pod或Container的securityContext.seLinuxOptions的设置。
(1)AllowedProcMountTypes:设置允许的ProcMountTypes类型列表,可以设置allowedProcMountTypes或DefaultProcMount。
(2)AppArmor:设置对容器可执行程序的访问控制权限。
(3)Seccomp:设置允许容器使用的系统调用(System Calls)的profile,可以通过设置PodSecurityPolicy的Annotation进行控制,在Kubernetes中目前是Alpha阶段。可以设置的Annotation如下。
◎ seccomp.security.alpha.kubernetes.io/defaultProfileName:设置作用于容器的默认Seccomp Profile名称,可以设置的值包括unconfined(不使用Seccomp)、runtime/default(使用默认的容器运行时Profile)、docker/default(使用Docker默认的Seccomp Profile,从Kubernetes 1.11版本开始弃用,改用runtime/default)、localhost/<path>(使用Node的<seccomp_root>/<path>目录下的Profile文件)、<seccomp_root>由kubelet启动参数--seccomp-profile-root进行指定)。
◎ seccomp.security.alpha.kubernetes.io/allowedProfileNames:设置允许的Pod Seccomp Profile名称,多个值以逗号分隔,设置为“*”表示允许所有Profile。
(4)Sysctl:设置允许调整的内核参数,默认情况下全部安全的sysctl都被允许。可以设置的字段如下。
◎ forbiddenSysctls:禁止的sysctl列表,设置为“*”表示禁止全部sysctl。
◎ allowedUnsafeSysctls:设置允许的不安全sysctl列表(默认禁用),不应该在forbiddenSysctls中设置。
例如下面设置了允许的不安全的以“kernel.msg”开头的内核参数,并且禁止kernel.shm_rmid_forced:

下面是几种常用的PodSecurityPolicy安全策略配置示例。
(1)特权策略:


在这种Pod安全策略下,系统将允许创建任意安全设置的Pod,几乎等于没有开启PodSecurityPolicy。
(2)受限策略:


经过这个PodSecurityPolicy的限制,系统将要求:Pod或容器的运行用户必须为非特权用户;禁止容器内进程提升权限;不允许使用宿主机网络、hostPort等资源;限制可以使用的Volume类型,等等。
(3)基线(baseline)策略:



该PodSecurityPolicy设置了容器常用的Linux能力、允许使用的Volume类型等,可作为基线(baseline)配置。
Kubernetes建议使用RBAC授权机制来设置针对Pod安全策略PodSecurityPolicy的授权,实现方式是通过创建Role(或ClusterRole)和RoleBinding(或ClusterRoleBinding)对使用PodSecurityPolicy进行授权。
下面是一个ClusterRole示例,授权的目标资源对象为“podsecuritypolicies”,动词为“use”,并通过resourceNames设置允许使用的PodSecurityPolicy列表:

然后创建一个ClusterRoleBinding将ServiceAccount或用户和ClusterRole绑定:


也可以创建RoleBinding对相同命名空间中的Pod进行授权,通常可以对组(Group)进行设置,例如:

下面通过一个完整的示例对如何通过设置RBAC权限管理ServiceAccount使用PodSecurityPolicy策略进行说明。前提条件是Kubernetes集群开启PodSecurityPolicy Admission Controller和RBAC授权模式,并且kubectl使用具有集群管理员角色的kubeconfig进行操作。
下面的示例都将在名为“psp-example”的命名空间中进行操作:

在命名空间psp-example中创建一个ServiceAccount“fake-user”:

为ServiceAccount“fake-user”授权ClusterRole“edit”,使其有权限创建Pod(注意,这是命名空间范围的edit权限,无集群管理员权限):

接下来创建一个有一定限制(禁止特权模式)的PodSecurityPolicy策略:

通过kubectl create命令创建这个PodSecurityPolicy策略:

然后尝试用ServiceAccount“fake-user”身份创建一个Pod,可以在kubectl命令中使用--as参数设置使用的身份,例如:

Pod的YAML配置如下:


使用kubectl create命令创建这个Pod时,系统将提示“无法验证pod security policy”的错误信息,创建Pod失败:

这是因为系统还没有对ServiceAccount“fake-user”授权使用PodSecurityPolicy,可以使用kubectl auth can-i use命令进行验证,结果为no说明没有权限:

接下来通过一个Role和RoleBinding进行授权,psp-restricted-rbac.yaml配置文件的内容如下:


在Role的定义中,设置的是对PodSecurityPolicy“restricted”的use操作。
在RoleBinding的定义中,将ServiceAccount“fake-user”与Role进行绑定,完成对其允许使用PodSecurityPolicy的授权。
使用kubectl create命令创建:

再次使用kubectl auth can-i use命令进行验证,结果为yes,说明授权成功:

再次创建Pod即可成功:

查看Pod的YAML配置,可以看到系统为其在Annotation中设置使用的PodSecurityPolicy为“restricted”:


虽然RBAC授权是成功的,但是由于PodSecurityPolicy设置了禁止特权模式,所以尝试创建使用特权模式的容器仍会被系统禁止:

创建Pod时,系统将提示“禁止特权模式”的报错信息:

通过上面的示例,我们可以了解Kubernetes是如何通过RBAC对PodSecurityPolicy的使用进行权限管理的。
Kubernetes可以为Pod设置应用程序运行所需的权限或者访问控制等安全设置,涉及多种Linux Kernel安全相关的系统参数,这些安全设置被称为Security Context,在Pod或Container级别通过securityContext字段进行设置(如果在Pod和Container级别都设置了相同的安全字段,则容器将使用Container级别的设置)。
管理员设置的集群范围的PodSecurityPolicy策略会对Pod的Security Context安全设置进行校验,对于不满足PodSecurityPolicy策略的Pod,系统将禁止创建。
Pod的Security Context安全策略包括但不限于以下内容(将来可能会扩展)。
◎ 访问控制相关:基于User ID和Group ID进行控制,例如runAsUser、runAsGroup、runAsNonRoot、Supplementary Group等。
◎ seLinuxOptions:SELinux相关设置。
◎ 特权模式(privileged):是否以特权模式运行。
◎ Linux能力(capabilities)相关:设置应用程序允许使用的Linux能力。
◎ AppArmor:设置对应用程序访问进行权限控制的profile。
◎ Seccomp:设置允许容器使用的系统调用(System Calls)的profile。
◎ allowPrivilegeEscalation:是否允许提升权限。
◎ readOnlyRootFilesystem:根文件系统是否为只读属性。
下面通过几个例子对Pod的Security Context安全设置进行说明。
YAML文件的示例如下:

在spec.securityContext中设置了如下参数。
◎ runAsUser=1000:所有容器都将以User ID 1000运行程序,所有新生成文件的User ID也被设置为1000。
◎ runAsGroup=3000:所有容器都将以Group ID 3000运行程序,所有新生成文件的Group ID也被设置为3000。
◎ fsGroup=2000:挂载的卷“/data/demo”及其中创建的文件都将属于Group ID 2000。
使用kubectl create命令创建Pod:

进入容器环境,查看到运行进程的用户ID为1000:

查看Volume挂载到容器内的/data/demo目录权限,其Group ID为2000(fsGroup字段设置):

在该目录下创建一个新文件,可见文件owner的User ID为1000(runAsUser字段设置),Group ID为2000(fsGroup字段设置):

查看用户ID信息,可见其Group ID为3000(runAsGroup字段设置):

如果未设置runAsGroup字段,则gid将被系统设置为0,即root组,这表示通过User ID 1000运行的进程可与同组(god=0)的其他进程通信。
如果Pod设置了securityContext.fsGroup,则在kubelet进行挂载Volume到容器内的操作时,系统会对挂载目录及其子目录和全部文件检查权限并设置Group ID,这对于子目录和文件数量非常多的Volume来说非常耗时,会导致Pod启动很长时间。为了减少修改目录和文件权限的时间,Kubernetes从1.18版本开始引入fsGroupChangePolicy机制,用于管理是否需要对Volume的用户权限进行校验,以加快Pod的启动速度,目前该特性为Alpha阶段。
该特性通过securityContext.fsGroupChangePolicy字段进行设置,可以设置的值如下。
◎ OnRootMismatch:当根目录的权限(Permission和Ownership)与预期权限不同时,仅修改根目录的权限,这将有助于减少修改所有子目录和文件权限的时间。
◎ Always:始终进行修改。
如需启用该特性,则需要在kube-apiserver、kube-controller-manager和kubelet的启动参数中开启--feature-gates="ConfigurableFSGroupPolicy=true"。配置示例如下:

注意,该特性对Secret、ConfigMap、emptyDir这几种类型的Volume不起作用。
如果在Pod级别也设置了相同的securityContext,则容器将使用Container级别的设置,本例中在Pod和Container级别都设置了runAsUser:


使用kubectl create命令创建Pod:

进入容器环境,查看到运行进程的用户ID为2000:

本例为容器设置允许使用的Linux能力包括NET_ADMIN和SYS_TIME。
我们先看一个没有添加这些能力的容器环境的默认能力设置:


使用kubectl create命令创建Pod:

进入容器环境,查看1号进程的Capabilities信息:


Linux能力相关参数的命名以Cap开头,例如CapInh、CapPrm、CapEff等。
接下来为容器设置允许使用的Linux能力增加两个:CAP_NET_ADMIN和CAP_SYS_TIME,通过securityContext.capabilities字段进行设置,YAML文件的内容如下:


使用kubectl create命令创建Pod:

进入容器环境,查看1号进程的Capabilities信息:

对比没有设置这两个能力的配置:CapPrm:00000000a80425fb,可以看到系统在Linux Capabilities的第12位添加了CAP_NET_ADMIN,在第25位添加了CAP_SYS_TIME这两个Linux能力。对比如下:

需要注意的是,Linux Capabilities的命名都以CAP_开头,但是在Kubernetes的securityContext.capabilities中设置时需要删除CAP_前缀,例如上例中设置的值为NET_ADMIN和SYS_TIME,对应Linux中的CAP_NET_ADMIN和CAP_SYS_TIME。
Security Context还可以通过seLinuxOptions字段为Pod或Container设置SELinux标签level="s0:c123,c456",例如:

securityContext.seLinuxOptions可以设置的SELinux标签包括:level、role、type、user。
注意,要使seLinuxOptions设置的SELinux标签生效,需要宿主机Linux操作系统开启SELinux安全功能。
通过为Pod或容器进行应用程序运行所需的权限或者访问控制等安全设置,管理员就能够对容器应用进程的安全管理进行更加精细的控制,同时配合集群范围的PodSecurityPolicy策略设置,使整个Kubernetes集群中的服务运行更加安全。