阿巩
日拱一卒
最近有大佬问了阿巩个问题,“开发PaaS平台遇到多租户的权限管理问题该如何处理”。考虑到k8s默认提供了类似的RBAC机制,于是想着借鉴或者直接利用k8s的RBAC来实现,下面是阿巩梳理的这部分内容,特来与大伙分享也让自己对这部分知识更加深化。
在介绍 RBAC 之前,先看下k8s的kube-apiserver都支持哪些授权机制。
对于访问k8s集群,用户可以通过 kubectl、客户端库或构造 REST 请求来访问。在客户端请求通过认证之后,会来到授权阶段,kube-apiserver 支持同时开启多个授权功能,并按照顺序执行授权器。6种授权机制,分别是AlwaysAllow、AlwaysDeny、ABAC、Webhook、RBAC、Node,可通过指定 --authorization-mode 参数设置授权机制。
kube-apiserver --authorization-mode=Example,RBAC --<其他选项> --<其他选项>
RBAC 授权器是目前使用最为广泛的授权模型,用户通过加入某些角色从而得到这些角色的操作权限,这极大地简化了权限管理。
在 kube-apiserver中的 RBAC 授权器中,新增了角色与集群绑定的概念。所以 kube-apiserver提供了4种对象来表达基于角色的授权,它们分别是Role、ClusterRole、RoleBinding 和 ClusterRoleBinding,这4种数据类型定义在vendor/k8s.io/api/rbac/v1/types.go中。
我们先来看角色与集群角色的数据结构,这里只列出较重要字段。图中的PolicyRule 规则相当于操作权限,权限控制资源的操作方法(即 Verbs):
Role 角色是一组用户的集合,与规则相关联,Role 只能被赋予某一 namespace 的权限,即创建 Role 时必须指定Role 所属的 namespace。
下面是一个位于 "ns-a" 名字空间的 Role 的示例,可用来授予对 pods 的读访问权限:
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: ns-a # 定义于 namespace 下
name: pod-reader
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "watch", "list"]
ClusterRole 集群角色也是一组用户的集合,与规则相关联,但ClusterRole被授予集群范围的权限,即不但能够作用于某个namespace下,还可以作用于cluster范围下。
下面是一个 ClusterRole 的示例,可用来为任一特定名字空间中的 Secret 授予读访问权限, 或者跨名字空间的访问权限(取决于该角色是如何绑定的):
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
# "namespace" 被忽略,因为 ClusterRoles 不受名字空间限制
name: secret-reader
rules:
- apiGroups: [""] # 资源组,"" 缺省为 core 组资源,其它诸如 apps 等
# 在 HTTP 层面,用来访问 Secret 资源的名称为 "secrets"
resources: ["secrets"] # 资源,比如 pods、deployments、services、secrets 等
verbs: ["get", "watch", "list"] # 操作动词,如 get、list、watch、create、delete、update 等
再来看角色绑定和集群角色绑定的数据结构,其中Subject主体可以是user,group 和 serviceaccounts;RoleRef 指被授予权限的角色的引用信息。
RoleBinding 角色绑定,绑定 Role/ClusterRole 及角色引用信息到 Subject,角色只生效于具体的 namespace 范围资源;ClusterRoleBinding 集群角色绑定,绑定 ClusterRole 及角色引用信息到 Subject,角色生效于 cluster 范围资源。
K8s 中权限控制的范围(namespace/cluster)是由 Binding 的类型决定的,而不是根据 Role 和 ClusterRole 决定的。
到这里时阿巩产生了一个疑问,对于作用于namespace范围,我需要使用 RoleBinding,但是应该选择 Role/ClusterRole 的哪种角色呢,如何选择呢?
我们通过示例对比来看,首先是 Role + RoleBinding 组合,要实现在namespace 范围内的 Subject 与 Role 绑定,无非是通过配置一个 RoleBinding 将两者联系起来。
下面的例子中的 RoleBinding 将 "pod-reader" Role 授予在 "ns-a" 名字空间中的用户 "jane"。 这样用户 "jane" 就具有了读取 "ns-a" 名字空间中 pods 的权限。代码如下:
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: read-pods
namespace: ns-a
subjects:
- kind: User
name: jane
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: Role
name: pod-reader
apiGroup: rbac.authorization.k8s.io
使用上述方法可以完成授权,但是假如有两个或者多个namespace都需要这么一个有读取 pods 的 Role,就需要为每个 namespace 都定义一个 PodReader 的 Role 了,这样做过程不优雅而且造成资源浪费。所以我们要复用这个 Role 就需用到 ClusterRole + RoleBinding 的组合:
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: read-pods
namespace: ns-a
subjects:
- kind: User
name: jane
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole # 这里使用集群角色
name: pod-reader
apiGroup: rbac.authorization.k8s.io
作用于集群范围的方案只有一个,就是 ClusterRoleBinding + ClusterRole,即全局方法只能使用 ClusterRole 这样的全局量:
apiVersion: rbac.authorization.k8s.io/v1
# 此集群角色绑定允许 “manager” 组中的任何人访问任何名字空间中的 Secret 资源
kind: ClusterRoleBinding
metadata:
name: read-secrets-global
subjects:
- kind: Group
name: manager # 'name' 是区分大小写的
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: secret-reader
apiGroup: rbac.authorization.k8s.io
创建了绑定之后,就不能再修改绑定对象所引用的 Role 或 ClusterRole。试图改变绑定对象的 roleRef 将导致合法性检查错误。如果想要改变现有绑定对象中 roleRef 字段的内容,必须删除重新创建绑定对象。
我们在上一篇文章 k8s核心数据结构 中提到了 k8s 本质上是一个资源控制系统。在 Kubernetes API 中,大多数资源都是使用对象名称的字符串表示来呈现与访问的。 例如,对于 Pod 应使用 "pods"。
RBAC 使用对应 API 端点的 URL 中呈现的名字来引用资源。有一些 Kubernetes API 涉及 子资源(subresource),例如 Pod 的日志。对 Pod 日志的请求看起来像这样:
GET /api/v1/namespaces/{namespace}/pods/{name}/log
在这里,pods 对应名字空间作用域的 Pod 资源,而 log 是 pods 的子资源。在 RBAC 角色表达子资源时,使用斜线(/)来分隔资源和子资源。要允许某主体读取 pods 同时访问这些 Pod 的 log 子资源,可以这样写:
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: default
name: pod-and-pod-logs-reader
rules:
- apiGroups: [""]
resources: ["pods", "pods/log"]
verbs: ["get", "list"]
对于某些请求,也可以通过 resourceNames 列表按名称引用资源。在指定时,可以将请求限定为资源的单个实例。下面的例子中限制可以 get 和 update 一个名为 my-configmap 的 ConfigMap:
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: default
name: configmap-updater
rules:
- apiGroups: [""]
# 在 HTTP 层面,用来访问 ConfigMap 资源的名称为 "configmaps"
resources: ["configmaps"]
resourceNames: ["my-configmap"]
verbs: ["update", "get"]
注意不能使用资源名字来限制 create 或者 deletecollection 请求。对于 create 请求而言,这是因为在鉴权时可能还不知道新对象的名字。
如果使用 resourceName 来限制 list 或者 watch 请求, 客户端必须在它们的 list 或者 watch 请求里包含一个与指定的 resourceName 匹配的 metadata.name 字段选择器,例如:
kubectl get configmaps --field-selector=metadata.name=my-configmap
在为主体绑定角色时,我们说Subject主体可以是user,group 和 serviceaccounts。大多数时候,我们在基于k8s做二次开发时都是选择通过ServiceAccount + RBAC 的方式。这里我找了个示例,链接放到下面:
https://blog.51cto.com/u_12965094/2649513
总结:
参考:
https://kubernetes.io/zh-cn/docs/reference/access-authn-authz/rbac/
https://blog.yingchi.io/posts/2020/7/k8s-rbac.html
END