kubernetes集群创建好了之后,默认kubeconfig文件中的用户相当于DB的超级管理员。权限足以摧毁整个kubernetes 集群!但如果其它的同事也想使用kubectl工具,在命令行进行一些简单操作,作为集群admin,我们该怎样做呢?

小小需求

如序言提到的,在实际使用k8s中,有些开发同学也想直接在自己的终端命令行上对服务进行一些简单操作。如查看某个 服务的日志,或进到容器里执行一些ad-hoc命令。

仔细的分析

从最早用容器集群,虽然知道一些关于RBAC的概念,但一直没有上手实践过,在我记忆里,就把它记成是基于角色的权限控制。😂 因为我之前一直没彻底搞清楚在kubernetes中,这个实体用户(User)是怎么来的。

今天经过一些操作实践后,来分享一些关于RBAC的知识。

先看下 Authentication, Who?

我们知道,在Kubernetes中,所有的操作基本都离不开API Server,包括集群内部各节点上组件的交互以及kubectl客户端 的操作。

当API Server接收到一个请求,它会查找当前集群配置的认证(Authentication)插件,并从请求中提取一些信息,如User ID、Username,如果 认证通过,将会继续到授权(Authorization)阶段。以此查看该用户的相对应的权限。能对哪些资源做哪些操作。

kubernetes支持的认证插件有以下几种,通过这几种方式可以获取到用户信息:

用户

在k8s中,只会对认证之后的用户做检验,决定它可以完成哪些和不能完成哪些工作。k8s集群不存储这些用户信息。

除集群自带组件外,与kubernetes API server 连接的客户端还有两种类型: * 实际的用户(Users) * Pods(pod内运行的应用程序)

他们是有区别的。Pods 认证可以调用ServiceAccounts(aka SA)资源,SA可以直接创建并以ServiceAccount资源存储在集群里。 而 User 账户是不能通过API server 来进行增删改查的。Users 类型的认证可以由其它额外的SSO系统来提供,如上面提到的ldap系统,或者http基本认证。

ServiceAccounts

简单说下sa,在每个namespace下,都有一个名为default的 serviceaccount资源。默认情况下,我们创建的 每个pod,都会自动挂载这个serviceaccount创建的secret 到容器路径下:/var/run/secrets/kubernetes.io/serviceaccount/

当该pod 去与API Server交互时,便会以该namespace下默认的sa账户去做认证。默认在启用rbac授权插件的情况下,该sa是没有什么权限的。 这时我们就可以单独的创建属于该pod的ServiceAccounts 资源,并创建相对应的role以及rolebinding绑定该serviceaccount去做授权。

再来看看授权(Authorization),What can be done ?

在kubernetes中,API Server 可以授权认证通过的用户可以做哪些事情。API server 对外暴露REST接口,用户通过发送 相关的HTTP 请求到 API Server上。在认证方面就是看用户发送请求时携带的凭据(token、username&password、客户端证书)

我们知道,kubernetes集群中,几乎把上层所有的组件都定义成资源。而我们可以对这些资源进行的操作不外乎 增删改查。比如:

介绍下 RBAC插件

RBAC是基于用户角色作为决定该用户可以对哪些资源 完成哪些操作.

配置RBAC 规则可以通过以下4种资源,以可操作的资源级别来说,可分为两种:

从名字上我们可以看出ClusterRoles是集群级别的资源。(比如:namespaces、nodes、pv等资源)。想详细了解其它资源可以通过 以下命令进行查看:

kubectl api-resources | grep false

而 Roles 这是属于namespace里面的资源(比如:pod、service、ingress、pvc等资源)

Role/ClusterRole、ClusterRole/ClusterRoleBindings和用户的关系可以通过下图来概括。

who --> User / SA / Group
       | |
       | |
        V
 RoleBindings / ClusterRoleBindings
       ^^
       ||
       ||
 what can to do --> Cluster resource and Verb ( Roles / ClusterRoles )

另外,一个用户(User/SA)可以绑定多个role或clusterrole。最终的权限是累加的。最佳实践就是创建多种Roles/ClusterRoles, 然后通过RoleBindings/ClusterRoleBindings绑定到最终用户上。

下面进入主题,来看下实际操作,如何限制用户的权限。

上手 Roles 和 RoleBindings

为了演示,我们先创建一个用户。这个用户将使用证书的方式去得到API server的验证。这里使用kubeadm工具
[root@k8s-m1 ~]# kubeadm alpha kubeconfig user --client-name=aliasmee --apiserver-advertise-address=172.16.193.245 --apiserver-bind-port=8443

通过运行上面的命令,将输出保存到一个文件中new-k8s-kubeconfig.yml,之后导入到本地kubectl默认目录下~/.kube/config,当然也 可以将该文件放到其它你喜欢的目录中。不过在运行kubectl时,需要通过参数--kubeconfig明确指定kubectl配置文件的完整路径。

合并新集群的kubeconfig文件到config文件中。
$ export KUBECONFIG=/Users/`whoami`/.kube/config:/path/to/file/new-k8s-kubeconfig.yml
$ kubectl config view --flatten > /Users/`whoami`/.kube/config

检查下配置文件是否合并成功:

$ kubectl config get-contexts
CURRENT   NAME                                                 CLUSTER             AUTHINFO           NAMESPACE
*         aliasmee@kubernetes                                  kubernetes-office   aliasmee           
          kubernetes-admin-cb79817c894e24ead82daad2b606607a9   kubernetes          kubernetes-admin   

确保切换到新的集群。可以运行下面的命令

kubectl config use-context aliasmee@kubernetes

Tips: 这里分享下我在合并kubeconfig文件过程中遇到的一个奇葩问题。就是 contexts内容字段不能出现重复定义,虽然合并时不会报错。 但是合并后新文件中重复字段的值会被覆盖掉。不能出现重复的字段是集群name认证用户、以及contexts名字。

我们来看下当前的用户可以做哪些操作。

$ kubectl get po
No resources found.
Error from server (Forbidden): pods is forbidden: User "aliasmee" cannot list resource "pods" in API group "" in the namespace "default"

可以看到默认新建的用户是没有权限列出default namespace下的pods资源。

现在新用户已经准备就绪了。我们开始给该用户加点权限(Roles),然后使用RoleBinDings授权给aliasmee用户。
# kubectl create -f - <<EOF
kind: Role
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  namespace: default               # 定义可以操作的namespace
  name: manager-default-role
rules:
- apiGroups: ["", "extensions", "apps"]
  resources: ["deployments", "replicasets", "pods","pods/log", "services"]
  verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]

EOF

将上面的Role 通过 RoleBinding 资源绑定到aliasmee用户。

# kubectl create -f - <<EOF
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  name: manager-default-binding
  namespace: default              # 定义可以操作的namespace
subjects:
- kind: User                      # 授权的用户类型
  name: aliasmee                  # 授权给aliasmee
  apiGroup: ""
roleRef:
  kind: Role
  name: manager-default-role      # 授权绑定的Role Name
  apiGroup: ""
EOF

现在我们可以再次运行kubectl来看下效果:

$ kubectl get po -ndefault
NAME                          READY     STATUS    RESTARTS   AGE
curl-custom-sa                2/2       Running   0          109m
nginx-test-79cd7499bf-kcbjp   1/1       Running   0          3d

如你看到的一样。新的授权已经生效了。你也可以验证除了get pod之外,看下能否get ing,或者node。答案 肯定是不能的。是因为我们刚新建的Role中不包含ingress资源,以及node是集群级别的资源。继续往下看。

如何授权集群级别的权限呢?

创建ClusterRole 以及ClusterRoleBind 资源

# kubectl create -f - <<EOF
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  labels:
    kubernetes.io/bootstrapping: rbac-defaults
  name: aliasmee                  # ClusterRole 资源定义时不需要指定namespace!
rules:
- apiGroups:
  - ""
  resources:
  - nodes
  verbs:
  - get
  - list
  - watch
- apiGroups:
  - ""
  resources:
  - namespaces
  verbs:
  - get
  - list
  - watch
  - delete
- apiGroups:
  - ""
  resources:
  - pods
  verbs:
  - get
  - list

EOF
创建 ClusterRoleBinding
# kubectl create -f - <<EOF
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  labels:
    kubernetes.io/bootstrapping: rbac-defaults
  name: aliasmee
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: aliasmee
subjects:
- apiGroup: rbac.authorization.k8s.io
  kind: User                      # 同样,绑定User类型
  name: aliasmee
  apiGroup: ""                    # “” 是指kubernetes core API group

EOF

创建完这俩资源后,我们在本地使用kubectl验证下是否可以操作集群级别的资源。

$ kubectl get po -nmetallb-system
NAME                        READY     STATUS    RESTARTS   AGE
controller-55d74449-vd6f6   1/1       Running   0          20h
speaker-5c6dk               1/1       Running   0          20h
speaker-g74q6               1/1       Running   0          20h
speaker-hrcsw               1/1       Running   0          20h
speaker-ndx67               1/1       Running   0          20h
$ kubectl get nodes
NAME      STATUS    ROLES     AGE       VERSION
k8s-m1    Ready     master    4d2h      v1.15.3
k8s-m2    Ready     master    4d2h      v1.15.3
k8s-m3    Ready     master    4d2h      v1.15.3
k8s-n1    Ready     worker    4d2h      v1.15.3

Tips:

  1. 在定义Role时,我们指定的是某个类型的资源,k8s也提供resourceName字段,这样我们可以限制只对某个Resource类型的实体进行操作, 而不是所有该类型的资源。比如,看下面的定义:
- apiGroups:
  - ""
  resourceNames:
  - ng-test
  resources:
  - services
  verbs:
  - get
  - list

这样只能查看名为ng-test的service。

  1. API server还提供 non-resource的URL。访问这些也需要得到明确授权才可以。我们可以在定义rules指定 nonResourceURLs,而不是resources。如下面的定义:
rules:
- nonResourceURLs:
  - /api
  - /version
  verbs:
  - get
  1. 另外RoleBinding 也可以在指定roleRef时,指定role 的kind类型为ClusterRole。这意味着,你会得到这个 ClusterRole下的所有权限,但只在namespace级别范围下,即权限将会限制在创建RoleBinding资源namspace范围下。

创建一个预置的集群级别的只读管理员

以下文件源码位置:github.com/aliasmee/kube-ops

kubectl apply -f https://raw.githubusercontent.com/aliasmee/kube-ops/master/lab3/readonly-cluster-roles.yml

创建一个ClusterRoleBindings将权限绑定到我们的aliasmee用户上。

# kubectl create -f - <<EOF
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  labels:
    kubernetes.io/bootstrapping: rbac-defaults
  name: aliasmee
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cs:ns:readonly
subjects:
- apiGroup: rbac.authorization.k8s.io
  kind: User                      # 同样,绑定User类型
  name: aliasmee
  apiGroup: ""                    # “” 是指kubernetes core API group

EOF

该总结了

Kubernetes中的RBAC策略可以对用户权限做到最小化授权。保证集群内的操作安全。不仅仅是实体用户。对于 运行在Pod中的应用程序本身,我们也可以通过对ServiceAccount做授权限制。在认证这块,kubernetes可以让 用户自主选择认证方式。而k8s只专注于授权权限部分。良好的权限设计不仅可以让用户做更多自主的控制,并且方便扩展。

题外话:对于pod与pod之间的网络通信限制,我们也可以使用NetworkPolicy来做限制。不过依赖与底层的cni插件。 这个时候,istio可以通过sidecar来实现网络控制。下一次有时间再来分享。

参考

Kubernetes In Action Kubernetes RBAC