kubernetes高階之pod安全策略

周國通發表於2019-06-24

系列目錄

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

相關文章