深入理解Kubernetes的认证与授权机制

如题所述

第1个回答  2022-07-13

众所周知,任意一个系统的安全机制的核心都是基于 认证与授权 (Authentication and Authorization),即首先通过某种方式确认“你”的身份,再根据一定的授权策略确定“你”在我的系统里面能做什么操作。对于K8S来说,就是体现为客户端对于 kube-apiserver 的api接口操作的鉴权(客户端可以是集群内的工作负载pod,任意服务器上的kubectl程序,或是计算节点上的kubelet组件等等)。Kubernets项目作为一个成熟的开源云计算基础设施项目,让我们来看看他们是如何解决认证与授权这两个核心问题的:

k8s的用户认证机制的官方文档地址: https://kubernetes.io/docs/reference/access-authn-authz/authentication/ 。

Kubernetes中的用户类型分为 service accounts 和 normal users 两类。service accounts是k8s内部自建的一套用户认证体系,主要用于pod里直接调用kube-apiserver过程中的认证。而普通用户类型依赖于外部认证服务,k8本身不关心。

当创建service account对象时,会对应的创建一个secret对象,内含了这个用户的认证token,当pod启动时,只要yaml里申明了绑定特定的service account账号,那么系统会自动把secret的token注入到pod中的指定目录,接下来当pod调用apiserver接口时,系统都会自动的附加上这个token,这样apiserver就可以识别出这个pod的身份,结合role和rolebinding的配置信息,就可以正确的授权了。service account是基于k8内部的认证体系,使用起来比较方便,直接在集群内创建sa资源即可。此种类型的用户不是本篇文章讨论的重点,想了解具体的操作可以参考我之前的这篇文章: 构建云原生微服务网关系列-篇二:Zuul ,里面有详细的service account的使用说明。

本文讨论的重点,针对普通用户类型,很多人的理解会比较模糊,对此官网有一段说明:

也就是说,k8项目认为普通用户认证应该由外部服务供应商解决,k8本身不关心认证过程,只要告诉他最终的认证结果,即这个用户是“谁”。认证方式可以用公私钥对,或者openstack 的 keystone认证服务、google的Google Accounts服务,甚至是一个有着用户名和密码列表的文件,对于k8s来说,都是一样的。不管用何种方式去认证,最终结果都是告诉k8s,这个用户是“谁”,也就是它的用户id。这里需要注意的时,对于普通用户类型,k8是不会存储用户信息的,而对于service account类型的用户,k8会保存在etcd里面。普通用户也无法通过api调用直接创建。

Kubernetes 支持使用客户端证书、bearer token、认证代理或者http basic auth等方式进行认证,而无论使用哪种方式,认证插件都需要将以下的用户信息和http请求进行关联:

api-server目前支持的认证方式有:

使用由 k8s 根 CA 签发的证书,提取cn字段作为用户id,O字段作为用户组。我们可以使用openssl工具来进行证书的签发(kubernetes 1.4之后支持了证书中携带用户组信息):

上述操作会生成一个证书请求,username为 jbeda ,并同时属于两个用户组 app1 和 app2 。

静态token列表文件,需要预先在 API Server 服务器上放置该文件,并且在api server的启动参数中加上 --token-auth-file=SOMEFILE ,token文件为csv格式,应至少包含token, user name, user uid这三个字段(逗号分隔)以及一个可选的group names字段,例如:

注意如果用户组有多个的话,整个用户组需要用双引号括起来。

1.18版本进入稳定版的新特性,支持可以在集群启动时动态的创建和管理token,配置比较多,这里不多赘述,有兴趣直接参考官方文档

跟静态 Token 文件类似,只是使用了用户名密码的形式进行认证,使用的是http basic auth类型的认证方式,启动参数为 --basic-auth-file=SOMEFILE ,文件格式为:

很好理解,不多说了

之前介绍过了,k8s内部用户体系 Service Account 使用的 Token,认证方式也是bearer token。这里需要注意的是官方文档有一个描述:

因为api-server本身并不关注流量是从哪里过来的,所以基于service account创建的token,只要你拿到了这个token,是可以从 集群外部 发起请求的,api-server会将此请求认证为对应的service account用户。拿到token的方式官网也做了说明:

注意和serviceaccount绑定的secret类型为 kubernetes.io/service-account-token ,其中token字段即为我们需要的令牌(jwt格式),拿着这个令牌就可以直接发起请求了。 注意在secret中保存的token是经过base64编码的,实际使用时还需要先进行base64解码操作,可以使用jwt.io网站来查看这个令牌,以下是k8s签发的一个jwt令牌payload部分字段的示例:

新出来的一种认证方式,基于Oauth2,比较复杂,有兴趣可以参考官方文档,这里不介绍了。对于Oauth2认证以及JWT技术比较感兴趣的,可以参考我之前的博文 深入理解Spring Cloud Security OAuth2及JWT 。(阅读量4万多了,也算爆款了:)

搞定了认证,接下来就是授权了。得益于k8s优良的设计,认证和授权是解耦的,所以只要k8系统识别出了用户身份(username或者uid),接下来要做的事情就是一样的了。关于授权部分的官方文档地址: https://kubernetes.io/docs/reference/access-authn-authz/rbac/

事实上k8s本身也支持多种授权类型,比如rbac,abac,node,dynamic admission 等等。这里只介绍下最常用的rbac(基于角色的访问控制),实际使用中,api-server开启 --authorization-mode=RBAC 参数,即启动了rbac功能。

如果你对于rbac本身已经比较了解,那么其实k8s里面的rbac功能就非常容易理解了。涉及rbac的有两个api对象,role定义了一个角色,申明了此角色可以操作的功能列表,rolebinding其实就是把用户和角色做了一个绑定。

role的api对象示例:

这个yaml定义了一个Role对象,名称为 pod-reader , 作用域为 default 这个namespace,可以对 pods 这个对象进行 get 、 watch 、 list 操作。
kubernetes完整的操作类型列表如下,都很好理解,不一一说明了:

值得注意的是,有些资源还有子类型,比如pod的logs,如果需要查看,也是需要授权的(添加 pods/log 资源类型)

RoleBinding资源的作用也非常容易理解, 就是绑定Role和用户。下面是一个RoleBinding的示例:

这个例子里把一个类型为User,name叫做jane的用户,和pod-reader的Role做了绑定。注意subjects里面的 kind 字段,即用户类型,前面介绍过了,分别是 User 、 Group 和 ServiceAccount 。绑定完成之后,当使用 jane 这个用户身份对k8s的api进行调用,就可以进行指定的 watch 、 get 、 list 操作了。

这两资源其实和Role、RoleBinding的配置方式是完全一样的,区别在于ClusterRole和ClusterRoleBinding的作用域是集群范围的,并且可以操作 node 这样的集群范围资源,而Role、RoleBinding在metadata中需要指定namespace字段,其他方面没有区别。

弄清原理之后,现在让我们来实际操作一下,目标是使用kubectl客户端工具对于给定的k8集群进行受限操作。基于上述的认证策略的介绍,我们使用 客户端证书 方式来进行用户认证,即使用K8集群的根证书来签发一个用户证书,使用该证书来进行用户认证及授权操作。

关于RSA证书的制作,可以参考官网文档: https://kubernetes.io/docs/concepts/cluster-administration/certificates/ ,这里我们使用常用的openssl工具来制作证书:

1、创建一个2048位长度的RSA格式私钥

2、创建证书签名请求(csr),CN-对应Username O-对应用户组,上面的文章中已经介绍过

3、使用集群根证书签发这个证书请求(days是证书到期时间,可根据实际需要配置)

首先先找到一台准备作为客户端访问k8集群的linux服务器(或者windows、mac都可以),确保客户端与集群的api-server端口网络联通(一般为6443端口,注意必须是https连接),出于安全考虑,最好开一个操作k8的专用的操作系统账号。把集群master节点中的kubectl二进制文件拷贝至此服务器/usr/bin目录内,同时拷贝release.csr、release.key、ca.pem这三个文件至服务器上的指定目录。

在新建用户的home目录下创建.kube目录,在此目录下新建config文件(或者直接执行kubectl config set-cluster test操作,kubectl会自动创建该文件),编辑该文件填写如下内容:

完成之后可以执行 kubectl config view 来验证一下配置是否正确。

使用管理员登陆k8集群,进行权限配置,这里以添加集群范围的运维用户权限为例:

可以看到,我们定义了一个角色 release ,用于应用的部署及日常运维操作。为了满足日常运维,给其分配了一组受限的资源权限。

具体来说,该角色对"deployments","services","configmap","pvc"资源有全部的操作权限,对于"nodes","events","pods","pods/log","endpoints"只有查看权限,对于其他资源没有任何权限。

这里我们定义了一个ClusterRoleBinding,把User和ClusterRole进行了绑定,到这里全部操作就完成了。

登陆客户端,一切顺利的话,执行 kubectl get pods 就可以返回远程集群的default命名空间下的pods列表了,其他配置的权限应该也可以正常操作。而如果这个用户想访问受限资源,比如想查看secrets信息,则会出现如下的报错信息(403 Forbidden):

验证成功!

基于上述的描述,可以知道,其实在集群里面创建一个service account,把它的token拷贝出来,配置在客户端的kubectl配置文件中,可以达到同样的效果,这里就不再演示了。

因为service account的token机密信息实际上都是存放于secret对象中,而secret经常被人吐槽的是存放的数据是明文(只是做了base64编码),所以这里多说一句secret的安全性问题。其实k8s是支持secret加密存放的,支持的加密类型还挺多,具体可以看我这篇文章: 使用加密插件加密secrets中的数据 。但其实我并不建议使用这种方式,原因是使用加密插件只能加密存放在etcd里面的数据,而使用api server调取出的数据仍然是解密过后的,这意味着你执行 kubectl get secrets 或者是进入容器的环境变量查看,仍然可以看到明文数据。k8s项目更推荐的权限管理方式是:

做好上面两点,对于一般公司的安全管控来讲已经足够,毕竟集群管理员的权限只是掌握在很小一部分人的手中。而对于安全审计要求更高的企业(比如金融机构),审计可能会有要求敏感数据必须加密存放,此时可使用api-server的加密插件。

相似回答