CategoryResourceRepost/极客时间专栏/深入剖析Kubernetes/容器编排与Kubernetes作业管理/26 | 基于角色的权限控制:RBAC.md
louzefeng d3828a7aee mod
2024-07-11 05:50:32 +00:00

18 KiB
Raw Blame History

你好我是张磊。今天我和你分享的主题是基于角色的权限控制之RBAC。

在前面的文章中我已经为你讲解了很多种Kubernetes内置的编排对象以及对应的控制器模式的实现原理。此外我还剖析了自定义API资源类型和控制器的编写方式。

这时候,你可能已经冒出了这样一个想法:控制器模式看起来好像也不难嘛,我能不能自己写一个编排对象呢?

答案当然是可以的。而且这才是Kubernetes项目最具吸引力的地方。

毕竟在互联网级别的大规模集群里Kubernetes内置的编排对象很难做到完全满足所有需求。所以很多实际的容器化工作都会要求你设计一个自己的编排对象实现自己的控制器模式。

而在Kubernetes项目里我们可以基于插件机制来完成这些工作而完全不需要修改任何一行代码。

不过你要通过一个外部插件在Kubernetes里新增和操作API对象那么就必须先了解一个非常重要的知识RBAC。

我们知道Kubernetes中所有的API对象都保存在Etcd里。可是对这些API对象的操作却一定都是通过访问kube-apiserver实现的。其中一个非常重要的原因就是你需要APIServer来帮助你做授权工作。

在Kubernetes项目中负责完成授权Authorization工作的机制就是RBAC基于角色的访问控制Role-Based Access Control

如果你直接查看Kubernetes项目中关于RBAC的文档的话可能会感觉非常复杂。但实际上等到你用到这些RBAC的细节时再去查阅也不迟。

而在这里,我只希望你能明确三个最基本的概念。

  • Role角色它其实是一组规则定义了一组对Kubernetes API对象的操作权限。
  • Subject被作用者既可以是“人”也可以是“机器”也可以是你在Kubernetes里定义的“用户”。
  • RoleBinding定义了“被作用者”和“角色”的绑定关系。
  • 而这三个概念其实就是整个RBAC体系的核心所在。

    我先来讲解一下Role。

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

    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那就是使用的是默认Namespacedefault。

    然后这个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对象Namespaced Object它们对权限的限制规则仅在它们自己的Namespace内有效roleRef也只能引用当前Namespace里的Role对象。

    那么,对于非NamespacedNon-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"]
    
    

    这些就是当前Kubernetesv1.11里能够对API对象进行的所有操作了。

    类似地Role对象的rules字段也可以进一步细化。比如你可以只针对某一个具体的对象进行权限设置如下所示

    rules:
    - apiGroups: [""]
      resources: ["configmaps"]
      resourceNames: ["my-config"]
      verbs: ["get"]
    
    

    这个例子就表示这条规则的“被作用者”只对名叫“my-config”的ConfigMap对象有进行GET操作的权限。

    而正如我前面介绍过的在大多数时候我们其实都不太使用“用户”这个功能而是直接使用Kubernetes里的“内置用户”。

    这个由Kubernetes负责管理的“内置用户”正是我们前面曾经提到过的ServiceAccount。

    接下来我通过一个具体的实例来为你讲解一下为ServiceAccount分配权限的过程。

    首先我们要定义一个ServiceAccount。它的API对象非常简单如下所示

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

    可以看到一个最简单的ServiceAccount对象只需要Name和Namespace这两个最基本的字段。

    然后我们通过编写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也就是我在这篇文章一开始定义的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没有声明serviceAccountNameKubernetes会自动在它的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。这个具体怎么做就当作思考题留给你了。

    除了前面使用的“用户”UserKubernetes还拥有“用户组”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查看到它们。

    一般来说这些系统ClusterRole是绑定给Kubernetes系统组件对应的ServiceAccount使用的。

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

    $ 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来供用户直接使用

  • cluster-admin
  • admin
  • edit
  • 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下的默认ServiceAccountdefault ServiceAccount绑定一个只读权限的Role呢请你提供ClusterRoleBinding或者RoleBinding的YAML文件。

    感谢你的收听,欢迎你给我留言,也欢迎分享给更多的朋友一起阅读。