前言
本文介绍K8s中的鉴权模块。对其4种鉴权模式都进行了概述讲解。结合例子着重对大家日常中使用最多的RBAC鉴权模式进行了说明。
鉴权概述
《搞懂K8s认证》中,我们提到不论是通过kubectl客户端还是REST请求访问K8s集群,最终都需要经过API Server来进行资源的操作并通过Etcd。整个过程如下图1所示,可以分成4个阶段:
图1 K8s API请求访问过程
请求发起方进行K8s API请求,经过Authentication(认证)、Authorization(鉴权)、AdmissionControl(准入控制)三个阶段的校验,最后把请求转化为对K8s对象的变更操作持久化至etcd中。
其中认证主要解决的是请求来源能否访问的问题。即通过了认证,那么可以认为它是一个合法的请求对象。那么如何去决定请求对象能访问哪些资源以及对这些资源能进行哪些操作,便是鉴权所要完成的事情了。
鉴权的最终目的,是区分请求对象,限定操作的影响范围,让其使用最小的权限完成自己所要进行操作,从而进一步保证安全。权限控制的划分方式有许多种,K8s中提供了4种鉴权模式,分别为Node、ABAC、RBAC和Webhook。
默认情况下,我们可以从/etc/kubernates/manifests/kube-apiserver.yaml文件中查看apiserver启动时认证模式,片段如图2所示:
图2 kube-apiserver的认证参数
其中可以使用的参数如表1所示:
表1 鉴权模块参数标识表
参数配置 | 含义 | 一般的使用场景 |
--authorization-mode=ABAC | 使用基于属性的访问控制(ABAC) | 根据用户的用户名或者组名来控制其对集群资源的访问权限,适用于较小的组织或开发团队 |
--authorization-mode=RBAC | 使用基于角色的访问控制(RBAC) | 自定义ServiceAccount,绑定资源根据角色来控制资源的访问权限,适用于较大型的组织或者开发运维团队 |
--authorization-mode=Webhook | 使用HTTP回调模式,允许你使用远程REST端点管理鉴权 | 将鉴权角色交给外部服务进行处理,根据自身需求,定制和扩展鉴权策略,如自定义Webhook鉴权模块对跨云平台的应用进行集中的的访问控制 |
--authorization-mode=Node | 针对kubelet发出的API请求执行鉴权 | 验证节点身份,确保只有经过身份验证且具有所需权限的Node才能连接到K8s集群 |
--authorization-mode=AlwaysDeny | 阻止所有请求 | 一般仅用作测试 |
--authorization-mode=AlwaysAllow | 允许所有请求 | 不需要API请求进行鉴权的场景 |
如图2所示,可以同时配置多个鉴权模块(多个模块之间使用逗号分隔),排在靠前的模块优先执行,任何模式允许或拒接请求,则立即返回该决定,并不会与其他鉴权模块协商。
Node鉴权
Node鉴权是一种特殊用途的鉴权模式,旨在对kubelet发出API请求进行授权。Node鉴权允许kubelet执行API的操作分成读和写两部分。读取操作控制范围为:services、endpoints、nodes、pods以及绑定到kubelet节点 Pod相关的secret、configmap、pvc和持久卷。写入操作的范围主要是节点和节点状态、Pod和Pod状态以及事件,若要限制kubelet只能修改自己的节点,则还需要在Apiserver启动时,开启NodeRestriction准入插件(见图2第二个红框)。
开启Node鉴权模块后,kubellet为了获取授权,必须使用一个特定规则的凭据,如图3所示:
图3 kubelet的证书凭据
从图中我们看到,kubelet使用了一个证书凭据,其中O=system:nodes表示其所在组,CN=system:node:paas-cnp-k8s-kce-01表示其用户名,满足了Node鉴权模块要求的组名必须为system:nodes,用户名必须为system:node:<nodeName>的要求。其中<nodeName>默认由hostname或kubelet --hostname-override选项提供指定,其必须与kubelet提供的主机名称精确匹配。
system:nodes是K8s的内置用户组,我们可以通过其默认的ClusterRoleBinding,如图4所示:
图4 system:nodes的ClusterRoleBinding内容
我们可以发现,它指示指向了system:node这个ClusterRole,并没有subjects的内容,即其没有绑定system:node:paas-cnp-k8s-kce-01用户也没有绑定system:nodes组。其正是因为K8s基于 Node鉴权模块来限制kubelet只能读取和修改本节点上的资源,并不是使用 RBAC来鉴权(涉及到部分RBAC的内容,下文会进行详解)。
ABAC鉴权
基于属性的访问控制,K8s中可以表述将访问策略授予用户或者组。与RBAC不同的点在于,其策略是由任何类型的属性(用户属性、资源属性、对象、环境等)进行描述。
启用ABAC模式,类似于图2需要在apiserver启动时指定--authorization-mode=ABAC以及--authorization-policy-file=<策略文件路径> 。图5是K8s官网给出的一个策略样例文件:
图5 ABAC策略实例文件内容
文件中每行都是一个JSON对象,其中版本控制属性"apiVersion": "abac.authorization.kubernetes.io/v1beta1"和"kind": "Policy"可以理解成固定写法,是被K8s本身所使用,便于以后进行版本控制和转换。spec部分,其中user,来自于--token-auth-file的用户字符串,其指定的user名称必须与这个文件中的字符串相匹配;group,必须与经过身份验证的用户的一个组匹配, system:authenticated 匹配所有经过身份验证的请求。 system:unauthenticated 匹配所有未经过身份验证的请求。其他的匹配属性,主要描述被访问的访问,分为资源属性和非资源属性(其具体定义可参见官网)。readonly,主要限制是否只允许对资源进行 get、list 和 watch 操作,非资源属性只进行get操作。
例如:
{"apiVersion": "abac.authorization.kubernetes.io/v1beta1", "kind": "Policy", "spec": {"user": "alice", "namespace": "*", "resource": "*", "apiGroup": "*"}}
表示用户alice可以对所有namespace下的所有资源进行任何操作;
{"apiVersion": "abac.authorization.kubernetes.io/v1beta1", "kind": "Policy", "spec": {"user": "bob", "namespace": "projectCaribou", "resource": "pods", "readonly": true}}
表示用户bob只能对namespace为projectCaribou的Pod进行list、get和watch操作。
对于ServiceAccount的ABAC管控。Service Account会自动生成一个ABAC用户名,其格式为:system:serviceaccount:<namespace>:<serviceaccountname>,创建新的命名空间时,K8s会为我们在当前namespace下创建一个名为default的ServiceAccount,如果现在假定我们需要namespace为test名为default的ServiceAccount具有该namespace下的全部权限,则可以添加这样一条规则:
{"apiVersion":"abac.authorization.kubernetes.io/v1beta1","kind":"Policy","spec":{"user":"system:serviceaccount:test:default","namespace":"test","resource":"*","apiGroup":"*"}}
我们发现,ABAC虽然对用户访问进行了一定的划分,但是如果添加了新的ABAC策略,则需要重启API Server以使其生效,资源的访问权限细粒度也只控制到了是否只读,当用户规模增大时,这是远远不能满足需求的。对于较大组织的团队进行更加细粒度的管控,就需要使用到K8s的RBAC鉴权模块了。
RBAC鉴权
启用RBAC鉴权,可以如图2所示,在--authorization-mode的启动参数中包含RBAC即可。相对于其他访问控制方式,RBAC具有如下优势:
- 对集群中的资源和非资源权限均有完整的覆盖。
- RBAC由几个API对象完成。同其他API对象一样,可以用kubectl或API进行操作和调整,不必重新启动ApiServer。
K8s的RBAC主要阐述解决了主体(subject)是什么角色的问题。其中主体就是K8s中的用户,包含了常规用户(User、Group,平时常用的kubectl命令都是普通用户执行的)和服务账户(ServiceAccount,主要用于集群进程与K8s的API通信)。角色决定了能够对资源进行什么样的操作,RBAC中我们可以定义namespace级别的Role也可以定义集群级别的ClusterRole。最后K8s再通过RoleBinding和ClusterRoleBinding把用户和角色的关系进行关联绑定,其中RoleBinding既可以绑定Role也可以绑定ClusterRole,ClusterRole指定绑定ClusterRole,最终实现了不同用户对不同资源进行特定操作的控制。Role针对特定的命名空间,ClusterRole在整个集群范围内都生效,所以后者访问范围也更大,能够限制访问Node等资源。其关系图如图6所示:
图6 RBAC整体关系图
现在假设你所在部门使用名为rbac-team的命名空间工作,现在要为部署流水线创建一个新的ClusterRole名为deployment-clusterrole,且仅允许创建Deployment、StatefulSet、DaemonSet三种资源,同时在rbac-team这个命名空间下创建一个新的ServiceAccount名为cicd-test使用这个ClusterRole。K8s中我们可以使用kubectl客户端或者API资源的方式来实现这个RBAC的功能。
Kubectl客户端方式。
- 第一步,我们可以先执行命令:kubectl create clusterrole deployment-clusterrole --verb=create --resource=deployments,statefulsets,daemonsets 创建所需的ClusterRole,其中--verb代表可以执行的操作,--reasource代表授权操作的资源范围;
- 第二步,执行命令:kubectl -n rbac-team create serviceaccount cicd-test创建要求的ServiceAccount;
- 第三步,限于rbac-test中绑定所需权限,执行命令:kubectl -n rbac-team create rolebinding cicd-rolebinding --clusterrole=deployment-clusterrole --serviceaccount=rbac-team:cicd-test,对新创建的ServiceAccount与我们的ClusterRole进行绑定。
Api资源的方式。
- 第一步,准备ClusterRole的资源描述,创建名为cr-deployment.yaml的文件,如图7所示:
图7 deployment-clusterrole资源
其中verbs用于限定可执行的操作,可配置参数有:“get”, “list”, “watch”, “create”, “update”, “patch”, “delete”, “exec”。resources用于限定可访问的资源范围,可配置参数有:“services”, “endpoints”, “pods”,“secrets”,“configmaps”,“crontabs”,“deployments”,“jobs”,“nodes”,“rolebindings”,“clusterroles”,“daemonsets”,“replicasets”,“statefulsets”,“horizontalpodautoscalers”,“replicationcontrollers”,“cronjobs”。apiGroups用于划分访问的资源组,可配置参数有:“”,“apps”, “autoscaling”, “batch”。
- 第二步,准备ServiceAccount的资源描述,创建名为sa-cicd.yaml的文件,如图8所示:
图8 sa-cicd资源
- 第三步,准备RoleBinding的资源描述,创建名为rb-cicd.yaml的文件,如图9所示:
图9 rolebinding资源
将前面两步的clusterrole和ServiceAccount进行绑定,其中subjects指明了被授权的主体,当被授权对象是User或者Group时,可以使用类似如下片段subjects即可(Group的话只需要将kind的值改为Group):
- kind: User
name: "test-user"
apiGroup: rbac.authorization.k8s.io
- 第四步,使用kubectl -f 分别应用上述资源,完成主体(subject)、角色的创建与绑定。
当K8s的进程资源需要使用上述身份凭证进行权限划分时,只需要在资源声明文件中进行配置即可,如图9所示:
图10 pod资源使用ServiceAccount
为特定应用的Service Account赋权进行权限管控也是最安全的最值得推荐的。
APIServer默认的ClusterRole和ClusterRoleBinding对象,其中很多是以"system:"为前缀的,对这些对象的改动可能造成集群故障。例如Node鉴权模块提到的system:node,这个ClusterRole为kubelet定义了权限,如果这个集群角色被改动了,kubelet就会停止工作。
所有默认的ClusterRole和RoleBinding都会用标签kubernetes.io/bootstrapping: rbac-defaults进行标记。
Webhook鉴权
启用Webhook模式的方式与ABAC模式相似,我们需要在apiserver启动时指定--authorization-mode=ABAC以及一个HTTP配置文件用以指定策略吗,可以通过参数--authorization-webhook-config-file=<策略文件路径>,策略文件配置使用的是kubeconfig文件的格式。users部分表示APIServer的webhook,cluster代表着远程服务。图11部分是K8s官网给出的一个示例。
图11 Webhook的策略配置示例
当进行认证时,API服务器会生成一个JSON对象来描述这个动作,对于一个资源类型的请求,其内容主要包含了需要被访问的资源或请求特征,如图12所示:
图12 资源类型的Webhook鉴权请求示例
从图12中我们可以看到,这个请求是User名为jane,Group为group1、gourp2为主体,请求的资源为命名空间为kittensandpoines下的pods资源,资源所属group为unicorn.example.org,请求的执行操作为get这个资源信息。若我们的Webhook服务URL同意了这个请求,则可以返回如图13格式的响应体。
图13 Webhook请求同意响应示例
从图13中我们可以看出,我们只需要填充SubjectAccessReview的status对象信息的值即可。若要拒绝请求,则可以返回图14或图15的响应。
图14 Webhook请求拒绝响应示例1
图15 Webhook请求拒绝响应示例2
图14和图15都是对请求进行拒绝,都给出了拒绝通过的原因,差别在于图14的拒绝响应中增加了denied:true的信息,其表示当我们配置多了多个鉴权模块的时候,当这个响应返回时立即拒绝请求。(默认当存在多个鉴权模块,Webhook拒绝后可以再通过其他模块进行鉴权,只有当其他模块也不通过或者不存在时,才禁止访问)
对于非资源的路径访问,生成的JSON请求示例如图16所示:
图16 非资源的路径请求的Webhook鉴权请求示例
总结
鉴权主要是解决“谁可以对什么资源进行哪些操作”的问题。K8s的鉴权模块主要有4个,发生在鉴权认证阶段的第二阶段。Node模块主要针对kubelet需要访问APIServer的场景;ABAC主要针对User和Group的常规用户,控制力度较粗;RBAC即可以用于常规用户也可以针对ServiceAccount进行管控,且管控力度十分细腻,是常用的K8s鉴权方式;Webhook主要用于定制自定义的鉴权逻辑,可用于跨云的集中式鉴权。
参考文献:
https://kubernetes.io/zh-cn/docs/reference/access-authn-authz/node/
https://kubernetes.io/zh-cn/docs/reference/access-authn-authz/abac/
https://kubernetes.io/zh-cn/docs/reference/access-authn-authz/rbac/
https://kubernetes.io/zh-cn/docs/reference/access-authn-authz/webhook/ https://www.cnblogs.com/maiblogs/p/16271997.html
https://cloud.tencent.com/developer/article/2149852