深入理解k8s中的訪問控制(認證、鑒權、審計)流程

Kubernetes自身並沒有用戶管理能力,無法像操作Pod一樣,通過API的方式創建/刪除一個用戶實例,也無法在etcd中找到用戶對應的存儲對象。
在Kubernetes的訪問控制流程中,用戶模型是通過請求方的訪問控制憑證(如kubectl使用的kube-config中的證書、Pod中引入的ServerAccount)產生的
Kubernetes API的請求從發起到其持久化入庫的流程如圖:
 
 
一、認證階段(Authentication)
判斷用戶是否為能夠訪問集群的合法用戶。
apiserver目前提供了9種認證機制。每一種認證機制被實例化後會成為認證器(Authenticator),每一個認證器都被封裝在http.Handler請求處理函數中,它們接收組件或客戶端的請求並認證請求。
 
假設所有的認證器都被啟用,當客戶端發送請求到kube-apiserver服務,該請求會進入Authentication Handler函數(處理認證相關的Handler函數)。在Authentication Handler函數中,會遍歷已啟用的認證器列表,嘗試執行每個認證器,當有一個認證器返回true時,則認證成功,否則繼續嘗試下一個認證器;如果用戶是個非法用戶,那apiserver會返回一個401的狀態碼,並終止該請求。
1、RequestHeader認證
Kubernetes可以設置一個認證代理,客戶端發送的認證請求可以通過認證代理將驗證資訊發送給apiserver
apiserver需要配置:
    –requestheader-username-headers=X-Remote-User
    –requestheader-group-headers=X-Remote-Group
    –requestheader-extra-headers-prefix=X-Remote-Extra-
    –requestheader-client-ca-file:防止頭部欺騙
    –requestheader-allowed-names:設置允許的CN列表
 
2、BasicAuth認證
啟動apiserver時通過–basic-auth-file參數啟用BasicAuth認證。
AUTH_FILE(Static Password file)是一個CSV文件,文件格式為:
password,user,uid,"group1,group2,group3"
發起請求時在HTTP中添加頭即可:
Authorization: Basic BASE64ENCODED(USER:PASSWORD)
 
3、clientCA認證
X509認證是Kubernetes組件間默認使用的認證方式,同時也是kubectl客戶端對應的kube-config中經常使用到的訪問憑證。它是一個比較安全的方式。
首先訪問者會使用由集群CA簽發的,或是添加在apiserver配置中的授信CA簽發的客戶端證書去訪問apiserver。apiserver在接收到請求後,會進行TLS的握手流程。
除了驗證證書的合法性,apiserver還會校驗客戶端證書的請求源地址等資訊,開啟雙向認證。
 
進行證書籤發的步驟:
(1)創建根CA
cat << EOF | tee ca-config.json
{
  "signing": {
    "default": {
      "expiry": "87600h"
    },
    "profiles": {
      "kubernetes": {
         "expiry": "87600h",
         "usages": [
            "signing",
            "key encipherment",
            "server auth",
            "client auth"
        ]
      }
    }
  }
}
EOF

其中:

  • profiles:指定不同的過期時間、使用場景等參數。文件中可以定義多個,分別後續在簽名證書時使用某一個
  • signing:表示該證書可用於簽名其它證書,生成的ca.pem證書中CA=TRUE
  • key encipherment:表示密鑰用法為密鑰加密
  • server auth:表示client可以用該CA 對server提供的證書進行驗證
  • client auth:表示server可以用該CA對client提供的證書進行驗證
cat << EOF | tee ca-csr.json
{
    "CN": "kubernetes",
    "key": {
        "algo": "rsa",
        "size": 2048
    },
    "names": [
        {
            "C": "CN",
            "L": "Shenzhen",
            "ST": "Shenzhen",
            "O": "k8s",
            "OU": "System"
        }
    ]
}
EOF

其中:

  • CN:Common Name,用於從中提取該欄位作為請求的用戶名
  • C:Country, 國家
  • ST: State,州,省
  • L: Locality,地區,城市
  • O: Organization Name, 用於從中提前該欄位作為請求用戶所屬的組
  • OU: Organization Unit Name,組織單位名稱,公司部門
cfssl gencert -initca ca-csr.json | cfssljson -bare ca
執行後生成文件ca.csr、ca-key.pem、ca.pem
(2)簽發其它系統組件的證書
Kubernetes集群中所有系統組件與apiserver通訊用到的證書,其實都是由集群根CA來簽發的。
①如kube-proxy對應的csr文件
cat << EOF | tee kube-proxy-csr.json
{
  "CN": "system:kube-proxy",
  "hosts": [],
  "key": {
    "algo": "rsa",
    "size": 2048
  },
  "names": [
    {
      "C": "CN",
      "L": "Shenzhen",
      "ST": "Shenzhen",
      "O": "k8s",
      "OU": "System"
    }
  ]
}
EOF

使用根CA簽署證書:

cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=kubernetes kube-proxy-csr.json | cfssljson -bare kube-proxy
執行後生成文件kube-proxy.csr、kube-proxy-key.pem、kube-proxy.pem
②kubelet啟動時實際需要指定兩個配置文件
  –kubeconfig指定的是kube-config文件,其中內置了集群根CA公鑰以及自己作為客戶端的公鑰和私鑰
  –config指定的是kubelet的配置,格式如下:
kind: KubeletConfiguration
apiVersion: kubelet.config.k8s.io/v1beta1
address: xxx.xxx.xxx.xxx
port: 10250
readOnlyPort: 10255
cgroupDriver: cgroupfs
clusterDNS: ["10.0.0.2"]
clusterDomain: cluster.local.
failSwapOn: false
authentication:
    anonymous:
        enabled: true

kubelet組件在工作時,採用主動的查詢機制,即定期請求apiserver 獲取自己所應當處理的任務,如哪些pod分配到了自己身上,從而去處理這些任務;同時kubelet自己還會暴露出兩個本身api的埠,用於將自己本身的私有api暴露出去,這兩個埠分別是該配置文件中指定的10250與10255。

對於10250埠,kubelet會在其上採用TLS加密以提供適當的鑒權功能;對於10255埠,kubelet會以只讀形式暴露組件本身的私有api,並且不做鑒權處理。
因此,kubelet上實際上有兩個地方用到證書,一個是用於與 API server通訊所用到的證書,另一個是該配置文件中設置的kubelet的10250私有api埠需要用到的證書。
(3)簽發用戶的證書
①首先開發人員需用通過OpenSSL等證書工具生成私鑰
openssl genrsa -out test.key 2048
②創建對應的x509 csr請求文件(需要在subj欄位中指定user和group)
openssl req -new -key test.key -out test.csr -subj "/CN=xxxx/O=xxxx"
③Kubernetes集群本身就提供了證書籤發的API certificates.k8s.io/v1beta1。調用後,api-server會根據請求,以csr資源對象的形式創建對應的簽發請求。
例如,在集群的創建過程中,像kubeadm這樣的集群安裝工具,也會基於不同的csr簽發請求調用api-server對應介面,創建不同的csr資源對象。
用戶可以通過API創建K8s csr實例並等待管理員審批。
cat <<EOF | kubectl apply -f -
apiVersion: certificates.k8s.io/v1beta1
kind: CertificateSigningRequest
metadata:
  name: xxxx
spec:
  groups:
  - system:authenticated
  request: $(cat test.csr | base64 | tr -d "\n")
  usages:
  - client auth
EOF

PS:request中是base64編碼的csr文件

剛開始創建的簽發實例都會處於pending狀態:
NAME   AGE   REQUESTOR   CONDITION
xxxx   10s   admin       Pending
直到有許可權的管理員進行審批後,這個csr才會處於approved狀態,請求對應的證書就會被簽發
# kubectl certificate approve john
certificatesigningrequest.certificates.k8s.io/xxxx approved
證書以base64存在csr資源對象的.status.certificate中,配合私鑰、CA公鑰即可製作kubeconfig文件
集群管理員也可以直接讀取集群根CA,並通過x509 csr請求文件簽發證書。簽發示例如下:
# openssl x509 -req -in test.csr -CA CA_LOCATION/ca.crt -Cakey CA_LOCATION/ca.key -Cacreateserial -out test.crt -days 365
命令中需要指明csr和ca.crt的文件路徑,以及簽發證書的過期時間資訊
④查看kubeconfig配置
# kubectl config view

kubectl默認會從$HOME/.kube目錄下查找文件名為config 的文件,也可以通過設置環境變數KUBECONFIG或者通過設置–kubeconfig去指定其它kubeconfig文件。

文件格式為:

{
  "apiVersion": "v1",
  "kind": "Config",
  "preferences": {},
 
  "clusters": [
    {
      "cluster": {
        "certificate-authority": 
        "server": "//ip:6443"
      },
      "name": {cluster-name}
    }
  ],
  "contexts": [
    {
      "context": {
        "cluster": {cluster-name},
        "user": {user-name}
      },
      "name": {context-name}
    }
  ],
 
  "users": [
    {
      "name": {user-name},
      "user": {
        "client-certificate": 
        "client-key": 
      }
    }
  ]
  "current-context": {context-name},
}

若想要用base64編碼數據代替認證文件,需要添加後綴-data,將 certificate-authority、client-certificate、client-key改為certificate-authority-data、client-certificate-data、client-key-data

從config文件還原證書的方法:
# grep 'client-key-data' /etc/kubernetes/admin.conf | head -n 1 | awk '{print $2}' | base64 -d 
# grep 'client-certificate-data' /etc/kubernetes/admin.conf | head -n 1 | awk '{print $2}' | base64 -d
⑤下載集群ca公鑰文件ca.pem,使用kubectl添加集群連接資訊
# kubectl config set-cluster xxx --certificate-authority=ca.pem --embed-certs=true --server=https://ip:6443
⑥使用kubectl設置kubeconfig的users配置段資訊,需要將用戶秘鑰資訊加入kubectl配置中
# kubectl config set-credentials {user-name} --client-certificate=test.crt --client-key=test.key --embed-certs=true
⑦添加新的context入口到kubectl配置中
# kubectl config set-context {context-name} --cluster={cluster-name} --user={user-name}
⑧多集群config的合併和切換
# export KUBECONFIG=file1:file2:file3 
# kubectl config view --merge --flatten > ~/.kube/all-config 
# export KUBECONFIG = ~/.kube/all-config
⑨查看和切換上下文
# kubectl config get-contests 
# kubectl config use-context {your-contexts}
 
4、TokenAuth認證
啟動apiserver時通過–token-auth-file參數啟用TokenAuth認證。
AUTH_FILE(Static Password file)是一個CSV文件,文件格式為:
token,user,uid,"group1,group2,group3"
發起請求時在HTTP中添加頭即可:
Authorization: Bearer 31ada4fd-adec-460c-809a-9e56ceb75269
 
5、ServiceAccountAuth認證
serviceaccount是k8s中唯一能夠通過API方式管理的apiserver訪問憑證,通常用於pod中的業務進程與apiserver的交互
ServiceAccount解決Pod在集群裡面的身份認證問題,認證使用的授權資訊存在secret裡面(由SecretAccount Controller自行創建)。
當一個namespace創建完成後,會同時在該namespace下生成名為default的serviceaccount和對應Secret:
apiVersion: v1
kind: ServiceAccount
metadata:
  creationTimestamp: "2019-11-19T03:07:32Z"
  name: default
  namespace: default
  resourceVersion: "191"
  selfLink: /api/v1/namespaces/default/serviceaccounts/default
  uid: b2322727-08d5-4095-acbe-1afee4fb5e6c
secrets:
- name: default-token-nfdr4   
對應的Secret里:
    data欄位有兩塊數據:ca.crt用於對服務端的校驗,token用於Pod的身份認證,它們都是用base64編碼過的。
    metadata里annotations欄位表明了關聯的ServiceAccount資訊(被哪個ServiceAccount使用)。
    type欄位表明了該Secret是service-account-token類型
apiVersion: v1
data:
  ca.crt: LS0tLS1...
  namespace: ZGVmYXVsdA==
  token: ZXlKaG...
kind: Secret
metadata:
  annotations:
    kubernetes.io/service-account.name: default
    kubernetes.io/service-account.uid: b2322727-08d5-4095-acbe-1afee4fb5e6c
  creationTimestamp: "2019-11-19T03:07:32Z"
  name: default-token-nfdr4
  namespace: default
  resourceVersion: "190"
  selfLink: /api/v1/namespaces/default/secrets/default-token-nfdr4
  uid: cbb919a4-6309-43c0-ac0b-566e30e9b116
type: kubernetes.io/service-account-token
此外,用戶也可以通過api創建其它名稱的ServiceAccount,並在該namespace的Pod的.spec.ServiceAccount下指定,默認是default。
Pod創建的時候,Admission Controller會根據指定的ServiceAccount把對應secret的ca.crt和token文件掛載到固定目錄/var/run/secrets/kubernetes.io/serviceaccount下。
    volumeMounts:
    - mountPath: /var/run/secrets/kubernetes.io/serviceaccount
      name: default-token-jbcp7
      readOnly: true
pod要訪問集群的時候,默認利用Secret其中的token文件來認證Pod的身份,利用ca.crt校驗服務端
默認token的認證資訊為:
    – Group:system:serviceaccounts:[namespace-name]
    – User:system:serviceaccount:[namespace-name]:default
Pod身份被認證合法後,其許可權需要通過RBAC來配置,默認只有資源GET許可權
 
PS:如果是在Pod創建過程中,發現指定的ServiceAccount不存在,則該Pod創建過程會被終止。
PS:對於已經創建的Pod,不能更新其已經掛載的ServiceAccount內容。
 
6、Bootstrap Token認證
如果節點多起來,為每個節點單獨簽署證書將是一件非常繁瑣的事情
TLS bootstrapping的功能就是讓kubelet先使用一個預定的低許可權用戶連接到apiserver,向apiserver申請證書,證書由apiserver動態簽署
在配合RBAC授權模型下的工作流程大致如下所示:
TLS bootstrapping下kubelet發起的CSR請求大致分為以下三種:
    nodeclient:kubelet以O=system:nodes、CN=system:node:(node name)形式發起的CSR請求(僅在第一次啟動時產生)
    selfnodeclient:kubelet發起的更新自己的作為client的證書的CSR請求(與上一個證書有相同的O、CN)
    selfnodeserver:kubelet發起的更新自己的作為server的證書(即kubelet 10250 api埠證書)的CSR請求
 
①使用TLS Bootstrapping Token時的配置流程:
(1)創建TLS Bootstrapping Token
# head -c 16 /dev/urandom | od -An -t x | tr -d ' '
8f01b7072246e0f3409d54e379c8699f
(2)修改用戶的描述文件token.csv(相當於預設的用戶配置),基本格式為Token,user,uid,group:
8f01b7072246e0f3409d54e379c8699f,kubelet-bootstrap,10001,"system:kubelet-bootstrap"
(3)在apiserver配置中添加–enable-bootstrap-token-auth開啟TLS bootstrapping功能,通過–token-auth-file參數指定token.csv文件,apiserver啟動時會將其載入,相當於在集群內創建了這個用戶。
(4)kubelet-bootstrap用戶沒有任何許可權(包括創建CSR請求),需要創建一個ClusterRoleBinding,將預設用戶kubelet-bootstrap用戶與內置的ClusterRole system:node-bootstrapper綁定到一起,使其能夠發起 CSR 請求
# kubectl create clusterrolebinding kubelet-bootstrap \ 
  
--clusterrole=system:node-bootstrapper \
  --user=kubelet-bootstrap
否則kubelet會報401無權訪問apiserver的錯誤
(4)創建kubelet的配置文件bootstrapping.kubeconfig
BOOTSTRAP_TOKEN=01f6717d648e3e7e71282a9632dd99ab
KUBE_APISERVER="//132.224.197.35:6443"

執行命令:

# kubectl config set-cluster kubernetes \
  --certificate-authority=./ca.pem \
  --embed-certs=true \
  --server=${KUBE_APISERVER} \
  --kubeconfig=bootstrap.kubeconfig
 
# kubectl config set-credentials kubelet-bootstrap \
  --token=${BOOTSTRAP_TOKEN} \
  --kubeconfig=bootstrap.kubeconfig
 
# kubectl config set-context default \
  --cluster=kubernetes \
  --user=kubelet-bootstrap \
  --kubeconfig=bootstrap.kubeconfig
 
# kubectl config use-context default --kubeconfig=bootstrap.kubeconfig
kubelet-bootstrap用戶的Token和apiserver的使用的CA證書被寫入了該配置文件中
首次請求時,kubelet使用配置文件中的apiserver CA證書與apiserver建立TLS通訊,使用配置文件中的用戶Token向apiserver聲明自己的RBAC授權身份。
(5)啟動kubelet時需要指定–kubeconfig和–bootstrap-kubeconfig(兩種身份),指定–cert-dir用來存放所有證書
(6)在kubelet首次啟動後,如果用戶Token沒問題,並且RBAC也做了相應的設置,那麼此時在集群內應該能看到kubelet發起的CSR 請求。出現CSR請求後,可以使用kubectl手動簽發kubelet的證書
(7)當成功簽發證書後,目標節點的 kubelet 會將證書寫入到–cert-dir選項指定的目錄中
    kubelet-client.crt、kubelet-client.key:kubelet與apiserver通訊所使用的證書
    kubelet.crt、kubelet.key:用於kubelet的10250埠做鑒權使用(這個證書是個獨立於apiserver CA的自簽CA,並且刪除後kubelet會重新生成它)
 
配置controller manager自動簽署證書
kubelet發起的CSR請求都是由controller manager來做實際簽署的
(1)kubelet啟動時增加–feature-gates=RotateKubeletClientCertificate=true,RotateKubeletServerCertificate=true參數,則證書即將到期時會自動發起一個renew自己證書的CSR請求
    配置了–feature-gates=RotateKubeletClientCertificate=true後,kubelet首次啟動時仍會使用kubelet-client.crt證書與apiserver通訊。續期請求被批准後會生成一個kubelet-client-時間戳.pem,kubelet-client-current.pem文件則始終軟連接到最新的真實證書文件
    配置了–feature-gates=RotateKubeletServerCertificate=true後不再生成kubelet.crt,改為生成kubelet-server-時間戳.pem,kubelet-server-current.pem文件則始終軟連接到最新的真實證書文件
(2)controller manager啟動時增加–feature-gates=RotateKubeletServerCertificate=true參數,則會在kubelet發起證書請求的時候自動幫助其簽署證書
PS:如果不配置該參數,則即使配置了相關的RBAC規則,也只會自動批准kubelet client的更新證書請求
此外,還需要配置RBAC 規則,保證 controller manager只對kubelet發起的特定CSR請求自動批准
(3)針對kubelet發起的3種CSR請求創建3種對應的ClusterRole:
# A ClusterRole which instructs the CSR approver to approve a user requesting node client credentials.
kind:ClusterRole
apiVersion:rbac.authorization.k8s.io/v1
metadata:
name:approve-node-client-csr
rules:
-apiGroups:["certificates.k8s.io"]
resources:["certificatesigningrequests/nodeclient"]
verbs:["create"]
 
---
 
# A ClusterRole which instructs the CSR approver to approve a node renewing its own client credentials.
kind:ClusterRole
apiVersion:rbac.authorization.k8s.io/v1
metadata:
name:approve-node-client-renewal-csr
rules:
-apiGroups:["certificates.k8s.io"]
resources:["certificatesigningrequests/selfnodeclient"]
verbs:["create"]
 
---
 
# A ClusterRole which instructs the CSR approver to approve a node requesting a serving cert matching its client cert.
kind:ClusterRole
apiVersion:rbac.authorization.k8s.io/v1
metadata:
name:approve-node-server-renewal-csr
rules:
-apiGroups:["certificates.k8s.io"]
resources:["certificatesigningrequests/selfnodeserver"]
verbs:["create"]
PS:1.8後的apiserver自動創建了前兩條ClusterRole
(4)將適當的ClusterRole綁定到 kubelet 自動續期時所所採用的用戶或者用戶組身上
自動批准kubelet的首次CSR請求(用於與apiserver通訊的證書):
# kubectl create clusterrolebinding node-client-auto-approve-csr --clusterrole=approve-node-client-csr --group=system:bootstrappers

自動批准 kubelet 後續 renew 用於與 apiserver 通訊證書的 CSR 請求:

# kubectl create clusterrolebinding node-client-auto-renew-crt --clusterrole=approve-node-client-renewal-csr --group=system:nodes
自動批准 kubelet 發起的用於 10250 埠鑒權證書的 CSR 請求(包括後續 renew):
# kubectl create clusterrolebinding node-server-auto-renew-crt --clusterrole=approve-node-server-renewal-csr --group=system:nodes
PS:在 1.8 後kubelet需要增加–rotate-certificates參數,kubelet 才會自動重載新證書
PS:在 1.7 版本以後kube-controller-manager可以通過–experimental-cluster-signing-duration參數來設置簽署的證書有效時間,默認為8760h0m0s(1年)。
 
②使用TLS Bootstrapping Token Secret時的配置流程:
(1)生成token
echo "$(head -c 6 /dev/urandom | md5sum | head -c 6)"."$(head -c 16 /dev/urandom | md5sum | head -c 16)」
47f392.d22d04e89a65eb22
Token 必須滿足 [a-z0-9]{6}\.[a-z0-9]{16} 格式;以 . 分割,前面的部分被稱作Token ID(可以暴露出去),後面的部分稱為Token Secret(需要保密)
(2)創建Bootstrap Token Secret
apiVersion: v1
kind: Secret
metadata:
  name: bootstrap-token-07401b
  namespace: kube-system
type: bootstrap.kubernetes.io/token
stringData:
  description: "The default bootstrap token generated by 'kubeadm init'."
  token-id: 47f392
  token-secret: d22d04e89a65eb22
  expiration: 2018-09-10T00:00:11Z
  usage-bootstrap-authentication: "true"
  usage-bootstrap-signing: "true"
  auth-extra-groups: system:bootstrappers:worker,system:bootstrappers:ingress
type 必須為 bootstrap.kubernetes.io/token
name 必須為 bootstrap-token-<token id> 
usage-bootstrap-authentication、usage-bootstrap-signing 必須設置為 true
expiration 欄位是可選的,如果設置則到期後將由 Controller Manager 中的 tokencleaner 自動清理
auth-extra-groups 也是可選的,令牌的擴展認證組,組必須以system:bootstrappers:開頭
 
(3)引導時,kubelet使用Token發起的請求其用戶名為system:bootstrap:<token id>,用戶組為system:bootstrappers
創建ClusterRoleBinding時要綁定到這個用戶或者組上
允許 system:bootstrappers 組用戶創建 CSR 請求:
# kubectl create clusterrolebinding kubelet-bootstrap \
  --clusterrole=system:node-bootstrapper \
  --group=system:bootstrappers
自動批准 system:bootstrappers 組用戶 TLS bootstrapping 首次申請證書的 CSR 請求:
# kubectl create clusterrolebinding node-client-auto-approve-csr \
  --clusterrole=system:certificates.k8s.io:certificatesigningrequests:nodeclient \
  --group=system:bootstrappers
自動批准 system:nodes 組用戶更新 kubelet 自身與 apiserver 通訊證書的 CSR 請求:
# kubectl create clusterrolebinding node-client-auto-renew-crt \
  --clusterrole=system:certificates.k8s.io:certificatesigningrequests:selfnodeclient \
  --group=system:nodes

自動批准 system:nodes 組用戶更新 kubelet 10250 api 埠證書的 CSR 請求:

# kubectl create clusterrolebinding node-server-auto-renew-crt \
    --clusterrole=system:certificates.k8s.io:certificatesigningrequests:selfnodeserver \
    --group=system:nodes
(4)Controller Manager需要添加參數–controllers=*,bootstrapsigner,tokencleaner以啟用 tokencleaner 和 bootstrapsigner
(5)kubelet使用的kubeconfig文件也要相應變化:
# kubectl config set-cluster kubernetes \
  --certificate-authority=/etc/kubernetes/ssl/k8s-root-ca.pem \
  --embed-certs=true \
  --server=https://127.0.0.1:6443 \
  --kubeconfig=bootstrap.kubeconfig
 
# kubectl config set-credentials system:bootstrap:47f392 \
  --token=47f392.d22d04e89a65eb22 \
  --kubeconfig=bootstrap.kubeconfig
 
# kubectl config set-context default \
  --cluster=kubernetes \
  --user=system:bootstrap:47f392 \
  --kubeconfig=bootstrap.kubeconfig
 
# kubectl config use-context default --kubeconfig=bootstrap.kubeconfig
 
7、OIDC認證
所謂OIDC(OpenID Connect),就是先向identity provider獲取伺服器簽名的JSON Web Token (JWT)
identity provider會提供access_token、id_token、refresh_token
使用kubectl時通過–token參數添加id_token,或者直接把它添加到kubeconfig文件中,kubectl會把id_token添加到http頭裡
apiserver會通過證書確認JWT是否有效、確認JWT是否過期、身份是否合法等
使用OIDC認證,apiserver需要配置:
    –oidc-issuer-url:identity provider的地址
    –oidc-client-id:client id,一般配成kubernetes
    –oidc-username-claim,如sub
    –oidc-groups-claim,如groups
    –oidc-ca-file:為identity provider簽名的CA公鑰
 
8、Webhook TokenAuth認證
當客戶端發送的認證請求到達apiserver時,apiserver回調鉤子方法,將驗證資訊發送給遠程的Webhook伺服器進行認證,然後根據Webhook伺服器返回的狀態碼來判斷是否認證成功
通過指定如下參數啟用WebhookTokenAuth認證:
    –authentication-token-webhook-config-file:Webhook配置文件描述了如何訪問遠程Webhook服務
    –authentication-token-webhook-cache-ttl:快取認證時間,默認值為2分鐘
 
9、Anonymous認證
未被其他認證器拒絕的請求都可視為匿名請求,匿名用戶權值很低
apiserver通過指定–anonymous-auth參數啟用Anonymous認證,默認該參數值為true
 
認證流程之後,api-server會將請求中憑證中的用戶身份轉化為對應的User和Groups。在隨後的鑒權操作和審計操作流程中,api-server都會使用該用戶模型實例。
二、鑒權階段(Authorization)
採用RBAC判斷用戶是否有許可權進行請求中的操作。如果無權進行操作,api-server會返回403的狀態碼,並終止該操作
RBAC包含三個要素:
  • Subjects:可以是開發人員、集群管理員這樣的自然人,也可以是系統組件進程、Pod中的業務進程;
  • API Resource:也就是請求對應的訪問目標,在Kubernetes集群中指各類資源對象;
  • Verbs:對應為請求對象資源可以進行哪些操作,如list、get、watch等。
部分常用操作需要的許可權如下:
 
Role:定義了用戶在指定的Kubernetes namespace上可以進行哪些操作
通過RoleBinding進行role和Subject的綁定:
除了定義指定namespace中的許可權模型,也可以通過ClusterRole定義一個集群維度的許可權模型。以定義集群維度的許可權(如PV、Nodes等namespace中不可見的資源)
ClusterRole編排文件幾乎和Role一樣,刪除指定namespace的那行即可。
通過ClusterRoleBinding進行ClusterRole和Subject的綁定。
 
系統預置的ClusterRole:
    system:basic-user:system:unauthenticated組(未認證用戶組)默認綁定Role,無任何操作許可權
    cluster-admin:system:masters組默認綁定的ClusterRole,有集群管理員許可權
    系統組件(kube-controller-manager、kube-scheduler、kube-proxy……)都綁定了默認的ClusterRole
 
三、審計階段(AdmissionControl)
Admission Controller(准入控制器)是一個攔截器,被編譯進API Server的可執行文件內部
它以插件的形式運行在apiserver進程中,會在鑒權階段之後、對象被持久化到etcd之前,攔截apiserver的請求,對請求的資源對象執行自定義(校驗、修改或拒絕等)操作。
AC有幾十種,大體上分為3類:
    validating(驗證型)用於驗證k8s的資源定義是否符合規則
    mutating(修改型)用於修改k8s的資源定義,如添加label,一般運行在validating之前
    既是驗證型又是修改型
只要有一個準入控制器拒絕了該請求,則整個請求被拒絕(HTTP 403Forbidden)並返回一個錯誤給客戶端。
可通過–enable-admission-plugins參數指定啟用的准入控制器列表,通過–disable-admission-plugins參數指定禁用的准入控制器列表
查看打開的AC:
# kube-apiserver -h | grep enable-admission-plugins
      --admission-control strings              Admission is divided into two phases. In the first phase, only mutating admission plugins run. In the second phase, only validating admission plugins run. The names in the below list may represent a validating plugin, a mutating plugin, or both. The order of plugins in which they are passed to this flag does not matter. Comma-delimited list of: AlwaysAdmit, AlwaysDeny, AlwaysPullImages, DefaultStorageClass, DefaultTolerationSeconds, DenyEscalatingExec, DenyExecOnPrivileged, EventRateLimit, ExtendedResourceToleration, ImagePolicyWebhook, LimitPodHardAntiAffinityTopology, LimitRanger, MutatingAdmissionWebhook, NamespaceAutoProvision, NamespaceExists, NamespaceLifecycle, NodeRestriction, OwnerReferencesPermissionEnforcement, PersistentVolumeClaimResize, PersistentVolumeLabel, PodNodeSelector, PodPreset, PodSecurityPolicy, PodTolerationRestriction, Priority, ResourceQuota, SecurityContextDeny, ServiceAccount, StorageObjectInUseProtection, TaintNodesByCondition, ValidatingAdmissionWebhook. (DEPRECATED: Use --enable-admission-plugins or --disable-admission-plugins instead. Will be removed in a future version.)
      --enable-admission-plugins strings       admission plugins that should be enabled in addition to default enabled ones (NamespaceLifecycle, LimitRanger, ServiceAccount, TaintNodesByCondition, Priority, DefaultTolerationSeconds, DefaultStorageClass, StorageObjectInUseProtection, PersistentVolumeClaimResize, MutatingAdmissionWebhook, ValidatingAdmissionWebhook, ResourceQuota). Comma-delimited list of admission plugins: AlwaysAdmit, AlwaysDeny, AlwaysPullImages, DefaultStorageClass, DefaultTolerationSeconds, DenyEscalatingExec, DenyExecOnPrivileged, EventRateLimit, ExtendedResourceToleration, ImagePolicyWebhook, LimitPodHardAntiAffinityTopology, LimitRanger, MutatingAdmissionWebhook, NamespaceAutoProvision, NamespaceExists, NamespaceLifecycle, NodeRestriction, OwnerReferencesPermissionEnforcement, PersistentVolumeClaimResize, PersistentVolumeLabel, PodNodeSelector, PodPreset, PodSecurityPolicy, PodTolerationRestriction, Priority, ResourceQuota, SecurityContextDeny, ServiceAccount, StorageObjectInUseProtection, TaintNodesByCondition, ValidatingAdmissionWebhook. The order of plugins in this flag does not matter.

可見,AC一共有幾十種,下面介紹一些常用的:

1、ResourceQuota和LimitRanger
ResourceQuota可以限制namespace資源用量
apiVersion: v1
kind: ResourceQuota
metadata:
  name: ns-quota-cns-test
  namespace: cns-test
spec:
  hard:
    pods: "4"
    requests.cpu: "1"
    requests.memory: 1Gi
    limits.cpu: "26"
    limits.memory: 2Gi
  scopeSelector:
    matchExpressions: 
    - operator: Exists
      scopeName: NotBestEffort
spec.hard:除了基礎的資源,還可以可以限制Pod數量
spec.scopeSelector:定義更豐富的索引能力,包括Terminating/Not Terminating、BestEffort/NotBestEffort、PriorityClass。
創建ResourceQuota後,如果用戶用超了資源,在提交Pod時,會收到一個forbidden的403錯誤,提示exceeded quota。但假如用戶提交沒有包含在這個ResourceQuota方案裡面的資源,還是能成功的。
 
在某個namespace下創建LimitRange,則會自動為該namespace下的容器添加request和limit
例如創建該LimitRange:
apiVersion: v1 
kind: LimitRange 
metadata: 
    name: mem-limit-range 
spec: 
    limits:     
    - default: 
        memory: 512Mi 
      defaultRequest:     
        memory: 256Mi 
        type: Container
會自動為所在命名空間下的容器添加256M的記憶體request和512M的記憶體limit
 
2、SecurityContextDeny
SecurityContext用於限制容器的一個行為,保證系統和其他容器的安全。從粒度上又分為以下兩種:
  Container-level Security Context:僅應用到指定的容器
  Pod-level Security Context:應用到Pod內所有容器以及Volume
該能力不是Kubernetes或者容器runtime本身的能力,而是kubernetes和runtime通過用戶的配置,最後下傳到內核里,再通過內核的機制讓其生效。
Pod級別和容器級別配置SecurityContext的例子:
apiVersion: v1
kind: Pod
metadata:
  name: security-context-demo
spec:
  securityContext:
    runAsUser: 1000
    runAsGroup: 3000
    fsGroup: 2000
  volumes:
  - name: sec-ctx-vol
    emptyDir: {}
  containers:
  - name: sec-ctx-demo
    image: busybox
    command: [ "sh", "-c", "sleep 1h" ]
    volumeMounts:
    - name: sec-ctx-vol
      mountPath: /data/demo
    securityContext:
      allowPrivilegeEscalation: false
SecurityContext設置項主要包括:
(1)通過用戶ID和組ID來控制文件訪問許可權;
(2)SELinux:通過策略配置來控制用戶或者進程對文件的訪問控制;
(3)特權容器;
(4)Capabilities:給特定進程來配置一個privileged能力;
(5)AppArmor:通過一些配置文件來控制可執行文件的一個訪問控制許可權(比如說一些埠的讀寫);
(6)對系統調用的控制;
(7)對子進程能否獲取比父親更多的許可權的一個限制
最後其實都是落到內核來控制它的一些許可權。
 
3、PodSecurityPolicy
Pod Security Policies(PSP)是應用到集群內部所有Pod以及Volume的安全策略
PSP的使用:
(1)通過在apiserver的admission-plugin參數中添加PodSecurityPolicy開啟:
(2)在集群中創建PSP策略實例,支援的控制項包括:
(3)配置策略與身份的RBAC策略綁定(大多數pod中使用的身份都是Serviceaccount)
(4)啟動PSP後admission會強制要求pod在鑒權後找到至少一個對應的策略實例,因此最好設置一個集群維度的全局策略,同時針對指定namespace配置細化策略
(5)如果同時有多個PSP滿足許可權綁定關係,優先從非mutating(不改變Pod模型的策略)的滿足策略中按照實例name名字母排序選擇第一個
 
4、ValidatingAdmissionWebhook和MutatingAdmissionWebhook
webhook就是一個HTTP回調,接收API Server發送的admissionReview請求,處理(驗證或修改)並返回admissionResponse。
使用步驟:
(1)創建ValidatingWebhookConfiguration文件和MutatingWebhookConfiguration文件:
apiVersion: admissionregistration.k8s.io/v1beta1
kind: ValidatingWebhookConfiguration
metadata:
  name: validation-kube-webhook-cfg
  namespace: paas
  labels:
    app: paas-webhook
webhooks:
  - name: nodeport.kube-webhook.cn
    clientConfig:
      service:
        name: paas-webhook-svc
        namespace: paas
        path: "/validating"
      caBundle: LS0tLS1...
    rules:
      - operations: [ "CREATE" ]
        apiGroups: ["apps", "extensions", ""]
        apiVersions: ["v1", "v1beta1"]
        resources: ["services"]
    namespaceSelector:
      matchLabels:
        paas-webhook: enabled
其中rules定義了匹配規則,當發給apiserver的請求滿足該規則的時候,apiserver就會給clientConfig中配置的service發送Admission請求。
(2)開發的webhook程式需要實現IWebHookServer介面:
type IWebHookServer interface {
    mutating(ar *v1beta1.AdmissionReview) *v1beta1.AdmissionResponse
    validating(ar *v1beta1.AdmissionReview) *v1beta1.AdmissionResponse
    Start()
    Stop()
}
例如,定義一個結構體:
type webHookServer struct {
    server *http.Server
}
實現這四個方法:
①開始
func (ws *webHookServer) Start() {
   ws.server.ListenAndServeTLS("", "")
}
②結束
func (ws *webHookServer) Stop() {
   glog.Infof("Got OS shutdown signal, shutting down wenhook server gracefully...")
   ws.server.Shutdown(context.Background())
}
在main函數中調用go ws.Start()後,可以以如下方式阻塞,等待退出訊號後再調用ws.Stop()
signalChan := make(chan os.Signal, 1)
signal.Notify(signalChan, syscall.SIGINT, syscall.SIGTERM)
<-signalChan
 
ws.Stop()
③mutating負責修改
④validating負責審核
可以用一個req := ar.Request接下請求
req.Kind是metav1.GroupVersionKind(GVK結構體)
例如req.Kind.Kind是Service時的反序列化:
var service corev1.Service
json.Unmarshal(req.Object.Raw, &service)
resourceName, resourceNamespace, objectMeta = service.Name, service.Namespace, &service.ObjectMeta
最後返回結果的數據結構:
type AdmissionResponse struct {
   UID types.UID `json:"uid" protobuf:"bytes,1,opt,name=uid"`
   Allowed bool `json:"allowed" protobuf:"varint,2,opt,name=allowed"`
   Result *metav1.Status `json:"status,omitempty" protobuf:"bytes,3,opt,name=status"`
   Patch []byte `json:"patch,omitempty" protobuf:"bytes,4,opt,name=patch"`
   PatchType *PatchType `json:"patchType,omitempty" protobuf:"bytes,5,opt,name=patchType"`
   AuditAnnotations map[string]string `json:"auditAnnotations,omitempty" protobuf:"bytes,6,opt,name=auditAnnotations"`
}
返回示例:
allowed := true
result = &metav1.Status{
   Reason: "Unauthorized nodeport",
}
return &v1beta1.AdmissionResponse{
   Allowed: allowed,
   Result:  result,
}

webhook可以做到很多事情,例如限制每個namespace使用的埠號、為每個Pod插入sidecar容器等。

 

參考資料:

[1] //kubernetes.io/docs/home/

[2] //edu.aliyun.com/roadmap/cloudnative

[3] //mritd.me/2018/08/28/kubernetes-tls-bootstrapping-with-bootstrap-token/

[4] //mritd.me/2018/01/07/kubernetes-tls-bootstrapping-note/

[5] 鄭東旭《Kubernetes源碼剖析》