1. 程式人生 > 實用技巧 >26 | 基於角色的許可權控制:RBAC

26 | 基於角色的許可權控制:RBAC

你好,我是張磊。今天我和你分享的主題是:基於角色的許可權控制之 RBAC。
在前面的文章中,我已經為你講解了很多種 Kubernetes 內建的編排物件,以及對應的控制器模式的實現原理。此外,我還剖析了自定義 API 資源型別和控制器的編寫方式。
這時候,你可能已經冒出了這樣一個想法:控制器模式看起來好像也不難嘛,我能不能自己寫一個編排物件呢?
答案當然是可以的。而且,這才是 Kubernetes 專案最具吸引力的地方。
畢竟,在網際網路級別的大規模叢集裡,Kubernetes 內建的編排物件,很難做到完全滿足所有需求。所以,很多實際的容器化工作,都會要求你設計一個自己的編排物件,實現自己的控制器模式。
在 Kubernetes 專案裡,我們可以基於外掛機制來完成這些工作,而完全不需要修改任何一行程式碼。

不過,你要通過一個外部外掛,在 Kubernetes 裡新增和操作 API 物件,那麼就必須先了解一個非常重要的知識:RBAC。

RBAC

我們知道,Kubernetes 中所有的 API 物件,都儲存在 Etcd 裡。可是,對這些 API 物件的操作,卻一定都是通過訪問 kube-apiserver 實現的。其中一個非常重要的原因,就是你需要 APIServer 來幫助你做授權工作。
而在 Kubernetes 專案中,負責完成授權(Authorization)工作的機制,就是 RBAC:基於角色的訪問控制(Role-Based Access Control)。
如果你直接檢視 Kubernetes 專案中關於 RBAC 的文件的話,可能會感覺非常複雜。但實際上,等到你用到這些 RBAC 的細節時,再去查閱也不遲。
而在這裡,我只希望你能明確三個最基本的概念。
  1. Role:角色,它其實是一組規則,定義了一組對 Kubernetes API 物件的操作許可權。
  2. Subject:被作用者,既可以是“人”,也可以是“機器”,也可以使你在 Kubernetes 裡定義的“使用者”。
  3. RoleBinding:定義了“被作用者”和“角色”的繫結關係。
而這三個概念,其實就是整個 RBAC 體系的核心所在。我先來講解一下 Role。

Role

實際上,Role 本身就是一個 Kubernetes 的 API 物件,定義如下所示:
role.yaml

kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  namespace: mynamespace
  name: example-role
rules:
- apiGroups: [""]
  resources: ["pods"]
  verbs: ["get", "watch", "list"]
首先,這個 Role 物件指定了它能產生作用的 Namepace 是:mynamespace。
Namespace 是 Kubernetes 專案裡的一個邏輯管理單位。不同 Namespace 的 API 物件,在通過 kubectl 命令進行操作的時候,是互相隔離開的。
比如,kubectl get pods -n mynamespace。
當然,這僅限於邏輯上的“隔離”,Namespace 並不會提供任何實際的隔離或者多租戶能力。而在前面文章中用到的大多數例子裡,我都沒有指定 Namespace,那就是使用的是預設 Namespace:default。
然後,這個 Role 物件的 rules 欄位,就是它所定義的許可權規則。在上面的例子裡,這條規則的含義就是:允許“被作用者”,對 mynamespace 下面的 Pod 物件,進行 GET、WATCH 和 LIST 操作。
那麼,這個具體的“被作用者”又是如何指定的呢?這就需要通過 RoleBinding 來實現了。
當然,RoleBinding 本身也是一個 Kubernetes 的 API 物件。它的定義如下所示:
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: example-rolebinding
  namespace: mynamespace
subjects:
- kind: User
  name: example-user
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: Role
  name: example-role
  apiGroup: rbac.authorization.k8s.io
可以看到,這個 RoleBinding 物件裡定義了一個 subjects 欄位,即“被作用者”。它的型別是 User,即 Kubernetes 裡的使用者。這個使用者的名字是 example-user。
可是,在 Kubernetes 中,其實並沒有一個叫作“User”的 API 物件。而且,我們在前面和部署使用 Kubernetes 的流程裡,既不需要 User,也沒有建立過 User。
這個 User 到底是從哪裡來的呢?
實際上,Kubernetes 裡的“User”,也就是“使用者”,只是一個授權系統裡的邏輯概念。它需要通過外部認證服務,比如 Keystone,來提供。或者,你也可以直接給 APIServer 指定一個使用者名稱、密碼檔案。那麼 Kubernetes 的授權系統,就能夠從這個檔案裡找到對應的“使用者”了。當然,在大多數私有的使用環境中,我們只要使用 Kubernetes 提供的內建“使用者”,就足夠了。這部分知識,我後面馬上會講到。
接下來,我們會看到一個 roleRef 欄位。正是通過這個欄位,RoleBinding 物件就可以直接通過名字,來引用我們前面定義的 Role 物件(example-role),從而定義了“被作用者(Subject)”和“角色(Role)”之間的繫結關係。
需要再次提醒的是,Role 和 RoleBinding 物件都是 Namespaced 物件(NamespacedObject),它們對許可權的限制規則僅在它們自己的 Namespace 內有效,roleRef 也只能引用當前 Namespace 裡的 Role 物件。
那麼,對於非 Namespaced(Non-namespaced)物件(比如:Node),或者,某一個 Role 想要作用於所有的 Namespace 的時候,我們又該如何去做授權呢?
這時候,我們就必須要使用 ClusterRole 和 ClusterRoleBinding 這兩個組合了。這兩個 API 物件的用法跟 Role 和 RoleBinding 完全一樣。只不過,它們的定義裡,沒有了 Namespace 欄位,如下所示:
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: example-clusterrole
rules:
- apiGroups: [""]
  resources: ["pods"]
  verbs: ["get", "watch", "list"]
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: example-clusterrolebinding
subjects:
- kind: User
  name: example-user
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: ClusterRole
  name: example-clusterrole
  apiGroup: rbac.authorization.k8s.io
上面的例子裡的 ClusterRole 和 ClusterRoleBinding 的組合,意味著名叫 example-user 的使用者,擁有對所有 Namespace 裡的 Pod 進行 GET、WATCH 和 LIST 操作的許可權。
更進一步地,在 Role 或者 ClusterRole 裡面,如果要賦予使用者 example-user 所有許可權,那你就可以給它指定一個 verbs 欄位的全集,如下所示:verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]這些就是當前 Kubernetes(v1.11)裡能夠對 API 物件進行的所有操作了。
類似的,Role 物件的 rules 欄位也可以進一步細化。比如,你可以只針對某一個具體的物件進行許可權設定,如下所示:
rules:
- apiGroups: [""]
  resources: ["configmaps"]
  resourceNames: ["my-config"]
  verbs: ["get"]
這個例子就表示,這條規則的“被作用者”,只對名叫“my-config”的 ConfigMap 物件,有進行 GET 操作的許可權。
而正如我前面介紹過的,在大多數時候,我們其實都不太使用“使用者”這個功能,而是直接使用 Kubernetes 裡的“內建使用者”。
這個由 Kubernetes 負責管理的“內建使用者”,正是我們前面曾經提到過的:ServiceAccount。
接下來,我通過一個具體的例項來為你講解一下為 ServiceAccount 分配許可權的過程。

ServiceAccount

首先,我們要定義一個 ServiceAccount。它的 API 物件非常簡單,如下所示:
svc-account.yaml

apiVersion: v1
kind: ServiceAccount
metadata:
  namespace: mynamespace
  name: example-sa
可以看到,一個最簡單的 ServiceAccount 物件只需要 Name 和 Namespace 這兩個最基本的欄位。

RoleBinding

然後,我們通過編寫 RoleBinding 的 YAML 檔案,來為這個 ServiceAccount 分配許可權:
role-binding.yaml

kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: example-rolebinding
  namespace: mynamespace
subjects:
- kind: ServiceAccount
  name: example-sa
  namespace: mynamespace
roleRef:
  kind: Role
  name: example-role
  apiGroup: rbac.authorization.k8s.io
可以看到,在這個 RoleBinding 物件裡,subjects 欄位的型別(kind),不再是一個 User,而是一個名叫 example-sa 的 ServiceAccount。而 roleRef 引用的 Role 物件,依然名叫 example-role,也就是我在這篇文章一開始定義的 Role 物件。
接著,我們用 kubectl 命令建立這三個物件:
$ kubectl create -f svc-account.yaml
$ kubectl create -f role-binding.yaml
$ kubectl create -f role.yaml
然後,我們來檢視一下這個 ServiceAccount 的詳細資訊:
$ kubectl get sa -n mynamespace -o yaml
- apiVersion: v1
  kind: ServiceAccount
  metadata:
    creationTimestamp: 2018-09-08T12:59:17Z
    name: example-sa
    namespace: mynamespace
    resourceVersion: "409327"
    ...
  secrets:
  - name: example-sa-token-vmfg6
可以看到,Kubernetes 會為一個 ServiceAccount 自動建立並分配一個 Secret 物件,即:上述 ServiceAcount 定義裡最下面的 secrets 欄位。
這個 Secret,就是這個 ServiceAccount 對應的、用來跟 APIServer 進行互動的授權檔案,我們一般稱它為:Token。Token 檔案的內容一般是證書或者密碼,它以一個 Secret 物件的方式儲存在 Etcd 當中。
這時候,使用者的 Pod,就可以宣告使用這個 ServiceAccount 了,比如下面這個例子:
apiVersion: v1
kind: Pod
metadata:
  namespace: mynamespace
  name: sa-token-test
spec:
  containers:
  - name: nginx
    image: nginx:1.7.9
  serviceAccountName: example-sa
在這個例子裡,我定義了 Pod 要使用的要使用的 ServiceAccount 的名字是:example-sa。
等這個 Pod 執行起來之後,我們就可以看到,該 ServiceAccount 的 token,也就是一個 Secret 物件,被 Kubernetes 自動掛載到了容器的 /var/run/secrets/kubernetes.io/serviceaccount 目錄下,如下所示:
$ kubectl describe pod sa-token-test -n mynamespace
Name:               sa-token-test
Namespace:          mynamespace
...
Containers:
  nginx:
    ...
    Mounts:
      /var/run/secrets/kubernetes.io/serviceaccount from example-sa-token-vmfg6 (ro)
這時候,我們可以通過 kubectl exec 檢視到這個目錄裡的檔案:
$ kubectl exec -it sa-token-test -n mynamespace -- /bin/bash
root@sa-token-test:/# ls /var/run/secrets/kubernetes.io/serviceaccount
ca.crt namespace  token
如上所示,容器裡的應用,就可以使用這個 ca.crt 來訪問 APIServer 了。更重要的是,此時它只能夠做 GET、WATCH 和 LIST 操作。因為 example-sa 這個 ServiceAccount 的許可權,已經被我們綁定了 Role 做了限制。
此外,我在第 15 篇文章《深入解析 Pod 物件(二):使用進階》中曾經提到過,如果一個 Pod 沒有宣告 serviceAccountName,Kubernetes 會自動在它的 Namespace 下建立一個名叫 default 的預設 ServiceAccount,然後分配給這個 Pod。
但在這種情況下,這個預設 ServiceAccount 並沒有關聯任何 Role。也就是說,此時它有訪問 APIServer 的絕大多數許可權。當然,這個訪問所需要的 Token,還是預設 ServiceAccount 對應的 Secret 物件為它提供的,如下所示。
$kubectl describe sa default
Name:                default
Namespace:           default
Labels:              <none>
Annotations:         <none>
Image pull secrets:  <none>
Mountable secrets:   default-token-s8rbq
Tokens:              default-token-s8rbq
Events:              <none>
 
$ kubectl get secret
NAME                  TYPE                                  DATA      AGE
kubernetes.io/service-account-token   3         82d
 
$ kubectl describe secret default-token-s8rbq
Name:         default-token-s8rbq
Namespace:    default
Labels:       <none>
Annotations:  kubernetes.io/service-account.name=default
              kubernetes.io/service-account.uid=ffcb12b2-917f-11e8-abde-42010aa80002
 
Type:  kubernetes.io/service-account-token
 
Data
====
ca.crt:     1025 bytes
namespace:  7 bytes
token:      <TOKEN 資料 >
可以看到,Kubernetes 會自動為預設 ServiceAccount 建立並繫結一個特殊的 Secret:它的型別是kubernetes.io/service-account-token;它的 Annotation 欄位,聲明瞭kubernetes.io/service-account.name=default,即這個 Secret 會跟同一 Namespace 下名叫 default 的 ServiceAccount 進行繫結。
所以,在生產環境中,我強烈建議你為所有 Namespace 下的預設 ServiceAccount,繫結一個只讀許可權的 Role。這個具體怎麼做,就當做思考題留給你了。

Group

除了前面使用的“使用者”(User),Kubernetes 還擁有“使用者組”(Group)的概念,也就是一組“使用者”的意思。如果你為 Kubernetes 配置了外部認證服務的話,這個“使用者組”的概念就會由外部認證服務提供。
而對於 Kubernetes 的內建“使用者”ServiceAccount 來說,上述“使用者組”的概念也同樣適用。實際上,一個 ServiceAccount,在 Kubernetes 裡對應的“使用者”的名字是:
system:serviceaccount:<ServiceAccount 名字 >而它對應的內建“使用者組”的名字,就是:
system:serviceaccounts:<Namespace 名字 >這兩個對應關係,請你一定要牢記。
比如,現在我們可以在 RoleBinding 裡定義如下的 subjects:
subjects:
- kind: Group
  name: system:serviceaccounts:mynamespace
  apiGroup: rbac.authorization.k8s.io
這就意味著這個 Role 的許可權規則,作用於 mynamespace 裡的所有 ServiceAccount。這就用到了“使用者組”的概念。
而下面這個例子:
subjects:
- kind: Group
  name: system:serviceaccounts
  apiGroup: rbac.authorization.k8s.io
就意味著這個 Role 的許可權規則,作用於整個系統裡的所有 ServiceAccount。

ClusterRole

最後,值得一提的是,在 Kubernetes 中已經內建了很多個為系統保留的 ClusterRole,它們的名字都以 system: 開頭。你可以通過 kubectl get clusterroles 檢視到它們。
一般來說,這些系統 ClusterRole,是繫結給 Kubernetes 系統元件對應的 ServiceAccount 使用的。
比如,其中一個名叫 system:kube-scheduler 的 ClusterRole,定義的許可權規則是 kube-scheduler(Kubernetes 的排程器元件)執行所需要的必要許可權。你可以通過如下指令檢視這些許可權的列表:
$ kubectl describe clusterrole system:kube-scheduler
Name:         system:kube-scheduler
...
PolicyRule:
  Resources                    Non-Resource URLs Resource Names    Verbs
  ---------                    -----------------  --------------    -----
...
  services                     []                 []                [get list watch]
  replicasets.apps             []                 []                [get list watch]
  statefulsets.apps            []                 []                [get list watch]
  replicasets.extensions       []                 []                [get list watch]
  poddisruptionbudgets.policy  []                 []                [get list watch]
  pods/status                  []                 []                [patch update]
這個 system:kube-scheduler 的 ClusterRole,就會被繫結給 kube-system Namesapce 下名叫 kube-scheduler 的 ServiceAccount,它正是 Kubernetes 排程器的 Pod 宣告使用的 ServiceAccount。
除此之外,Kubernetes 還提供了四個預先定義好的 ClusterRole 來供使用者直接使用:
  1. cluster-admin;
  2. admin;
  3. edit;
  4. view。

通過它們的名字,你應該能大致猜出它們都定義了哪些許可權。比如,這個名叫 view 的 ClusterRole,就規定了被作用者只有 Kubernetes API 的只讀許可權。
而我還要提醒你的是,上面這個 cluster-admin 角色,對應的是整個 Kubernetes 專案中的最高許可權(verbs=*),如下所示:
$ kubectl describe clusterrole cluster-admin -n kube-system
Name:         cluster-admin
Labels:       kubernetes.io/bootstrapping=rbac-defaults
Annotations:  rbac.authorization.kubernetes.io/autoupdate=true
PolicyRule:
  Resources  Non-Resource URLs Resource Names  Verbs
  ---------  -----------------  --------------  -----
  *.*        []                 []              [*]
             [*]                []              [*]
所以,請你務必要謹慎而小心地使用 cluster-admin。

總結

在今天這篇文章中,我主要為你講解了基於角色的訪問控制(RBAC)。
其實,你現在已經能夠理解,所謂角色(Role),其實就是一組許可權規則列表。而我們分配這些許可權的方式,就是通過建立 RoleBinding 物件,將被作用者(subject)和許可權列表進行繫結。
另外,與之對應的 ClusterRole 和 ClusterRoleBinding,則是 Kubernetes 叢集級別的 Role 和 RoleBinding,它們的作用範圍不受 Namespace 限制。
而儘管許可權的被作用者可以有很多種(比如,User、Group 等),但在我們平常的使用中,最普遍的用法還是 ServiceAccount。所以,Role + RoleBinding + ServiceAccount 的許可權分配方式是你要重點掌握的內容。我們在後面編寫和安裝各種外掛的時候,會經常用到這個組合。
思考題請問,如何為所有 Namespace 下的預設 ServiceAccount(default ServiceAccount),繫結一個只讀許可權的 Role 呢?請你提供 ClusterRoleBinding(或者 RoleBinding)的 YAML 檔案。
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: readonly-all-default
subjects:
  - kind: ServiceAccount
    name: system.serviceaccount.default
roleRef:
  kind: ClusterRole
  name: view
  apiGroup: rbac.authorization.k8s.io

前面的朋友寫的問題在於,default應該是serciveacount