Kubernetes(k8s)权限管理RBAC详解

Kubernetes(k8s)权限管理RBAC详解

王先森2023-08-162023-08-16

RBAC 讲解

在K8S中支持授权有AlwaysDenyAlwaysAllowABACWebhookRBACNode共6种模式,从1.6版本起,K8S默认启用RBAC访问控制策略,目前RBAC已作为稳定的功能,管理员可以通过 Kubernetes API 动态配置策略来启用RBAC,需要在 kube-apiserver 中添加参数--authorization-mode=RBAC

代码语言:javascript
复制
cat /opt/kubernetes/cfg/kube-apiserver.yaml
...
    - --authorization-mode=Node,RBAC
...
  • AlwaysDeny:表示拒绝所有请求,一般用于测试。
  • AlwaysAllow:允许接收所有请求。如果集群不需要授权流程,则可以采用该策略,这也是Kubernetes的默认配置。
  • ABAC(Attribute-Based Access Control):基于属性的访问控制。表示使用用户配置的授权规则对用户请求进行匹配和控制。
  • Webhook:通过调用外部REST服务对用户进行授权。
  • RBAC:Role-Based Access Control,基于角色的访问控制(本章讲解)。
  • Node:是一种专用模式,用于对kubelet发出的请求进行访问控制。

更多权限管理,可参考:https://kubernetes.io/docs/reference/access-authn-authz/authorization/#authorization-modules

API 对象

在学习 RBAC 之前,我们还需要再去理解下 Kubernetes 集群中的对象,我们知道,在 Kubernetes 集群中,Kubernetes 对象是我们持久化的实体,就是最终存入 etcd 中的数据,集群中通过这些实体来表示整个集群的状态。前面我们都直接编写的 YAML 文件,通过 kubectl 来提交的资源清单文件,然后创建的对应的资源对象,那么它究竟是如何将我们的 YAML 文件转换成集群中的一个 API 对象的呢?

这个就需要去了解下声明式 API的设计,Kubernetes API 是一个以 JSON 为主要序列化方式的 HTTP 服务,除此之外也支持 Protocol Buffers 序列化方式,主要用于集群内部组件间的通信。为了可扩展性,Kubernetes 在不同的 API 路径(比如/api/v1 或者 /apis/batch)下面支持了多个 API 版本,不同的 API 版本意味着不同级别的稳定性和支持:

  • Alpha 级别,例如 v1alpha1 默认情况下是被禁用的,可以随时删除对功能的支持,所以要慎用
  • Beta 级别,例如 v2beta1 默认情况下是启用的,表示代码已经经过了很好的测试,但是对象的语义可能会在随后的版本中以不兼容的方式更改
  • 稳定级别,比如 v1 表示已经是稳定版本了,也会出现在后续的很多版本中。

在 Kubernetes 集群中,一个 API 对象在 Etcd 里的完整资源路径,是由:Group(API 组)Version(API 版本)Resource(API 资源类型)三个部分组成的。通过这样的结构,整个 Kubernetes 里的所有 API 对象,实际上就可以用如下的树形结构表示出来:

apiserver tree

从上图中我们也可以看出 Kubernetes 的 API 对象的组织方式,在顶层,我们可以看到有一个核心组(由于历史原因,是 /api/v1 下的所有内容而不是在 /apis/core/v1 下面)和命名组(路径 /apis/NAME/VERSION)和系统范围内的实体,比如 /metrics。我们也可以用下面的命令来查看集群中的 API 组织形式:

代码语言:javascript
复制
➜  ~ kubectl get --raw /
{
  "paths": [
    "/api",
    "/api/v1",
    "/apis",
    "/apis/",
    ......
    "/version"
  ]
}

比如我们来查看批处理这个操作,在我们当前这个版本中存在 1 个版本的操作:/apis/batch/v1,暴露了可以查询和操作的不同实体集合,同样我们还是可以通过 kubectl 来查询对应对象下面的数据:

代码语言:javascript
复制
➜  ~ kubectl get --raw /apis/batch/v1 | python -m json.tool
{
    "apiVersion": "v1",
    "groupVersion": "batch/v1",
    "kind": "APIResourceList",
    "resources": [
        {
            "categories": [
                "all"
            ],
            "kind": "CronJob",
            "name": "cronjobs",
            "namespaced": true,
            "shortNames": [
                "cj"
            ],
            "singularName": "",
            "storageVersionHash": "sd5LIXh4Fjs=",
            "verbs": [
                "create",
                "delete",
                "deletecollection",
                "get",
                "list",
                "patch",
                "update",
                "watch"
            ]
        },
        {
            "kind": "CronJob",
            "name": "cronjobs/status",
            "namespaced": true,
            "singularName": "",
            "verbs": [
                "get",
                "patch",
                "update"
            ]
        },
        {
            "categories": [
                "all"
            ],
            "kind": "Job",
            "name": "jobs",
            "namespaced": true,
            "singularName": "",
            "storageVersionHash": "mudhfqk/qZY=",
            "verbs": [
                "create",
                "delete",
                "deletecollection",
                "get",
                "list",
                "patch",
                "update",
                "watch"
            ]
        },
        {
            "kind": "Job",
            "name": "jobs/status",
            "namespaced": true,
            "singularName": "",
            "verbs": [
                "get",
                "patch",
                "update"
            ]
        }
    ]
}

但是这个操作和我们平时操作 HTTP 服务的方式不太一样,这里我们可以通过 kubectl proxy 命令来开启对 apiserver 的访问:

代码语言:javascript
复制
➜  ~ kubectl proxy
Starting to serve on 127.0.0.1:8001

然后重新开启一个新的终端,我们可以通过如下方式来访问批处理的 API 服务:

代码语言:javascript
复制
➜  ~ curl http://127.0.0.1:8001/apis/batch/v1
{
  "kind": "APIResourceList",
  "apiVersion": "v1",
  "groupVersion": "batch/v1",
  "resources": [
    {
      "name": "jobs",
      "singularName": "",
      "namespaced": true,
      "kind": "Job",
      "verbs": [
        "create",
        "delete",
        "deletecollection",
        "get",
        "list",
        "patch",
        "update",
        "watch"
      ],
      "categories": [
        "all"
      ],
      "storageVersionHash": "mudhfqk/qZY="
    },
    {
      "name": "jobs/status",
      "singularName": "",
      "namespaced": true,
      "kind": "Job",
      "verbs": [
        "get",
        "patch",
        "update"
      ]
    }
  ]
}

通常,Kubernetes API 支持通过标准 HTTP POSTPUTDELETEGET 在指定 PATH 路径上创建、更新、删除和检索操作,并使用 JSON 作为默认的数据交互格式。

比如现在我们要创建一个 Deployment 对象,那么我们的 YAML 文件的声明就需要怎么写:

代码语言:javascript
复制
apiVersion: apps/v1
kind: Deployment

其中 Deployment 就是这个 API 对象的资源类型(Resource),apps 就是它的组(Group),v1 就是它的版本(Version)。API Group、Version 和 资源就唯一定义了一个 HTTP 路径,然后在 kube-apiserver 端对这个 url 进行了监听,然后把对应的请求传递给了对应的控制器进行处理而已,当然在 Kuberentes 中的实现过程是非常复杂的。

RBAC 权限控制编写

Kubernetes 所有资源对象都是模型化的 API 对象,允许执行 CRUD(Create、Read、Update、Delete) 操作(也就是我们常说的增、删、改、查操作),比如下面的这些资源:

  • Pods
  • ConfigMaps
  • Deployments
  • Nodes
  • Secrets
  • Namespaces
  • ……

对于上面这些资源对象的可能存在的操作有:

  • create
  • get
  • delete
  • list
  • update
  • edit
  • watch
  • exec
  • patch

在更上层,这些资源和 API Group 进行关联,比如 Pods 属于 Core API Group,而 Deployements 属于 apps API Group,现在我们要在 Kubernetes 中通过 RBAC 来对资源进行权限管理,除了上面的这些资源和操作以外,我们还需要了解另外几个概念:

  • Rule:规则,规则是一组属于不同 API Group 资源上的一组操作的集合
  • RoleClusterRole:角色和集群角色,这两个对象都包含上面的 Rules 元素,二者的区别在于,在 Role 中,定义的规则只适用于单个命名空间,也就是和 namespace 关联的,而 ClusterRole 是集群范围内的,因此定义的规则不受命名空间的约束。另外 Role 和 ClusterRole 在 Kubernetes 中都被定义为集群内部的 API 资源,和我们前面学习过的 Pod、Deployment 这些对象类似,都是我们集群的资源对象,所以同样的可以使用 YAML 文件来描述,用 kubectl 工具来管理
  • Subject:主题,对应集群中尝试操作的对象,集群中定义了 3 种类型的主题资源:
    • User Account:用户,这是有外部独立服务进行管理的,管理员进行私钥的分配,用户可以使用 KeyStone 或者 Goolge 帐号,甚至一个用户名和密码的文件列表也可以。对于用户的管理集群内部没有一个关联的资源对象,所以用户不能通过集群内部的 API 来进行管理
    • Group:组,这是用来关联多个账户的,集群中有一些默认创建的组,比如 cluster-admin
    • Service Account:服务帐号,通过 Kubernetes API 来管理的一些用户帐号,和 namespace 进行关联的,适用于集群内部运行的应用程序,需要通过 API 来完成权限认证,所以在集群内部进行权限操作,我们都需要使用到 ServiceAccount,这也是我们这节课的重点
  • RoleBindingClusterRoleBinding:角色绑定和集群角色绑定,简单来说就是把声明的 Subject 和我们的 Role 进行绑定的过程(给某个用户绑定上操作的权限),二者的区别也是作用范围的区别:RoleBinding 只会影响到当前 namespace 下面的资源操作权限,而 ClusterRoleBinding 会影响到所有的 namespace。

K8s角色&角色绑定(以ServiceAccount展开讲解)

授权介绍

在RABC API中,通过如下的步骤进行授权:

  1. 定义角色:在定义角色时会指定此角色对于资源的访问控制的规则。
  2. 绑定角色:将主体与角色进行绑定,对用户进行访问授权。

角色

  • Role:授权特定命名空间的访问权限
  • ClusterRole:授权所有命名空间的访问权限

角色绑定

  • RoleBinding:将角色绑定到主体(即subject)
  • ClusterRoleBinding:将集群角色绑定到主体

主体(subject)

  • User:用户
  • Group:用户组
  • ServiceAccount:服务账号

图解如下:

实战

只能访问某个 namespace 的普通用户

普通用户并不是通过k8s来创建和维护,是通过创建证书和切换上下文环境的方式来创建和切换用户。

创建证书

Kubernetes 没有 User Account 的 API 对象,不过要创建一个用户帐号的话也是挺简单的,利用管理员分配给你的一个私钥就可以创建了,这个我们可以参考官方文档中的方法,这里我们来使用 OpenSSL 证书来创建一个 User

代码语言:javascript
复制
# 创建私钥
openssl genrsa -out wangxiansen.key 2048

# 用此私钥创建一个csr(证书签名请求)文件
openssl req -new -key wangxiansen.key -subj "/CN=wangxiansen/O=BoySec.cn" -out wangxiansen.csr

# 拿着私钥和请求文件生成证书
openssl x509 -req -in wangxiansen.csr -CA /opt/kubernetes/ssl/ca.pem -CAkey /opt/kubernetes/ssl/ca-key.pem -CAcreateserial -out wangxiansen.crt -days 365

使用刚刚创建的证书文件和私钥文件在集群中创建新的凭证和上下文

代码语言:javascript
复制
# 生成账号
kubectl config set-credentials wangxiansen --client-certificate=./wangxiansen.crt --client-key=./wangxiansen.key --embed-certs=true

设置上下文, 默认会保存在 $HOME/.kube/config

kubectl config set-context wangxiansen-context --cluster=kubernetes --user=wangxiansen --namespace=kube-system

查看

kubectl config get-contexts wangxiansen-context

此时用户 wangxiansen 就已经创建成功了,现在我们使用wangxiansen这个用户安全上下文来操作 kubectl 命令的时候,应该会出现错误,因为我们还没有为该用户定义任何操作的权限。

代码语言:javascript
复制
$ kubectl get pods --context=wangxiansen-context
Error from server (Forbidden): pods is forbidden: User "wangxiansen" cannot list resource "pods" in API group "" in the namespace "kube-system"
对用户授权

用户创建完成后,接下来就需要给该用户添加操作权限,我们来定义一个 YAML 文件,创建一个允许用户操作 Deployment、Pod、ReplicaSets 的角色,如下定义:

代码语言:javascript
复制
cat > wangxiansen-role-bind.yaml << EOF
apiVersion: rbac.authorization.k8s.io/v1
kind: Role # 角色
metadata:
name: Wxs-role
namespace: kube-system
rules:

  • apiGroups: ['', 'apps'] # ''代表核心api组
    resources: ['deployments', 'replicasets', 'pods'] # 用户可以操作Deployment、Pod、ReplicaSets的角色
    verbs: ['get', 'list', 'watch', 'create', 'update', 'patch', 'delete'] # 也可以使用['*']

apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding # 角色绑定
metadata:
name: Wxs-rolebinding
namespace: kube-system
subjects:

  • kind: User
    name: wangxiansen # 目标用户
    apiGroup: ''
    roleRef:
    kind: Role
    name: Wxs-role # 角色信息
    apiGroup: rbac.authorization.k8s.io # 留空字符串也可以,则使用当前的apiGroup
    EOF

其中 Pod 属于 core 这个 API Group,在 YAML 中用空字符就可以,而 Deployment 和 ReplicaSet 现在都属于 apps 这个 API Group(如果不知道则可以用 kubectl explain 命令查看),所以 rules 下面的 apiGroups 就综合了这几个资源的 API Group:[“”, “apps”],其中 verbs 就是我们上面提到的可以对这些资源对象执行的操作,我们这里需要所有的操作方法,所以我们也可以使用['*']来代替,然后直接创建这个 Role:

代码语言:javascript
复制
 kubectl apply -f wangxiansen-role-bind.yaml</code></pre></div></div><h5 id="a9fqg" name="%E9%AA%8C%E8%AF%81">验证</h5><p>现在我们应该可以上面的 <code>wangxiansen-context</code> 上下文来操作集群</p><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>javascript</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-javascript"><code class="language-javascript" style="margin-left:0"> kubectl get pods --context=wangxiansen-context
NAME READY STATUS RESTARTS AGE
coredns-7d9d4c67b4-lqtm5 1/1 Running 0 47h
traefik-v2-755454d858-24t5s 1/1 Running 0 47h
traefik-v2-755454d858-b2cql 1/1 Running 0 47h
traefik-v2-755454d858-gcm85 1/1 Running 0 47h

我们可以看到我们使用 kubectl 的使用并没有指定 namespace,这是因为我们我们上面创建这个 Context 的时候就绑定在了 kube-system 这个命名空间下面,如果我们在后面加上一个-n default试看看呢?

代码语言:javascript
复制
$ kubectl get pods --context=wangxiansen-context -n default
Error from server (Forbidden): pods is forbidden: User "wangxiansen" cannot list resource "pods" in API group "" in the namespace "default"

只能访问某个 namespace 的普通用户组

因为跟user类型,这里就不过多文字介绍,直接上命令和配置

代码语言:javascript
复制
# 创建私钥
openssl genrsa -out devgroups.key 2048

# 用此私钥创建一个csr(证书签名请求)文件
openssl req -new -key devgroups.key -subj "/CN=wangxiansen/O=BoySec.cn" -out devgroups.csr

# 拿着私钥和请求文件生成证书
openssl x509 -req -in devgroups.csr -CA /opt/kubernetes/ssl/ca.pem -CAkey /opt/kubernetes/ssl/ca-key.pem -CAcreateserial -out devgroups.crt -days 365

生成账号

$ kubectl config set-credentials devgroups --client-certificate=./devgroups.crt --client-key=./devgroups.key --embed-certs=true

设置上下文参数

$ kubectl config set-context devgroups-context --cluster=kubernetes --user=devgroups --namespace=kube-system

查看

$ kubectl config get-contexts devgroups-context

对组授权
代码语言:javascript
复制
cat >devgroups-role-bind.yaml<<EOF
kind: Role # 角色
apiVersion: rbac.authorization.k8s.io/v1
metadata:
namespace: kube-system
name: devgroups-role
rules:

  • apiGroups: [""] # ""代表核心api组
    resources: ["services","pods"]
    verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]

kind: RoleBinding # 角色绑定
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: devgroups-rolebinding
namespace: kube-system
subjects:

  • kind: Group # 用户组
    name: BoySec.cn # 目标用户组
    apiGroup: rbac.authorization.k8s.io
    roleRef:
    kind: Role
    name: devgroups-role # 角色信息
    apiGroup: rbac.authorization.k8s.io
    EOF
执行并验证
代码语言:javascript
复制
$ kubectl apply -f devgroups-role-bind.yaml
$ kubectl get pods --context=devgroups-context
NAME READY STATUS RESTARTS AGE
coredns-7d9d4c67b4-lqtm5 1/1 Running 0 47h
traefik-v2-755454d858-24t5s 1/1 Running 0 47h
traefik-v2-755454d858-b2cql 1/1 Running 0 47h
traefik-v2-755454d858-gcm85 1/1 Running 0 47h

只能访问某个 namespace 的 ServiceAccount

我们创建了一个只能访问某个命名空间下面的普通用户,我们前面也提到过 subjects 下面还有一种类型的主题资源:ServiceAccount,现在我们来创建一个集群内部的用户只能操作 kube-system 这个命名空间下面的 pods 和 deployments,首先来创建一个 ServiceAccount 对象:

代码语言:javascript
复制
kubectl create sa boysec-sa -n kube-system

当然我们也可以定义成 YAML 文件的形式来创建:

代码语言:javascript
复制
apiVersion: v1
kind: ServiceAccount
metadata:
name: boysec-sa
namespace: kube-system

boysec-sa用户进行绑定授权。

代码语言:javascript
复制
cat > boysec-sa-role-bing.yaml <<EOF
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: boysec-sa-role
namespace: kube-system
rules:
  • apiGroups: ['']
    resources: ['pods']
    verbs: ['get', 'watch', 'list']
  • apiGroups: ['apps']
    resources: ['deployments']
    verbs: ['get', 'list', 'watch', 'create', 'update', 'patch', 'delete']

kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: boysec-sa-rolebinding
namespace: kube-system
subjects:

  • kind: ServiceAccount # 绑定主体为服务账户
    name: boysec-sa
    namespace: kube-system
    roleRef:
    kind: Role
    name: boysec-sa-role
    apiGroup: rbac.authorization.k8s.io
    EOF
应用验证
代码语言:javascript
复制
$ kubectl apply -f boysec-sa-role-bing.yaml
$ kubectl get secret -n kube-system |grep boysec-sa
$ kubectl get secret boysec-sa-token-r85g5 -o jsonpath={.data.token} -n kube-system |base64 -d

使用这里的 token 去 Dashboard 页面进行登录,我们登录进来后默认跳转到 default 命名空间,我们切换到 kube-system 命名空间才能查看到POD

https://dashboard.od.com/#/workloads?namespace=kube-system

可以全局访问的 ServiceAccount

刚刚我们创建的 boysec-sa 这个 ServiceAccount 和一个 Role 角色进行绑定的,如果我们现在创建一个新的 ServiceAccount,需要他操作的权限作用于所有的 namespace,这个时候我们就需要使用到 ClusterRoleClusterRoleBinding 这两种资源对象了。同样,首先新建一个 ServiceAcount 对象

代码语言:javascript
复制
# kubectl create sa boysec-admin -n kube-system
apiVersion: v1
kind: ServiceAccount
metadata:
name: boysec-admin
namespace: kube-system

然后创建一个 ClusterRoleBinding 对象

代码语言:javascript
复制
cat > admin-clusterolebinding.yaml <<EOF
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: boysec-admin-clusterrolebinding
subjects:
  • kind: ServiceAccount
    name: boysec-admin
    namespace: kube-system
    roleRef:
    kind: ClusterRole
    name: cluster-admin
    apiGroup: rbac.authorization.k8s.io
    EOF
  • 从上面我们可以看到我们没有为这个资源对象声明 namespace,因为这是一个 ClusterRoleBinding 资源对象,是作用于整个集群的,我们也没有单独新建一个 ClusterRole 对象,而是使用的 cluster-admin 这个对象,这是 Kubernetes 集群内置的 ClusterRole 对象,我们可以使用 kubectl get clusterrolekubectl get clusterrolebinding 查看系统内置的一些集群角色和集群角色绑定,这里我们使用的 cluster-admin 这个集群角色是拥有最高权限的集群角色,所以一般需要谨慎使用该集群角色。

    创建上面集群角色绑定资源对象,创建完成后同样使用 ServiceAccount 对应的 token 去登录 Dashboard 验证下:

    代码语言:javascript
    复制
    $ kubectl apply -f admin-clusterolebinding.yaml
    $ kubectl get secret -n kube-system |grep boysec-admin
    $ kubectl get secret boysec-admin-token-jf6xb -o jsonpath={.data.token} -n kube-system |base64 -d