Kubernetes集群安全概述
轉自Kubernetes集群安全概述 - 我是程序員 - 博客園 (cnblogs.com)
API的訪問安全性
API Server的端口和地址
在默認情況下,API Server通過本地端口和安全端口兩個不同的HTTP端口,對外提供API服務,其中本地端口是基于HTTP協議的,用于在本機(API Server所在主機)無限制的訪問API Server,而安全端口則是基于HTTPS協議的,用于遠程有限制的訪問API Server,下面就這兩種端口做詳細的介紹。
本地端口(Localhost Port)
在API Server的默認配置中,本地端口默認綁定到地址127.0.0.1上,所以,在默認情況下,本地端口只能在本機(API Server所在主機)訪問,由于API Server不對任何通過本地端口的訪問做任何權限控制(通過本地端口的訪問繞過了認證和授權過程),換句話說,只要能夠訪問本地端口,任何人都可以通過本地端口無限制的訪問API Server,基于安全性方面的考慮,在配置API Server時,盡量不要將本地端口綁定到地址127.0.0.1以外的地址上,以避免將本地端口暴露到本機以外。
本地端口默認綁定到端口8080上,本地端口的綁定端口號可以通過API Server的啟動參數--insecure-port來進行指定,如果將綁定的端口號指定為0,則表示關閉本地端口。另外,可以通過API Server的啟動參數--insecure-bind-address來指定本地端口的綁定地址。
注意:在生產環境中盡量避免將本地端口綁定到127.0.0.1以外的地址,以避免帶來不必要的安全問題。
安全端口(Secure Port)
顧名思義,安全端口是API Server對外提供的,用于外部訪問的、安全的、可控的API調用接口,API Server只允許通過認證(Authentication)的用戶才能夠通過安全端口訪問API Server,無論是認證為User Account或者Service Account都可以正常的通過安全端口訪問API Server,但是對于尚未認證的匿名用戶,當通過安全端口訪問API Server時,服務端總是返回401(Unauthorized),拒絕其后續訪問。
安全端口默認綁定到端口6443上,并且總是通過HTTPS協議對外提供服務。安全端口綁定的端口號可以通過API Server的啟動參數--secure-port來進行指定,和本地端口的配置類似,如果將安全端口綁定的端口號指定為0,則表示關閉安全端口。安全端口默認綁定到地址0.0.0.0上(理論上應該是0.0.0.0/32,表示本機的所有源地址),當然也可以通過API Server的啟動參數--bind-address來進行顯示指定。
由于安全端口是基于HTTPS協議對外提供服務的,當未顯示指定HTTPS證書和私鑰的情況下,API Server會自動在主機路徑/var/run/kubernetes下生成用于HTTPS的自簽名證書和私鑰(版本1.2的Kubernetes生成的自簽名證書和私鑰文件分別為:apiserver.crt和apiserver.key),當然,如果希望使用指定的證書和私鑰,則可以通過API Server的啟動參數--tls-cert-file和--tls-private-key-file來分別指定。
代理和防火墻
在實際的生產環境中,可能存在現有的認證體系無法與Kubernetes集群集成或者需要執行特殊認證和授權邏輯的情況,在這種情況下,可以考慮引入代理(Proxy)來解決認證和授權的問題,在認證通過之后,代理將請求轉發到API Server。根據,代理能否與API Server部署在同一臺主機的不同,需要分為以下兩種情況進行分別討論。
代理與API Server能夠部署在同一臺主機
當代理能夠與API Server部署在同一臺主機時,建議按照下面的方式進行集成:
關閉安全端口(將安全端口綁定到端口號0),確保所有的API請求只能通過代理接入
將本地端口綁定到地址127.0.0.1上,確保只能在本機訪問
設置防火墻規則,僅開放本機的443端口
配置nginx監聽443端口,并且在此端口上配置認證和HTTPS
配置nginx將請求轉發到本地端口,默認情況下為127.0.0.1:8080
代理與API Server無法部署在同一臺主機
當代理無法與API Server部署在同一臺主機時,從安全性的角度來看,再試圖通過本地端口與API Server來集成將不會是一個好的選擇,在這種情況下,使用安全端口與API Server集成成立唯一的選擇。由于安全接口的認證和授權體系比較復雜,具體的集成方式在后續的內容中進行深入的討論。
賬號類型賬號
Kubernetes根據使用賬號的進程是否在Pod內部運行這個標準將賬號劃分為用于提供給外部進程使用的用戶賬號(User Accounts)和用于提供給內部進程使用的服務賬號(Service Accounts),這兩種不同的賬號類型。
當進程在Pod內部運行時,一般建議該進程使用服務賬號來訪問API Server,而當進程運行在Pod之外甚至在Kubernetes集群之外時,則建議該進程使用用戶賬號來訪問API Server;當然,這個標準并不是絕對的,例如,當Pod內部運行的進程使用用戶賬號來訪問API Server時,并不會被API Server視為不合法而拒絕訪問。
用戶賬號(User Accounts)
用戶賬號是一個非常傳統的概念,可以簡單的理解為用戶名和密碼,當調用方通過API Server提供的認證接口傳入用戶名、密碼通過認證之后,調用方就扮演了這個用戶與API Server進行交互。與一般的用戶名的概念相同,在Kubernetes中,用戶名在一個集群中是全局唯一的,也就是說在同一個集群中,只允許有一個指定名稱的用戶賬號,而與集群中創建了多少個命名空間(Namespace)或者啟動了多少個API Server無關。
此外,用戶賬號可以從數據庫等第三方系統同步到進來,以實現與其它系統共享用戶賬號信息。
服務賬號(Service Accounts)
從外在的表現來看,服務賬號與用戶賬號的最大的不同點,表現在服務賬號是命名空間唯一,而用戶賬號是整個集群唯一。在Kubernetes中,每一個命名空間都可以創建具有相同名稱的服務賬號,在默認情況下,每一個namespace在創建時,都會自動創建一個名為default的默認服務賬號,如果在API Server開啟了ServiceAccount插件的情況下(通過API Server的–admission-control啟動參數指定),該默認服務賬號會在Pod創建或者更新時,被自動的關聯到該Pod上,并且自動的將默認服務賬號的憑證(Token)部署到Pod中所有容器文件系統的目錄/var/run/secrets/kubernetes.io/serviceaccount/下。
從實質上來看,服務賬號與用戶賬號并沒有本質上的不同,可以認為每一個服務賬號的背后都自動關聯了一個隱藏的用戶賬號,就以以默認服務賬號為例,假設在默認命名空間下有一個默認服務賬號(default),那么當某一個進程使用這個服務賬號訪問API Server時,可以簡單理解為使用名為system:serviceaccount:default:default的用戶賬號來訪問API Server,所以希望控制某一個服務賬號的權限時,就可以簡單的通過對名為system:serviceaccount:<命名空間>:<服務賬號名稱>的隱藏用戶賬號進行權限控制就可以達到目的。
TODO 實驗服務賬號是否可以采用與用戶賬號相同的認證方式
認證(Authentication)
1.2版本的Kubernetes,提供了客戶端證書認證、Token認證、OpenID認證、HTTP基本認證以及Keystone認證等五種不同的認證方式,下面將會就這些認證方式進行詳細的介紹。
需要注意的是,這五種認證方式之間不是互斥的,同一個API Server允許同時開啟一種或者多種不同的認證方式,并不會存在開啟客戶端證書認證而Token認證自動失效的情況,在開啟多種認證的情況下,客戶端可以自由的選擇合適的認證方式來進行認證。
例如,假設服務器同時開啟了客戶端證書認證和Token認證,客戶端可以僅僅傳入合法的Token訪問,也可以傳入合法的證書私鑰對訪問,也可以同時傳入Token和證書私鑰對進行訪問,當客戶端同時使用多種認證方式同時認證時,只要一種認證方式通過認證,就可以繼續訪問,如果所有的認證方式都無法通過認證,則服務端會拒絕客戶端繼續訪問。
TODO確認認證的優先級別
客戶端證書認證(Client certificate authentication)
客戶端認證的開啟非常的簡單,只需要通過API Server的啟動參數--client-ca-file指定用于客戶端認證的證書文件即可(注意:證書文件中可以包含一個或者多個證書)。
當客戶端通過客戶端證書認證后,用于認證證書的公用名(Common name of the subject)將作為用于后續訪問的用戶名。所以,當希望對于客戶端證書認證用戶進行權限控制時,對名為證書公用名的用戶進行授權就是對客戶端證書認證用戶進行授權。
以下就以自簽名證書為例,演示如何配置API Server的客戶端證書認證:
創建自簽名證書
可以使用如下的命令創建一個用于客戶端認證的證書
openssl req
-new
-nodes \
-x509
-subj "/C=CN/ST=GuangDong/L=ShenZhen/O=HuaWei/OU=PaaS/CN=batman"
-days 3650
-keyout 私鑰.key
-out 證書.crt
說明:
/C 表示國家只能為兩個字母的國家縮寫,例如CN,US等
/ST 表示州或者省份
/L 表示城市或者地區
/O 表示組織機構名稱
/OU 表示組織機構內的部門或者項目名稱
/CN 表示公用名,如果用來作為SSL證書則應該填入域名或者子域名,
如果作為客戶端認證證書則可以填入期望的用戶名
為API Server指定要應用的客戶端認證證書 將上一步創建的證書文件拷貝到API Server所在的主機,然后通過啟動參數--client-ca-file將證書文件的路徑傳遞給API Server。
驗證客戶端認證證書
可以使用如下命令來驗證客戶端認證是否起效:
kubectl
--server=https://192.168.0.1:6443
--insecure-skip-tls-verify=true
--client-certificate=證書.crt
--client-key=私鑰.key
get nodes
說明:
--server 用來指定API Server的地址,注意一定要使用安全端口
--insecure-skip-tls-verify 表示不驗證證書,當服務端證書為自簽名證書時指定
--client-certificate 指定客戶端認證證書
--client-key 指定客戶端認證證書的私鑰
Token認證(Token File)
Token認證的開啟也同樣非常簡單,只需要通過API Server的啟動參數--token-auth-file指定包含Token信息的Token文件即可。Token文件是一個3到4列的csv文件,這個csv文件中,從左到右分別為Token、用戶名(User Name)、用戶UID(User UID)以及用戶所屬的組,其中前3列為必須列,用戶組列為可選列,如果用戶隸屬于多個組,則需要將所有的組名通過雙引號括起來:
token,user name,uid,"group1,group2,grooup3"
需要注意的是,Token認證沒有過期的概念,所有的Token理論上可以認為永不過期,另外,除非重啟API Server,否則無法更新或者刪除Token。
以下演示如何配置Token認證:
創建Token文件 我們通過手工創建內容如下的Token文件:
demo,demo,demo,demo
為API Server指定要應用的Token文件 將上一步創建的Token文件拷貝到API Server所在的主機,然后通過啟動參數--token-auth-file將Token文件的路徑傳遞給API Server。
驗證Token認證
可以使用如下命令來驗證Token認證是否起效:
kubectl
--server=https://192.168.0.1:6443
--insecure-skip-tls-verify=true
--token=demo
get nodes
或者
curl
-k
-H "Authorization: Bearer demo"
https://192.168.0.1:6443/api/v1/nodes
OpenID認證(OpenID Connect ID Token)
OpenID認證的開啟相對比較復雜,開啟OpenID認證需要設置如下幾項啟動參數:
–oidc-issuer-url(必須指定)
用于指定用于提供OpenID認證服務的服務地址。注意:服務地址必須為HTTPS的URL。
–oidc-client-id (必須指定) 用于指定 TODO
HTTP基本認證(HTTP Basic Authentication)
HTTP基本認證的開啟也同樣非常簡單,只需要通過API Server的啟動參數--basic-auth-file指定包含用戶信息的用戶配置文件即可。用戶配置文件是一個3列的csv文件,這個csv文件中,從左到右分別為Token、用戶名(User Name)、用戶ID(User ID):
password,user name,user id
需要注意的是,HTTP基本認證和Toke認證一樣沒有過期的概念,所有只有重啟API Server才能更新或者刪除用戶信息。此外,HTTP基本認證是作為便利性方面的考慮才加以支持的,在正式生產環境中應該優先考慮上述的幾種認證方式。
以下演示如何配置HTTP基本認證:
創建用戶配置文件 我們通過手工創建內容如下的用戶配置文件:
password,zhangsan,zhangsan
為API Server指定要應用的HTTP基本認證用戶配置文件 將上一步創建的用戶配置文件拷貝到API Server所在的主機,然后通過啟動參數--basic-auth-file將用戶配置文件的路徑傳遞給API Server。
驗證HTTP基本認證
可以使用如下命令來驗證HTTP基本認證是否起效:
kubectl
--server=https://192.168.0.1:6443
--insecure-skip-tls-verify=true
--username=zhangsan
--password=password
get nodes
或者
curl
-k
-u zhangsan:password
https://192.168.0.1:6443/api/v1/nodes
Keystone認證(Keystone Authentication)
Keystone認證的開啟非常簡單,只需要通過API Server的啟動參數--experimental-keystone-url指定Keystone服務提供的認證地址即可。由于目前版本的(版本1.2)Kubernetes對Keystone認證的支持還處于試驗狀態,在這里就不進行詳細的介紹了,詳細的信息可以參考Keystone官方文檔。
Kubeconfig文件
在測試環境中,Slave(Kubelet)一般通過本地端口與API Server集成,但是在正式生產環境中,基于安全性方面的考慮,一般都會選擇關閉API Server的本地端口或者只允許在API Server所在主機上訪問本地端口,在這種情況下Slave只能通過安全端口與API Server集成。
為了能夠通過安全端口與API Server集成,Kubelet提供了--client-certificate、-client-key、--username、--password以及--token等啟動參數來支持上述認證方式,通過這些啟動參數,Kubelet可以選擇一種當前API Server提供的認證方式通過安全端口與API Server集成。
雖然上述的方式能夠實現Kubelet與API Server的集成,但是配置上稍顯復雜,需要在Kubelet的啟動參數中指定很多的認證信息。為了簡化配置以及方便在多個集群之間進行切換,Kubelet支持一種名為kubeconfig的機制,可以將集群信息、認證信息等配置信息保存到一個或者多個YAML格式的配置文件中(默認配置文件的路徑為/var/lib/kubelet/kubeconfig),具體的信息可以參看Kubeconfig。 在配置合理的情況,可以不需要指定Kubelet的任何啟動參數,Kubelet就可以順利的加入到集群中。
以下為一個配置文件的示例:
apiVersion: v1
kind: Config
clusters:
集群配置信息,可以通過--cluster參數指定使用
- cluster:
api-version: v1
server:?https://192.168.0.150:6443
insecure-skip-tls-verify: true
name: local
contexts:
集群上下文配置信息,可以通過--context參數指定使用
- context:
表示加入到哪一個集群
cluster: local表示引用哪個用戶進行進行認證
user: kubelet
name: service-account-context
users:
配置用戶信息,用戶名可以通過--user啟動參數指定使用
- name: kubelet
user:以下認證任選一種
token: Token認證
username: HTTP基本認證用戶名
password: HTTP基本認證密碼
client-certificate: 客戶端認證證書
client-key: 客戶端認證私鑰
默認使用的上下文名稱
current-context: service-account-context
授權(Authorization)
在Kubernetes中,授權和認證是兩個相互相對獨立的過程,當客戶端通過安全端口訪問API Server時,API Server會對客戶端發起的請求進行認證,如果請求無法通過認證,哪怕后續的授權過程不對請求做任何限制(AlwaysAllow),該請求任然會被API Server拒絕,只有當請求通過認證之后,才會輪到授權插件來對請求進行權限校驗。
Kubernetes的授權是通過插件的方式來實現的,,目前Kubernetes內置提供了AlwaysDeny、AlwaysAllow、ABAC以及WebHook等四種不同的授權插件,用戶可以通過賦予API Server啟動參數--authorization-mode授權插件的名稱來指定希望啟用的授權模式,下面,就這些授權模式做進一步的詳解介紹。
AlwaysDeny
顧名思義,當API Server的授權模式設置為AlwaysDeny模式時,服務端將會拒絕任何對安全端口的請求,以前面介紹的Token認證的例子為例,當服務端的授權模式設置為AlwaysDeny時,再使用命令curl -k --H "Authorization: Bearer demo"?https://192.168.0.1:6443/api/v1/nodes?訪問服務端時,服務端總是返回Forbidden: "/api/v1/nodes",表示訪問被拒絕。
AlwaysDeny模式主要用于測試,當然也可以用來暫時停止集群的對外服務。
AlwaysAllow
與AlwaysDeny模式相反,當API Server的授權模式設置為AlwaysAllow模式時,只要通過認證,服務端將會接受任何對安全端口的請求,換句話說就是除了認證沒有任何權限限制。
當集群不需要授權時,則可以考慮將授權模式設置為AlwaysAllow模式,以降低配置的復雜性。
ABAC(基于屬性的訪問控制)
ABAC是英文Attribute-based access control的縮寫,ABAC的核心是根據請求的相關屬性,例如用戶屬性、資源屬性以及環境屬性等屬性,作為授權的基礎來進行訪問控制,以解決分布式系統的可信任關系的訪問控制問題。
基于身份的訪問控制(Identity-based access control)和基于角色的訪問控制(Role-based access control)都可以認為是ABAC的一個單屬性特例。
目前,Kubernetes主要根據請求的以下幾個屬性進行授權:
用戶名
用戶組
是否訪問資源
請求的地址
是否訪問雜項接口(Miscellaneous Endpoints)
對資源的請求動作類型(Request Verb)
對非資源的HTTP動作類型(HTTP Verb)
訪問的資源類型
訪問對象所屬的命名空間(Namespace)
訪問的API的所屬API組(API Grooup)
如果需要啟用ABAC授權模式,首先需要通過將API Server的啟動參數--authorization-mode設置為ABAC將授權模式設置為ABAC,然后通過API Server的啟動參數--authorization-policy-file將 ABAC的策略文件路徑傳遞給API Server。
ABAC的策略文件是一個one JSON object per line格式的文本文件,下面就是一個策略文件的例子:
{ "apiVersion": "abac.authorization.kubernetes.io/v1beta1","kind": "Policy","spec": { "user": "","nonResourcePath": "","readonly": true } }
{ "apiVersion": "abac.authorization.kubernetes.io/v1beta1","kind": "Policy","spec": { "user": "admin","namespace": "","resource": "","apiGroup": "" } }
{ "apiVersion": "abac.authorization.kubernetes.io/v1beta1","kind": "Policy","spec": { "user": "scheduler","namespace": "","resource": "pods","readonly": true } }
{ "apiVersion": "abac.authorization.kubernetes.io/v1beta1","kind": "Policy","spec": { "user": "scheduler","namespace": "","resource": "bindings" } }
{ "apiVersion": "abac.authorization.kubernetes.io/v1beta1","kind": "Policy","spec": { "user": "kubelet","namespace": "","resource": "pods","readonly": true } }
{ "apiVersion": "abac.authorization.kubernetes.io/v1beta1","kind": "Policy","spec": { "user": "kubelet","namespace": "","resource": "services","readonly": true } }
{ "apiVersion": "abac.authorization.kubernetes.io/v1beta1","kind": "Policy","spec": { "user": "kubelet","namespace": "","resource": "endpoints","readonly": true } }
{ "apiVersion": "abac.authorization.kubernetes.io/v1beta1","kind": "Policy","spec": { "user": "kubelet","namespace": "","resource": "events" } }
{ "apiVersion": "abac.authorization.kubernetes.io/v1beta1","kind": "Policy","spec": { "user": "alice","namespace": "projectCaribou","resource": "","apiGroup": "" } }
{ "apiVersion": "abac.authorization.kubernetes.io/v1beta1","kind": "Policy","spec": { "user": "bob","namespace": "projectCaribou","resource": "","apiGroup": "*","readonly": true } }
ABAC的授權過程可以簡單的理解為將請求屬性轉換為一個spec對象,然后拿到這個spec對象與策略文件中定義的spec對象進行匹配,如果這個spec對象能夠與策略文件中定義的任何一條規則允許的spec對象匹配,那么授權通過;如果這個spec對象無法與任何一條規則匹配,那么授權失敗。
下面是一個完整的spec對象的例子:
{
"apiVersion": "abac.authorization.kubernetes.io/v1beta1",
"kind": "Policy",
"spec":
{
"user": "用戶名",
"group": "用戶組",
"readonly": "是否只讀",
"apiGroup": "訪問的API所屬的API組",
"namespace": "訪問對象的所屬命名空間",
"resource": “訪問的資源類型”,
"nonResourcePath": "訪問的非資源路徑"
}
}
假設只允許名為bob的用戶讀取命名空間projectCaribou下的Pod信息,則可以創建如下規則:
{
"apiVersion": "abac.authorization.kubernetes.io/v1beta1",
"kind": "Policy",
"spec":
{
"user": "bob",
"namespace": "projectCaribou",
"resource": "pods",
"apiGroup": "*",
"readonly": true
}
}
以下面的策略配置為例:
{"apiVersion": "abac.authorization.kubernetes.io/v1beta1", "kind": "Policy", "spec": {"user":"zhangsan", "namespace": "","resource": "pods","apiGroup": "","readonly": true }}
{"apiVersion": "abac.authorization.kubernetes.io/v1beta1", "kind": "Policy", "spec": {"user":"admin", "namespace": "","resource": "","apiGroup": "","readonly": true, "nonResourcePath": "" }}
{"apiVersion": "abac.authorization.kubernetes.io/v1beta1", "kind": "Policy", "spec": {"user":"lisi", "namespace": "","resource": "nodes","apiGroup": "","readonly": true }}
相當于定義了如下規則:
用戶 能力
zhangsan 允許讀取Pod信息
admin 允許讀取所有資源信息
lisi 允許讀取Node信息
請求在轉換為spec對象的過程中,如果某一個屬性請求具備多值,例如group屬性,那么可以理解為將請求轉換為多個spec對象,每一個對象持有多值屬性中的一個值,然后這些對象分別于策略文件進行匹配,只要任何一個對象匹配通過,則請求授權通過。對于請求無法提供的屬性,例如group屬性,那么在轉換為spec對象的過程中該屬性被設置為該屬性聲明類型的默認值,例如,字符串類型的屬性設置為空字符串,而整數型的屬性設置為0。
規則文件中,可以使用來進行通配,例如,在規則中出現如下定義"user": "",則表示該規則匹配任何用戶,也就是說任何用戶的請求,在用戶名這一項上該規則都匹配。需要注意的是,缺省不代表匹配任何項,當某一個屬性缺省時,可以理解為該屬性設置為默認值。
在上面的介紹中,有人可能會有疑惑:為什么要將請求路徑劃分為資源和非資源路徑兩種不同的屬性?原因是:Kubernetes實現了一種名API Group的特性,由于這種特性導致了同一種資源可能有N個入口可以訪問,以Pod為例,用戶可能通過地址https://192.168.0.1:6443/api/v1/pods訪問,也可以通過地址https://192.168.0.1:6443/api/v1/namespaces/default/pods訪問,如果依靠請求地址來控制資源的訪問的話,會導致規范定義的極速膨脹;此外,一旦增加了新的API組,又會導致同一個資源的訪問增加大量的請求地址,所以從易用性以及性能等方面的考慮,對于資源直接使用資源類型進行控制是相對較好的一種方案。
由于非資源的請求地址相對固定,不會存在這個問題,而且又沒有關聯的可供識別的對象,所以對于非資源請求使用訪問地址來做限制就是一種顯而易見的選擇了。
WebHook
WebHook模式是一種擴展授權模式,在這種模式下,API Server將授權過程委派到外部的一個REST服務,由外部的服務決定是否授予指定請求繼續訪問的權限。
WebHook模式的開啟非常的簡單,只需要通過API Server的啟動參數--authorization-mode設置為WebHook并且通過啟動參數--authorization-webhook-config-file將外部授權服務的配置信息告訴API Server即可。
由于WebHook模式的授權策略完全由外部授權服務來決定,在這里就不進行詳解的介紹,具體的信息可以參看Kubernetes官方文檔。
自定義插件
此外,Kubernetes也支持通過開發新的插件的方式支持新的授權模式,插件的開發非常簡單,只需要實現如下接口即可,在這里就不做展開討論:
type Authorizer interface {
Authorize(a Attributes) error
}
如何識別各種認證方式的用戶名和用戶組
TODO 需要進一步驗證
認證方式 用戶名 用戶組
客戶端證書認證 證書的公共名 無
Token認證 Token文件中指定的用戶名 Token文件中指定的用戶組
HTTP基本認證 配置文件中指定的用戶名 無
OpenID認證 通過啟動參數--oidc-username-claim指定 通過啟動參數--oidc-groups-claim指定
Secret
在實際的生產環境中,在大多數情況下,容器都不是孤立存在的,一般都需要與其它服務或者系統進行通訊或者集成,而其它服務或者系統一般都需要調用者提供密碼、認證Token以及SSH秘鑰等信息來確保信息安全。
在常規的容器化實踐中,一般采用環境變量、命令行參數、掛載文件甚至直接Build到鏡像中等方式將這些敏感信息傳遞到容器中,以達到容器能夠在運行中獲得這些敏感信息的目的。然而,上述的方式存在容易泄露、難以變更以及維護困難等問題,為了解決這些問題,在Kubernetes中引入了秘密(Secret)的概念。
在Kubernetes中,秘密可以簡單的理解為一個命名對象,在這個對象中保存了特定的敏感信息,用戶可以簡單的通過Pod定義文件、Service Account甚至在運行中動態獲取等方式,在容器獲得秘密中保存的敏感信息。此外,通過Pod定義文件、服務賬號等靜態方式掛接在Pod上的秘密,在Pod沒有啟動之前,任何對秘密的更改都會在Pod啟動之后直接反應到Pod中,而在Pod啟動之后的更改,則需要重新啟動Pod。
目前,Kubernetes提供了以下三種不同的秘密:
不透明秘密(Opaque Secret)
不透明秘密可以簡單的理解為可以隨便放任何數據的字典,Kubernetes只是簡單的將秘密中包含的數據傳遞到包含在Pod中的容器,具體的內容只有提供方和使用方能夠理解。需要注意的是,單個秘密的大小上限是1MB,如果希望傳遞更多的內容,可以考慮將內容拆分到多個小的秘密中。
API Token Secret
API Token一般與服務賬號配對使用,通過準入控制(Admission Control)提供的Service Account插件自動的將API Token掛載到容器中(默認掛載到容器的/var/run/secrets/kubernetes.io/serviceaccount/路徑下),以實現在容器中能夠有權限訪問API Server。當然,在不使用準入控制的情況下,也可以采用與其它秘密相同的方式掛載到容器中。
imagePullSecret
imagePullSecret用來保存鏡像倉庫的認證信息,以方便Kubelet在啟動Pod時,能夠獲得鏡像倉庫的認證信息,確保能Kubelet夠有權限從鏡像倉庫中下載Pod所需的鏡像。
此外,為了確保鏡像的安全以及保證只有授權的用戶才能給使用特定的鏡像,建議在生產環境中啟用準入控制的AlwaysPullImages插件,當啟用這個插件時,將無視Pod定義中的鏡像下載策略(imagePullPolicy),強制Kubelet總是從鏡像倉庫中下載鏡像,而不使用本地鏡像,從效果上看相當于將Pod定義中的鏡像下載策略設置為Always。
Opaque Secret
創建
通過命令行創建(Kubernetes 1.2新增加的特性)
假設需要將以下MySQL的連接信息通過秘密傳入到容器中:
db-user-name:mysql
db-user-pass:password
db-address:192.168.0.1:3306
db-name:database
可以采用下面的命令創建秘密:
通過文件創建
echo "mysql" > ./username.txt
echo "password" > ./password.txt
echo "192.168.0.1:3306" > ./address.txt
echo "database" > ./name.txt
./kubectl create secret
generic mysql-database-secret
--from-file=db-user-name=./username.txt
--from-file=db-user-pass=./password.txt
--from-file=db-address=./address.txt
--from-file=db-name=./name.txt
也可以通過字面參數直接創建
./kubectl create secret
generic mysql-database-secret
--from-literal=db-user-name=mysql
--from-literal=db-user-pass=password
--from-literal=db-address=192.168.0.1:3306
--from-literal=db-name=database
如果創建成功,則可以使用命令./kubectl describe secret mysql-database-secret查看創建的秘密:
Name: mysql-database-secret
Namespace: default
Labels:
Annotations:
Type: Opaque
Data
db-name: 9 bytes
db-user-name: 6 bytes
db-user-pass: 9 bytes
db-address: 17 bytes
通過定義文件創建
創建如下內容的YAML文件,然后使用命名./kubectl create -f 文件路徑即可創建秘密,其中的數據內容是各項數據的Base64編碼,可以簡單的利用如下命令echo 內容 | Base64,生成指定內容的Base64編碼。
apiVersion: v1
data:
db-address: MTkyLjE2OC4wLjE6MzMwNg==
db-name: ZGF0YWJhc2U=
db-user-name: bXlzcWw=
db-user-pass: cGFzc3dvcmQ=
kind: Secret
metadata:
name: mysql-database-secret
namespace: default
type: Opaque
更新
相對于創建而言,更新只能通過文件來實現了,簡單的方式是首先使用如下的命名導出秘密定義:
./kubectr get secret mysql-database-secret -o yaml > mysql-database-secret.yaml
或者
./kubectr get secret mysql-database-secret -o json > mysql-database-secret.json
然后在更新文件內容之后,再使用如下命令更新秘密:
./kubectl replace -f mysql-database-secret.yaml
或者
./kubectl replace -f mysql-database-secret.json
使用
掛載為文件
針對上一步創建的秘密,可以通過如下的定義直接掛載到容器的文件系統中:
apiVersion: v1
kind: Pod
metadata:
name: demo
spec:
containers:
- name: demo
imagePullPolicy: IfNotPresent
image: image
volumeMounts:
- name: mysql
mountPath: /etc/mysql
readOnly: true
volumes:
- name: mysql
secret:
secretName: mysql-database-secret
掛載成功之后可以,在容器的文件系統中看到秘密的內容:
docker exec -it containerId ls /etc/mysql
db-address db-name db-user-name db-user-pass
docker exec -it containerId cat /etc/mysql/db-user-pass
password
掛載為環境變量
可以采用下面的定義直接將秘密掛載為環境變量:
apiVersion: v1
kind: Pod
metadata:
name: demo
spec:
containers:
- name: demo
imagePullPolicy: IfNotPresent
image: image
env:
- name: SECRET_USERNAME
valueFrom:
secretKeyRef:
name: mysql-database-secret
key: db-user-name
- name: SECRET_PASSWORD
valueFrom:
secretKeyRef:
name: mysql-database-secret
key: db-user-pass
- name: SECRET_NAME
valueFrom:
secretKeyRef:
name: mysql-database-secret
key: db-name
- name: SECRET_ADDRESS
valueFrom:
secretKeyRef:
name: mysql-database-secret
key: db-address
然后使用如下命令,就可以看到秘密的內容已經掛載為環境變量:
docker exec -it containerId env
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=nginx
SECRET_USERNAME=mysql
SECRET_PASSWORD=password
SECRET_NAME=database
SECRET_ADDRESS=192.168.0.1:3306
KUBERNETES_SERVICE_PORT_HTTPS=443
KUBERNETES_PORT=tcp://172.0.0.1:443
KUBERNETES_PORT_443_TCP=tcp://172.0.0.1:443
KUBERNETES_PORT_443_TCP_PROTO=tcp
KUBERNETES_PORT_443_TCP_PORT=443
KUBERNETES_PORT_443_TCP_ADDR=172.0.0.1
KUBERNETES_SERVICE_HOST=172.0.0.1
KUBERNETES_SERVICE_PORT=443
...
自動掛載
目前Opaque Secret尚未實現自動掛載,也許在Kubernetes的后續版本中會提供這個功能,具體的信息可以參看Issue 9902。
imagePullSecret
創建
imagePullSecret的創建和更新方式與Opaque Secret的創建和更新方式類似,支持在創建和更新中一些參數稍有區別。
下面是一個完整的YAML格式的imagePullSecret定義文件:
apiVersion: v1 data:.dockercfg: eyJET0NLRVJfUkVHSVNUUllfU0VSVkVSIjp7InVzZXJuYW1lIjoiRE9DS0VSX1VTRVIiLCJwYXNzd29yZCI6IkRPQ0tFUl9QQVNTV09SRCIsImVtYWlsIjoiRE9DS0VSX0VNQUlMIiwiYXV0aCI6IlJFOURTMFZTWDFWVFJWSTZSRTlEUzBWU1gxQkJVMU5YVDFKRSJ9fQ== kind: Secret metadata:name: local-registry-secretnamespace: default type: kubernetes.io/dockercfg上面的定義文件中有兩點需要注意:
Secret的類型
imagePullSecret的類型為kubernetes.io/dockercfg
Secret的數據
imagePullSecret中只包含一個名為.dockercfg的數據,注意,這個名稱是固定的,而具體的內容是以下內容的Base64編碼:
{
"DOCKER_REGISTRY_SERVER":
{
"username":"用戶名",
"password":"密碼",
"email":"郵件地址",
"auth":"RE9DS0VSX1VTRVI6RE9DS0VSX1BBU1NXT1JE"
}
}
其中auth屬性是必須的,明文部分除了郵件地址以外,用戶名和密碼這兩個屬性都可以不要,而auth屬性的值就是用戶名:密碼的簡單Base64編碼,可以使用如下命令簡單生成:
echo 用戶名:密碼 | base64
以下是最小內容的示例:
{
"DOCKER_REGISTRY_SERVER":
{
"username":"用戶名",
"password":"密碼",
"email":"郵件地址",
"auth":"RE9DS0VSX1VTRVI6RE9DS0VSX1BBU1NXT1JE"
}
}
此外,還可以通過Docker提供的login命令生成imagePullSecret的內容,以下為通過Docker生成的示例命令:
docker login -u 用戶名 -p 密碼 -e 郵件地址 鏡像庫地址
命令的執行結果會寫入到如下路徑:
~/.docker/config.json
最后通過如下命令就可以簡單生成imagePullSecret的內容了:
cat ~/.docker/config.json | base64
此外,還可以通過kubectl命令來生成imagePullSecret,下面是命令的示例:
./kubectl create secret docker-registry
鏡像下載秘密名稱
--docker-server=鏡像庫地址
--docker-username=用戶名
--docker-password=密碼
--docker-email=郵件地址
-s API Server地址
使用
imagePullSecret的使用方式與Opaque Secret的掛載方式不同,由于imagePullSecret用于提供給kubectl來下載鏡像,而不需要掛載到容器中,所以對于imagePullSecret而言,只需要在Pod定義中聲明使用即可。在Pod可以聲明多個imagePullSecret,使得Kubelet可以從多個不同的鏡像倉庫中下載鏡像,當kubectl下載鏡像時,會根據鏡像倉庫的不同選擇合適的imagePullSecret去執行鏡像下載操作。
目前,主要有以下兩種方式將imagePullSecret綁定到Pod上:
在Pod中直接定義
可以在Pod定義中,直接聲明需要綁定的imagePullSecret,以下為Pod中綁定imagePullSecret定義文件的示例:
apiVersion: v1
kind: Pod
metadata:
name: foo
namespace: awesomeapps
spec:
containers:
- name: foo
image: janedoe/awesomeapp:v1
imagePullSecrets:
- name: 秘密名稱
- name: 秘密名稱
- ...
在服務賬號中定義
可以在服務賬號中,聲明需要綁定到服務賬號的imagePullSecret,當服務賬號被隱式或者顯式的綁定到Pod上時,服務賬號中聲明的秘密,包括imagePullSecret也自動被綁定到Pod。以下為在服務賬號中綁定imagePullSecret定義文件的示例:
apiVersion: v1
kind: ServiceAccount
metadata:
name: default
namespace: default
imagePullSecrets:
- name: 秘密名稱
- name: 秘密名稱
- ...
在Pod中顯式的聲明服務賬號
apiVersion: v1
kind: Pod
metadata:
name: foo
namespace: awesomeapps
spec:
containers:
- name: foo
image: janedoe/awesomeapp:v1
serviceAccountName: 服務賬號名稱
注意:秘密和服務賬號都是命名空間敏感的,所以無論在Pod中引用秘密、服務賬號或者在服務賬號中引用秘密,都只能本命名空間內的秘密和服務賬號,不能夠跨命名空間引用其它命名空間的秘密和服務。
對于已經存在的服務賬號,希望往服務賬號中添加或者刪除imagePullSecret,可以按照如下步驟實現:
導出現有服務賬號的定義文件
kubectl get serviceaccount 服務賬號名稱 -o 格式(json或者yaml) > 定義文件路徑
更新服務定義
修改上一步導出的定義文件,在定義文件中添加或者刪除imagePullSecret。
更新服務賬號
kubectl replace serviceaccount 服務賬號名稱 -thef 定義文件路徑
API Token秘密
API Token Secret一般用于綁定到服務賬號,用于標識服務賬號,從而實現在Pod中能夠以服務賬號的身份訪問API Server,具體的內容可以參看在Pod中訪問API Server。
雖然,API Token Secret可以手動創建,但是大多數情況下都不需要手動創建,而是伴隨服務賬號自動創建,如果確實要手動創建,則可以使用下面的模板進行創建:
apiVersion: v1
kind: Secret
metadata:
name: 秘密名稱
annotations:
kubernetes.io/service-account.name: 服務賬號名稱
type: kubernetes.io/service-account-token
創建成功的API Token秘密可以按照普通秘密相同的方式掛載到服務賬號或者Pod,在這里就不進行詳細討論了。
服務賬號的自動化以及授權
Kubernetes內置提供一種機制,可以實現默認服務賬號的自動創建和自動掛載,對于大多數情況而言,使用這種內置機制基本上可以滿足服務賬號的使用要求了。當然如果需要進一步的細化權限,則必須手動創建服務賬號手動綁定服務賬號了。
Kubernetes通過ServiceAccount插件、Token Controller以及Service Account Controller等三個組件實現服務賬號的自動化,下面就這個三個組件的分工做簡要概述。
ServiceAccount插件
ServiceAccount插件運行在API Server中,通過API Server的--admission-control參數啟用,當啟用了ServiceAccunt插件,ServiceAccount插件將在Pod啟動或者更新的過程中執行下面的動作:
確保Pod綁定了服務賬號,如果沒有顯示綁定,則自動綁定到default服務賬號
確保Pod綁定的服務賬號是存在的,如果不存在,則拒絕Pod啟動
如果Pod沒有顯示聲明ImagePullSecret,則自動將服務賬號上聲明的ImagePullSecret綁定到Pod
將服務賬號中綁定的API Token通過卷的方式自動加載到容器的文件系統/var/run/secrets/kubernetes.io/serviceaccount
Token Controller
Token Controller是Kubernetes Controller Manager的一個組件,用于同步服務賬號和密碼,主要實現了下面的功能:
當服務賬號創建時,自動創建一個API Token秘密
當服務賬號刪除時,自動刪除服務賬號的所有API Token秘密
當秘密刪除時,自動從服務賬號中刪除引用關系
創建API Token秘密是確保服務賬號存在,并且自動添加一個用于訪問API的Token
Service Account Controller
Service Account Controller用于管理命名空間中的服務賬號,并且確保每一個活動的命名空間中都存在default服務賬號。
對于服務賬號的授權,在前面的章節中已近做了一些概要的介紹,從本質上來說與用戶賬號的授權是一樣的,只是需要注意服務賬號的賬號名。
由于服務賬號一般用于提供給Pod來訪問API Server,所以從安全性的角度來看,盡量限制服務賬號為只讀,且最好不允許跨命名空間訪問(在Kubernetes中,一般采用命名空間的方式來實現多租戶)。
在Pod中訪問API Server
在Pod中訪問API Server或者說在容器中訪問API Server,存在很多種可能的方式,但是從安全性的角度而言,使用服務賬號并且只通過安全端口訪問API Server是一種受控和安全的訪問方式。
如果要使用服務賬號訪問API Server,建議通過服務賬號自動化機制,自動的將用于訪問API Server的Token掛載到容器中,在容器中就可以簡單的使用Token認證來訪問API Server了。
此外,也可以使用kubectl的proxy命令,創建一個到API Server的代理。當啟用Kubectl代理時,在代理中已經處理了服務地址以及認證信息,客戶端只需要簡單的訪問代理提供的地址,就可以以指定的身份訪問API Server了。
關于Kubectl代理的詳細信息可以參考訪問集群。
總結
以上是生活随笔為你收集整理的Kubernetes集群安全概述的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: K8s普通用户配置权限解决User “s
- 下一篇: 堆和栈存放的内容