k8s中的PodSecurityPolicy

简介

k8s中内置了一种安全策略,能够用来约束pod的行为,他叫PodSecurityPolicy,位于apiserver中,默认被关闭。psp定义了哪些是能做的,他的作用范围大都是在securityContext这个结构中,其他也有,比如可以定义哪些volume是支持的,定义哪些端口是允许的。他通过限制这些结构来达到约束pod的目的。

但是psp是一个即将被废弃的功能,如果你看到文章的时候k8s的版本已经出到了v1.25了那么你可以不用看这部分了,根据官方文档,psp会在v1.25被彻底拿掉。至于psp的继任者Pod Security Admission我会在后续补上,当前我本地安装的k8s版本还不能使用,要v1.22才能使用。

1
2
3
https://kubernetes.io/docs/concepts/policy/pod-security-policy/

PodSecurityPolicy is deprecated as of Kubernetes v1.21, and will be removed in v1.25. It has been replaced by Pod Security Admission.

我们来了解一下这个功能,并演示以下如何开启并使用他。

以下的操作是基于自己搭建的k8s集群,搭建集群的步骤参考树莓派搭建k8s集群

psp为什么废弃

1
2
3
4
5
6
7
8
9
10
11
12
13
14
https://kubernetes.io/blog/2021/04/06/podsecuritypolicy-deprecation-past-present-and-future/#why-is-podsecuritypolicy-going-away

The way PSPs are applied to Pods has proven confusing to nearly everyone that has attempted to use them. It is easy to accidentally grant broader permissions than intended, and difficult to inspect which PSP(s) apply in a given situation. The “changing Pod defaults” feature can be handy, but is only supported for certain Pod settings and it’s not obvious when they will or will not apply to your Pod. Without a “dry run” or audit mode, it’s impractical to retrofit PSP to existing clusters safely, and it’s impossible for PSP to ever be enabled by default.

psp的授权有两个比较大的问题,一个是除非被明确授予权限,否则默认是没有权限,啥都不能干,这就导致不能随意开启,初始时开启了就没法操作了,而到了线上再开启就很容易影响pod,导致有些pod没有了权限,所以只能初始不开启,然后配置好了,然后开启,然后部署到线上。
另一个问题是psp的授权还依赖RBAC,而RBAC是很间接的,要找到某个service account,再找到相关的role,role中再定义对应的psp,psp中再详细的定义约束,同时如果能找到多个这样role,那么整个路线是这样的:
create pod->user/sa->rolebinding1->role1->psp1->psp rules
->rolebinding2->role2->psp2->psp rules
这里我们可以看到找到pod对应的psp,这个路径是又长又冗余的,而pod最终只能选择一个psp,要么psp1要么psp2这就给找到psp规则带来了更大的复杂性。

所以官方文档中提到的3点,第一点说容易意外的分配过广的权限,这本质是第二个问题的复杂性带来的;
第二点说默认值配置好用,但是对于是否会应用到你的pod这点并不明显,这本质也是第二个问题的复杂性带来的;
第三点说psp没法安全的在现有集群开启并且默认不能开启,这本质是第一个问题带来的。

如何使用psp

psp用法需要(admission-control enable psp)+(clusterrole/role)+(clusterrolebinding/rolebinding).

也就是psp需要开关进行使能,同时psp是基于RBAC绑定到user/sa的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
我们定义3个psp,一个是没权限,一个是部分受约束的权限,一个是全开的权限;然后创建对应的3个clusterrole;然后把受约束的clusterrole绑定给用户组system:authenticated和system:serviceaccounts,同时把全开的权限给kubelet用户;没权限的psp这里不去使用。

~$ vi previleges-psp.yaml
apiVersion: policy/v1beta1
kind: PodSecurityPolicy
metadata:
name: no-privilege
spec:
privileged: false
seLinux:
rule: RunAsAny
supplementalGroups:
rule: RunAsAny
runAsUser:
rule: RunAsAny
fsGroup:
rule: RunAsAny
volumes:
- '*'

---
apiVersion: policy/v1beta1
kind: PodSecurityPolicy
metadata:
name: restrict-privileged
spec:
privileged: false
# Required to prevent escalations to root.
allowPrivilegeEscalation: false
requiredDropCapabilities:
- ALL
# Allow core volume types.
volumes:
- 'configMap'
- 'emptyDir'
- 'projected'
- 'secret'
- 'downwardAPI'
# Assume that ephemeral CSI drivers & persistentVolumes set up by the cluster admin are safe to use.
- 'csi'
- 'persistentVolumeClaim'
- 'ephemeral'
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

---
apiVersion: policy/v1beta1
kind: PodSecurityPolicy
metadata:
name: privileged
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'
allowedHostPaths:
- pathPrefix: "/"

---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: no-previlege-psp-clusterrole
rules:
- apiGroups: ['policy']
resources: ['podsecuritypolicies']
verbs: ['use']
resourceNames:
- no-privilege
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: restrict-previleged-psp-clusterrole
rules:
- apiGroups: ['policy']
resources: ['podsecuritypolicies']
verbs: ['use']
resourceNames:
- restrict-privileged
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: previleged-psp-clusterrole
rules:
- apiGroups: ['policy']
resources: ['podsecuritypolicies']
verbs: ['use']
resourceNames:
- privileged
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: previleged-psp-binding
roleRef:
kind: ClusterRole
name: previleged-psp-clusterrole
apiGroup: rbac.authorization.k8s.io
subjects:
# Authorize all kubelet:
# https://github.com/kubernetes/kubernetes/blob/a1513161b3056d4c5ef711ab1c5314e97e90811a/cluster/gce/addons/podsecuritypolicies/node-binding.yaml
- kind: Group
apiGroup: rbac.authorization.k8s.io
name: system:nodes
- kind: User
apiGroup: rbac.authorization.k8s.io
# Legacy node ID
name: kubelet

---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: restrict-previleged-psp-binding
roleRef:
kind: ClusterRole
name: restrict-previleged-psp-clusterrole
apiGroup: rbac.authorization.k8s.io
subjects:
# Authorize all service accounts:
- kind: Group
apiGroup: rbac.authorization.k8s.io
name: system:serviceaccounts
# all authenticated users:
- kind: Group
apiGroup: rbac.authorization.k8s.io
name: system:authenticated





# 然后apply
~$ kl apply -f previleges-psp.yaml
Warning: policy/v1beta1 PodSecurityPolicy is deprecated in v1.21+, unavailable in v1.25+
podsecuritypolicy.policy/no-privilege created
podsecuritypolicy.policy/restrict-privileged created
podsecuritypolicy.policy/privileged created
clusterrole.rbac.authorization.k8s.io/no-previlege-psp-clusterrole created
clusterrole.rbac.authorization.k8s.io/restrict-previleged-psp-clusterrole created
clusterrole.rbac.authorization.k8s.io/previleged-psp-clusterrole created
clusterrolebinding.rbac.authorization.k8s.io/previleged-psp-binding created
clusterrolebinding.rbac.authorization.k8s.io/restrict-previleged-psp-binding created

接着我们编辑kube-apiserver.yaml来开启psp功能。

1
2
3
4
5
6
ubuntu@server1:/etc/kubernetes/manifests$ sudo vi kube-apiserver.yaml
...
- --enable-admission-plugins=NodeRestriction,PodSecurityPolicy
...

# enable-admission-plugins后面添加PodSecurityPolicy

之后我们来尝试创建pod,然后查看pod被psp应用了没有。

create pod中我们创建过这样一个sa:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
~$ alias kl=kubectl

# user createpod
~$ kl create sa createpod
serviceaccount/createpod created
~$ kl create role createpodrole --verb=list,create --resource=pod
role.rbac.authorization.k8s.io/createpodrole created
~$ kl create rolebinding createpod --serviceaccount=default:createpod --role=createpodrole
rolebinding.rbac.authorization.k8s.io/createpod created
~$ kl get secret | grep createpod
createpod-token-dl28b kubernetes.io/service-account-token 3 66s
~$ TOKEN=$(kl get secret createpod-token-dl28b -o jsonpath='{.data.token}' | base64 -d)
~$ kl config set-credentials createpod --token=$TOKEN
User "createpod" set.
~$ kl config set-context createpod --cluster kubernetes --user createpod
Context "createpod" created.

我们尝试用这个sa来创建pod,这个sa所在的group为system:serviceaccounts,所以按照预期,这个pod会绑定clusterrole:restrict-previleged-psp-binding, restrict-previleged-psp-binding会绑定psp:restrict-privileged.我们来确认一下:

1
2
3
4
5
~$ kl --context=createpod run ng --image=nginx
pod/ng created

~$ kl get pod ng -o jsonpath='{.metadata.annotations}'
{"kubernetes.io/psp":"restrict-privileged"}

与我们预期的一致。

注意一个特例,在我们初始的集群中,kubectl使用的用户是system:masters组下的,是一个拥有特权的用户,所以对于系统中的所有psp,都是有use权限的,因此system:masters下的用户使用的psp会从系统中全部的psp中选择一个。

1
2
3
4
5
~$ kl run ng2 --image=nginx
pod/ng2 created

~$ kl get pod ng2 -o jsonpath='{.metadata.annotations}'
{"kubernetes.io/psp":"no-privilege"}

可以看到psp:no-privilege虽然没有被任何rolebinding和clusterrolebinding所绑定,依然被pod选为使用的psp。

psp优先级选择

我们上面提过一嘴,如果一个pod关联了多个psp,那么只能选择一个,选择的过程就相对复杂一些。

1
2
create pod->user/sa->rolebinding1->role1->psp1->psp rules
->rolebinding2->role2->psp2->psp rules

我们可以通过这些文档了解psp选择的逻辑:

1
2
3
4
5
https://kubernetes.io/docs/concepts/policy/pod-security-policy/#policy-order
https://mozillazg.com/2020/05/k8s-kubernetes-use-which-psp-when-there-are-multiple-pod-security-policies.html

代码:plugin/pkg/admission/security/podsecuritypolicy/admission.go
func (p *Plugin) computeSecurityContext(...)

官方文档这么描述:

1
2
3
4
1.PodSecurityPolicies which allow the pod as-is, without changing defaults or mutating the pod, are preferred. The order of these non-mutating PodSecurityPolicies doesn't matter.
2.If the pod must be defaulted or mutated, the first PodSecurityPolicy (ordered by name) to allow the pod is selected.
# 即优先选择不修改pod的psp,其次选择字母序更小的会修改pod的psp。
# 他这里说对于不修改pod的psp,无所谓选择了哪一个。从效果看确实是无所谓的,但是从事实上的选择来说,对于不修改pod的psp,也是按照字母序选择更小的一个psp。

更简单的规则描述是,psp按照两层优先级选择最优的一个。首先不修改pod的优先级>修改pod的优先级,其次字母序更小的优先级>字母序更大的优先级。

参考

树莓派搭建k8s集群
Pod Security Admission
Pod Security Policies
Why is PodSecurityPolicy going away
node-binding.yaml
create pod
policy-order
当有多个可用的 Pod Security Policy 时 k8s 的 PSP 选择策略