API的訪問安全性
API Server的端口和地址node
在默認狀況下,API Server經過本地端口和安全端口兩個不一樣的HTTP端口,對外提供API服務,其中本地端口是基於HTTP協議的,用於在本機(API Server所在主機)無限制的訪問API Server,而安全端口則是基於HTTPS協議的,用於遠程有限制的訪問API Server,下面就這兩種端口作詳細的介紹。mysql
本地端口(Localhost Port)nginx
在API Server的默認配置中,本地端口默認綁定到地址127.0.0.1上,因此,在默認狀況下,本地端口只能在本機(API Server所在主機)訪問,因爲API Server不對任何經過本地端口的訪問作任何權限控制(經過本地端口的訪問繞過了認證和受權過程),換句話說,只要可以訪問本地端口,任何人均可以經過本地端口無限制的訪問API Server,基於安全性方面的考慮,在配置API Server時,儘可能不要將本地端口綁定到地址127.0.0.1之外的地址上,以免將本地端口暴露到本機之外。web
本地端口默認綁定到端口8080上,本地端口的綁定端口號能夠經過API Server的啓動參數--insecure-port來進行指定,若是將綁定的端口號指定爲0,則表示關閉本地端口。另外,能夠經過API Server的啓動參數--insecure-bind-address來指定本地端口的綁定地址。sql
注意:在生產環境中儘可能避免將本地端口綁定到127.0.0.1之外的地址,以免帶來沒必要要的安全問題。docker
安全端口(Secure Port)數據庫
顧名思義,安全端口是API Server對外提供的,用於外部訪問的、安全的、可控的API調用接口,API Server只容許經過認證(Authentication)的用戶纔可以經過安全端口訪問API Server,不管是認證爲User Account或者Service Account均可以正常的經過安全端口訪問API Server,可是對於還沒有認證的匿名用戶,當經過安全端口訪問API Server時,服務端老是返回401(Unauthorized),拒絕其後續訪問。json
安全端口默認綁定到端口6443上,而且老是經過HTTPS協議對外提供服務。安全端口綁定的端口號能夠經過API Server的啓動參數--secure-port來進行指定,和本地端口的配置相似,若是將安全端口綁定的端口號指定爲0,則表示關閉安全端口。安全端口默認綁定到地址0.0.0.0上(理論上應該是0.0.0.0/32,表示本機的全部源地址),固然也能夠經過API Server的啓動參數--bind-address來進行顯示指定。api
因爲安全端口是基於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參數指定使用
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
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-secret namespace: 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代理的詳細信息能夠參考訪問集羣。