Kubernetes過一系列機制來實現集群的安全機制,包括API Server的認證授權、准入控制機制及保護敏感信息的Secret機制等。集群的安全性必須考慮以下的幾個目標:
- 保證容器與其所在宿主機的隔離;
- 限制容器給基礎設施及其他容器帶來消極影響的能力;
- 最小權限原則,合理限制所有組件權限,確保組件只執行它被授權的行為,通過限制單個組件的能力來限制他所能達到的權限范圍;
- 明確組件間邊界的划分;
- 划分普通用戶和管理員角色;
- 在必要的時候允許將管理員權限賦給普通用戶;
- 允許擁有Secret數據(Keys、Certs、Passwords)的應用在集群中運行;
下面分別從Authentication、Authorization、Admission Control、Secret和Service Account等方面來說明集群的安全機制。
API Server認證
Kubernetes集群中所有資源的訪問和變更都是通過Kubernetes API Server的REST API來實現的,所以集群安全的關鍵點在於識別認證客戶端身份(Authentication)以及訪問權限的授權(Authorization)。
Kubernetes提供管理三種級別的客戶端身份認證方式:
- 最嚴格的HTTPS證書認證:基於CA根證書簽名的雙向數字證書認證方式;
- HTTP Token認證:通過一個Token來識別合法用戶;
- HTTP Base認證:通過用戶名+密碼的方式認證;
SSL雙向認證步驟:
- HTTPS通信雙方的務器端向CA機構申請證書,CA機構是可信的第三方機構,它可以是一個公認的權威的企業,也可以是企業自身。企業內部系統一般都使用企業自身的認證系統。CA機構下發根證書、服務端證書及私鑰給申請者;
- HTTPS通信雙方的客戶端向CA機構申請證書,CA機構下發根證書、客戶端證書及私鑰個申請者;
- 客戶端向服務器端發起請求,服務端下發服務端證書給客戶端。客戶端接收到證書后,通過私鑰解密證書,並利用服務器端證書中的公鑰認證證書信息比較證書里的消息,例如域名和公鑰與服務器剛剛發送的相關消息是否一致,如果一致,則客戶端認為這個服務器的合法身份;
- 客戶端發送客戶端證書給服務器端,服務端接收到證書后,通過私鑰解密證書,獲得客戶端的證書公鑰,並用該公鑰認證證書信息,確認客戶端是否合法;
- 客戶端通過隨機秘鑰加密信息,並發送加密后的信息給服務端。服務器端和客戶端協商好加密方案后,客戶端會產生一個隨機的秘鑰,客戶端通過協商好的加密方案,加密該隨機秘鑰,並發送該隨機秘鑰到服務器端。服務器端接收這個秘鑰后,雙方通信的所有內容都都通過該隨機秘鑰加密;
CA認證流程圖:
上述是雙向SSL協議的具體通信過程,這種情況要求服務器和用戶雙方都有證書。單向認證SSL協議不需要客戶擁有CA證書,對應上面的步驟,只需將服務器端驗證客戶端證書的過程去掉,以及在協商對稱密碼方案和對稱通話秘鑰時,服務器端發送給客戶端的是沒有加過密的(這並不影響SSL過程的安全性)密碼方案。
HTTP Token原理:HTTP Token的認證是用一個很長的特殊編碼方式的並且難以被模仿的字符串——Token來表明客戶身份的一種方式。在通常情況下,Token是一個復雜的字符串,比如我們用私鑰簽名一個字符串的數據就可以作為一個Token,此外每個Token對應一個用戶名,存儲在API Server能訪問的一個文件中。當客戶端發起API調用請求時,需要在HTTP Header里放入Token,這樣一來API Server就能夠識別合法用戶和非法用戶了。
HTTP Base:常見的客戶端賬號登錄程序,這種認證方式是把“用戶名+冒號+密碼”用BASE64算法進行編碼后的字符串放在HTTP REQUEST中的Header Authorization域里發送給服務端,服務端收到后進行解碼,獲取用戶名及密碼,然后進行用戶身份的鑒權過程。
API Server授權
對合法用戶進行授權(Authorization)並且隨后在用戶訪問時進行鑒權,是權限與安全系統的重要一環。授權就是授予不同用戶不同訪問權限,API Server目前支持一下集中授權策略:
- AlwaysDeny:拒絕所有請求,該配置一般用於測試;
- AlwaysAllow:接收所有請求,如果集群不需要授權流程,可以采用該策略,此為Kubernetes默認的策略;
- ABAC:(Attribute-Base Access Control)為基於屬性的訪問控制,表示使用用戶配置的授權規則去匹配用戶的請求;
為了簡化授權的復雜度,對於ABAC模式的授權策略,Kubernetes僅有下面四個基本屬性:
- 用戶名(代表一個已經被認證的用戶的字符型用戶名)
- 是否是只讀請求(REST的GET操作是只讀的)
- 被訪問的是哪一類資源,例如Pod資源/api/v1/namespaces/default/pods
- 被訪問對象所屬的Namespace
當API Server啟用ABAC模式時,需要指定授權文件的路徑和名字(--authorization_policy_file=SOME_FILENAME),授權策略文件里的每一行都是一個Map類型的JOSN對象,被稱為訪問策略對象,我們可以通過設置“訪問策略對象”中的如下屬性來確定具體的授權行為:
- user:字符串類型,來源於Token文件或基本認證文件中的用戶名字段的值;
- readonly:true時表示該策略允許GET請求通過;
- resource:來自於URL的資源,例如“Pod”;
- namespace:表明該策略允許訪問某個namespace的資源;
eg:
- {"user":"alice"}
- {"user":"kubelet","resource":"Pods","readonly":true}
- {"user":"kubelet","resource":"events"}
- {"user":"bob","resource":"Pods","readonly":true,"ns":"myNamespace"}
Admission Control准入控制
通過認證和鑒權之后,客戶端並不能得到API Server的真正響應,這個請求還需通過Admission Control所控制的一個“准入控制鏈”的層層考驗,Admission Control配備有一個“准入控制器”的列表,發送給API Server的任何請求都需要通過列表中每個准入控制器的檢查,檢查不通過API Server拒絕此調用請求。此外,准入控制器還能夠修改請求參數以完成一些自動化的任務。比如Service Account這個控制器,當前可配置的准入控制如下:
- AlwaysAdmit:允許所有請求;
- AlwaysPullmages:在啟動容器之前總去下載鏡像,相當於在每個容器的配置項imagePullPolicy=Always
- AlwaysDeny:禁止所有請求,一般用於測試;
- DenyExecOnPrivileged:它會攔截所有想在Privileged Container上執行命令的請求,如果你的集群支持Privileged Container,你又希望限制用戶在這些Privileged Container上執行命令,強烈推薦你使用它;
- Service Account:這個plug-in將ServiceAccount實現了自動化,默認啟用,如果你想使用ServiceAccount對象,那么強烈你推薦使用它;
- SecurityContextDeny:這個插件將使用SecurityContext的Pod中的定義全部失效。SecurityContext在Container中定義了操作系統級別的安全設定(uid,gid,capabilityes,SELinux等)
- ResourceQuota:用於配額管理目的,作用於namespace上,它會觀察所有請求,確保在namespace上的配額不會超標。推薦在Admission Control參數列表中這個插件排最后一個;
- LimitRanger:用於配額管理,作用於Pod與Container,確保Pod與Container上的配額不會超標;
- NamespaceExists(已過時):對所有請求校驗namespace是否已存在,如果不存在則拒絕請求,已合並至NamespaceLifecycle。
- NamespaceAutoProvision(已過時):對所有請求校驗namespace,如果不存在則自動創建該namespace,推薦使用NamespaceLifecycle。
- NamespaceLifecycle:如果嘗試在一個不存在的namespace中創建資源對象,則該創建請求將被拒絕。當刪除一個namespace時,系統將會刪除該namespace中所有對象,保存Pod,Service等。
在API Server上設置--admission-control參數,即可定制我們需要的准入控制鏈,如果啟用多種准入控制選項,則建議的設置如下:
- --admission-control=NamespaceLifecycle,LimitRanger,SecurityContextDeny,ServiceAccount,ResourceQuota
下面着重介紹三個准入控制器:
SecurityContextDeny
Security Context時運用於容器的操作系統安全設置(uid、gid、capabilities、SELinux role等),Admission Control的SecurityContextDeny插件的作用是,禁止創建設置了Security Context的Pod,例如包含以下配置項的Pod:
- spec.containers.securityContext.seLinuxOptions
- spec.containers.securityContext.runAsUser
ResourceQuota
ResourceQuota不僅能夠限制某個Namespace中創建資源的數量,而且能夠限制某個namespace中被Pod所請求的資源總量。該准入控制器和資源對象ResourceQuota一起實現了資源的配額管理;
LimitRanger
准入控制器LimitRanger的作用類似於上面的ResourceQuota控制器,這對Namespace資源的每個個體的資源配額。該插件和資源對象LimitRange一起實現資源限制管理。
Service Account
Servuce Account是一種賬號,但他並不是給Kubernetes的集群的用戶(系統管理員、運維人員、租戶用戶等),而是給運行在Pod里的進程用的,它為Pod里的進程提供必要的身份證明。
Pod中訪問Kubernetes API Server服務的時候,是以Service方式訪問服務名為kubernetes這個服務的,而kubernetes服務又只在HTTPS安全端口443上提供服務,那么如何進行身份認證呢?在Kubernetes的官方文檔並沒有清除的說明這個問題。
通過查看源碼獲知這是在用一種類似HTTP Token的新的認證方式--ServiceAccount Auht,Pod中的客戶端調用Kubernetes API的時候,在HTTP Header中傳遞了一個Token字符串,這類似於之前提到的HTTP Token認證方式,存在以下幾個不同點:
- 此處的Token的內容來源於Pod里指定路徑下的一個文件(/run/secrets/kubernetes.io/serviceaccount/token),這種token是動態生成的,確切的說,是由KubernetesController進程用API Server的私鑰(--service-account-private-key-file指定的私鑰)簽名生成的一個JWT Secret。
- 官方提供的客戶端REST框架代碼里,通過HTTPS方式與API Server建立鏈接后,會用Pod里指定路徑下的一個CA證書(/run/secrets/kubernetes.io/serviceaccount/ca.crt)驗證API Server發來的證書,驗證是否是被CA證書簽名的合法證書。
- API Server收到這個Token以后,采用自己的私鑰(實際是使用參數service-account-key-file)指定的私鑰,如果此參數沒有設置,則默認采用tls-private-key-file指定的參數,即自己的私鑰,對token進行合法性驗證。
明白原理之后。接下來分析認證過程中涉及的Pod中的三個文件:
- /run/secrets/kubernetes.io/serviceaccount/token
- /run/secrets/kubernetes.io/serviceaccount/ca.crt
- /run/secrets/kubernetes.io/serviceaccount/namespace(客戶端采用這里指定的namespace作為參數調用Kubernetes API)
這三個文件由於參與到Pod進程與API Server認證的過程中,起到了類似Secret(私密憑據)的作用,所以他們被稱為Kubernetes Secret對象。Secret從屬於ServiceAccount資源對象,屬於Service Account的一部分,一個ServiceAccount對象里面可以包括多個不同的Secret對象,分別用於不同目的的認證活動。
下面通過命令來直觀的加深對ServiceAccount的認識:
查看系統中ServiceAccount對象,可以看到一個名為default的Service Account對象,包含一個名為default-token-xxx的Secret,這個Secret同時是“Mountable secrets”,表明他是需要被Mount到Pod上的。
- kubectl describe serviceaccounts
- kubectl describe secrets default-token-xxx
default-token-xxx包括三個數據項:
- token
- ca.crt
- namespace
聯想到“Mountable secrets”的標記,以及之前看到的Pod中的三個文件的文件名:每個namespace下有一個名為default的默認的ServiceAccount對象,這個ServiceAccount里有一個名為Tokens的可以作為Volume一樣被Mount到Pod里的Secret,當Pod啟動時這個Secret會被自動Mount到Pod的指定目錄下,用來協助完成Pod中的進程訪問API Server時的身份鑒權過程。
一個ServiceAccount可以包括多個Secrets對象:
- 名為Tokens的Secret用於訪問API Server的Secret,也被稱為ServiceAccountSecret;
- 名為Image Pull secrets的Secret用於下載容器鏡像時的認證過程,通常鏡像庫運行在Insecure模式下,所以這個Secret為空;
- 用戶自定義的其他Secret,用於用戶的進程;
如果一個Pod在定義時沒有指定spec.service.AccountName屬性,則系統會自動為其賦值為“Default”,即使用同一namespace下默認的ServiceAccount,如果某個Pod需要使用非default的ServiceAccount,需要在定義時指定:
apiVersion:v1
kind:Pod
metadata:
name:mypod
spec:
containers:
- name:mycontainer
image:
serviceAccountName:myserviceaccount
Kubernetes之所以要創建兩套獨一的賬號系統,原因如下:
- User賬號是給人用的,ServiceAccount是給Pod里的進程使用的,面向對象不同;
- User賬號是全局性的,ServiceAccount則屬於某個具體的Namespace;
- 通常來說,User賬號是與后端的用戶數據庫同步的,創建一個新用戶通常要走一套復雜的業務流程才能實現,ServiceAccount的創建則需要極輕量級實現方式,集群管理員可以很容易為某些特定任務組創建一個ServiceAccount。
- 對於這兩種不同的賬戶,其審計要求通常不同;
- 對於一個復雜的系統來說,多個組件通常擁有各種賬號的配置信息,ServiceAccount是Namespace隔離的,可以針對組件進行一對一的定義,同時具備很好的“便攜性”。
下面分析Service Account與Secret相關的一些運行機制:
Controller manager創建了ServiceAccountController與Token Controllerl兩個安全相關的控制器。其中ServiceAccountController一直監聽Service Account和Namespace的事件,如果一個Namespace中沒有default Service Account,那么Service Account Controller就會為該Namespace創建一個默認的(default)的Service Account,這就是我們之前看到的每個namespace下都有一個名為default的ServiceAccount的原因。
如果Controller manager進程在啟動時指定了API Server私鑰(service-account-private-key-file)參數,那么Controller manager會創建Token Controller。Token Controller也監聽Service Account的事件,如果發現新建的Service Account里沒有對應的Service Account Secret,則會用API Server私鑰創建一個Token(JWT Token),並用該Token、CA證書Namespace名稱等三個信息產生一個新的Secret對象,然后放入剛才的Service Account中;如果監聽到的事件是刪除Service Account事件,則自動刪除與該Service Account相關的所有Secret。此外,Token Controller對象同時監聽Secret的創建、修改和刪除事件,並根據事件的不同做不同的處理。
當我們在API Server的鑒權過程中啟用了Service Account類型的准入控制器,即在kube-apiserver的啟動參數中包括下面的內容時:
- --admission_control=ServiceAccount
則針對Pod新增或修改的請求,Service Account准入控制器會驗證Pod里Service Account是否合法。
- 如果spec.serviceAccount域沒有被設置,則Kubernetes默認為其制定名字為default的Serviceaccount;
- 如果Pod的spec.serviceAccount域指定了default以外的ServiceAccount,而該ServiceAccount沒有事先被創建,則該Pod操作失敗;
- 如果在Pod中沒有指定“ImagePullSecrets”,那么該sec.serviceAccount域指定的ServiceAccount的“ImagePullSecrets”會被加入該Pod;
- 給Pod添加一個新的Volume,在該Volume中包含ServiceAccountSecret中的Token,並將Volume掛載到Pod中所有容器的指定目錄下(/var/run/secrets/kubernetes.io/serviceaccount);
綜上所述,ServiceAccount正常運行需要以下幾個控制器:
- Admission Controller
- Token Controller
- Service Account Controller
Secret私密憑據
Secret主要作用是保管私密數據,比如密碼、OAuth Tokens、SSH Keys等信息。將這些私密信息放在Secret對象中比直接放在Pod或Docker Image中要更安全,也便於使用和分發。
- 創建Secret
secret.yaml
apiVersion:v1
kind:Secret
metadata:
name:mysecret
type: Opaque
data:
password:dmfsdWUtMg0k
username:dmfsdWUtMg0k
kubectl create -f secret.yaml
在上面的data域中的各子域的值必須為BASE64編碼值,其中password域和username域BASE64編碼前的值分別為value-1和value-2。一旦secret被創建,可以通過以下三個方式使用它:
- 在創建Pod時,通過為Pod指定ServiceAccount來自動使用該Seret;
- 通過掛載該Secret到Pod來使用它;
- Docker鏡像下載時使用,通過指定Pod的spec.ImagePullSecrets來引用它;
- 第一種方式主要用在API Server鑒權方面;
- 第二種方式如下:
apiVersion:v1
kind:Pod
metadata:
name:mypod
namespace:myns
spec:
containers:
- name:mycontainer
image:redis
volumeMounts:
- name:foo
mountPath:“/etc/foo”
readOnly:true
volumes:
- name:foo
secret:
secretName:mysecret
- 第三種方式如下:
- 執行login登錄私有registry
- docker login localhost:5000(輸入賬戶及密碼,則會創建新用戶,並把相關信息寫入~/。dockercfg文件中)
- 用BASE64編碼dockercfg的內容;
- cat ~/.dockercfg|grep base64
- 將上一步命令的輸出結果作為secret的“data.dockercfg”域的內容,由此來創建一個Secret:
image-pull-secret.yaml:
apiVersion:v1
kind:Secret
metadata:
name:myregistrykey
data:
.dockercfg:xxx
type:kubernetes.io/dockercfg
- kubectl create -f image-pull-secret.yaml
- 在創建Pod的時候引用該Secret:
pods.yaml
apiVersion:v1
kind:Pod
metadata:
name:mypod2
spec:
containers:
- name:foo
image:xxxxxx:v1
imagePullSecrets:
- name:myregistrykey
- kubectl create -f pods.yaml
每個單獨的Secret大小不能超過1M,Kubernetes不鼓勵創建大尺寸的Secret,因為如果使用大尺寸的Secret,則將大量占用API Server和kubelet的內存。當然創建許多小的Secret也能耗盡API Server和kubelet的內存。
在使用Mount方式掛載Secret時,Container中Secret的“data”域的各個域的key值作為目錄中的文件,Value值被BASE64編碼后存儲在相應的文件中。前面的例子中創建的Secret,被掛載到一個叫做mycontainer的container中,在該container中可以通過命令查看所生產的文件和文件中的內容:
- ls /etc/foo
username
password
- cat /etc/foo/username
value-1
- cat /etc/foo/password
value-2
我們可以通過Secret保管其他系統的敏感信息(比如數據庫用戶名和密碼),並以Mount的方式將Secret掛載到Container中,然后通過訪問目錄中的文件的方式獲取該敏感信息。當Pod被API Server創建時,API Server不會校驗該Pod引用的Secret是否存在。一旦這個Pod被調度,則Kubelet將試着獲取Secret的值。如果Secret不存在或暫時無法連接到API Server,則kubelet將按一定的時間間隔定期重試獲取該Secret,並發送一個Event來解釋Pod沒有啟動的原因。一旦Secret被Pod獲取,則Kubelet將創建並Mount包含Secret的Volume。只有所有的Volume被Mount后,Pod中的Container才會被啟動。在kubelet啟動Pod中container后,Container中和Secret相關的Volume將不會被改變,即使Secret本身被修改了。為了使用更新后的Secret,必須刪除舊的Pod,並重新創建一個新的Pod,因此更新Secret的流程和部署一個新的Image是一樣的。