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

网上基于Role+RoleBinding+ServiceAccount组合进行权限控制的介绍比较多,本篇主要介绍使用kubenetes的User 用户类型进行rbac权限控制的具体说明
众所周知,任意一个系统的安全机制的核心都是基于认证与授权(Authentication and Authorization),即首先通过某种方式确认“你”的身份,再根据一定的授权策略确定“你”在我的系统里面能做什么操作。对于K8S来说,就是体现为客户端对于 kube-apiserver 的api接口操作的鉴权(客户端可以是集群内的工作负载pod,任意服务器上的kubectl程序,或是计算节点上的kubelet组件等等)。Kubernets项目作为一个成熟的开源云计算基础设施项目,让我们来看看他们是如何解决认证与授权这两个核心问题的:
Authentication k8s的用户认证机制的官方文档地址:https://kubernetes.io/docs/reference/access-authn-authz/authentication/。
用户类型
Kubernetes中的用户类型分为service accountsnormal users两类。service accounts是k8s内部自建的一套用户认证体系,主要用于pod里直接调用kube-apiserver过程中的认证。而普通用户类型依赖于外部认证服务,k8本身不关心。
Service Accounts 当创建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的使用说明。
Normal Users 本文讨论的重点,针对普通用户类型,很多人的理解会比较模糊,对此官网有一段说明:
Normal users are assumed to be managed by an outside, independent service. An admin distributing private keys, a user store like Keystone or Google Accounts, even a file with a list of usernames and passwords. In this regard, Kubernetes does not have objects which represent normal user accounts. Normal users cannot be added to a cluster through an API call.
也就是说,k8项目认为普通用户认证应该由外部服务供应商解决,k8本身不关心认证过程,只要告诉他最终的认证结果,即这个用户是“谁”。认证方式可以用公私钥对,或者openstack 的 keystone认证服务、google的Google Accounts服务,甚至是一个有着用户名和密码列表的文件,对于k8s来说,都是一样的。不管用何种方式去认证,最终结果都是告诉k8s,这个用户是“谁”,也就是它的用户id。这里需要注意的时,对于普通用户类型,k8是不会存储用户信息的,而对于service account类型的用户,k8会保存在etcd里面。普通用户也无法通过api调用直接创建。
认证策略
Kubernetes 支持使用客户端证书、bearer token、认证代理或者http basic auth等方式进行认证,而无论使用哪种方式,认证插件都需要将以下的用户信息和http请求进行关联:
  • Username: 用户的标识符,用于k8s识别用户,可以是字符串或者邮箱地址。例如:kube-admin 或者 jane@example.com.
  • UID: 终端用户标识符,相对于Username来说更容易保持唯一性
  • Groups: 用户所关联的用户组
  • Extra fields: 附加域,认证系统可以根据需要填写一些额外的信息
api-server目前支持的认证方式有:
X509 Client Certs 使用由 k8s 根 CA 签发的证书,提取cn字段作为用户id,O字段作为用户组。我们可以使用openssl工具来进行证书的签发(kubernetes 1.4之后支持了证书中携带用户组信息):
openssl req -new -key jbeda.pem -out jbeda-csr.pem -subj "/CN=jbeda/O=app1/O=app2"

上述操作会生成一个证书请求,username为jbeda,并同时属于两个用户组app1app2
Static Token File: 静态token列表文件,需要预先在 API Server 服务器上放置该文件,并且在api server的启动参数中加上--token-auth-file=SOMEFILE,token文件为csv格式,应至少包含token, user name, user uid这三个字段(逗号分隔)以及一个可选的group names字段,例如:
token,user,uid,"group1,group2,group3"

注意如果用户组有多个的话,整个用户组需要用双引号括起来。
Bootstrap Tokens 【深入理解Kubernetes的认证与授权机制】1.18版本进入稳定版的新特性,支持可以在集群启动时动态的创建和管理token,配置比较多,这里不多赘述,有兴趣直接参考官方文档
Static Password File 跟静态 Token 文件类似,只是使用了用户名密码的形式进行认证,使用的是http basic auth类型的认证方式,启动参数为--basic-auth-file=SOMEFILE,文件格式为:
password,user,uid,"group1,group2,group3"

很好理解,不多说了
Service Account Tokens 之前介绍过了,k8s内部用户体系 Service Account 使用的 Token,认证方式也是bearer token。这里需要注意的是官方文档有一个描述:
Service account bearer tokens are perfectly valid to use outside the cluster and can be used to create identities for long standing jobs that wish to talk to the Kubernetes API.
因为api-server本身并不关注流量是从哪里过来的,所以基于service account创建的token,只要你拿到了这个token,是可以从集群外部发起请求的,api-server会将此请求认证为对应的service account用户。拿到token的方式官网也做了说明:
kubectl get secret jenkins-token-1yvwg -o yaml

apiVersion: v1 data: ca.crt: (APISERVER'S CA BASE64 ENCODED) namespace: ZGVmYXVsdA== token: (BEARER TOKEN BASE64 ENCODED) kind: Secret metadata: # ... type: kubernetes.io/service-account-token

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

OpenID Connect Tokens 新出来的一种认证方式,基于Oauth2,比较复杂,有兴趣可以参考官方文档,这里不介绍了。对于Oauth2认证以及JWT技术比较感兴趣的,可以参考我之前的博文深入理解Spring Cloud Security OAuth2及JWT。(阅读量4万多了,也算爆款了:)
Authorization 搞定了认证,接下来就是授权了。得益于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
role的api对象示例:
apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: namespace: default name: pod-reader rules: - apiGroups: [""] # "" indicates the core API group resources: ["pods"] verbs: ["get", "watch", "list"]

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

值得注意的是,有些资源还有子类型,比如pod的logs,如果需要查看,也是需要授权的(添加pods/log资源类型)
RoleBinding
RoleBinding资源的作用也非常容易理解, 就是绑定Role和用户。下面是一个RoleBinding的示例:
apiVersion: rbac.authorization.k8s.io/v1 # This role binding allows "jane" to read pods in the "default" namespace. # You need to already have a Role named "pod-reader" in that namespace. kind: RoleBinding metadata: name: read-pods namespace: default subjects: # You can specify more than one "subject" - kind: User name: jane # "name" is case sensitive apiGroup: rbac.authorization.k8s.io roleRef: # "roleRef" specifies the binding to a Role / ClusterRole kind: Role #this must be Role or ClusterRole name: pod-reader # this must match the name of the Role or ClusterRole you wish to bind to apiGroup: rbac.authorization.k8s.io

这个例子里把一个类型为User,name叫做jane的用户,和pod-reader的Role做了绑定。注意subjects里面的kind字段,即用户类型,前面介绍过了,分别是UserGroupServiceAccount。绑定完成之后,当使用jane这个用户身份对k8s的api进行调用,就可以进行指定的watchgetlist操作了。
ClusterRole和ClusterRoleBinding
这两资源其实和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格式私钥
openssl genrsa -out release.key 2048

2、创建证书签名请求(csr),CN-对应Username O-对应用户组,上面的文章中已经介绍过
openssl req -new -key release.key -out release.csr -subj "/CN=release/O=k8s"

3、使用集群根证书签发这个证书请求(days是证书到期时间,可根据实际需要配置)
openssl x509 -req -in release.csr -CA /etc/kubernetes/ssl/ca.pem -CAkey /etc/kubernetes/ssl/ca-key.pem -CAcreateserial -out release.crt -days 365

kubectl配置
首先先找到一台准备作为客户端访问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会自动创建该文件),编辑该文件填写如下内容:
apiVersion: v1 clusters: # 集群信息配置,可以配置多个k8集群 - cluster: certificate-authority: /home/kube/kube-certs/ca.pem #集群根证书,用于验证远程api-server server: https://192.168.1.12:6443 #api-server的url地址,必须为'https' name: kubernetes contexts: # 配置上下文, 指定kubectl连接哪个集群 - context: cluster: kubernetes user: release name: test current-context: test kind: Config preferences: {} users: - name: release user: client-certificate: /home/kube/kube-certs/release.crt #用户证书 client-key: /home/kube/kube-certs/release.key #用户私钥

完成之后可以执行kubectl config view来验证一下配置是否正确。
集群权限配置
使用管理员登陆k8集群,进行权限配置,这里以添加集群范围的运维用户权限为例:
ClusterRole
apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: release rules: - apiGroups: [""] resources: ["nodes","events","pods","pods/log","endpoints"] verbs: ["get", "list"] - apiGroups: [""] resources: ["deployments","services","configmap","pvc"] verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]

可以看到,我们定义了一个角色release,用于应用的部署及日常运维操作。为了满足日常运维,给其分配了一组受限的资源权限。
具体来说,该角色对"deployments","services","configmap","pvc"资源有全部的操作权限,对于"nodes","events","pods","pods/log","endpoints"只有查看权限,对于其他资源没有任何权限。
ClusterRoleBinding
kind: ClusterRoleBinding metadata: name: release subjects: - kind: User name: release # 名称区分大小写 apiGroup: rbac.authorization.k8s.io roleRef: kind: ClusterRole name: release apiGroup: rbac.authorization.k8s.io

这里我们定义了一个ClusterRoleBinding,把User和ClusterRole进行了绑定,到这里全部操作就完成了。
验证 登陆客户端,一切顺利的话,执行kubectl get pods就可以返回远程集群的default命名空间下的pods列表了,其他配置的权限应该也可以正常操作。而如果这个用户想访问受限资源,比如想查看secrets信息,则会出现如下的报错信息(403 Forbidden):
[dce@localhost kube-certs]$ kubectl get secrets Error from server (Forbidden): secrets is forbidden: User "release" cannot list resource "secrets" in API group "" in the namespace "default"

验证成功!
扩展思考 基于上述的描述,可以知道,其实在集群里面创建一个service account,把它的token拷贝出来,配置在客户端的kubectl配置文件中,可以达到同样的效果,这里就不再演示了。
secret的安全性问题 因为service account的token机密信息实际上都是存放于secret对象中,而secret经常被人吐槽的是存放的数据是明文(只是做了base64编码),所以这里多说一句secret的安全性问题。其实k8s是支持secret加密存放的,支持的加密类型还挺多,具体可以看我这篇文章:使用加密插件加密secrets中的数据。但其实我并不建议使用这种方式,原因是使用加密插件只能加密存放在etcd里面的数据,而使用api server调取出的数据仍然是解密过后的,这意味着你执行kubectl get secrets或者是进入容器的环境变量查看,仍然可以看到明文数据。k8s项目更推荐的权限管理方式是:
  • 禁止etcd的非授权直接访问。事实上k8s项目使用的是etcd的v3版本api,默认需要配置证书,只有k8的核心组件才有权限直接访问etcd。所以只要你没有做过特别的配置(比如把etcd的证书导出给其他组件使用),这部分的安全性是可以保障的。
  • 做好集群的rbac权限管理。在掌握了上述的集群权限相关知识后,不难发现,只要不给普通的运维用户配置secrets的操作权限,以及pod的pods/exec权限,那么普通用户是无法获取secrets中的信息的。(重点:即使开启了加密插件,这部分也是必须要做的,原因上面已经讲过)
做好上面两点,对于一般公司的安全管控来讲已经足够,毕竟集群管理员的权限只是掌握在很小一部分人的手中。而对于安全审计要求更高的企业(比如金融机构),审计可能会有要求敏感数据必须加密存放,此时可使用api-server的加密插件。

    推荐阅读