Introduction

在互联网的大规模集群里,Kubernetes内置的编排对象,很难做到完全满足所有需求。所以,很多实际的容器化工作,都会要求设计自定义编排对象,实现自定义控制器模式。
在Kubernetes中,可以基于插件机制来完成这些工作,而完全不需要修改任何和一行代码。
Kubernetes中所有的API对象,都保存在Etcd里。可是,对这些API对象的操作,却一定都是通过访问kube-apiserver实现的。其中一个非常重要的原因,就是需要APIServer来帮助实现授权。
在Kubernetes中,负责完成授权(Authorization)工作的机制,就是RBAC:基于角色的访问控制(Role-based Access Control)。

  • Role:角色,它其实是一组规则,定义了一组对Kubernetes API对象的操作权限。
  • Subject:被作用者,既可以是“人”,也可以是“机器”,也可以是Kubernetes里定义的“用户”。
  • RoleBinding:定义了“被作用者”和“角色”的绑定关系。

Role & RoleBinding

实际上,Role本身就是一个Kubernetes的API对象,定义如下所示:

  1. kind: Role
  2. apiVersion: rbac.authorization.k8s.io/v1
  3. metadata:
  4. namespace: mynamespace
  5. name: example-role
  6. rules:
  7. - apiGroups: [""]
  8. resources: ["pods"]
  9. verbs: ["get", "watch", "list"]

首先,这个Role对象指定了它能产生作用的Namespace是: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。

Kubernetes里的User,也就是用户,只是一个授权系统里的逻辑概念,它需要通过外部认证服务,比如Keystone,来提供。或者,也可以直接给APIServer指定一个用户名、密码文件。那么Kubernetes的授权系统,就能够从这个文件里找到对应的用户。

最后,RoleBinding对象通过roleRef字段来引用定义的Roe对象(example-role),从而定义了“被作用者”和“角色”之间的绑定关系。
Role和RoleBinding对象都是Namespaced对象,它们对权限的限制规则仅在它们自己的Namespace内有效,roleRef也只能引用当前Namespace里的Role对象。

ClusterRole & ClusterRoleBinding

对于非Namespaced(Non-namespaced)对象(比如:Node),某一个Role想要作用于所有的Namespace的时候,就需要ClusterRole和ClusterRoleBinding这两个组合了。

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和ClusterRoeBinding的组合,意味着名叫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。

Practice

首先,创建Namespace:

apiVersion: v1
kind: Namespace
metadata:
  name: mynamespace

接着,定义一个ServiceAccount:

apiVersion: v1
kind: ServiceAccount
metadata:
  namespace: mynamespace
  name: example-sa

然后,通过编写RoleBinding的YAML文件,来为这个ServiceAccount分配权限:

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。

接着,使用kubectl命令创建这四个对象:

$ kubectl apply -f mynamespace.yaml
$ 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
items:
- apiVersion: v1
  kind: ServiceAccount
  metadata:
    creationTimestamp: "2022-06-27T05:33:02Z"
    name: default
    namespace: mynamespace
    resourceVersion: "28748"
    selfLink: /api/v1/namespaces/mynamespace/serviceaccounts/default
    uid: 90c62e94-4408-464f-a557-7589a3fab553
  secrets:
  - name: default-token-tkdbq
- apiVersion: v1
  kind: ServiceAccount
  metadata:
    creationTimestamp: "2022-06-27T05:33:28Z"
    name: example-sa
    namespace: mynamespace
    resourceVersion: "28810"
    selfLink: /api/v1/namespaces/mynamespace/serviceaccounts/example-sa
    uid: fe0d391f-1099-470c-81a0-42818c69f67d
  secrets:
  - name: example-sa-token-cqg7z
kind: List
metadata:
  resourceVersion: ""
  selfLink: ""

可以看到,Kubernetes会为一个ServiceAccount自动创建并分配一个Secret对象,即:上述ServiceAccount定义里最下面的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。

使用kubectl创建这个Pod:

$ kubectl apply -f sa-nginx.yaml

等这个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
Priority:     0
Node:         node1/192.168.117.157
Start Time:   Mon, 27 Jun 2022 13:53:48 +0800
Labels:       <none>
Annotations:  Status:  Pending
IP:
IPs:          <none>
Containers:
  nginx:
    Container ID:
    Image:          nginx:1.7.9
    Image ID:
    Port:           <none>
    Host Port:      <none>
    State:          Waiting
      Reason:       ContainerCreating
    Ready:          False
    Restart Count:  0
    Environment:    <none>
    Mounts:
      /var/run/secrets/kubernetes.io/serviceaccount from example-sa-token-cqg7z (ro)
Conditions:
  Type              Status
  Initialized       True
  Ready             False
  ContainersReady   False
  PodScheduled      True
Volumes:
  example-sa-token-cqg7z:
    Type:        Secret (a volume populated by a Secret)
    SecretName:  example-sa-token-cqg7z
    Optional:    false
QoS Class:       BestEffort
Node-Selectors:  <none>
Tolerations:     node.kubernetes.io/not-ready:NoExecute for 300s
                 node.kubernetes.io/unreachable:NoExecute for 300s
Events:
  Type    Reason     Age   From               Message
  ----    ------     ----  ----               -------
  Normal  Scheduled  21s   default-scheduler  Successfully assigned mynamespace/sa-token-test to node1
  Normal  Pulling    21s   kubelet, node1     Pulling image "nginx:1.7.9"

这时候,我们通过kubectl exec进入Pod查看这个目录里的文件:

$ 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做了限制。

如果一个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-8cscl
Tokens:              default-token-8cscl
Events:              <none>
$ kubectl get secret
NAME                  TYPE                                  DATA   AGE
default-token-8cscl   kubernetes.io/service-account-token   3      226d
$ kubectl describe secret default-token-8cscl
Name:         default-token-8cscl
Namespace:    default
Labels:       <none>
Annotations:  kubernetes.io/service-account.name: default
              kubernetes.io/service-account.uid: 3423cd7c-6c98-4e78-85dc-38c94306439d

Type:  kubernetes.io/service-account-token

Data
====
namespace:  7 bytes
token:      eyJhbGciOiJSUzI1NiIsImtpZCI6IjFEWXd0cWlNQzVEWmVnazBCTlZyNnd4cHBpZ0t5WWdValhJbC1Sc2ZrancifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJkZWZhdWx0Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZWNyZXQubmFtZSI6ImRlZmF1bHQtdG9rZW4tOGNzY2wiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC5uYW1lIjoiZGVmYXVsdCIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50LnVpZCI6IjM0MjNjZDdjLTZjOTgtNGU3OC04NWRjLTM4Yzk0MzA2NDM5ZCIsInN1YiI6InN5c3RlbTpzZXJ2aWNlYWNjb3VudDpkZWZhdWx0OmRlZmF1bHQifQ.TROTm_0aqQ-FWPRr8ya9OXnjKu42dqH393IV73Sl71aWmPOmBcZOQGfhFc9_41VTRJiHIDH_MCRiZ2UHTPaYbVR0IzU4a1nZBymo_cgvnxN9lSMafu8ZzjcN1xBgpial0P9qDp8fmFbBDnqlB869gBo00TSqBA-ubzZaM4b5f4k2V8djZNcHLIYAd3h6KzmThv4ZwskD_X2a_b2n92NELIZnB8vZkqf538uYlCTrWE-qws6zcwhkkH_aktQanCGaLebWAwMbBMuzNiCLFFS08sTPhyB1EPA6tqEOyUwNtt8wlqx4rvQEhbmvSsae0TrdqwCtSsOYQDaoxUfA7XLVxg
ca.crt:     1025 bytes

可以看到,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:<Namespace名字>:<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。

在Kubernetes中已经内置了很多个为系统保留的ClusterRole它们的名字都以system:开头。可以通过kubectl get clusterroles查看到它们。

比如,其中一个名叫system:kube-scheduler的ClusterRole,定义的权限规则是kube-scheduler(Kubernetes的调度器组件)运行所需要的必要权限。可以通过如下指令查看这些权限的列表:

$ kubectl describe clusterrole system:kube-scheduler
Name:         system:kube-scheduler
Labels:       kubernetes.io/bootstrapping=rbac-defaults
Annotations:  rbac.authorization.kubernetes.io/autoupdate: true
PolicyRule:
  Resources                                  Non-Resource URLs  Resource Names    Verbs
  ---------                                  -----------------  --------------    -----
  events                                     []                 []                [create patch update]
  events.events.k8s.io                       []                 []                [create patch update]
  bindings                                   []                 []                [create]
  endpoints                                  []                 []                [create]
  pods/binding                               []                 []                [create]
  tokenreviews.authentication.k8s.io         []                 []                [create]
  subjectaccessreviews.authorization.k8s.io  []                 []                [create]
  leases.coordination.k8s.io                 []                 []                [create]
  pods                                       []                 []                [delete get list watch]
  nodes                                      []                 []                [get list watch]
  persistentvolumeclaims                     []                 []                [get list watch]
  persistentvolumes                          []                 []                [get list watch]
  replicationcontrollers                     []                 []                [get list watch]
  services                                   []                 []                [get list watch]
  replicasets.apps                           []                 []                [get list watch]
  statefulsets.apps                          []                 []                [get list watch]
  replicasets.extensions                     []                 []                [get list watch]
  poddisruptionbudgets.policy                []                 []                [get list watch]
  csinodes.storage.k8s.io                    []                 []                [get list watch]
  endpoints                                  []                 [kube-scheduler]  [get update]
  leases.coordination.k8s.io                 []                 [kube-scheduler]  [get update]
  pods/status                                []                 []                [patch update]

这个system:kube-scheduler的ClusterRole,就会绑定给kube-system Namespace下名叫kube-scheduler的ServiceAccount,它正是Kubernetes调度器的Pod声明使用的ServiceAccount。

除此之外,Kubernetes还提供了四个预先定义好的ClusterRole来供用户直接使用:

  1. cluster-admin;
  2. admin;
  3. edit;
  4. view。

上面的四个角色,权限依次期间,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
  ---------  -----------------  --------------  -----
  *.*        []                 []              [*]
             [*]                []              [*]

Problem

如何为所有 Namespace 下的默认 ServiceAccount(default ServiceAccount),绑定一个只读权限的 Role 呢?请你提供 ClusterRoleBinding(或者 RoleBinding)的 YAML 文件。

kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: readonly-all-default
subjects:
- kind: Group
  name: system:serviceaccounts
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: ClusterRole
  name: view
  apiGroup: rbac.authorization.k8s.io