1.在pod中使用宿主機命名空間、端口等資源
pod中的容器通常在分開的Linux命名空間中運行。這些命名空間將容器中的進程與其他容器中,或者宿主機默認命名空間中的進程隔離開來。
例如,每一個pod有自己的IP和端口空間,這是因為它擁有自己的網絡命名空間。類似地,每一個pod擁有自己的進程樹,因為它有自己的PID命名空間。同樣的,pod擁有自己的IPC命名空間,僅允許同一pod內的進程通過進程間通信(Inter Process Communication,簡稱IPC)機制進行交流。
1.1 在pod中使用宿主節點的網絡命名空間
部分pod(特別是系統pod)需要在宿主節點的默認命名空間中運行,以允許它們看到和操作節點級別的資源和設備。例如,某個pod可能需要使用宿主節點上的網絡適配器,而不是自己的虛擬網絡設備。這可以通過將pod spec中的hostNetwork設置為true實現。
如圖13.1所示,在這種情況下,這個pod可以使用宿主節點的網絡接口,而不是擁有自己獨立的網絡。這意味着這個pod沒有自己的IP地址;如果這個pod中的某一進程綁定了某個端口,那么該進程將被綁定到宿主節點的端口上。
可以嘗試運行這樣一個pod,以下的代碼清單展示了此種pod的一個例子。
#代碼13.1 —個使用宿主節點默認的網絡命名空間的pod: pod-with-host-network.yaml apiVersion: v1 kind: Pod metadata: name: pod-with-host-network spec: hostNetwork: true #使用宿主節點的網絡命名空間 containers: - name: main image: alpine command: ["/bin/sleep", "999999"]
在運行了這個pod之后,可以用如下的命令來驗證它確實使用了宿主節點的網絡命名空間(例如,它可以看到宿主節點上所有的網絡接口)
#代碼13.2 使用宿主機網絡命名空間的pod網絡 $ kubectl exec pod-with-host-network ifconfig docker0 Link encap:Ethernet HWaddr 02:42:14:08:23:47 inet addr:172.17.0.1 Bcast:0.0.0.0 Mask:255.255.0.0 ... eth0 Link encap:Ethernet HWaddr 08:00:27:F8:FA:4E inet addr:10.0.2.15 Bcast:10.0.2.255 Mask:255.255.255.0 ... lo Link encap:Local Loopback inet addr:127.0.0.1 Mask:255.0.0.0 ... veth1178d4f Link encap:Ethernet HWaddr 1E:03:8D:D6:E1:2C inet6 addr: fe80::1c03:8dff:fed6:e12c/64 Scope:Link UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 ...
Kubernetes控制平面組件通過pod部署時(例如,使用kubeadm部署Kubernetes集群),這些pod都會使用hostNetwork選項,讓它們的行為與不在pod中運行時相同。
1.2 綁定宿主節點上的端口而不使用宿主節點的網絡命名空間
一個與此有關的功能可以讓pod在擁有自己的網絡命名空間的同時,將端口綁定到宿主節點的端口上。這可以通過配置pod的spec, containers, ports字段中某個容器某一端口的hostPort屬性來實現。
不要混淆使用hostPort的pod和通過NodePort服務暴露的pod。如圖13.2 所示,它們是不同的。
在圖中首先注意到的是,對於一個使用hostPort的pod,到達宿主節點的端口的連接會被直接轉發到pod的對應端口上;然而在NodePort服務中,到達宿主節點的端口的連接將被轉發到隨機選取的pod上(這個pod可能在其他節點上)。另外一個區別是,對於使用hostPort的pod,僅有運行了這類pod的節點會綁定對應的端口;而NodePort類型的服務會在所有的節點上綁定端口,即使這個節點上沒有運行對應的pod(如圖中所示的節點3)。
很重要的一點是,如果一個pod綁定了宿主節點上的一個特定端口,每個宿主節點只能調度一個這樣的pod實例,因為兩個進程不能綁定宿主機上的同一個端口。調度器在調度pod時會考慮這一點,所以它不會把兩個這樣的pod調度到同一個節點上(如圖13.3所示),如果要在3個節點上部署4個這樣的pod副本,只有3個副本能夠成功部署(剩余1個pod保持Pending狀態)。
如何在pod的YAML定義文件中定義hostPort選項。以下代碼清單展示了一個運行kubia pod,並將該pod綁定到宿主機的9000端口的YAML描述文件。
#代碼13.3 將pod中的一個端口綁定到宿主節點默認網絡命名空間的端口:kubia-hostport.yaml apiVersion: v1 kind: Pod metadata: name: kubia-hostport spec: containers: - image: luksa/kubia name: kubia ports: - containerPort: 8080 #該容器可以通過pod IP 8080 端口訪問 hostPort: 9000 #也可以通過所在節點的9000端口訪問 protocol: TCP
創建這個pod之后,可以通過它所在節點的9000端口訪問這個pod。有多個宿主節點時,並不能通過其他宿主節點的同一端口訪問該pod。
hostPort功能最初是用於暴露通過DeamonSet部署在每個節點上的系統服務的。最初,這個功能也用於保證一個pod的兩個副本不被調度到同一節點上,但是現在有更好的方法來實現這一需求。這個在<pod的高級調度中介紹>。
1.3 使用宿主節點的PID與IPC命名空間
pod spec中的hostPID和hostIPC選項與hostNetwork相似。當它們被設置為true時,Pod中的容器會使用宿主節點的PID和IPC命名空間,分別允許它們看到宿主機上的全部進程,或通過IPC機制與它們通信。以下代碼清單是一個使用hostPID和hostIPC的pod的例子。
#代碼 13.4使用宿主節點的PID和IPC命名空間:pod-with-host-pid-and-ipc.yaml apiVersion: v1 kind: Pod metadata: name: pod-with-host-pid-and-ipc spec: hostPID: true #希望使用這個pod使用宿主節點的PID命名空間 hostIPC: true #同樣,希望使用宿主幾點的IPC命名空間 containers: - name: main image: alpine command: ["/bin/sleep", "999999"]
pod中通常只能看到自己內部的進程,但在這個pod的容器中列出進程,可以看到宿主機上的所有進程,而不僅僅是容器內的進程,就如同以下的代碼清單所示。
#代碼13.5 配置hostPID: true的pod內可見的進程列表 $ kubectl exec pod-with-host-pid-and-ipc ps aux PID USER TIME COMMAND 1 root 0:01 /usr/lib/systemd/systemd --switched-root --system ... 2 root 0:00 [kthreadd] 3 root 0:00 [ksoftirqd/0] 5 root 0:00 [kworker/0:0H] 6 root 0:00 [kworker/u2:0] 7 root 0:00 [migration/0] 8 root 0:00 [rcu_bh] 9 root 0:00 [rcu_sched] 10 root 0:00 [watchdog/0] ...
將hostIPC設置為true, pod中的進程就可以通過進程間通信機制與宿主機上的其他所有進程進行通信。
2.配置節點的安全上下文
除了讓pod使用宿主的命名空間外,還可以在pod或其所屬容器的描述中通過security-Context選項配置其他與安全性相關的特性。這個選項可以運用於整個pod,或者每個pod中單獨的容器。
了解安全上下文中可以配置的內容
配置安全上下文可以允許你做很多事:
-
- 指定容器中運行進程的用戶(用戶ID)。
- 阻止容器使用root用戶運行(容器的默認運行用戶通常在其鏡像中指定,所以可能需要阻止容器以root用戶運行)。
- 使用特權模式運行容器,使其對宿主節點的內核具有完全的訪問權限。
- 與以上相反,通過添加或禁用內核功能,配置細粒度的內核訪問權限。
- 設置SELinux (Security Enhaced Linux,安全增強型Linux)選項,加強對容器的限制。
- 阻止進程寫入容器的根文件系統。
以下內容將開始探索這些選項的細節。
運行pod而不配置安全上下文
首先,運行一個沒有任何安全上下文配置的pod (不指定任何安全上下文選項), 與配置了安全上下文的pod形成對照:
$ kubectl run pod-with-defaults --image alpine --restart Never -- /bin/sleep 999999 pod "pod-with-defaults" created
來看一看這個容器中的用戶ID和組ID,以及它所屬的用戶組。這可以通過在容器中運行id命令查看。
$ kubectl exec pod-with-defaults id uid=0(root) gid=0(root) groups=0(root), 1(bin), 2(daemon), 3(sys), 4(adm), 6(disk), 10(wheel), 11(floppy), 20(dialout), 26(tape), 27(video)
這個容器在用戶ID(uid)為0的用戶,即root,用戶組ID(gid)為0(同樣是root)的用戶組下運行。它同樣還屬於一些其他的用戶組。
注意:容器運行時使用的用戶在鏡像中指定。在Dockerfile中,這是通過使用USER命令實現的。如果該命令被省略,容器將使用root用戶運行。
現在來運行一個使用特定用戶運行容器的pod。
2.1 使用指定用戶運行容器
為了使用一個與鏡像中不同的用戶ID來運行pod,需要設置該pod的securityContext.runAsUser選項。可以通過以下代碼清單來運行一個使用guest用戶運行的容器,該用戶在alpine鏡像中的用戶ID為405。
#代碼13.6 使用特定用戶運行容器:pod-as-user-guest.yaml apiVersion: v1 kind: Pod metadata: name: pod-as-user-guest spec: containers: - name: main image: alpine command: ["/bin/sleep", "999999"] securityContext: runAsUser: 405 #需要指明一個用戶ID,而不是用戶名(id405對應guest用戶)
現在可以像之前一樣在pod中運行id命令,查看runAsUser選項的效果:
$ kubectl exec pod-as-user-guest id uid=405(guest) gid=100(users)
與要求的一樣,該容器在guest用戶下運行。
2.2 阻止容器以root用戶運行
如果你不關心容器是哪個用戶運行的,只是希望阻止以root用戶運行呢?
假設有一個已經部署好的pod,它使用一個在Dockerfile中使用USER daemon命令制作的鏡像,使其在daemon用戶下運行。如果攻擊者獲取了訪問鏡像倉庫的權限,並上傳了一個標簽完全相同,在root用戶下運行的鏡像,會發生什么?當Kubernetes的調度器運行該pod的新實例時,kubelet會下載攻擊者的鏡像,並運行該鏡像中的任何代碼。
雖然容器與宿主節點基本上是隔離的,使用root用戶運行容器中的進程仍然是一種不好的實踐。例如,當宿主節點上的一個目錄被掛載到容器中時,如果這個容器中的進程使用了root用戶運行,它就擁有該目錄的完整訪問權限;如果用非root用戶運行,則沒有完整權限。
為了防止以上的攻擊場景發生,可以進行配置,使得pod中的容器以非root用戶運行,如以下的代碼清單所示。
#代碼13.7 阻止容器使用root用戶運行:pod-run-as-non-root.yaml apiVersion: v1 kind: Pod metadata: name: pod-run-as-non-root spec: containers: - name: main image: alpine command: ["/bin/sleep", "999999"] securityContext: runAsNonRoot: true #這個容器只予許以非root用戶運行
部署這個pod之后,它會被成功調度,但是不允許運行:
$ kubectl get po pod-run-as-non-root NAME READY STATUS pod-run-as-non-root 0/1 container has runAsNonRoot and image will run as root
現在,即使攻擊者篡改了鏡像,他們也無法做出進一步的破壞。
2.3 使用特權模式運行pod
有時pod需要做它們的宿主節點上能夠做的任何事,例如操作被保護的系統設備,或使用其他在通常容器中不能使用的內核功能。
這種pod的一個樣例就是kube-proxy pod,這種pod修改宿主機的iptables規則來讓Kubernetes中的服務規則生效。使用kubeadm部署集群時,會看到每個節點上都運行了kube-proxy pod,並且可以查看YAML描述文件中所有使用到的特殊特性。
為獲取宿主機內核的完整權限,該pod需要在特權模式下運行。這可以通過將容器的securityContext中的privileged設置為true實現。可以通過以下代碼清單中的YAML文件創建一個特權模式的pod。
#代碼13.8 一個帶有特權容器的pod:pod-privileged.yaml apiVersion: v1 kind: Pod metadata: name: pod-privileged spec: containers: - name: main image: alpine command: ["/bin/sleep", "999999"] securityContext: privileged: true #這個容器將在特權模式下運行
部署這個pod,然后與之前部署的非特權模式的pod做對比。
熟悉Linux的會知道Linux中有一個叫作/dev的特殊目錄,該目錄包含系統中所有設備對應的設備文件。這些文件不是磁盤上的常規文件,而是用於與設備通信的特殊文件。通過列出/dev目錄下文件的方式查看先前部署的非特權模式容器(名為Pod-with-defaults的pod)中的設備,如以下代碼清單所示。
#代碼13.9 非特權pod可用設備列表 $ kubectl exec -it pod-with-defaults ls /dev core null stderr urandom fd ptmx stdin zero full pts stdout fuse random termination-log mqueue shm tty
這個相當短的列表己經列出了全部的設備,將這個列表與下面的列表比較。下面的列表列出了在特權pod中能看到的特權設備。
#代碼13.10 特權pod可用的設備列表 $ kubectl exec -it pod-privileged ls /dev autofs snd tty46 bsg sr0 tty47 btrfs-control stderr tty48 core stdin tty49 cpu stdout tty5 cpu_dma_latency termination-log tty50 fd tty tty51 full tty0 tty52 fuse tty1 tty53 hpet tty10 tty54 hwrng tty11 tty55 ... ... ...
由於完整的設備列表過長,以上沒有完整列出所有的設備,但這己經足以證明這個設備列表遠遠長於之前的列表。事實上,特權模式的pod可以看到宿主節點上的所有設備。這意味着它可以自由使用任何設備。
舉個例子,如果要在樹莓派上運行一個pod,用這個pod來控制相連的LED, 那么必須使用特權模式運行這個pod。
2.4 為容器單獨添加內核功能
上一節中己經介紹了一種給予容器無限力量的方法。過去,傳統的UNIX實現只區分特權和非特權進程,但是經過多年的發展,Linux己經可以通過內核功能支持更細粒度的權限系統。
相比於讓容器運行在特權模式下以給予其無限的權限,一個更加安全的做法是只給予它使用真正需要的內核功能的權限。Kubernetes允許為特定的容器添加內核功能,或禁用部分內核功能,以允許對容器進行更加精細的權限控制,限制攻擊者潛在侵入的影響。
例如,一個容器通常不允許修改系統時間(硬件時鍾的時間)。可以通過在pod-with-defaults pod中修改設定時間來驗證:
$ kubectl exec -it pod-with-defaults -- date +%T -s "12:00:00" date:can't set date: Operation not permitted
如果需要允許容器修改系統時間,可以在容器capbilities里add—項名為CAP_SYS_TIME的功能,如以下代碼清單所示。
#代碼13.11 添加CAP_SYS_TIME功能:pod-add-settime-capability. yaml apiVersion: v1 kind: Pod metadata: name: pod-add-settime-capability spec: containers: - name: main image: alpine command: ["/bin/sleep", "999999"] securityContext: #在securityContext中添加或者禁用內核功能 capabilities: add: #在這里添加了SYS_TIME功能 - SYS_TIME
注意:Linux內核功能的名稱通常以CAP_開頭。但在pod spec中指定內核功能時,必須省略CAP_前綴。
在新的容器中運行同樣的命令,可以成功修改系統時間:
$ kubectl exec -it pod-add-settime-capability -- date +%T -s "12:00:00" 12:00:00 $ kubectl exec -it pod-add-settime-capability -- date Sun May 7 12:00:03 UTC 2017
警告:自行嘗試時,請注意這樣可能導致節點不可用。在Minikube中,盡管系統時間成功被網絡時間協議(Network Time Protocol,NTP)重置,仍然不得不重啟節點以調度新的pod。
添加內核功能遠比通過設置privileged:true更好,誠然這樣需要使用者了解各種內核功能。
2.5 在容器中禁用內核功能
己經了解到如何給容器添加內核功能,另一方面你也可以禁用容器中的內核功能。例如,默認情況下容器擁有CAP_CHOWN權限,允許進程修改文件系統中文件的所有者。
在以下示例中可以看到,可以在pod-with-defaults中將/tmp目錄的所有者改為guest用戶:
$ kubectl exec pod-with-defaults chown guest /tmp $ kubectl exec pod-with-defaults -- ls -la / | grep tmp drwxrwxrwt 2 guest root 6 May 25 15:18 tmp
為了阻止容器的此種行為,需要如以下代碼清單所示,在容器的securityContext.capabilities.drop列表中加入此項,以禁用這個修改文件所有者的內核功能。
#代碼 13.12禁用容器中的內核功能:pod-drop-chown-capability.yaml apiVersion: v1 kind: Pod metadata: name: pod-drop-chown-capability spec: containers: - name: main image: alpine command: ["/bin/sleep", "999999"] securityContext: capabilities: drop: #在這里禁用了容器修改文件所有者 - CHOWN
禁用CHOWN內核功能后,不允許在這個pod中修改文件所有者:
$ kubectl exec pod-drop-chown-capability chown guest /tmp chown: /tmp: Operation not permitted
這里已經對容器安全上下文的大部分選項研究完畢。下面再介紹一個選項。
2.6 阻止對容器根文件系統的寫入
因為安全原因,可能需要阻止容器中的進程對容器的根文件系統進行寫入,僅允許它們寫入掛載的存儲卷。
假如在運行一個有隱藏漏洞,可以允許攻擊者寫入文件系統的PHP應用。這些PHP文件在構建時放入容器的鏡像中,並且在容器的根文件系統中提供服務。由於漏洞的存在,攻擊者可以修改這些文件,在其中注入惡意代碼。
這一類攻擊可以通過阻止容器寫入自己的根文件系統(應用的可執行代碼的通常儲存位置)來防止。可以如以下代碼所示,將容器的securityContext.readOnlyRootFilesystem設置為true來實現。
#代碼13.13 根文件系統只讀的容器:pod-with-readonly-filesystem.yaml apiVersion: v1 kind: Pod metadata: name: pod-with-readonly-filesystem spec: containers: - name: main image: alpine command: ["/bin/sleep", "999999"] securityContext: #這個容器的根文件系統不予許寫入 readOnlyRootFilesystem: true volumeMounts: #但是向/volume寫入是予許的,因為這個目錄掛載了一個存儲卷 - name: my-volume mountPath: /volume readOnly: false volumes: - name: my-volume emptyDir:
這個pod中的容器雖然以root用戶運行,擁有/目錄的寫權限,但在該目錄下寫入一個文件會失敗:
$ kubectl exec -it pod-with-readonly-filesystem touch /new-file touch:/new-file: Read-only file system
另一方面,對掛載的卷的寫入是允許的:
$ kubectl exec -it pod-with-readonly-filesystem touch /volume/newfile $ kubectl exec -it pod-with-readonly-filesystem -- ls -la /volume/newfile -rw-r--r-- 1 root root 0 May 7 19:11 /mountedVolume/newfile
如以上例子所示,如果容器的根文件系統是只讀的,很可能需要為應用會寫入的每一個目錄(如日志、磁盤緩存等)掛載存儲卷。
提示:為了增強安全性,請將在生產環境運行的容器的readOnlyRootFilesystem選項設置為true。
設置pod級別的安全上下文
以上的例子都是對單獨的容器設置安全上下文。這些選項中的一部分也可以從pod級別設定(通過pod.spec.securityContext屬性)。它們會作為pod中每一個容器的默認安全上下文,但是會被容器級別的安全上下文覆蓋。下面將會介紹pod級別安全上下文獨有的內容.
2.7 容器使用不同用戶運行時共享存儲卷
存儲卷在pod的不同容器中共享數據。可以順利地在一個容器中寫入數據,在另一個容器中讀出這些數據。
但這只是因為兩個容器都以root用戶運行,對存儲卷中的所有文件擁有全部權限。現在假設使用前面介紹的runAsUser選項。可能需要在一個pod中用兩個不同的用戶運行兩個容器(可能是兩個第三方的容器,都以它們自己的特定用戶運行進程)。如果這樣的兩個容器通過存儲卷共享文件,它們不一定能夠讀取或寫入另一個容器的文件。
因此,Kubernetes允許為pod中所有容器指定supplemental組,以允許它們無論以哪個用戶ID運行都可以共享文件。這可以通過以下兩個屬性設置:
-
- fsGroup
- supplementalGroups
下面來看一下如何在pod中使用它們,以及它們效果。以下代碼清單描述了一個擁有兩個共享同一存儲卷的容器的pod。
#代碼 13.14 fsGroup和supplementalGroups: pod-with-shared-volume-fsgroup.yaml apiVersion: v1 kind: Pod metadata: name: pod-with-shared-volume-fsgroup spec: securityContext: #fsGroup和supplementalGroups在pod級別的安全上下文中定義 fsGroup: 555 supplementalGroups: [666, 777] containers: - name: first image: alpine command: ["/bin/sleep", "999999"] securityContext: #第一個容器使用的用戶ID為1111 runAsUser: 1111 volumeMounts: #兩個容器使用同一個存儲卷 - name: shared-volume mountPath: /volume readOnly: false - name: second image: alpine command: ["/bin/sleep", "999999"] securityContext: runAsUser: 2222 #第二個容器使用的用戶ID為2222 volumeMounts: - name: shared-volume mountPath: /volume readOnly: false volumes: - name: shared-volume emptyDir:
創建這個pod之后,進入第一個容器查看它的用戶ID和組ID:
$ kubectl exec -it pod-with-shared-volume-fsgroup -c first sh /$ id uid=1111 gid=0(root) groups=555,666,777
id命令顯示,這個pod運行在ID為1111的用戶下,它的用戶組為0 (root),但用戶組555、666、777也關聯到了該用戶下。
在pod的定義中,將fsGroup設置成了555,因此,存儲卷屬於用戶組ID為555的用戶組:
/$ls -l / | grep volume
drwxrwsrwx 2 root 555 6 May 29 12:23 volume
該容器在這個存儲卷所在目錄中創建的文件,所屬的用戶ID為1111(即該容器運行時使用的用戶ID),所屬的用戶組ID為555:
/ $ echo foo > /volume/foo /$ls -l /volume total 4 -rw-r--r-- 1 1111 555 4 May 29 12:25 foo
這個文件的所屬用戶情況與通常設置下的新建文件不同。在通常情況下,某一用戶新創建文件所屬的用戶組ID,與該用戶的所屬用戶組ID相同,在這種情下是0。在這個容器的根文件系統中創建一個文件,可以驗證這一點:
/ $ echo foo > /tmp/foo /$ls -l /tmp
total 4
-rw-r--r-- 1 1111 root 4 May 29 12:41 foo
如你所見,安全上下文中的fsGroup屬性當進程在存儲卷中創建文件時起作用,而supplementalGroups屬性定義了某個用戶所關聯的額外的用戶組。
3.限制pod使用安全相關的特性—PodSecurityPolicy
上面的例子都是在任一宿主節點做任何想做的事。比如,部署一個特權模式的pod,可以在任一宿主節點上做任何想做的事。但是需要有一種機制阻止用戶使用其中部分功能。集群管理人員可以通過創建PodSecurityPolicy資源來限制對以上提到的安全相關的特性的使用。
3.1 PodSecurityPolicy資源介紹
PodSecurityPolicy是一種集群級別(無命名空間)的資源,它定義了用戶能否在pod中使用各種安全相關的特性。維護PodSecurityPolicy資源中配置策略的工作由集成在API服務器中的PodSecurityPolicy准入控制插件完成。
注意:集群中不一定啟用了PodSecurityPolicy准入控制插件。這個請確保已啟動
當有人向API服務器發送pod資源時,PodSecurityPolicy准入控制插件會將這個pod與己經配置的PodSecurityPolicy進行校驗。如果這個pod符合集群中己有安全策略,它會被接收並存入etcd;否則它會立即被拒絕。這個插件也會根據安全策略中配置的默認值對pod進行修改。
在Minikube中啟用RBAC和PodSecurityPolicy准入控制
這里使用Minkubev0.19.0來運行以下樣例。這個版本沒有啟用RBAC和PodSecurityPolicy准入控制插件,這些在以下的部分練習中是必需的。其中一個練習需要以不同用戶認證,因此還需要開啟basic authenticate插件,其中用戶的信息在一個文件中定義。
為了在Minikube中啟用這些插件,需要運行如下命令(或類似的命令,這取決於使用的版本):
$ minikube start --extra-config apiserver.Authentication.PasswordFile. ➥ BasicAuthFile=/etc/kubernetes/passwd --extra-config=apiserver. ➥ Authorization.Mode=RBAC --extra-config=apiserver.GenericServerRun ➥ Options.AdmissionControl=NamespaceLifecycle,LimitRanger,Service ➥ Account,PersistentVolumeLabel,DefaultStorageClass,ResourceQuota, ➥ DefaultTolerationSeconds,PodSecurityPolicy
這個API服務器需要創建在以上命令中制定的口令文件才能開始運行。以下命令可以創建這個文件:
cat <<EOF | minikube ssh sudo tee /etc/kubernetes/passwd password,alice,1000,basic-user password,bob,2000,privileged-user EOF
了解PodSecurityPolicy可以做的事
一個PodSecurityPolicy資源可以定義以下事項:
-
- 是否允許pod使用宿主節點的PID、IPC、網絡命名空間
- pod允許綁定的宿主節點端口
- 容器運行時允許使用的用戶ID
- 是否允許擁有特權模式容器的pod
- 允許添加哪些內核功能,默認添加哪些內核功能,總是禁用哪些內核功能
- 允許容器使用哪些SELinux選項
- 容器是否允許使用可寫的根文件系統
- 允許容器在哪些文件系統組下運行
- 允許pod使用哪些類型的存儲卷
檢視一個PodSecurityPolicy樣例
以下代碼清單展示了一個PodSecurityPolicy的樣例。它阻止了pod使用宿主節點的PID、IPC、網絡命名空間,運行特權模式的容器,以及綁定大多數宿主節點的端口(除10000-11000和13000-14000范圍內的端口)。它沒有限制容器運行時使用的用戶、用戶組和SELinux選項。
#代碼13.15 一個 PodSecurityPolicy 的樣例:pod-security-policy.yaml apiVersion: extensions/v1beta1 kind: PodSecurityPolicy metadata: name: default spec: hostIPC: false #容器不予許使用宿主節點的IPC,PID和網絡命名空間 hostPID: false hostNetwork: false hostPorts: #容器只能綁定宿主節點的10000-11000端口(含端點)或13000-14000端口 - min: 10000 max: 11000 - min: 13000 max: 14000 privileged: false #容器不能在特權模式下運行 readOnlyRootFilesystem: true #容器強制使用只讀根文件系統 runAsUser: #容器可以以任意用戶和用戶組運行 rule: RunAsAny fsGroup: rule: RunAsAny supplementalGroups: rule: RunAsAny seLinux: #它們也可以使用任何Selinux的選項 rule: RunAsAny volumes: #pod可以使用所有類型的存儲卷 - '*'
以上樣例的大部分選項是不言自明的,這個PodSecurityPolicy在集群中創建成功之后,API服務器將不再允許之前樣例中的特權pod。
$ kubectl create -f pod-privileged.yaml Error from server (Forbidden): error when creating "pod-privileged.yaml" pods "pod-privileged" is forbidden: unable to validate against any pod security policy: [spec.containers[0].securityContext.privileged:Invalid value: true: Privileged containers are not allowed]
類似地,集群中不能再部署使用宿主節點的PID、IPC、網絡命名空間的pod了。 同樣,因為以上策略中的readOnlyRootFilesystem選項己設置為true,容器的根文件系統將變為只讀(容器只能寫入掛載的存儲卷)。
3.2 了解 runAsUser、fsGroup 和 supplementalGroup 策略
前面的例子中的策略沒有對容器運行時可以使用的用戶和用戶組施加任何限制,因為它們在runAsUser、fsGroup、supplementalGroups等字段中使用了runAsAny規則。如果需要限制容器可以使用的用戶和用戶組ID,可以將規則改為MustRunAs,並指定允許使用的ID范圍。
使用MustRunAs規則
來看以下的例子。為了只允許容器以用戶ID 2的身份運行並限制默認的文件系統組和增補組ID在2-10或20-30的范圍(包含臨界值)內,需要在PodSecurityPolicy資源中加入如以下代碼清單所示片段。
#代碼 13.16指定容器運行時必須使用的用戶和用戶組ID : psp-must-run-as.yaml apiVersion: extensions/v1beta1 kind: PodSecurityPolicy metadata: name: default spec: hostIPC: false hostPID: false hostNetwork: false hostPorts: - min: 10000 max: 11000 - min: 13000 max: 14000 privileged: false readOnlyRootFilesystem: true runAsUser: rule: MustRunAs ranges: #添加一個max=min的range來制定一個特定的ID - min: 2 max: 2 fsGroup: # 支持指定多個區間---這里,組ID可以在2-10(含端點)或20-30之間 rule: MustRunAs ranges: - min: 2 max: 10 - min: 20 max: 30 supplementalGroups: rule: MustRunAs ranges: - min: 2 max: 10 - min: 20 max: 30 seLinux: rule: RunAsAny volumes: - '*'
如果pod spec試圖將其中的任一字段設置為該范圍之外的值,這個pod將不會被API服務器接收。可以通過刪除之前PodSecurityContextPolicy,並通過psp-must-run-as.yaml文件創建一個新的來實踐這一點。
注意:修改策略對已經存在的pod無效,因為PodSecurityPolicy資源僅在創建和升級pod時起作用。
部署runAsUser在指定范圍之外的pod
如果嘗試使用之前的pod-as-user-guest.yaml文件部署一個pod,其中指定了容器運行的用戶ID為405,API服務器會拒絕這個pod:
$ kubectl create -f pod-as-user-guest.yaml Error from server (Forbidden): error when creating "pod-as-user-guest.yaml" :pods "pod-as-user-guest" is forbidden: unable to validate against any pod security policy: [securityContext.runAsUser:Invalid value: 405: UID on container main does not match required range. Found 405, allowed: [{2 2}]]
好,這個是顯然的。但是如果部署pod時沒有指定runAsUser屬性,但用戶ID被注入到鏡像的情況下(在Dockerfile中使用USER命令),會發生什么?
部署鏡像中用戶ID在指定范圍之外的pod
現在創建一個不同版本的Node.js鏡像,這個鏡像被配置為使用用戶ID為5的用戶運行。該鏡像使用的Dockerfile如以下代碼清單所示。
#代碼13.17 包含USER指令的Dockerfile FROM node:7 ADD app.js /app.js USER 5 ENTRYPOINT ["node", "app.js"] #app.js const http = require('http'); const os = require('os'); console.log("Kubia server starting..."); var handler = function(request, response) { console.log("Received request from " + request.connection.remoteAddress); response.writeHead(200); response.end("You've hit " + os.hostname() + "\n"); }; var www = http.createServer(handler); www.listen(8080);
鏡像命名為uksa/kubia-run-as-user-5,dockerHub可以找到。 如果使用這個鏡像創建pod,API服務器不會拒絕:
$ kubectl run run-as-5 --image luksa/kubia-run-as-user-5 --restart Never pod "run-as-5" created
與之前不同,API服務器接收了這個pod,kubelet也運行了這個容器。接下來查看這個容器使用的用戶ID和用戶組ID:
$ kubectl exec run-as-5 -- id uid=2(bin) gid=2(bin) groups=2(bin)
可以看到,這個容器運行時使用的用戶ID為2,就是在PodSecurityPolicy中指定的ID。PodSecurityPolicy可以將硬編碼覆蓋到鏡像中的用戶ID。
在runAsUser字段中使用mustRunAsNonRoot規則
runAsUser字段中還可以使用另一種規則:mustRunAsNonRoot。正如其名,它將阻止用戶部署以root用戶運行的容器。在此種情況下,spec容器中必須指定runAsUser字段,並且不能為0(0為root用戶的ID),或者容器的鏡像本身指定了用一個非0的用戶ID運行。這種做法的好處己經在之前介紹過。
3.3 配置允許、默認添加、禁止使用的內核功能
容器可以運行在特權模式下,也可以通過對每個容器添加或禁用Linux內核功能來定義更細粒度的權限配置。以下三個字段會影響容器可以使用的內核功能:
-
- allowedCapabilities
- defaultAddCapabilities
- requiredDropCapabilities
下面看一個例子,然后討論這三個字段各自的行為。以下代碼清單展示了一個定義了這三個字段的PodSecurityPolicy資源。
#代碼13.18 在PodSecurityPolicy資源中指定內核功能:psp-capabilities.yaml apiVersion: extensions/v1beta1 kind: PodSecurityPolicy metadata: name: default spec: allowedCapabilities: #予許容器添加SYS_TIME功能 - SYS_TIME defaultAddCapabilities: #為每個容器自動添加CHOWN功能 - CHOWN requiredDropCapabilities: #要求容器禁用SYS_ADMIN功能和SYS_MODULE功能 - SYS_ADMIN - SYS_MODULE .......
注意:SYS_ADMIN功能允許使用一系列的管理操作;SYS_MODULE功能允許加載或卸載Linux內核模塊。
指定容器中可以添加的內核功能
allowedCapabilities字段用於指定spec容器的securityContext.capabilities中可以添加哪些內核功能。之前的一個例子中,容器內添加了SYS_TIME內核功能。如果啟用了PodSecurityPolicy訪問控制插件,pod中不能添加以上內核功能,除非在PodSecurityPolicy中指明允許添加,如代碼清單13.18所示。
為所有容器添加內核功能
defaultAddCapabilities字段中列出的所有內核功能將被添加到每個已部署的pod的每個容器中。如果用戶不希望某個容器擁有這些功能,必須在容器的spec中顯式地禁用它們。
代碼清單13.18中的例子自動在每個容器中添加CAP_CH0WN功能,因此容器中的進程允許修改容器中文件的所有者(例如,使用chown命令)。
禁用容器中的內核功能
這個例子中的最后一個字段是requiredDropCapabilities。這個名字有點奇怪,但它並沒有那么復雜。在這個字段中列出的內核功能會在所有容器中被禁用(PodSecurityPolicy訪問控制插件會在所有容器的securityContext.capabilities.drop字段中加入這些功能)。
如果用戶試圖在創建的pod中顯式加入requiredDropCapabilities字段中的內核功能,這個pod會被拒絕:
$ kubectl create -f pod-add-sysadmin-capability.yaml Error from server (Forbidden): error when creating "pod-add-sysadmin-capability .yaml " : pods "pod-add-sysadmin-capability" is forbidden: unable to validate against any pod security policy: [capabilities.add: Invalid value: "SYS_ADMIN": capability may not be added]
3.4 限制pod可以使用的存儲卷類型
最后一項PodSecurityPolicy資源可以做到的是定義用戶可以在pod中使用哪些類型的存儲卷。在最低限度上,一個PodSecurityPolicy需要允許pod使用以下類型的存儲卷:empty Dir、configMap、secret、downwardAPI、persistentVolumeClaim。PodSecurityPolicy資源中的相關部分如以下代碼清單所示。
#代碼13.19 僅允許特定類型存儲卷的PodSecurityPolicy片段:psp-volumes.yaml apiVersion: extensions/v1beta1 kind: PodSecurityPolicy metadata: name: default spec: volumes: - emptyDir - configMap - secret - downwardAPI - persistentVolumeClaim
如果有多個PodSecurityPolicy資源,pod可以使用PodSecurityPolicy中允許使用的任何一個存儲卷類型(實際生效的是所有volume列表的並集)。
3.5 對不同的用戶與組分配不同的PodSecurityPolicy
已經提到過PodSecurityPolicy是集群級別的資源,這意味着它不能存儲和應用在某一特定的命名空間上。這是否意味着它總是會應用在所有的命名空間上呢?不是的,因為這樣會使得它們相當難以應用。畢竟,系統pod經常需要允許做一些常規pod不應當做的事情。
對不同用戶分配不同PodSecurityPolicy是通過RBAC機制實現的。這個方法是,創建需要的PodSecurityPolicy資源,然后創建ClusterRole資源並通過名稱將它們指向不同的策略,以此使PodSecurityPolicy資源中的策略對不同的用戶或組生效。通過ClusterRoleBinding資源將特定的用戶或組綁定到ClusterRole上,當PodSecurityPolicy訪問控制插件需要決定是否接納一個pod時,它只會考慮創建pod的用戶可以訪問到的PodSecurityPolicy中的策略。
可以在下面的練習中看到如何做到這些。首先,創建另一個PodSecurityPolicy。
創建一個允許部署特權容器的PodSecurityPolicy
首先,要創建一個特殊的PodSecurityPolicy,允許用戶創建擁有特權容器的pod。以下代碼清單展示了該PodSecurityPolicy的定義。
#代碼13.20 特權用戶使用的 PodSecurityPolicy: psp-privileged.yaml apiVersion: extensions/v1beta1 kind: PodSecurityPolicy metadata: name: privileged spec: privileged: true #予許創建特權容器 runAsUser: rule: RunAsAny fsGroup: rule: RunAsAny supplementalGroups: rule: RunAsAny seLinux: rule: RunAsAny volumes: - '*'
在向API服務器post這個PodSecurityPolicy之后,集群中有兩個策略:
S kubectl get psp
NAME PRIV CAPS SELINUX RUNASUSER FSGROUP
default false [] RunAsAny RunAsAny RunAsAny
privileged true [] RunAsAny RunAsAny RunAsAny
正如PRIV列中所示,default策略禁止運行特權容器,然而privileged策略是允許的。因為現在是以cluster-admin身份登錄的,所以可以看到所有的策略。部署pod時,如果任一策略允許使用pod中使用到的特性,API服務器就會接收這個pod。
現在考慮另外兩個使用該集群的用戶:Alice和Bob。希望Alice只能部署受限制的(非特權)pod,允許Bob部署特權pod。可以通過讓Alice只能使用default PodSecurityPolicy,而Bob可以使用以上兩個PodSecurityPolicy來做到。
使用RBAC將不同的PodSecurityPolicy分配給不同用戶
RBAC機制可以給用戶授予特定類型的資源的訪問權限,但RBAC機制也可以通過使用引用其名字來授予對特定資源實例的訪問權限。這就是為了讓不同用戶使用不同PodSecurityPolicy的方法。
首先需要創建兩個ClusterRole,分別允許使用其中一個策略。將第一個ClusterRole命名為psp-default並允許其使用default PodSecurityPolicy資源。可以使用 kubectl create clusterrole來操作:
$ kubectl create clusterrole psp-default --verb=use --resource=podsecuritypolicies --resource-name=default clusterrole "psp-default" created
注意:使用的動詞是use,而非get、list、watch或類似的動詞。
通過--resource-name選項引用了一個PodSecurityPolicy資源的特定實例。現在,創建另一個名為psp-privileged ClusterRole,指向privileged策略:
$ kubectl create clusterrole psp-privileged --verb=use --resource=podsecuritypolicies --resource-name=privileged clusterrole "psp-privileged" created
現在,需要把這兩個策略綁定到用戶上。綁定一個ClusterRole資源以授予對集群級別資源(PodSecurityPolicy資源就是集群級別的資源)的訪問權限,需要使用ClusterRoleBinding資源而非(有命名空間的)RoleBinding。
要將psp-default ClusterRole綁定到所有己認證用戶上,而非只有Alice。這是必需的,否則沒有用戶可以創建pod,因為PodSecurityPolicy訪問控制插件會因為沒有找到任何策略而拒絕創建pod。所有己認證用戶都屬於system: authenticated組,因此需要將該ClusterRole綁定到這個組:
$ kubectl create clusterrolebinding psp-all-users --clusterrole=psp-default --group=system:authenticated clusterrolebinding "psp-all-users" created
接着,需要將psp-privileged ClusterRole綁定到用戶Bob:
$ kubectl create clusterrolebinding psp-bob --clusterrole=psp-privileged --user=bob clusterrolebinding "psp-bob" created
作為一個己認證用戶,Alice現在擁有使用default PodSecurityPolicy的權限, 然而Bob擁有使用default和privileged PodSecurityPolicy的權限。Alice不能創建特權pod,而Bob可以。接下來看看是否確實如此。
為kubectl創建不同用戶
如何以Alice或Bob的身份通過認證,而非現在己經認證的用戶?這里不做詳細解釋這里只展示允許以Alice或Bob的身份使用kubectl的命令。
首先,需要使用如下命令,用kubectl的config子命令創建兩個新用戶:
$ kubectl config set-credentials alice --username=alice --password=password User "alice" set. $ kubectl config set-credentials bob --username=bob --password=password User "bob" set.
這些命令的行為應當很明顯。因為使用了用戶名和密碼作為憑據,kubectl將對這兩個用戶使用基礎HTTP認證進行認證(其他的認證方法包括token、客戶端證書等)。
使用不同用戶創建pod
現在,可以嘗試以Alice的身份認證,並創建一個特權pod。可以通過--user選項向kubectl傳達使用的用戶憑據:
$ kubectl --user alice create -f pod-privileged.yaml Error from server (Forbidden): error when creating "pod-privileged.yaml": pods "pod-privileged" is forbidden: unable to validate against any pod security policy: [spec.containers[0].securityContext.privileged: Invalid value: true: Privileged containers are not allowed]
與預期相同,API服務器不允許Alice創建特權pod。現在看一下Bob是否允許:
$ kubectl --user bob create -f pod-privileged.yaml pod "pod-privileged" created
現在成功地使用了RBAC,讓訪問控制插件對不同用戶使用不同的PodSecurityPolicy資源。
4.小結
1.PodSecurityPolicy資源可以通過RBAC中的ClusterRole和ClusterRoleBinding與特定用戶關聯。
2.集群級別的PodSecurity Policy資源可以用來防止用戶創建可能危及宿主節點的pod