Kubernetes的安全上下文是傳遞給容器運(yùn)行時(shí)的參數(shù),能夠在運(yùn)行時(shí)配置Pod和容器的安全性。要為Pod設(shè)置安全性設(shè)置,可以在Pod規(guī)約中包含securityContext字段。這個(gè)字段的值是一個(gè)PodSecurityContext對(duì)象,通過這種方式,可以為Pod設(shè)定所需的安全性配配置。
安全上下文(Security Context)定義 Pod 或 Container 的特權(quán)與訪問控制設(shè)置。 安全上下文包括但不限于:
1、自主訪問控制(Discretionary Access Control): 基于用戶 ID(UID)和組 ID(GID) 來(lái)判定對(duì)對(duì)象(例如文件)的訪問權(quán)限。
2;安全性增強(qiáng)的 Linux(SELinux): 為對(duì)象賦予安全性標(biāo)簽。
3、以特權(quán)模式或者非特權(quán)模式運(yùn)行。
4、Linux 權(quán)能: 為進(jìn)程賦予 root 用戶的部分特權(quán)而非全部特權(quán)。
5、AppArmor:使用程序配置來(lái)限制個(gè)別程序的權(quán)能。
6、Seccomp:過濾進(jìn)程的系統(tǒng)調(diào)用。
7、allowPrivilegeEscalation:控制進(jìn)程是否可以獲得超出其父進(jìn)程的特權(quán)。 此布爾值直接控制是否為容器進(jìn)程設(shè)置 no_new_privs標(biāo)志。 當(dāng)容器滿足一下條件之一時(shí),allowPrivilegeEscalation 總是為 true:
- 以特權(quán)模式運(yùn)行;
- 具有 CAP_SYS_ADMIN 權(quán)能。
8、readOnlyRootFilesystem:以只讀方式加載容器的根文件系統(tǒng)。
一、準(zhǔn)備
首先必須擁有一個(gè) Kubernetes 的集群,同時(shí)必須配置 kubectl 命令行工具與集群通信。 建議在至少有兩個(gè)不作為控制平面主機(jī)的節(jié)點(diǎn)的集群上運(yùn)行本教程。 如果還沒有集群,可以通過 Minikube 構(gòu)建一個(gè)自己的集群,或者可以使用下面的 Kubernetes 練習(xí)環(huán)境之一:
- Killercoda
- 玩轉(zhuǎn) Kubernetes
要獲知版本信息,請(qǐng)輸入 kubectl version.
二、Pod安全性上下文
要為 Pod 設(shè)置安全性設(shè)置,可在 Pod 規(guī)約中包含 securityContext 字段。securityContext 字段值是一個(gè) PodSecurityContext 對(duì)象。為 Pod 所設(shè)置的安全性配置會(huì)應(yīng)用到 Pod 中所有 Container 上。 下面是一個(gè) Pod 的配置文件,該 Pod 定義了 securityContext 和一個(gè) emptyDir 卷:
apiVersion: v1 kind: Pod metadata: name: security-context-demo spec: securityContext: runAsUser: 1000 runAsGroup: 3000 fsGroup: 2000 volumes: - name: sec-ctx-vol emptyDir: {} containers: - name: sec-ctx-demo image: busybox:1.28 command: [ "sh", "-c", "sleep 1h" ] volumeMounts: - name: sec-ctx-vol mountPath: /data/demo securityContext: allowPrivilegeEscalation: false
在配置文件中,runAsUser 字段指定 Pod 中的所有容器內(nèi)的進(jìn)程都使用用戶 ID 1000 來(lái)運(yùn)行。runAsGroup 字段指定所有容器中的進(jìn)程都以主組 ID 3000 來(lái)運(yùn)行。 如果忽略此字段,則容器的主組 ID 將是 root(0)。 當(dāng) runAsGroup 被設(shè)置時(shí),所有創(chuàng)建的文件也會(huì)劃歸用戶 1000 和組 3000。 由于 fsGroup 被設(shè)置,容器中所有進(jìn)程也會(huì)是附組 ID 2000 的一部分。 卷 /data/demo 及在該卷中創(chuàng)建的任何文件的屬主都會(huì)是組 ID 2000。
創(chuàng)建該 Pod:
kubectl apply -f https://k8s.io/examples/pods/security/security-context.yaml
檢查 Pod 的容器處于運(yùn)行狀態(tài):
kubectl get pod security-context-demo
開啟一個(gè) Shell 進(jìn)入到運(yùn)行中的容器:
kubectl exec -it security-context-demo -- sh
在 Shell 中,列舉運(yùn)行中的進(jìn)程:
ps
輸出顯示進(jìn)程以用戶 1000 運(yùn)行,即 runAsUser 所設(shè)置的值:
PID USER TIME COMMAND 1 1000 0:00 sleep 1h 6 1000 0:00 sh ...
在 Shell 中,進(jìn)入 /data 目錄列舉其內(nèi)容:
cd /data ls -l
輸出顯示 /data/demo 目錄的組 ID 為 2000,即 fsGroup 的設(shè)置值:
drwxrwsrwx 2 root 2000 4096 Jun 6 20:08 demo
在 Shell 中,進(jìn)入到 /data/demo 目錄下創(chuàng)建一個(gè)文件:
cd demo echo hello > testfile
列舉 /data/demo 目錄下的文件:
ls -l
輸出顯示 testfile 的組 ID 為 2000,也就是 fsGroup 所設(shè)置的值:
-rw-r--r-- 1 1000 2000 6 Jun 6 20:08 testfile
運(yùn)行下面的命令:
id
輸出類似于:
uid=1000 gid=3000 groups=2000
從輸出中會(huì)看到 gid 值為 3000,也就是 runAsGroup 字段的值。 如果 runAsGroup 被忽略,則 gid 會(huì)取值 0(root),而進(jìn)程就能夠與 root 用戶組所擁有以及要求 root 用戶組訪問權(quán)限的文件交互。
退出 Shell:
exit
三、配置卷訪問權(quán)限/屬主變更策略
默認(rèn)情況下,Kubernetes 在掛載一個(gè)卷時(shí),會(huì)遞歸地更改每個(gè)卷中的內(nèi)容的屬主和訪問權(quán)限, 使之與 Pod 的 securityContext 中指定的 fsGroup 匹配。 對(duì)于較大的數(shù)據(jù)卷,檢查和變更屬主與訪問權(quán)限可能會(huì)花費(fèi)很長(zhǎng)時(shí)間,降低 Pod 啟動(dòng)速度。 可以在 securityContext 中使用 fsGroupChangePolicy 字段來(lái)控制 Kubernetes 檢查和管理卷屬主和訪問權(quán)限的方式。
fsGroupChangePolicy – fsGroupChangePolicy 定義在卷被暴露給 Pod 內(nèi)部之前對(duì)其 內(nèi)容的屬主和訪問許可進(jìn)行變更的行為。此字段僅適用于那些支持使用 fsGroup 來(lái) 控制屬主與訪問權(quán)限的卷類型。此字段的取值可以是:
- OnRootMismatch:只有根目錄的屬主與訪問權(quán)限與卷所期望的權(quán)限不一致時(shí), 才改變其中內(nèi)容的屬主和訪問權(quán)限。這一設(shè)置有助于縮短更改卷的屬主與訪問 權(quán)限所需要的時(shí)間;
- Always:在掛載卷時(shí)總是更改卷中內(nèi)容的屬主和訪問權(quán)限。
例如:
securityContext: runAsUser: 1000 runAsGroup: 3000 fsGroup: 2000 fsGroupChangePolicy: "OnRootMismatch"
注意:此字段對(duì)于 secret、 configMap 和 emptydir 這類臨時(shí)性存儲(chǔ)無(wú)效。
四、更改委派給CSI驅(qū)動(dòng)程序
如果部署了一個(gè)容器存儲(chǔ)接口 (CSI) 驅(qū)動(dòng),而該驅(qū)動(dòng)支持 VOLUME_MOUNT_GROUP NodeServiceCapability, 在 securityContext 中指定 fsGroup 來(lái)設(shè)置文件所有權(quán)和權(quán)限的過程將由 CSI 驅(qū)動(dòng)而不是 Kubernetes 來(lái)執(zhí)行。在這種情況下,由于 Kubernetes 不執(zhí)行任何所有權(quán)和權(quán)限更改, fsGroupChangePolicy 不會(huì)生效,并且按照 CSI 的規(guī)定,CSI 驅(qū)動(dòng)應(yīng)該使用所指定的 fsGroup 來(lái)掛載卷,從而生成了一個(gè)對(duì) fsGroup 可讀/可寫的卷.
五、Container安全性上下文
若要為 Container 設(shè)置安全性配置,可以在 Container 清單中包含 securityContext 字段。securityContext 字段的取值是一個(gè) SecurityContext 對(duì)象。為 Container 設(shè)置的安全性配置僅適用于該容器本身,并且所指定的設(shè)置在與 Pod 層面設(shè)置的內(nèi)容發(fā)生重疊時(shí),會(huì)重寫 Pod 層面的設(shè)置。Container 層面的設(shè)置不會(huì)影響到 Pod 的卷。
下面是一個(gè) Pod 的配置文件,其中包含一個(gè) Container。Pod 和 Container 都有 securityContext 字段:
apiVersion: v1 kind: Pod metadata: name: security-context-demo-2 spec: securityContext: runAsUser: 1000 containers: - name: sec-ctx-demo-2 image: gcr.io/google-samples/node-hello:1.0 securityContext: runAsUser: 2000 allowPrivilegeEscalation: false
創(chuàng)建該 Pod:
kubectl apply -f https://k8s.io/examples/pods/security/security-context-2.yaml
驗(yàn)證 Pod 中的容器處于運(yùn)行狀態(tài):
kubectl get pod security-context-demo-2
啟動(dòng)一個(gè) Shell 進(jìn)入到運(yùn)行中的容器內(nèi):
kubectl exec -it security-context-demo-2 -- sh
在 Shell 中,列舉運(yùn)行中的進(jìn)程:
ps aux
輸出顯示進(jìn)程以用戶 2000 運(yùn)行。該值是在 Container 的 runAsUser 中設(shè)置的。 該設(shè)置值重寫了 Pod 層面所設(shè)置的值 1000。
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND 2000 1 0.0 0.0 4336 764 ? Ss 20:36 0:00 /bin/sh -c node server.js 2000 8 0.1 0.5 772124 22604 ? Sl 20:36 0:00 node server.js ...
退出 Shell:
exit
六、Container設(shè)置權(quán)能
使用 Linux 權(quán)能, 可以賦予進(jìn)程 root 用戶所擁有的某些特權(quán),但不必賦予其全部特權(quán)。 要為 Container 添加或移除 Linux 權(quán)能,可以在 Container 清單的 securityContext 節(jié)包含 capabilities 字段。
首先,看一下不包含 capabilities 字段時(shí)候會(huì)發(fā)生什么。 下面是一個(gè)配置文件,其中沒有添加或移除容器的權(quán)能:
apiVersion: v1 kind: Pod metadata: name: security-context-demo-3 spec: containers: - name: sec-ctx-3 image: gcr.io/google-samples/node-hello:1.0
創(chuàng)建該 Pod:
kubectl apply -f https://k8s.io/examples/pods/security/security-context-3.yaml
驗(yàn)證 Pod 的容器處于運(yùn)行狀態(tài):
kubectl get pod security-context-demo-3
啟動(dòng)一個(gè) Shell 進(jìn)入到運(yùn)行中的容器:
kubectl exec -it security-context-demo-3 -- sh
在 Shell 中,列舉運(yùn)行中的進(jìn)程:
ps aux
輸出顯示容器中進(jìn)程 ID(PIDs):
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND root 1 0.0 0.0 4336 796 ? Ss 18:17 0:00 /bin/sh -c node server.js root 5 0.1 0.5 772124 22700 ? Sl 18:17 0:00 node server.js
在 Shell 中,查看進(jìn)程 1 的狀態(tài):
cd /proc/1 cat status
輸出顯示進(jìn)程的權(quán)能位圖:
... CapPrm: 00000000a80425fb CapEff: 00000000a80425fb ...
記下進(jìn)程權(quán)能位圖,之后退出 Shell:
exit
接下來(lái)運(yùn)行一個(gè)與前例中容器相同的容器,只是這個(gè)容器有一些額外的權(quán)能設(shè)置。
下面是一個(gè) Pod 的配置,其中運(yùn)行一個(gè)容器。配置為容器添加 CAP_NET_ADMIN 和 CAP_SYS_TIME 權(quán)能:
apiVersion: v1 kind: Pod metadata: name: security-context-demo-4 spec: containers: - name: sec-ctx-4 image: gcr.io/google-samples/node-hello:1.0 securityContext: capabilities: add: ["NET_ADMIN", "SYS_TIME"]
創(chuàng)建 Pod:
kubectl apply -f https://k8s.io/examples/pods/security/security-context-4.yaml
啟動(dòng)一個(gè) Shell,進(jìn)入到運(yùn)行中的容器:
kubectl exec -it security-context-demo-4 -- sh
在 Shell 中,查看進(jìn)程 1 的權(quán)能:
cd /proc/1 cat status
輸出顯示的是進(jìn)程的權(quán)能位圖:
... CapPrm: 00000000aa0435fb CapEff: 00000000aa0435fb ...
比較兩個(gè)容器的權(quán)能位圖:
00000000a80425fb 00000000aa0435fb
在第一個(gè)容器的權(quán)能位圖中,位 12 和 25 是沒有設(shè)置的。在第二個(gè)容器中,位 12 和 25 是設(shè)置了的。位 12 是 CAP_NET_ADMIN 而位 25 則是 CAP_SYS_TIME。
說(shuō)明: Linux 權(quán)能常數(shù)定義的形式為 CAP_XXX。但是在 container 清單中列舉權(quán)能時(shí), 要將權(quán)能名稱中的 CAP_ 部分去掉。例如,要添加 CAP_SYS_TIME, 可在權(quán)能列表中添加 SYS_TIME。
七、容器Seccomp配置
若要為容器設(shè)置 Seccomp 配置(Profile),可在 Pod 或 Container 清單的 securityContext 節(jié)中包含 seccompProfile 字段。該字段是一個(gè) SeccompProfile 對(duì)象,包含 type 和 localhostProfile 屬性。 type 的合法選項(xiàng)包括 RuntimeDefault、Unconfined 和 Localhost。 localhostProfile 只能在 type: Localhost 配置下才可以設(shè)置。 該字段標(biāo)明節(jié)點(diǎn)上預(yù)先設(shè)定的配置的路徑,路徑是相對(duì)于 kubelet 所配置的 Seccomp 配置路徑(使用 –root-dir 設(shè)置)而言的。
下面是一個(gè)例子,設(shè)置容器使用節(jié)點(diǎn)上容器運(yùn)行時(shí)的默認(rèn)配置作為 Seccomp 配置:
... securityContext: seccompProfile: type: RuntimeDefault
下面是另一個(gè)例子,將 Seccomp 的樣板設(shè)置為位于 <kubelet-根目錄>/seccomp/my-profiles/profile-allow.json 的一個(gè)預(yù)先配置的文件。
... securityContext: seccompProfile: type: Localhost localhostProfile: my-profiles/profile-allow.json
八、Container SELinux標(biāo)簽
若要給 Container 設(shè)置 SELinux 標(biāo)簽,可以在 Pod 或 Container 清單的 securityContext 節(jié)包含 seLinuxOptions 字段。 seLinuxOptions 字段的取值是一個(gè) SELinuxOptions 對(duì)象。下面是一個(gè)應(yīng)用 SELinux 標(biāo)簽的例子:
... securityContext: seLinuxOptions: level: "s0:c123,c456"
注意:要指定 SELinux,需要在宿主操作系統(tǒng)中裝載 SELinux 安全性模塊。
默認(rèn)情況下,容器運(yùn)行時(shí)遞歸地將 SELinux 標(biāo)簽賦予所有 Pod 卷上的所有文件。 為了加快該過程,Kubernetes 使用掛載可選項(xiàng) -o context=<label> 可以立即改變卷的 SELinux 標(biāo)簽。
要使用這項(xiàng)加速功能,必須滿足下列條件:
1、必須啟用 ReadWriteOncePod 和 SELinuxMountReadWriteOncePod 特性門控。
2、Pod 必須以 accessModes: [“ReadWriteOncePod”] 模式使用 PersistentVolumeClaim。
3、Pod(或其中使用 PersistentVolumeClaim 的所有容器)必須設(shè)置 seLinuxOptions。
4、對(duì)應(yīng)的 PersistentVolume 必須是:
- 使用傳統(tǒng)樹內(nèi)(In-Tree) iscsi、rbd 或 fs 卷類型的卷;
- 或者是使用 {< glossary_tooltip text=”CSI” term_id=”csi” >}} 驅(qū)動(dòng)程序的卷 CSI 驅(qū)動(dòng)程序必須能夠通過在 CSIDriver 實(shí)例中設(shè)置 spec.seLinuxMount: true 以支持 -o context 掛載。
對(duì)于所有其他卷類型,重打 SELinux 標(biāo)簽的方式有所不同: 容器運(yùn)行時(shí)為卷中的所有節(jié)點(diǎn)(文件和目錄)遞歸地修改 SELinux 標(biāo)簽。 卷中的文件和目錄越多,重打標(biāo)簽需要耗費(fèi)的時(shí)間就越長(zhǎng)。
九、討論
Pod 的安全上下文適用于 Pod 中的容器,也適用于 Pod 所掛載的卷(如果有的話)。 尤其是,fsGroup 和 seLinuxOptions 按下面的方式應(yīng)用到掛載卷上:
1、fsGroup:支持屬主管理的卷會(huì)被修改,將其屬主變更為 fsGroup 所指定的 GID, 并且對(duì)該 GID 可寫。
2、seLinuxOptions:支持 SELinux 標(biāo)簽的卷會(huì)被重新打標(biāo)簽,以便可被 seLinuxOptions 下所設(shè)置的標(biāo)簽訪問。通常只需要設(shè)置 level 部分。 該部分設(shè)置的是賦予 Pod 中所有容器及卷的 多類別安全性(Multi-Category Security,MCS)標(biāo)簽。
注意: 在為 Pod 設(shè)置 MCS 標(biāo)簽之后,所有帶有相同標(biāo)簽的 Pod 可以訪問該卷。 如果需要跨 Pod 的保護(hù),必須為每個(gè) Pod 賦予獨(dú)特的 MCS 標(biāo)簽。
十、清理
刪除之前創(chuàng)建的所有 Pod:
kubectl delete pod security-context-demo kubectl delete pod security-context-demo-2 kubectl delete pod security-context-demo-3 kubectl delete pod security-context-demo-4