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

揚羽流風發表於2020-08-23
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": "https://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="https://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] https://kubernetes.io/docs/home/

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

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

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

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

相關文章