什么是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