这是kubernete编排技术的第六篇,本文主要讲一下RBAC。之前讲过,kubernete所有API对象,都保存在etcd里。要访问和操作这些对象,一定会通过apiserver,因为apiserver可以做授权工作。
kubernete发展至今,授权模式一共有如下6种,其中RBAC这种授权模式从1.8开始已经成了稳定功能,只要设置通过设置–authorization-mode=RBAC就可以启用。
- 基于属性的访问控制ABAC
- 基于角色的访问控制RBAC
- Webhook
- Node
- 总是拒绝AlwaysDeny
- 总是允许AlwaysAllow
作为业务开发者,我们多多少少也接触过授权,比如spring security,sso等。其实RBAC的授权很类似,它有3个角色相关定义:Role(角色)、Subject(主体或被作用者)和RoleBinding(绑定),还有2个规则:资源、操作类型。
所以在RBAC中,需要api授权的时候,首先需要定义Role,然后需要定义Role和subject绑定关系RoleBinding,定义角色的时候需要指定该角色可以访问的资源和操作类型。
定义Role和ClusterRole
Role首先是一个api对象,在kubernete中有2种,Role和ClusterRole,很明显这2个就是普通角色和集群角色。定义的时候只要把yaml中的kind指定为Role就可以了。下面我们定义了一个名字叫test-role的Role,这个Role可以对namespace是mynamespace的pod进行get、watch和list操作。
kind: Role apiVersion: rbac.authorization.k8s.io/v1 metadata: namespace: mynamespace name: test-role rules: - apiGroups: [""] resources: ["pods"] verbs: ["get", "watch", "list"]
下面我们再定义一个名字叫secret-reader的ClusterRole,需要注意集群角色是没有namespace的(因为集群角色不需要namespace进行逻辑隔离),这里定义的资源类型是secrets,secrets也是一种api对象,后面再讲,操作类型跟上面的Role一样。
kind:ClusterRole apiVersion:rbac.authorization.k8s.io/v1 metadata: name:secret-reader rules: - apiGroups:[""] resources:["secrets"] verbs:["get","watch","list"]
注意:
1.如果我们要给当前角色赋予所有权限,可以定义verbs如下:
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
2.这儿也可以针对具体的对象进行设置,比如下面就是只能对name是my-config的configmaps资源进行get操作。
rules: - apiGroups: [""] resources: ["configmaps"] resourceNames: ["my-config"] verbs: ["get"]
3.kubernete提供了四个预先定义好的ClusterRole供用户使用:cluster-admin(集群中所有操作)、admin(单节点所有操作)、edit(读写操作)、view(只读操作)。
需要提一下的是,cluster-admin对应的是整个集群中的最高权限(verbs=*)。下面的命令可以看出每个角色的权限列表:
kubectl describe clusterrole cluster-admin -n kube-system kubectl describe clusterrole admin -n kube-system kubectl describe clusterrole edit -n kube-system kubectl describe clusterrole view -n kube-system
角色的绑定RoleBinding
RoleBinding也是一个api对象,所以定义的时候只要申明kind是RoleBinding就可以了。下面这个RoleBinding名字叫test-rolebinding,定义了一个subjects即被作用对象,是一个叫jinjunzhu的用户。简而言之,就是jinjunzhu这个用户绑定了test-role这个角色,从而绑定了资源的get、watch和list操作。
kind: RoleBinding apiVersion: rbac.authorization.k8s.io/v1 metadata: name: test-rolebinding namespace: mynamespace subjects: - kind: User name: jinjunzhu apiGroup: rbac.authorization.k8s.io roleRef: kind: Role name: test-role apiGroup: rbac.authorization.k8s.io
RoleBinding也可以绑定集群角色角色,这个绑定只能在mynamespace中生效,绑定的正是我们前面定义的名字叫做secret-reader的ClusterRole,这样管理员可以在集群中复制这个公共角色,然后在命名空间中进行复用。yaml文件代码如下:
kind:RoleBinding apiVersion:rbac.authorization.k8s.io/v1 metadata: name:read-secrets namespace:mynamespace subjects: - kind:User name:jinjunzhu apiGroup:rbac.authorization.k8s.io roleRef: kind:ClusterRole name:secret-reader apiGroup:rbac.authorization.k8s.io
下面再看一个ClusterRoleBinding,这儿定义的被作用者subject是一个名字叫做jinjunzhu的Group,绑定关系是名字叫做secret-reader,这个绑定允许jinjunzhu这个组中的所有用户对集群中任何namespace中的secrets进行get、watch和list操作。
kind:ClusterRoleBinding apiVersion:rbac.authorization.k8s.io/v1 metadata: name:read-secrets-global subjects: - kind:Group name:jinjunzhu apiGroup:rbac.authorization.k8s.io roleRef: kind:ClusterRole name:secret-reader apiGroup:rbac.authorization.k8s.io
注意:这个绑定关系是没有namespace定义的。
主体或被作用者subject
在上面的subjects定义中,使用到了User和Group。
首先我们介绍一下User,它实际上是一个为了授权的逻辑概念。一般情况下,kubernete集群为我们提供的内置用户就能满足我们使用了,我们也可以通过外部认证服务比如keystone或者直接给APIServer指定用户名密码文件来指定。
如下是名字是jinjunzhu的用户:
subjects: - kind:User name:jinjunzhu apiGroup:rbac.authorization.k8s.io
而Group只是一组User,这一组用户也可以由外服授权服务来提供。
下面这个定义是用户名为jinjunzhu的Group:
subjects: - kind:Group name:jinjunzhu apiGroup:rbac.authorization.k8s.io
注意:
ServiceAccount中Users,在kubernete里对应的name是:
system:serviceaccount:<Namespace名字>:<ServiceAccount名字>
ServiceAccount中Group,在kubernete里对应的name是:
system:serviceaccounts:<Namespace名字>
下面这个定义是所有的用户:
subjects: - kind:Group name:system:authenticated #授权过的 apiGroup:rbac.authorization.k8s.io - kind:Group name:system:unauthenticated #未授权过的 apiGroup:rbac.authorization.k8s.io
下面的定义是所有内置用户:
subjects: - kind:Group name:system:serviceaccounts apiGroup:rbac.authorization.k8s.io
下面的定义是所有namespace是jinjunzhu的所有内置用户:
subjects: - kind:Group name:system:serviceaccounts:jinjunzhu apiGroup:rbac.authorization.k8s.io
关于如何使用外部授权服务,这里就不再详细介绍了。
进行试验
上面我们提到过,一般情况下,kubernete为我们提供的内置用户就能满足我们使用了,这个内置用户或者用户组就是ServiceAccount。
首先我定义一个name是jinjunzhu的ServiceAccount,serviceaccount.yaml文件内容如下:
apiVersion: v1 kind: ServiceAccount metadata: namespace: mynamespace name: jinjunzhu
接着我们定义一个Role,Role.yaml文件内容如下:
kind: Role apiVersion: rbac.authorization.k8s.io/v1 metadata: namespace: mynamespace name: -role rules: - apiGroups: [""] resources: ["pods"] verbs: ["get", "watch", "list"]
然后我们定义RoleBinding,RoleBinding.yaml文件内容如下:
kind: RoleBinding apiVersion: rbac.authorization.k8s.io/v1 metadata: name: test-rolebinding namespace: mynamespace subjects: - kind: ServiceAccount name: jinjunzhu namespace: mynamespace roleRef: kind: Role name: test-role apiGroup: rbac.authorization.k8s.io
因为上面用到了mynamespace,我们需要创建这个namespace, mynamespaces.yaml文件内容如下:
apiVersion: v1 kind: Namespace metadata: name: mynamespace labels: name: mynamespace
下面我们创建这4个api对象,命令如下:
kubectl create -f mynamespaces.yaml kubectl create -f serviceaccount.yaml kubectl create -f Role.yaml kubectl create -f RoleBinding.yaml
创建成功后,首先我们看一下ServiceAccount,kubernete为它分配了简写sa,查看时可以使用:
[root@master rbac]# kubectl get sa -n mynamespace -o yaml apiVersion: v1 items: - apiVersion: v1 kind: ServiceAccount metadata: creationTimestamp: "2020-08-16T07:11:37Z" name: default namespace: mynamespace resourceVersion: "2354" selfLink: /api/v1/namespaces/mynamespace/serviceaccounts/default uid: 4d234ef6-7a44-4ffc-bcd2-3a1a490eef0a secrets: - name: default-token-2tjlh - apiVersion: v1 kind: ServiceAccount metadata: creationTimestamp: "2020-08-16T07:14:34Z" name: jinjunzhu namespace: mynamespace resourceVersion: "2761" selfLink: /api/v1/namespaces/mynamespace/serviceaccounts/jinjunzhu uid: 31e92f92-e2dd-40dc-8a73-822b3be4c637 secrets: - name: jinjunzhu-token-qxttq kind: List metadata: resourceVersion: "" selfLink: ""
从上面的输出可以看到,kubernete为jinjunzhu这个ServiceAccount分配了一个secrets,这个名字叫jinjunzhu-token-qxttq的secrets,它就是用来跟apiserver进行交互的token信息,只不过是以secrets对象的格式信息保存在etcd当中。
然后,我们从下面的输出可以看出,Role和RoleBinding已经创建成功了。
[root@master rbac]# kubectl get Role -n mynamespace NAME AGE test-role 6m19s [root@master rbac]# kubectl get RoleBinding -n mynamespace NAME AGE test-rolebinding 6m8s
这时我们定义一个pod,这个pod名字叫sa-pod,镜像是我之前使用的一个springboot镜像,我们定义这个pod使用我们刚刚创建的名字叫jinjunzhu的ServiceAccount,
apiVersion: v1 kind: Pod metadata: namespace: mynamespace name: sa-pod spec: containers: - name: spingboot-mybatis imagePullPolicy: IfNotPresent image: zjj2006forever/springboot-mybatis:1.3 ports: - containerPort: 8300 serviceAccountName: jinjunzhu
这个文件我们命名为sa-pod.yaml,然后创建这个pod,命令如下:
kubectl create -f sa-pod.yaml
创建成功后,我们用describe看一下这个pod的详情:
[root@master rbac]# kubectl describe pod sa-pod -n mynamespace Name: sa-pod Namespace: mynamespace Priority: 0 Node: worker1/192.168.59.141 Start Time: Sun, 16 Aug 2020 03:40:56 -0400 Labels: <none> Annotations: <none> Status: Running IP: 10.244.1.2 IPs: IP: 10.244.1.2 Containers: spingboot-mybatis: Container ID: docker://8cb76ef92f94f128adedddd3dfc1d401cc238b2eef74af507c39b3d3e210814e Image: zjj2006forever/springboot-mybatis:1.3 Image ID: docker-pullable://zjj2006forever/springboot-mybatis@sha256:502c368a0a0ea9dc38c8175f2b97aa7527336dea314b3712151fccefe957eaf8 Port: 8300/TCP Host Port: 0/TCP State: Running Started: Sun, 16 Aug 2020 03:40:57 -0400 Ready: True Restart Count: 0 Environment: <none> Mounts: /var/run/secrets/kubernetes.io/serviceaccount from jinjunzhu-token-qxttq (ro) Conditions: Type Status Initialized True Ready True ContainersReady True PodScheduled True Volumes: jinjunzhu-token-qxttq: Type: Secret (a volume populated by a Secret) SecretName: jinjunzhu-token-qxttq 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 102s default-scheduler Successfully assigned mynamespace/sa-pod to worker1 Normal Pulled 101s kubelet, worker1 Container image "zjj2006forever/springboot-mybatis:1.3" already present on machine Normal Created 101s kubelet, worker1 Created container spingboot-mybatis Normal Started 101s kubelet, worker1 Started container spingboot-mybatis
上面输出的Mounts部分信息我们可以看到,前面我们创建jinjunzhu这个ServiceAccount时生成的token已经挂载到了一个目录下面,这是我们进入这个容器内部,如下所示:
[root@worker1 kubernetes]# kubectl exec -it sa-pod -n mynamespace -- /bin/sh / # cd /var/run/secrets/kubernetes.io/serviceaccount /run/secrets/kubernetes.io/serviceaccount # ls ca.crt namespace token
可以看到容器里面已经有了一个ca.crt文件,容器里的应用就是用这个文件来访问apiserver的。根据前面Role定义,这个ServiceAccount的操作权限只有get、watch和list。
最后要说的是,如果我们声明pod的时候,没有定义serviceAccountName,kubernete会怎么控制pod的操作权限呢?
还是上面的sa-pod.yaml,我们删除serviceAccountName,重新创建pod,然后用describe看一下详情,下面是我截取了部分输出:
[root@master rbac]# kubectl describe pod sa-pod -n mynamespace Name: sa-pod Namespace: mynamespace Priority: 0 Node: worker1/192.168.59.141 Start Time: Sun, 16 Aug 2020 04:12:02 -0400 Labels: <none> Annotations: <none> Status: Running IP: 10.244.1.3 IPs: IP: 10.244.1.3 Containers: spingboot-mybatis: Container ID: docker://8c73ccba58581c6d78714463d65fb75cb5733a7a6aa4b57d635a8fc4e2068f0f Image: zjj2006forever/springboot-mybatis:1.3 Image ID: docker-pullable://zjj2006forever/springboot-mybatis@sha256:502c368a0a0ea9dc38c8175f2b97aa7527336dea314b3712151fccefe957eaf8 Port: 8300/TCP Host Port: 0/TCP State: Running Started: Sun, 16 Aug 2020 04:12:03 -0400 Ready: True Restart Count: 0 Environment: <none> Mounts: /var/run/secrets/kubernetes.io/serviceaccount from default-token-2tjlh (ro) Conditions: Type Status Initialized True Ready True ContainersReady True PodScheduled True Volumes: default-token-2tjlh: Type: Secret (a volume populated by a Secret) SecretName: default-token-2tjlh 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 37s default-scheduler Successfully assigned mynamespace/sa-pod to worker1 Normal Pulled 37s kubelet, worker1 Container image "zjj2006forever/springboot-mybatis:1.3" already present on machine Normal Created 37s kubelet, worker1 Created container spingboot-mybatis Normal Started 36s kubelet, worker1 Started container spingboot-mybatis
可以看到,kubernete会为这个pod创建一个名字叫default的ServiceAccount,然后为它绑定一个特殊的Secret,我们看一下详情:
[root@master rbac]# kubectl get secret NAME TYPE DATA AGE default-token-zjndz kubernetes.io/service-account-token 3 81m [root@master rbac]# kubectl describe secret default-token-zjndz Name: default-token-zjndz Namespace: default Labels: <none> Annotations: kubernetes.io/service-account.name: default #这个可以看到这个Secret会跟名字是default的ServiceAccount进行绑定 kubernetes.io/service-account.uid: f7cd1a1e-9be7-42ba-a0fc-0aa7a8150a9e Type: kubernetes.io/service-account-token Data ==== ca.crt: 1025 bytes namespace: 7 bytes token: eyJhbGciOiJSUzI1NiIsImtpZCI6IlNvT3BWUkY0WWlrQWd2Qm1iT2cwZGJDdF94UU9JT3JrREszbWpLZ0RDZHcifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJkZWZhdWx0Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZWNyZXQubmFtZSI6ImRlZmF1bHQtdG9rZW4tempuZHoiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC5uYW1lIjoiZGVmYXVsdCIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50LnVpZCI6ImY3Y2QxYTFlLTliZTctNDJiYS1hMGZjLTBhYTdhODE1MGE5ZSIsInN1YiI6InN5c3RlbTpzZXJ2aWNlYWNjb3VudDpkZWZhdWx0OmRlZmF1bHQifQ.YUt48ZYexbxpioOoODGQ6UnrK6urKMFU5NwaGT1PCJe-inqM4ZKWuCxAb6Rd8JubRtf7JhKQOkjNIQcEe0jrjrSu25q0Zc485b_Nm20GRvB6smWC59lG6wJ2U4aanx4IAewTFCZsmct-1G8CU5YxY7gyvklbf2FWzplvCkeLOXx2kH-KHETbZuRDcNUXq-qo2G_X5_feyGRnDrL9tKuk7aBKsSYoQcwE2GWzSClPn9w8T7F2U139cWtI2T_Rk7K_85bk_-4o5AIdTcSLFckB8J4gbm2lDTS5sVxG-njFb7rOZq50xMsMfamPc350MZibgSiszqYzNNb_GgLqIwL16A
注意:default的ServiceAccount拥有的访问apiserver的权限比较多,所以我们一般应该声明一个ServiceAccount进行绑定,用来对操作权限进行控制。
总结
kubernete基于角色的访问控制,其实是通过Role和RoleBinding来实现的,角色中定义了操作权限,然后跟User进行绑定,这个User一般可以用ServiceAccount对象。跟集群相关的访问控制还有ClusterRole和ClusterRoleBinding,这2个对象是集群范围的,不受namespace的限制。