Kubernetes-深刻分析集羣安全機制

Kubernetes過一系列機制來實現集羣的安全機制,包括API Server的認證受權、准入控制機制及保護敏感信息的Secret機制等。集羣的安全性必須考慮如下的幾個目標:redis

  1. 保證容器與其所在宿主機的隔離;
  2. 限制容器給基礎設施及其餘容器帶來消極影響的能力;
  3. 最小權限原則,合理限制全部組件權限,確保組件只執行它被受權的行爲,經過限制單個組件的能力來限制他所能達到的權限範圍;
  4. 明確組件間邊界的劃分;
  5. 劃分普通用戶和管理員角色;
  6. 在必要的時候容許將管理員權限賦給普通用戶;
  7. 容許擁有Secret數據(Keys、Certs、Passwords)的應用在集羣中運行;

下面分別從Authentication、Authorization、Admission Control、Secret和Service Account等方面來講明集羣的安全機制。算法

API Server認證

Kubernetes集羣中全部資源的訪問和變動都是經過Kubernetes API Server的REST API來實現的,因此集羣安全的關鍵點在於識別認證客戶端身份(Authentication)以及訪問權限的受權(Authorization)。docker

Kubernetes提供管理三種級別的客戶端身份認證方式:數據庫

  1. 最嚴格的HTTPS證書認證:基於CA根證書籤名的雙向數字證書認證方式;
  2. HTTP Token認證:經過一個Token來識別合法用戶;
  3. HTTP Base認證:經過用戶名+密碼的方式認證;

SSL雙向認證步驟:後端

  1. HTTPS通訊雙方的務器端向CA機構申請證書,CA機構是可信的第三方機構,它能夠是一個公認的權威的企業,也能夠是企業自身。企業內部系統通常都使用企業自身的認證系統。CA機構下發根證書、服務端證書及私鑰給申請者;
  2. HTTPS通訊雙方的客戶端向CA機構申請證書,CA機構下發根證書、客戶端證書及私鑰個申請者;
  3. 客戶端向服務器端發起請求,服務端下發服務端證書給客戶端。客戶端接收到證書後,經過私鑰解密證書,並利用服務器端證書中的公鑰認證證書信息比較證書裏的消息,例如域名和公鑰與服務器剛剛發送的相關消息是否一致,若是一致,則客戶端認爲這個服務器的合法身份;
  4. 客戶端發送客戶端證書給服務器端,服務端接收到證書後,經過私鑰解密證書,得到客戶端的證書公鑰,並用該公鑰認證證書信息,確認客戶端是否合法;
  5. 客戶端經過隨機祕鑰加密信息,併發送加密後的信息給服務端。服務器端和客戶端協商好加密方案後,客戶端會產生一個隨機的祕鑰,客戶端經過協商好的加密方案,加密該隨機祕鑰,併發送該隨機祕鑰到服務器端。服務器端接收這個祕鑰後,雙方通訊的全部內容都都經過該隨機祕鑰加密;

CA認證流程圖:api

上述是雙向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僅有下面四個基本屬性:

  1. 用戶名(表明一個已經被認證的用戶的字符型用戶名)
  2. 是不是隻讀請求(REST的GET操做是隻讀的)
  3. 被訪問的是哪一類資源,例如Pod資源/api/v1/namespaces/default/pods
  4. 被訪問對象所屬的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是否合法。

  1. 若是spec.serviceAccount域沒有被設置,則Kubernetes默認爲其制定名字爲default的Serviceaccount;
  2. 若是Pod的spec.serviceAccount域指定了default之外的ServiceAccount,而該ServiceAccount沒有事先被建立,則該Pod操做失敗;
  3. 若是在Pod中沒有指定「ImagePullSecrets」,那麼該sec.serviceAccount域指定的ServiceAccount的「ImagePullSecrets」會被加入該Pod;
  4. 給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是同樣的。

相關文章
相關標籤/搜索