kubernetes高级之pod安全策略
什么是pod安全策略
pod安全策略是集群级别的用于控制pod安全相关选项的一种资源.PodSecurityPolicy
定义了一系列pod相要进行在系统中必须满足的约束条件,以衣一些默认的约束值.它允许管理员控制以下方面内容
Control Aspect | Field Names |
---|---|
以特权运行容器 | privileged |
使用宿主名称空间 | hostPID, hostIPC |
使用宿主网络和端口 | hostNetwork, hostPorts |
使用存储卷类型 | volumes |
使用宿主机文件系统 | allowedHostPaths |
flex存储卷白名单 | allowedFlexVolumes |
分配拥有 Pod 数据卷的 FSGroup | fsGroup |
只读root文件系统 | readOnlyRootFilesystem |
容器的用户id和组id | runAsUser, runAsGroup, supplementalGroups |
禁止提升到root权限 | allowPrivilegeEscalation, defaultAllowPrivilegeEscalation |
Linux能力 | defaultAddCapabilities, requiredDropCapabilities, allowedCapabilities |
SELinux上下文 | seLinux |
允许容器加载的proc类型 | allowedProcMountTypes |
The AppArmor profile used by containers | annotations |
The seccomp profile used by containers | annotations |
The sysctl profile used by containers | annotations |
启用pod安全策略
pod安全策略作为可选的(但强烈建议的)admission controller的实现.pod安全策略通过启用admission controller来实现,但是仅仅启用而没有对策略授权则会导致整个集群无法创建pod!
由于pod安全策略api(policy/v1beta1/podsecuritypolicy)独立于admission controller
之外启用,对于已经存在的集群建议在启用admission controller
之前添加并授权策略.
授权策略
当一个pod安全策略资源被创建(前面说过,psp(PodSecurityPolicy )pod安全策略是一种kubernetes资源),它什么都不会做.为了使用它,请求操作的用户或者目标pod的serviceaccount必须通过策略的use
动词来授权.
绝大部分kubernetes pod并不是直接由用户直接创建的.相反,典型使用场景是它们通过Deployment
或者ReplicaSet
间接被创建,或者通过控制器管理器的其它模板控制器来创建.对控制器进行策略授权也将对它所创建的所有pod进行策略授权.因此首选的授权方法是对pod的serviceaccount进行策略授权(后面有示例).
通过RBAC授权
RBAC是kubernetes标准的授权模式,并且很容易用于授权安全策略使用.
首先,一个角色(role)或者集群角色(clusterRole)需要被授权使用(use动词)
它想要的策略.对角色的授权类似下面
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: <role name>
rules:
- apiGroups: ['policy']
resources: ['podsecuritypolicies']
verbs: ['use']
resourceNames:
- 一系列要进行授权的资源名称
然后把集群角色(或角色)与授权的用户绑定
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: 绑定名称
roleRef:
kind: ClusterRole
name: 角色名称
apiGroup: rbac.authorization.k8s.io
subjects:
# Authorize specific service accounts:
- kind: ServiceAccount
name: 授权的serviceaccount名称
namespace: <authorized pod namespace>
# Authorize specific users (not recommended):
- kind: User
apiGroup: rbac.authorization.k8s.io
name: 授权的用户名
如果一个角色绑定(不是集群角色绑定)被使用,它仅对和它处于同一名称空间下的pod才能进行有效策略授权,这样同样适用于用户和用户组
# Authorize all service accounts in a namespace:
- kind: Group
apiGroup: rbac.authorization.k8s.io
name: system:serviceaccounts
# Or equivalently, all authenticated users in a namespace:
- kind: Group
apiGroup: rbac.authorization.k8s.io
name: system:authenticated
故障排除
控制器管理器必须运行在安全的api端口上,并且不能有超级权限.不然请求就会绕过认证和授权模块,将导致所有的策略均被允许,并且用户可以创建特权pod
策略顺序
除了限制pod的创建和更新,pod安全策略还用于提供它所控制的诸多字段的默认值.当有多个策略时,pod安全策略根据以下因素来选择策略
-
任何成功通过验证没有警告的策略将被使用
-
如果是请求创建pod,则按通过验证的策略按字母表顺序被选用
-
否则,如果是一个更新请求,将会返回错误.因为在更新操作过程中不允许pod变化
示例
以下示例假定你运行的集群开启了pod安全策略admission controller并且你有集群管理员权限
初始设置
我们为示例创建一个名称空间和一个serviceaccount.我们使用这个serviceaccount来模拟一个非管理员用户
kubectl create namespace psp-example
kubectl create serviceaccount -n psp-example fake-user
kubectl create rolebinding -n psp-example fake-editor --clusterrole=edit --serviceaccount=psp-example:fake-user
为了方便辨认我们使用的账户,我们创建两个别名
alias kubectl-admin='kubectl -n psp-example'
alias kubectl-user='kubectl --as=system:serviceaccount:psp-example:fake-user -n psp-example'
创建一个策略和一个pod
以下定义文件定义了一个简单pod安全策略(PodSecurityPolicy),这个策略仅仅阻止创建特权pod
apiVersion: policy/v1beta1
kind: PodSecurityPolicy
metadata:
name: example
spec:
privileged: false # Don't allow privileged pods!
# The rest fills in some required fields.
seLinux:
rule: RunAsAny
supplementalGroups:
rule: RunAsAny
runAsUser:
rule: RunAsAny
fsGroup:
rule: RunAsAny
volumes:
- '*'
我们使用kubectl命令来应用以上文件.
现在,做为一个非特权用户,我们创建一个简单pod
kubectl-user create -f- <<EOF
apiVersion: v1
kind: Pod
metadata:
name: pause
spec:
containers:
- name: pause
image: k8s.gcr.io/pause
EOF
Error from server (Forbidden): error when creating "STDIN": pods "pause" is forbidden: unable to validate against any pod security policy: []
发生了什么?尽管pod安全策略已创建,不管是pod的serviceaccount还是fack-user都没有权限使用这个策略.
kubectl-user auth can-i use podsecuritypolicy/example
no
创建一个rolebing
来授权fake-user
来使用example
策略(example是前面创建的策略的名称)
但是请注意这里并不是首选方式!后面的示例将介绍首选的方式
kubectl-admin create role psp:unprivileged \
--verb=use \
--resource=podsecuritypolicy \
--resource-name=example
role "psp:unprivileged" created
kubectl-admin create rolebinding fake-user:psp:unprivileged \
--role=psp:unprivileged \
--serviceaccount=psp-example:fake-user
rolebinding "fake-user:psp:unprivileged" created
kubectl-user auth can-i use podsecuritypolicy/example
yes
此时,再重新尝试创建pod
kubectl-user create -f- <<EOF
apiVersion: v1
kind: Pod
metadata:
name: pause
spec:
containers:
- name: pause
image: k8s.gcr.io/pause
EOF
pod "pause" created
这次正如我们期待的一样,可以正常工作.但是试图创建特权pod仍然会被阻止(因此策略本身阻止创建特权pod)
kubectl-user create -f- <<EOF
apiVersion: v1
kind: Pod
metadata:
name: privileged
spec:
containers:
- name: pause
image: k8s.gcr.io/pause
securityContext:
privileged: true
EOF
Error from server (Forbidden): error when creating "STDIN": pods "privileged" is forbidden: unable to validate against any pod security policy: [spec.containers[0].securityContext.privileged: Invalid value: true: Privileged containers are not allowed]
再运行一个其它pod
我们再尝试创建一个pod,这次有一点不同
ubectl-user run pause --image=k8s.gcr.io/pause
deployment "pause" created
kubectl-user get pods
No resources found.
kubectl-user get events | head -n 2
LASTSEEN FIRSTSEEN COUNT NAME KIND SUBOBJECT TYPE REASON SOURCE MESSAGE
1m 2m 15 pause-7774d79b5 ReplicaSet Warning FailedCreate replicaset-controller Error creating: pods "pause-7774d79b5-" is forbidden: no providers available to validate pod request
从以上可以看到deployment已经成功创建(kubectl run 实际上会创建一个deployment).但是使用kubectl get pod
命令却没有发现pod被创建.这是为什么?问题的答案隐藏在replicaset控制器里.Fake-user
成功创建的deployment(deployment又成功创建replicaset),但是当replicaset尝试创建pod的时候,它并没有被授权使用example
定义的策略.
为了解决这个问题,需要把psp:unprivileged
角色(前面创建的)绑定到pod的serviceaccount上(前面我们是绑定在了fake-user上).这里serviceaccount是default
(因为我们没有指定其它用户)
看到这里如果你仍然觉得难以理解,可以回头再看看,还是无法理解的话则需要补充关于角色,用户和RBAC相关的知识.
kubectl-admin create rolebinding default:psp:unprivileged \
--role=psp:unprivileged \
--serviceaccount=psp-example:default
rolebinding "default:psp:unprivileged" created
这时候等待若干分钟,replicaset的控制器最终会成功创建pod
kubectl-user get pods --watch
NAME READY STATUS RESTARTS AGE
pause-7774d79b5-qrgcb 0/1 Pending 0 1s
pause-7774d79b5-qrgcb 0/1 Pending 0 1s
pause-7774d79b5-qrgcb 0/1 ContainerCreating 0 1s
pause-7774d79b5-qrgcb 1/1 Running 0 2s
清理工作
删除名称空间以删除绝大部分示例中用到的资源
kubectl-admin delete ns psp-example
namespace "psp-example" deleted
注意现在刚刚创建的pod安全策略已经没有了名称空间,并且需要单独被清除
kubectl-admin delete psp example
podsecuritypolicy "example" deleted
策略示例
以下是一个最小限制的策略,和不使用pod安生策略admission controller效果一样
apiVersion: policy/v1beta1
kind: PodSecurityPolicy
metadata:
name: privileged
annotations:
seccomp.security.alpha.kubernetes.io/allowedProfileNames: '*'
spec:
privileged: true
allowPrivilegeEscalation: true
allowedCapabilities:
- '*'
volumes:
- '*'
hostNetwork: true
hostPorts:
- min: 0
max: 65535
hostIPC: true
hostPID: true
runAsUser:
rule: 'RunAsAny'
seLinux:
rule: 'RunAsAny'
supplementalGroups:
rule: 'RunAsAny'
fsGroup:
rule: 'RunAsAny'
以下的一个示例有限制性策略,需要用户是一个非特权用户,阻止pod的权限提升
之所以要求是非特权用户,因为特权用户将会绕过限制
apiVersion: policy/v1beta1
kind: PodSecurityPolicy
metadata:
name: restricted
annotations:
seccomp.security.alpha.kubernetes.io/allowedProfileNames: 'docker/default'
apparmor.security.beta.kubernetes.io/allowedProfileNames: 'runtime/default'
seccomp.security.alpha.kubernetes.io/defaultProfileName: 'docker/default'
apparmor.security.beta.kubernetes.io/defaultProfileName: 'runtime/default'
spec:
privileged: false
# Required to prevent escalations to root.
allowPrivilegeEscalation: false
# This is redundant with non-root + disallow privilege escalation,
# but we can provide it for defense in depth.
requiredDropCapabilities:
- ALL
# Allow core volume types.
volumes:
- 'configMap'
- 'emptyDir'
- 'projected'
- 'secret'
- 'downwardAPI'
# Assume that persistentVolumes set up by the cluster admin are safe to use.
- 'persistentVolumeClaim'
hostNetwork: false
hostIPC: false
hostPID: false
runAsUser:
# Require the container to run without root privileges.
rule: 'MustRunAsNonRoot'
seLinux:
# This policy assumes the nodes are using AppArmor rather than SELinux.
rule: 'RunAsAny'
supplementalGroups:
rule: 'MustRunAs'
ranges:
# Forbid adding the root group.
- min: 1
max: 65535
fsGroup:
rule: 'MustRunAs'
ranges:
# Forbid adding the root group.
- min: 1
max: 65535
readOnlyRootFilesystem: false
策略参考
特权的
它决定了pod中的所有容器是否被允许以特权方式运行.默认情况下容器不允许访问主机的设备,但是特权容器却被允许访问.这将允许容器有几乎和它所在的进程一样的访问主机的权利.这将非常有用当容器想要使用主机的功能,比如访问网络的设备.
Host名称空间
HostPID
- 控制容器是否可以共享主机的进程id名称空间
HostIPC
- 控制容器是否可以共享主机的IPC名称空间
HostNetwork
- 控制容器是否可以使用所在节点的网络名称空间.这将允许pod访问回环设备,监听localhost,并且可以窥探同一节点上其它pod的网络活动状况
AllowedHostPaths - 控制允许访问的宿主机路径
存储卷和文件系统
Volumes
- 提供了一系列的存储卷类型白名单.这些允许的值和创建存储卷时定义的资源类型相对应.想要获取所有存储卷类型,可以查看存储卷类型列表.此外,*
可以被用来允许所有的存储卷类型
以下是推荐的最小化的允许存储卷类型的安全策略配置
- configMap
- downwardAPI
- emptyDir
- persistentVolumeClaim
- secret
- projected
AllowedHostPaths
- 它定义了一个hostPath
类型的存储卷可用的宿主机路径的白名单.空集群意味着对宿主机的path无使用限制.它被定义为一个包含了一系列对象的单个pathPrefix
字段,允许hostpath类型的存储卷挂载以pathPrefix
字段开头的宿主机路径.readonly
字段意味着必须以readonly
方式挂载(即不能写入,只能读)
allowedHostPaths:
# This allows "/foo", "/foo/", "/foo/bar" etc., but
# disallows "/fool", "/etc/foo" etc.
# "/foo/../" is never valid.
- pathPrefix: "/foo"
readOnly: true # only allow read-only mounts
警告,一个可以无限制访问宿主机文件系统的容器可以有很多方式提升权限,包括读取其它容器内的数据,滥用系统服务的密钥,比如kubecctl
可写的hostpath目录存储卷允许容器写入到宿主机文件系统,并且可以遍历
pathPrefix
以外的文件系统,readOnly: true
在kubernetes 1.11+版本以后才能使用,并且在allowedHostPaths
必须使用以有效限制访问特定的pathPrefix
ReadOnlyRootFilesystem
- 限制容器必须以只读的root文件系统运行(没有可写层)
特权提升
这个选项控制着容器的allowPrivilegeEscalation
选项.这个布尔值直接控制着no_new_privs
是否设置到容器运行的进程.它将阻止setuid
来改变user ID,并且阻止文件有其它的能力(比如禁止使用ping工具).这个行为需要启用MustRunAsNonRoot
AllowPrivilegeEscalation
- 它决定着容器的安全上下文是否可以设置allowPrivilegeEscalation=true
,为true是默认值.设置为false将使得容器所有的子进程没有比父进程更高的特权
DefaultAllowPrivilegeEscalation
,为allowPrivilegeEscalation
设置默认值,从上面可以看到,默认的值为true.如果这个行为不是我们期待的,这个字段可以用于把它设置为不允许,但是仍然pod显式请求allowPrivilegeEscalation