【Kubernetes解读】Authenticating
认证(Authenticating)是对客户端的认证,通俗点就是用户名密码验证,授权(Authorization)是对资源的授权,k8s中的资源无非是容器,最终其实就是容器的计算,网络,存储资源,当一个请求经过认证后,需要访问某一个资源(比如创建一个pod),授权检查都会通过访问策略比较该请求上下文的属性,(比如用户,资源和Namespace),根据授权规则判定该资源(比如某namespace下的pod)是否是该客户可访问的。准入(Admission Control)机制是一种在改变资源的持久化之前(比如某些资源的创建或删除,修改等之前)的机制。 在k8s中,这三种机制如下图:
k8s的整体架构也是一个微服务的架构,所有的请求都是通过一个GateWay,也就是kube-apiserver这个组件(对外提供REST服务),由图中可以看出,k8s中客户端有两类,一种是普通用户,一种是集群内的Pod,这两种客户端的认证机制略有不同,后文会详述。但无论是哪一种,都需要依次经过认证,授权,准入这三个机制。
Kubernetes认证背景
为什么Kubernetes没有用户以及用户组?
Kubernetes的RBAC模型授权对象(Subject)是用户(User)或者用户组(Group),即使ServiceAccount也会当作为一个虚拟User。
但是很令人疑惑的是通过Kubernetes找不到真正的用户以及用户组信息,甚至连对应的User Resource以及Group Resource都没有,所以有时候在写rolebingding的时候会觉得很奇怪,Subjects需要填User或者Group,可是Kubernetes却没有办法列出可信任的User列表以及Group列表。
换句话说Kubernetes并没有提供用户管理和身份认证功能,除了Service Account外,所有的用户信息都依赖外部的用户管理系统来存储,因此通过api-serever根本无法列出User和Group。
这其实挺符合UNIX设计哲学的,即Do One Thing and Do It Well。
Kubernetes只专注于做应用编排,其他的功能则提供接口集成,除了认证和授权,我们发现网络、存储也都如此。
这样做的好处也显而易见,用户账户信息与Kubernetes集群松耦合,便于集成企业已有的身份认证系统,如AD、LADP、Keycloak等。
关于kubeconfig文件
kubectl是通过读取kubeconfig获取集群信息的,默认路径为$HOME/.kube/config,可以通过KUBECONFIG环境变量指定多个config文件。
kubeconfig文件主要包含如下三个部分:
- cluster: 存储api-server的CA根证书、api-server地址、集群名称等。
- user: 真正配置用户认证时的凭证信息,使用不同的认证策略,包含不同的字段。
- context: 把cluster和user关联起来组成一个集群环境信息,声明通过哪个user连哪个cluster。
手动编辑config文件非常麻烦,kubectl config子命令提供了大部分的参数自动填充kubeconfig文件,分别对应set-cluster、set-credentials、set-context,相对应的有get-clusters、get-contexts以及delete-cluster、delete-context,目前没有对应credential get和delete操作,只能手动编辑kubeconfig文件。
通过use-context切换集群上下文。
关于Kubernetes认证策略
Kubernetes虽然没有直接实现普通用户的身份认证功能,但是很好的支持集成各种已有的身份认证策略,并且支持同时使用多种认证策略,只要有其中一种认证策略校验通过就算认证通过。
可以说Kubernetes的认证方式五花八门,这些认证方式都有哪些优缺点,适合什么样的场景,这就是本文需要研究的两个问题。
本文接下来主要研究Kubernetes提供的几种认证策略,结合自己在使用过程以及阅读了大量的文献基础上,总结不同策略的优缺点以及适用场景。
Role以及Rolebingding配置
不仅Kubernetes的身份认证是通过外部系统集成,Kubernetes的授权其实也支持各种插件,本文不会详细讨论关于Kubernetes的授权机制,仅为了验证身份认证,假定授权使用的是RBAC插件。
预先创建了role和rolebingding如下:
- role:
|
|
- rolebingding:
|
|
静态密码认证
静态密码是最简单的认证方式,只需要在api-server启动时指定使用的密码本路径即可:
|
|
其中密码本格式为csv:
|
|
demo如下:
|
|
此时定义了一个用户int32bit-1,静态密码为NoMoreSecret,所属Group为intt32bit。
此时可生成凭证config文件:
|
|
使用该config文件如下:
int32bit-1由于所属int32itgroup,因此可以读取pod列表,但是无法删除pod。
通过静态密码的唯一优势是简单,其缺点也是非常明显:
- 静态密码是明文,非常不安全,还有可能被暴力破解。
- 非常不灵活,增加或者删除用户,必须手动修改静态密码文件并重启所有的api-server服务。
这种方式在实际场景中很少被使用,不建议生产环境使用。
x509证书认证
x509认证是默认开启的认证方式,api-server启动时会指定ca证书以及ca私钥,只要是通过ca签发的客户端x509证书,则可认为是可信的客户端。
使用kubeadm部署后的cluster-admin默认就是通过x509证书认证的,node节点和API server通信也是通过证书认证的。
这里需要注意的是,在kubectl config中会有两个证书,分别为certificate-authority证书和client-certificate证书。client-certificate用于客户端认证,这显而易见。
但certificate-authority用于做什么呢?答案是为了避免与api server通信时遭受中间人攻击,api server默认使用了https协议,但我们使用kubeadm部署Kubernetes集群时默认使用的是自签证书,为了让客户端信任api server的根证书,需要配置server端的证书。当然直接添加到OS的可信任根证书列表中也是可以的。如果使用权威机构颁发的证书则不需要配置。
综上,kubectl客户端与Kubernetes api-server认证时采用的是双向认证,这里不限于x509认证模式,所有的认证模式都应该配置为双向认证,因此后面将介绍的Token认证模式同样需要server端的根证书。
在使用client-certificate客户端证书认证时,CN(commom Name)对应Kubernetes的User,O(organization)对应Kubernetes的Group。
签发客户端证书有两种方式,一种是基于CA根证书签发证书,另一个种是发起CSR(Certificate Signing Requests)请求。
使用CA根证书签发客户端证书
使用CA根证书需要CA的私钥,假设要创建一个int32bit用户,所属的组为int32bit,使用openssl签发证书:
|
|
其中CA_LOCATION为api server的CA证书路径,使用kubeadm部署一般为/etc/kubernetes/pki/。
最后生成config文件:
|
|
注意使用--embed-certs参数,这样才会把证书内容填充到kubeconfig文件,否则仅填充证书路径。
通过CSR签发证书
前面通过CA签发证书需要有CA的私钥,其实Kubernetes可以直接发起CSR请求。
首先创建一个CSR请求,CN为test-csr,O为int32bit,即User为test-csr,Group为int32bbit。
|
|
声明一个CSR Resource:
|
|
创建该资源:
|
|
此时CSR的状态为Pending,通过kubectl certificate approve命令签发证书:
|
|
此时CSR显示已经完成签发,可以读取证书内容:
|
|
查看证书部分摘要信息:
|
|
配置kubeconfig使用证书认证的方式和前面的一样,这里不再赘述。
使用x509证书认证的问题
使用x509证书相对静态密码来说显然会更安全,只要证书不泄露,可以认为是无懈可击的。但是虽然颁发证书容易,目前却没有很好的方案注销证书。想想如果某个管理员离职,该如何回收他的权限和证书。有人说,证书轮转不就解决了吗?但这也意味着需要重新颁发其他所有证书,非常麻烦。
所以使用x509证书认证适用于Kubernetes内部组件之间认证,普通用户认证并不推荐通过证书的形式进行认证,参考Kubernetes – Don’t Use Certificates for Authentication。
Bearer Token认证
静态token认证
静态token认证和静态密码原理几乎完全一样,唯一不同的是静态token通过token-auth-file指定token文件,认证时头部格式为Authorization: Bearer ${Token},而静态密码通过basic-auth-file指定密码文件,认证头部为Basic base64encode(${username}:${password}),本质都是一样的。
因此其优点和缺点也和静态密码完全一样,这里不再赘述。
Bootstrap Token认证
前面提到的静态token在运行时是固定不变的,并且不会在Kubernetes集群中存储,意味着除非修改静态文件并重启服务,token不会改变。
而bootstrap token则是由Kubernetes动态生成的,通过Secret形式存储,并且具有一定的生命周期,一旦过期就会失效。
为了简便我们使用kubeadm生成一个token:
|
|
Token有两个部分组成,由.分割,前面部分为Token ID bpjp71,后面部分为Token Secret 6ckt2g3o3hso3gn4。Token默认TTL为一天,对应的group为system:bootstrappers:kubeadm:default-node-token,对应User为system:bootstrap:${Token ID}。
kubeadm创建一个Token会对应在Kubernetes的kube-system namespace创建一个secret,secret名为bootstrap-token-${TOKEN_ID},这里为bootstrap-token-bpjp71。
|
|
此时可以通过如下命令生成config:
|
|
为了验证boostrap token,我们把用户添加到int32bit-role中,注意对应的虚拟User名。
|
|
这种token主要用于临时授权使用,比如kubeadm初始化集群时会生成一个bootstrap token,这个token具有创建certificatesigningrequests权限,从而新Node能够发起CSR请求,请求客户端证书。
service account token认证
service account是Kubernetes唯一由自己管理的账号实体,意味着service account可以通过Kubernetes创建,不过这里的service account并不是直接和User关联的,service account是namespace作用域,而User是全cluster唯一。service account会对应一个虚拟User,User名为system:serviceaccount:${namespace}:${sa_name},比如在default namespace的test service account,则对应的虚拟User为system:serviceaccount:default:test。
和前面的bootstrap一样,service account也是使用Bearer Token认证的,不过和前面的Token不一样的是service account是基于JWT(JSON Web Token)认证机制,JWT原理和x509证书认证其实有点类似,都是通过CA根证书进行签名和校验,只是格式不一样而已,JWT由三个部分组成,每个部分由.分割,三个部分依次如下:
- Header(头部): Token的元数据,如alg表示签名算法,typ表示令牌类型,一般为
JWT,kid表示Token ID等。 - Payload(负载): 实际存放的用户凭证数据,如iss表示签发人,sub签发对象,exp过期时间等。
- Signature(签名):基于alg指定的算法生成的数字签名,为了避免被篡改和伪造。
为了便于HTTP传输,JWT Token在传递过程中会转成Base64URL编码,其中Base64URL相对我们常用的Base64编码不同的是=被省略、+替换成-,/替换成_,这么做的原因是因为这些字符在URL里面有特殊含义,更多关于JWT的介绍可参考阮一峰的JSON Web Token 入门教程。
我写了如下脚本实现Kubernetes Service Account的Token解码:
|
|
从解码的数据可见,JWT Token的颁发机构为kubernetes/serviceaccount,颁发对象为SA对应的虚拟用户system:serviceaccount:default:test,除此之外还存储着其他的SA信息,如SA name、namespace、uuid等。这里需要注意的是我们发现JWT Token中没有exp字段,即意味着只要这个SA存在,这个Token就是永久有效的。
通过如下方式配置kubeconfig:
|
|
为了验证test-sa,在刚刚创建的int32bit-rolebinding的subjects增加了ServiceAccount test-sa。
验证test-sa是否可以读取pod列表以及删除pod:
和预期一样,test-sa能够读取pod列表但没有删除pod权限。
service account除了可以用于集群外认证外,其还有一个最大的特点是可以通过Pod.spec.serviceAccountName把token attach到Pod中,这类似于AWS IAM把Role attach关联到EC2实例上。
此时Kubernetes会自动把SA的Token通过volume的形式挂载到/run/secrets/kubernetes.io/serviceaccount目录上,从而Pod可以读取token调用Kubernetes API.
针对一些需要和Kubernetes API交互的应用非常有用,比如coredns就需要监控endpoints、services的变化,因此关联了coredns SA,coredns又关联了system:coredns clusterrole。flannel需要监控pods以及nodes变化同样关联了flannel SA。
到这里为止,service account可能是Kubernetes目前最完美的认证方案了,既能支持集群外的客户端认证,又支持集群内的Pod关联授权。
但事实上,service account并不是设计用来给普通user认证的,而是给集群内部服务使用的。目前虽然token是永久有效的,但未来会改成使用动态token的方式,参考官方设计设计文档Bound Service Account Tokens,此时如果kubectl客户端认证则需要频繁更新token。
除此之外,SA虽然能够对应一个虚拟User,但不支持自定义Group,在授权体系中不够灵活。另外也不支持客户端高级认证功能,比如MFA、SSO等。
集成外部认证系统
前面已经介绍过Kubernetes集成简单的静态用户文件以及x509证书认证,Kubernetes最强大的功能是支持集成第三方Id Provider(IdP),主流的如AD、LADP以及OpenStack Keystone等,毕竟专业的人做专业的事。
本文接下来主要介绍集成OpenID Connect以及通过webhook集成Keystone。
通过OpenID Connect集成keycloak认证系统
当前支持OpenID Connect的产品有很多,如:
这里以Keycloak为例,这里仅为了实现测试,因此部署standalone模式,安装非常简单,可参考官方文档Getting Started Guide,这里不再赘述。
keycloak配置
由于Kubernetes要求必须是https,测试环境需要签发自己的CA,参考为Kubernetes 搭建支持 OpenId Connect 的身份认证系统:
|
|
由于没有配置固定域名,因此添加了alt_names并指定IP。
最后复制ssl/keycloak.p12到如下两个路径:
|
|
keycloak认证信息配置
登录keycloak管理页面创建一个realm以及client,名称都为int32bit-kubernetes。其中realm类似namespace概念,实现了多租户模型,client对应一个认证主体,所有使用keycloak认证的都需要创建一个对应的client。
每个client会对应有一个secret,这二者关系就是access key和access secret关系:
接下来通过Web管理页面执行如下操作:
- 在Roles中创建两个role分别为
int32bit-kubernetes-cluster-admin、int32bit-kubernetes-readonly。 - 在Users中创建两个用户
k8s-admin、k8s-readonlly。 k8s-admin关联int32bit-kubernetes-cluster-adminrole,k8s-readonlly关联int32bit-kubernetes-readonlyrole。
注: 管理员可以在User的Credentials面板中设置用户密码。
通过curl检查是否可认证获取token:
|
|
其中返回的id_token,在后面Kubernetes对接中非常重要,它也是一个JWT Token,解码后的内容如下:
|
|
我们发现id_token默认没有groups信息,为了支持Kubernetes的Group认证,需要在client中添加mappers字段groups。
这里之所以映射User Realm Role,而不是Group MemberShip,是因为Group会在id_token中添加前缀/,如/test-group1,/test-group2,这个暂时没想到怎么处理,或许有更好的办法。
再次生成token_id就会有groups信息了:
|
|
Kubernetes集成keycloak认证
在api-server中增加如下命令行启动参数:
|
|
--oidc-issuer-url路径需要具体到realm,这里为int32bit-kubernetes;--oidc-client-id对应client id,前面我们已经创建。--oidc-username-claim、--oidc-groups-claim告诉Kubernetes如何从id_token中读取username和groups,根据前面解码后的id_token,我们不难选择。--oidc-username-prefix告诉Kubernetes针对这个odic的用户需要添加什么前缀,如果集群同时有多个认证系统,建议添加个前缀加以区分,如指定前缀为odic:,则Kubernetes对应的User为odic: preferred_username。--oidc-ca-file指定keycloak的根证书,因为不是权威证书,不指定则不会信任该证书。
前面创建了两个用户,与Role的关联关系如下:
- k8s-admin: int32bit-kubernetes-cluster-admin
- k8s-readonly: int32bit-kubernetes-readonly
相对应的在Kubernetes创建两个clusterrolebinging:
cluster-admin:
|
|
cluster-readonly:
|
|
使用OpenId Connect认证
生成config文件:
|
|
为了便于登录,下载kube-login插件:
|
|
此时可以直接通过如下命令进行登录:
|
|
如果是图形界面,不指定参数直接使用kubectl oidc-login可自动打开浏览器进行登录校验。
可见使用k8s-admin具有所有权限,而k8s-readonly只有list的权限。
通过webhook集成OpenStack Keystone
webhook和odic一样也是集成外部认证系统的一种方式,当client发起api-server请求时会触发webhook服务TokenReview调用,webhook会检查用户的凭证信息,如果是合法则返回authenticated": true等信息。api-server会等待webhook服务返回,如果返回的authenticated结果为true,则表明认证成功,否则拒绝访问。
OpenStack Keystone配置
为了后续测试,我们在Keystone创建如下资源:
|
|
其中k8s-admin user关联k8s-admin role,k8s-viewer user关联k8s-viewer role,我们根据不同role角色设置不同的权限:
|
|
如上policy配置中,k8s-viewer只允许读namesapce default的资源,而k8s-admin允许create、update以及delete等所有权限。
创建如上configmap:
|
|
配置k8s-keystone-auth webhook插件
安装和配置k8s-keystone-auth可参考官方文档k8s-keystone-auth
安装完后验证webhook认证结果:
|
|
输出如果authenticated": true则说明认证成功。
当然也可以验证webhook的授权,如验证k8s-viewer是否具有list pods权限:
|
|
如果输出"allowed": true,则说明具有list pods权限。
配置Kubernetes使用keystone webhook
按照官方文档,创建webhook conf文件:
|
|
修改api-server配置文件:
|
|
如上开启了基于Webhook授权功能,如果仅使用Keystone认证而不使用Keystone授权,可以不开启。
使用Keystone认证
下载webhook插件,用于请求认证时获取keystone token:
|
|
通过如下脚本生成kubeconfig文件:
|
|
配置完后就可以通过Keystone实现认证了。
|
|
可见k8s-viewer用户可以查看pod但没有删除pod的权限。
|
|
k8s-admin用户既可以查看pod,也可以删除pod。
我们可以通过kubectl-access_matrix插件查看权限矩阵:
进一步说明k8s-admin用户具有default namespace的所有权限,而k8s-viewer只具有可读权限。
如果企业已经部署OpenStack,Kubernetes运行在OpenStack平台之上,或者通过Magnum部署,集成Keystone实现Kubernetes认证和授权非常方便,很好地把Kubernetes的认证和授权与OpenStack的认证授权统一管理整合在一块。
使用外部认证系统的优势
对比前面的认证方式,使用OpenID Connect认证以及基于Webhook的认证方式优势显而易见:
- 安全。基于JWT Token交换认证,JWT具有数字签名,可避免伪造。并且相对Service Account JWT,OpenID Connect认证的JWT具有有效期。
- 灵活。身份认证和集群本身是松耦合的,通过IDP配置账户信息不需要Kubernetes干预。
- 认证功能丰富。可使用企业身份系统的MFA、SSO等功能实现更完善更安全的认证策略。
总结
本文介绍了Kubernetes认证的几种策略,其中:
- 静态密码和静态token认证策略的优点是非常简单,缺点是非常不安全和不灵活,不推荐使用。
- x509证书认证本身的安全性保障没有问题,最大的问题是不支持证书回收,意味着一旦证书颁发出去就很难在回收过来。这种认证策略适合集群内部组件之间的认证通信。
- bootstrap token适合需要临时授权的场景,如集群初始化。
- service account基于JWT认证,JWT包含的字段比较简单,没有有效期和aud字段,存在安全隐患,不适用于普通用户认证,适用于集群内的Pod向api-server认证,如kube-proxy和flannel需要调用api-server监控service和pod的状态变化。
- OpenID Connect(oidc)以及webhook可集成企业已有的身份认证系统,如AD、LDAP,其特点是安全、灵活、功能全面,并且身份认证与Kubernetes集群解耦合,非常适用于普通用户的认证,推荐使用。
参考资料
-
No backlinks found.