RBAC (Role-Based Access Control) 基於角色的存取控制 (RBAC) 是一種控制資源存取的方法。使用者透過角色與作為安全目標的系統資源相關聯,並且使用者和系統資源之間的關係用於確定是否允許存取。
我們可以從公司制度理解這個概念:身為一個外人 (User),在公司是沒有任何權利,寸步難行。一旦你進入公司 (Binding) 成為管理人員職位 (Role),你便獲得了這個職位的權利,比如員工資料的查詢、修改權限等等。
RBAC 通過提供精細的訪問控制、簡化權限管理、提高安全性、支持多租戶環境、增強合規性和審計能力、提供靈活性和可擴展性,以及減少錯誤和提高運營效率,為系統的安全性和管理帶來了顯著的好處。
在 Kubernetes 裡也有 RBAC 方法,RBAC API 聲明了四種 Kubernetes 對象:
RBAC 的 Role 或 ClusterRole 中包含一組代表相關權限的規則。 這些權限是純粹累加的(不存在拒絕某操作的規則)。
Role 與 ClusterRole 很相似,但 Role 只能用來定義特定 namespace 內的資源操作,而 ClusterRole 則可以定義整個集群範圍內的資源操作。簡單來說:
查詢資源做用於 namespace 還是 Cluster,可以用以下指令查詢:
# 做用於 Namespace-scoped 的資源
kubectl api-resources --namespaced
# 做用於 Cluster-scoped 的資源
kubectl api-resources --namespaced=false
Role 示例
這是一個位於 default
namespace 的 Role 的示例,可用來授予對 Pod 的讀存取權:
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: default
name: pod-reader
rules:
- apiGroups: [""] # "" 標明 core API 組
resources: ["pods"]
verbs: ["get", "watch", "list"]
ClusterRole 示例
這是一個 ClusterRole 的示例,可用來授予對 Secret 的讀存取權:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
# "namespace" 被忽略,因為 ClusterRoles 不受 namespace 限制
name: secret-reader
rules:
- apiGroups: [""]
# 在 HTTP 層面,用來訪問 Secret 資源的名稱為 "secrets"
resources: ["secrets"]
verbs: ["get", "watch", "list"]
不過上面我們也提到過,ClusterRole 創建的讀存權限,實際上也可以授予給特定 namespace,這取決於我們使用 RoleBinding
還是 ClusterRoleBinding
。
角色绑定(Role Binding)是將角色中定義的權限賦予一個或者一組使用者。 它包含若干主體(Subject)(使用者、組或服務帳戶)的列表和對這些主體所獲得的角色的引用。 RoleBinding 在指定的 namespace 中執行授權,而 ClusterRoleBinding 在叢集範圍執行授權。
一個 RoleBinding 可以引用同一的 namespace 中的任何 Role。 或者,一個 RoleBinding 可以引用某 ClusterRole 並將該 ClusterRole 繫結到 RoleBinding 所在的 namespace 。 如果你希望將某 ClusterRole 繫結到叢集中所有 namespace,你要使用 ClusterRoleBinding。
用表格將 RoleBinding
和 ClusterRoleBinding
的內容整理如下:
特性 | RoleBinding | ClusterRoleBinding |
---|---|---|
角色定義的權限賦予 | 將角色中定義的權限賦予一個或者一組使用者 | 同左 |
主體(Subject)列表 | 包含使用者、組或服務帳戶 | 同左 |
授權範圍 | 在指定的 namespace 中執行授權 | 在叢集範圍內執行授權 |
引用 Role | 可以引用同一 namespace 中的任何 Role | 不適用 |
引用 ClusterRole | 可以引用 ClusterRole 並繫結到 RoleBinding 所在的 namespace | 繫結 ClusterRole 到叢集中所有 namespace |
RoleBinding 示例
下面的例子中的 RoleBinding 將 pod-reader
Role 授予在 default
namespace 中的使用者 jane
。 這樣,使用者 jane
就具有了讀取 default
namespace 中所有 Pod 的權限。
apiVersion: rbac.authorization.k8s.io/v1
# 此角色繫結允許 "jane" 讀取 "default" namespace 中的 Pod
# 你需要在該 namespace 中有一個名為 “pod-reader” 的 Role
kind: RoleBinding
metadata:
name: read-pods
namespace: default
subjects:
# 你可以指定不止一個“subject(主體)”
- kind: User
name: jane # "name" 是區分大小寫的
apiGroup: rbac.authorization.k8s.io
roleRef:
# "roleRef" 指定與某 Role 或 ClusterRole 的繫結關係
kind: Role # 此欄位必須是 Role 或 ClusterRole
name: pod-reader # 此欄位必須與你要繫結的 Role 或 ClusterRole 的名稱匹配
apiGroup: rbac.authorization.k8s.io
RoleBinding 也可以引用 ClusterRole,以將對應 ClusterRole 中定義的存取權授予 RoleBinding 所在 namespace 的資源。這種引用使得你可以跨整個叢集定義一組通用的角色, 之後在多個 namespace 中復用。
例如,儘管下面的 RoleBinding 引用的是一個 ClusterRole,dave
只能訪問 development
namespace 中的 Secret 對象,因為 RoleBinding 所在的 namespace 是 development
。
apiVersion: rbac.authorization.k8s.io/v1
# 此角色繫結使得使用者 "dave" 能夠讀取 "development" namespace 中的 Secret
# 你需要一個名為 "secret-reader" 的 ClusterRole
kind: RoleBinding
metadata:
name: read-secrets
# RoleBinding 的 namespace 決定了存取權的授予範圍。
# 這裡隱含授權僅在 "development" namespace 內的存取權。
namespace: development
subjects:
- kind: User
name: dave # 'name' 是區分大小寫的
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: secret-reader
apiGroup: rbac.authorization.k8s.io
ClusterRoleBinding 示例
要跨整個叢集完成存取權的授予,你可以使用一個 ClusterRoleBinding。 下面的 ClusterRoleBinding 允許 "manager" 組內的所有使用者訪問任何 namespace 中的 Secret。
apiVersion: rbac.authorization.k8s.io/v1
# 此叢集角色繫結允許 “manager” 組中的任何人訪問任何 namespace 中的 Secret 資源
kind: ClusterRoleBinding
metadata:
name: read-secrets-global
subjects:
- kind: Group
name: manager # 'name' 是區分大小寫的
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: secret-reader
apiGroup: rbac.authorization.k8s.io
建立了繫結之後,你不能再修改繫結對象所引用的 Role 或 ClusterRole,也就是 roleRef
欄位。如果你想要改變現有繫結對象中 roleRef
欄位的內容,必須刪除重新建立繫結對象。
大多數資源由代表其名字的字串表示,例如 pods,就像它們出現在相關 API endpoint 的 URL 中一樣。然而,有一些 Kubernetes API 還包含了"子資源",比如 pod 的 logs。在 Kubernetes 中,pod logs endpoint 的 URL 格式為:
GET /api/v1/namespaces/{namespace}/pods/{name}/log
在這種情況下,"pods" 是 namespace 資源,而 "log" 是 pods 的子資源。為了在 RBAC 中表示出這一點,我們需要使用斜線來劃分資源 與子資源。如果需要角色繫結主體讀取 pods 以及 pod log,需要這樣定義角色:
kind: Role
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
namespace: default
name: pod-and-pod-logs-reader
rules:
- apiGroups: [""]
resources: ["pods", "pods/log"]
verbs: ["get", "list"]
另外還可以通過 resourceNames
欄位針對特定資源賦予權限。例如,如果需要限定一個角色繫結主體只能 "get" 或者 "update" 一個 configmap 時,您可以定義以下角色:
kind: Role
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
namespace: default
name: configmap-updater
rules:
- apiGroups: [""]
resources: ["configmap"]
resourceNames: ["my-configmap"]
verbs: ["update", "get"]
值得注意的是,如果設定了 resourceNames
,則請求所使用的動詞不能是 list、watch、create 或者 deletecollection。 由於資源名不會出現在 create、list、watch 和 deletecollection 等 API 請求的 URL 中。
kubectl create serviceaccount demosa
apiVersion: v1
kind: Pod
metadata:
name: client
spec:
serviceAccount: demosa
containers:
- name: client
image: nginx
kubectl describe pod client
結果如下
Name: client
Namespace: default
Priority: 0
Service Account: demosa
[...]
Containers:
client:
[...]
Mounts:
/var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-pbz28 (ro)
[...]
kubectl exec client -- ls -l /var/run/secrets/kubernetes.io/serviceaccount
結果如下
total 0
lrwxrwxrwx 1 root root 13 Aug 7 10:18 ca.crt -> ..data/ca.crt
lrwxrwxrwx 1 root root 16 Aug 7 10:18 namespace -> ..data/namespace
lrwxrwxrwx 1 root root 12 Aug 7 10:18 token -> ..data/token
ca.crt
是一個 CA 憑證,用於驗證 Kubernetes API 伺服器的 SSL/TLS 憑證。Pod 在與 Kubernetes API 進行安全通信時,可以使用這個憑證來確保通信的安全性和完整性。
namespace
文件包含了 Pod 所在的 Kubernetes namespace 的名稱。該名稱用於在 API 請求中識別 Pod 的作用範圍。
token
是一個 Bearer Token (JWT 格式),用於 Pod 與 Kubernetes API 伺服器進行身份驗證。當 Pod 需要與 Kubernetes API 互動(例如讀取 ConfigMap、Secrets,或查詢叢集狀態)時,這個 Token 會被用來進行身份認證。
使用 JSON Web Tokens 解碼 token 內容,結果如下:
{
"aud": [
"https://kubernetes.default.svc.cluster.local"
],
"exp": 1754561936,
"iat": 1723025936,
"iss": "https://kubernetes.default.svc.cluster.local",
"jti": "32b625d5-40f1-4d2a-ba0f-88691886e6a9",
"kubernetes.io": {
"namespace": "default",
"node": {
"name": "wslkind-worker2",
"uid": "012ff8ef-8a88-4289-8fc4-0751ea78030f"
},
"pod": {
"name": "client",
"uid": "15396187-91f6-4ccc-9634-a91d4daed87e"
},
"serviceaccount": {
"name": "demosa",
"uid": "4f4bbe18-f38f-42bc-b0ca-aa379fdc4a45"
},
"warnafter": 1723029543
},
"nbf": 1723025936,
"sub": "system:serviceaccount:default:demosa"
}
kubectl exec -it client -- sh
TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
CACERT=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
curl --cacert $CACERT -X GET https://kubernetes.default.svc.cluster.local/api
結果如下
{
"kind": "Status",
"apiVersion": "v1",
"metadata": {},
"status": "Failure",
"message": "forbidden: User \"system:anonymous\" cannot get path \"/api\"",
"reason": "Forbidden",
"details": {},
"code": 403
}
我們已經成功連線到 api service,不過我們沒通過驗證。
curl --cacert $CACERT --header "Authorization: Bearer $TOKEN" -X GET https://kubernetes.default.svc.cluster.local/api
結果如下
{
"kind": "APIVersions",
"versions": [
"v1"
],
"serverAddressByClientCIDRs": [
{
"clientCIDR": "0.0.0.0/0",
"serverAddress": "172.18.0.3:6443"
}
]
}
我們已經成功通過驗證,取得回應。
雖然我們已經可以訪問 K8s api service,但我們沒有任何資源的操作權限,因為我們沒有綁定任何角色,這裡我們可以檢驗 Service Account 是否有權限對特定資源進行特定操作:
curl --cacert $CACERT --header "Authorization: Bearer $TOKEN" -X GET https://kubernetes.default.svc.cluster.local/api/v1/namespaces/default/pods
結果如下
{
"kind": "Status",
"apiVersion": "v1",
"metadata": {},
"status": "Failure",
"message": "pods is forbidden: User \"system:serviceaccount:default:demosa\" cannot list resource \"pods\" in API group \"\" in the namespace \"default\"",
"reason": "Forbidden",
"details": {
"kind": "pods"
},
"code": 403
}
另一種驗證方法:透過 kubectl auth can-i
指令:
kubectl auth can-i list pods --as=system:serviceaccount:default:demosa
---
no
現在我們要將 demosa
跟 Role 綁定,使 demosa
有權限完成對特定資源的操作。
# 建立角色 demorole,授予可以 get, list 資源 pods 的權限
kubectl create role demorole --verb=get,list --resource=pods
# 建立角色關聯 demorolebinding,將角色 demorole 與服務帳號 demosa 關聯
kubectl create rolebinding demorolebinding --role=demorole --serviceaccount=default:demosa
demosa
的操作權限kubectl auth can-i list pods --as=system:serviceaccount:default:demosa
---
yes
我們試著從 Pod 內部直接訪問 api service。
kubectl exec -it client -- sh
TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
CACERT=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
curl --cacert $CACERT --header "Authorization: Bearer $TOKEN" -X GET https://kubernetes.default.svc.cluster.local/api/v1/namespaces/default/pods
結果如下
{
"kind": "PodList",
"apiVersion": "v1",
"metadata": {
"resourceVersion": "737483"
},
"items": [...]
}
kubectl delete rolebinding demorolebinding
kubectl delete role demorole
kubectl delete serviceaccount demosa
我們會在這一章節實作,如何從頭建立一個新的 Normal User,然後透過 RBAC 賦予使用者合適的權限管理叢集。
我們需要為 Client Certificates 作為驗證手段,因此請在客戶端事先安裝 openssl
。
使用者私鑰
openssl genrsa -out demouser.key 2048
使用者私鑰
產生憑證簽署請求
(CSR)。openssl req -new -key demouser.key -out demouser.csr -subj "/CN=demouser/O=rd"
K8s 會查看 X.509 證書中的主體資訊 (Subject),將 CN (Common Name) 當作 UserName,O (Organization) 當作 Group。
接下來我們要使用 K8s 的 CA 憑證來簽署 CSR,有兩個做法。
cat demouser.csr | base64 | tr -d "\n"
組態文件: demouser-csr.yaml
apiVersion: certificates.k8s.io/v1
kind: CertificateSigningRequest
metadata:
name: demouser
spec:
groups:
- system:authenticated
# 將 Base64 加密過的 CSR 貼在這裡
request: <CSR>
signerName: kubernetes.io/kube-apiserver-client
usages:
- client auth
kubectl apply -f demouser-csr.yaml
CertificateSigningRequest
詳細內容kubectl describe certificatesigningrequests demouser
---
Name: demouser
Labels: <none>
Annotations: <ignore>
CreationTimestamp: Thu, 08 Aug 2024 02:05:29 +0800
Requesting User: kubernetes-admin
Signer: kubernetes.io/kube-apiserver-client
Status: Pending
Subject:
Common Name: demouser
Serial Number:
Events: <none>
可以看到登記的使用者是 CN 定義的 demouser
,建立請求者是 kubernetes-admin
這個我們正在使用的預設 User。
目前這個請求狀態還在 Pending 中,接下來要使用 kubectl 命令批准這個請求。
CertificateSigningRequest
kubectl certificate approve demouser
kubectl describe certificatesigningrequests demouser
---
Name: demouser
Labels: <none>
Annotations: <ignore>
CreationTimestamp: Thu, 08 Aug 2024 02:05:29 +0800
Requesting User: kubernetes-admin
Signer: kubernetes.io/kube-apiserver-client
Status: Approved,Issued
Subject:
Common Name: demouser
Serial Number:
Events: <none>
這樣我們就算完成了簽署。接下來我們要把使用者公鑰
提取出來。
使用者公鑰
,並將其用 Base64 解碼,輸出為 demouser.crt
檔案kubectl get certificatesigningrequests demouser -o jsonpath='{.status.certificate}' | base64 --decode > demouser.crt
使用者公鑰
的內容openssl x509 -noout -in demouser.crt -text
結果如下
Certificate:
Data:
[...]
Issuer: CN = kubernetes
Validity
Not Before: Aug 7 21:14:41 2024 GMT
Not After : Aug 7 21:14:41 2025 GMT
Subject: CN = demouser
[...]
不透過叢集簽署,我們需要取得叢集的 CA 私鑰和證書。
一般來說我們需要進入叢集任一節點中取得位於路徑 /etc/kubernetes/pki
下的 ca.crt
, ca.key
檔案。但我們的叢集節點等於 docker 容器,可以簡單的透過 docker cp
指令下載檔案 。
ca.crt
和 ca.key
檔案docker cp wslkind-control-plane:/etc/kubernetes/pki/ca.key ./ca.key
docker cp wslkind-control-plane:/etc/kubernetes/pki/ca.crt ./ca.crt
現在我們在本地的目錄應該會有以下 4 個檔案: ca.key
, ca.crt
, demouser.key
, demouser.csr
demouser.crt
openssl x509 -req -in demouser.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out demouser.crt -days 365
有了使用者證書後,接下來要在 kubeconfig
建立對應的 User
和 Context
,才能在 kubectl
使用。
kubectl config set-credentials demouser \
--client-certificate=demouser.crt \
--client-key=demouser.key \
--embed-certs=true
kubectl config get-users
---
NAME
demouser
kind-wslkind
kubectl config view --minify -o jsonpath="{.clusters[0].name}"
---
kind-wslkind
kubectl config set-context demo --user=demouser --cluster=kind-wslkind
kubectl config get-contexts
---
CURRENT NAME CLUSTER AUTHINFO NAMESPACE
demo kind-wslkind demouser
* kind-wslkind kind-wslkind kind-wslkind
儘管我們成功的建立 User 和 Context,但我們並沒有賦予任何權限給 demouser
,因此我們只能訪問叢集,而無法進行任何資源。我們可以測試一下:
kubectl config use-context demo
kubectl get node
---
Error from server (Forbidden): nodes is forbidden: User "demouser" cannot list resource "nodes" in API group "" at the cluster scope
現在我們要為 demouser
加上權限,使它有權限完成對資源的操作
kubectl config use-context kind-wslkind
# 建立允許只讀 pod 權限的叢集角色,用 rolebinding 將角色與使用者綁定,並指定 namespace kube-system
kubectl create clusterrole pod-viewer --verb=get,list --resource=pods
kubectl create rolebinding demouser-pod-viewer-binding --clusterrole=pod-viewer --user=demouser --namespace=kube-system
---
# 建立允許只讀 node 權限的叢集角色,用 clusterrolebinding 將角色與使用者綁定
kubectl create clusterrole node-viewer --verb=get,list --resource=nodes
kubectl create clusterrolebinding demouser-node-viewer-binding --clusterrole=node-viewer --user=demouser
---
# 建立允許只讀 clusterroles 權限的叢集角色,用 clusterrolebinding 將角色與 Group rd 綁定
kubectl create clusterrole node-viewer --verb=get,list --resource=clusterroles
kubectl create clusterrolebinding demouser-node-viewer-binding --clusterrole=node-viewer --group=rd
kubectl auth can-i get pods -n default --as=demouser
no
---
kubectl auth can-i get pods -n kube-system --as=demouser
yes
---
kubectl auth can-i get nodes --all-namespaces --as=demouser
yes
---
kubectl auth can-i get clusterroles --all-namespaces --as=demouser
no
---
kubectl auth can-i get clusterroles --all-namespaces --as=whatever --as-group=rd
yes
kubectl config use-context kind-wslkind
kubectl delete clusterrolebinding demouser-node-viewer-binding
kubectl delete rolebinding demouser-pod-viewer-binding -n kube-system
kubectl delete clusterrole pod-viewer node-viewer
kubectl config unset contexts.demo
kubectl config unset users.demouser
到今天為止,本系列教學的本篇算是告一段落。感謝各位讀者一路陪伴完成這系列的 Kubernetes 教學。希望大家在學習後,能夠勇敢地嘗試使用這些工具,解決實際問題,並將所學延伸到更廣的應用場景中。
Kubernetes 是一個充滿彈性和可能性的工具,只有透過實踐和探索,才能真正掌握它的力量。如果你在實作過程中有遇到挑戰,請不要害怕去尋找解決方案,這也是成長的重要一環。
未來我有新的心得或發現,也會以番外篇的形式,在部落格持續更新,與大家分享。希望我們能一同進步、探索更多可能性!
歡迎大家來我的部落格看看其他文章:Vincent's Blog