深入聊一聊 rancher 的认证与鉴权(一)

上一篇在将集群注册与集群管理的时候,对许多认证与鉴权的细节都略过了。这部分的概念确实比较弯弯绕绕。

举个直白的例子,在上一节隧道的 auth 中,很含糊的说明了 agent 上报一个 jwt,rancher 在 server 端通过这个 jwt 认可这个请求。那这玩意是认证,还是鉴权?似乎哪个够听得很顺耳对吧。再举个例子,OAuth 做的是认证还是授权?

面试的时候面过很多人,大家往往都反映知道这些东西最后就是让用户通过权限控制系统,可以访问业务资源。甚至有同学质问我:咱也不是孔乙己,扣啥字眼啊?能用不就完了。

从使用角度来看确实没错,但毕竟我们是工程师,所以这里就比较彻底的聊一聊认证和鉴权的两三事。

在本文里,我不会

  1. 讲述 k8s 和 rancher 的各种认证的技术细节,比如用什么命令来签发一个 kubeconfig;比如 k8s 的 RBAC 的各个字段是什么含义
  2. 讲述一些背景知识的细节,比如 SSL,jwt

如果想要了解这些 usage,可以移步到官方文档。在本文里,我会

  1. 介绍我们遇到的各种名词都是什么含义,他们的区别和联系是什么?
  2. 介绍 k8s 和 rancher 的认证和鉴权上的玩法,以及带来的优缺点

所以准确的来说,这篇其实不是一个入门读物,属于对 authn & authz 有一定了解,且有兴趣稍微深入研究一下的同学。

0. 我们该如何学习?

认证与鉴权是一套完整的知识体系和方法论,单看这一篇文章肯定是不够的,我更愿意在这里记录下我们该如何了解认证与鉴权的知识体系。从我的经验触发,我愿意从名词的解释中找出新的知识点,并建立与已有知识的联系

举个例子,认证(authentication)的维基百科上这样写道:

Authentication is the act of proving an assertion, such as the identity of a computer system user. In contrast with identification, the act of indicating a person or thing's identity, authentication is the process of verifying that identity.

首先翻译成自己的知识体系:认证是证明用户是他声明的身份的过程。读完通篇之后,我们引入了一些新的名词:用户(user)、身份(identity)、账号(account)。

鉴权(authorization)的维基百科写道:

Authorization is the function of specifying access rights/privileges to resources, which is related to general information security and computer security, and to access control in particular.

翻译成听得懂的话:鉴权是校验是否有权看特定资源的过程。于是我们引入了新的名词:授权(authorize)、接入控制(access control)、RBAC 等等……

通过分别寻找这些名词的解释,辨别他们的细微差别,我们就可以一步步的建立自己的关于认证和鉴权的知识体系。

1. 让我们从最基本的开始

1.1 我不是孔乙己,但我仍要告诉你回字的三种写法

列一些我个人觉得比较基础、关键的概念吧,这些是我觉得必须要仔细辨别清楚的。

关键词 中文 释义
user 用户 屏幕前的你我他,一个个活生生的人
identity 身份 一份在当前服务域中唯一的数据,描述一个唯一的终端用户
account 账号 一份系统内部保存的数据,记录一个该系统的使用者
identity provider 身份提供商 负责身份管理的系统组件,可提供认证功能
authentication 认证 是证明用户是他声明的身份的过程
token-based authentication 令牌认证 一种认证方式,使用者持有该令牌,可证明使用者的身份是该令牌声明的身份
credential 凭证 用户主动掌握的证明自己身份的凭证,例如用户名密码
authorization 鉴权 系统中验证使用者是否有权进行某种操作的过程

上面的释义对于新手来说可能比较晦涩,其实举个生活中的例子,就容易懂很多了。

假设小明拿着身份证去做高铁。

小明是用户(user),后端系统是高铁,小明要进行的操作是在某个时间搭乘去往某个站点的某趟列车。

小明在安检口将身份证放在闸机上,并通过人脸识别通过了闸机,进入到高铁站内。

小明的身份由身份证定义,身份证通过身份证号保持身份的唯一性。国家的身份信息网站是 identity provider,向高铁提供了身份信息。

小明要过闸机,他首先持有身份证,声明我的身份是小明对应的身份,这张身份证可以被视为一个凭证。最简单的,系统可以只认小明的身份证,认为持有这张身份证的人就是小明。但显然,如果小明身份证丢了,有不法分子拿着小明身份证怎么办呢?所以这里认证的过程还包含了人脸识别。

身份证和人脸识别可以类比到 token 和密码。身份证类似一个 token,是小明成年后,国家为其办法的身份凭证,并非小明自主、主动掌握的。小明的脸是一种生物密码,是小明天生就有,主动录入的,其主动权掌握在小明手中。

都通过之后,小明通过了系统的认证,证明了自己的身份。

小明上车前将身份证放在闸机,通过后走上了月台,顺利的乘上了车。

闸机既做了认证,又做了鉴权(认证的步骤和之前一样,通过身份证完成了认证)。在这个闸机刷卡的人是要搭乘 XXX 号列车。闸机查询后端系统,由于小明在 12306 买过票了,闸机查到了小明对应的用户账号的购票记录,认为这个账号对应的身份可以搭乘这趟列车,于是通过了鉴权。

这里一些归纳出四个非常重要的点:

  1. 用户与身份是一一对应的
  2. 每个账号都对应了唯一的一个身份
  3. 认证面向账号,需证明其身份
  4. 鉴权面向身份,需证明身份有足够权限

不知道通过这个例子,你是否对认证和鉴权有更透彻的了解?如果有的话,下一节我们就以 k8s 为例,看一下 k8s 是怎么做认证和鉴权的。

1.2 k8s authn & authz

读者可以参考官方的用户认证部分。整个文档可以概括为这么几个关键点:

  1. k8s 不做用户管理,只管理 service account
  2. k8s 支持用公私钥、token 进行认证,也支持 authentication proxy
  3. k8s 对 OIDC 的支持相当有限(这个一会儿再说)
  4. k8s 支持身份伪装(impersonation)

鉴权部分参考

1.2.1 service account 是啥

account 可以分为 user account 和 service account,其中 user account 对应一个终端用户,service account 是内部服务的账号,并不对应一个现实中的身份。例如在微服务中,一个 service account 可能就是一个微服务的实例的账号。

service account 通常由 k8s 自行管理,管理逻辑作为 admission controller 的一种,被编译进 apiserver 的源码中。参见使用准入控制器

pod 可以声明使用某个 service account(这里先不说 k8s 如何判断 pod 是否可使用这个 service account),之后从 pod 向 apiserver 中发起的请求,都会被 apiserver 认为是这个 service account 发起的。不管是 pod 中运行的程序,还是任何人 exec 到 pod 中发请求,都一样。

一般来说,一个 service account 会关联到一个 secret,这个 secret 存放着公开的 CA 证书、namespace 和 token:

  • CA 证书:用作服务端验证该请求,由于一般我们搭 k8s 用的是自签名的证书,故请求时需要携带一个“公钥”。这个涉及密码学的知识,超出了本文的范畴。有兴趣的同学可以参考协议森林17 我和你的悄悄话 (SSL/TLS协议)
  • token:是一个标准的 jwt。

这里先不提 jwt,不熟悉的读者就把它当做一个认证凭证就好了。我们可以通过 kubectl 看到 token 的内容,记得 base64 解码,然后可以在 JSON Web Tokens (JWT) 在线解密这个网站看一下解码后的内容。这些都比较 trivial,这里就不展开了。

1.2.2 impersonation 是啥

身份伪装(impersonation)听起来就很吓人,总有一股黑客的感觉。impersonation 允许通过添加特定的 header,让用户 A 以用户 B 的身份来执行命令。提前条件是用户 A 有足够的权限伪装为用户 B。

impersonation 通常与代管账号息息相关。代管账号是在不同系统对接时,系统 A 在系统 B 中,有一个有足够权限的管理账号,负责执行系统 A 向系统 B 的操作。

假设我基于 k8s 做了一个管理系统,用户可以登录到我的系统中,在 k8s 上创建任务。这时候,在账号方面,我可以有下面几种做法:

  1. 完全同步:我的账号就是 k8s 的账号。来一个新的用户,我在 k8s 上用 openssl 签发一个 x509 证书,把这个证书给用户。用户的请求携带这个证书,我直接转给 k8s。
  2. 代管账号:我在 k8s 上部署一个 agent,agent 用 cluster-admin 这样的管理员账号,用户请求过来的时候,我在我的管理系统中做好权限控制,用这个管理员账号进行所有的操作,绕开 k8s 的认证和鉴权。

第一个方案显然不可行,万一我底层 k8s 重装了,那就两眼一抹黑,所有的用户都没法通过认证了。

第二个方案的缺点在于,这时候我在 k8s 侧丢失了所有的用户信息,完全没法做审计、日志、追踪了。

impersonation 可以帮助我们缓解上面提到的缺点。这个代管账号可以声明自己的身份是个普通用户,此时 apiserver 认证部分还是用的代管账号的凭证,但鉴权部分(权限控制)就是按照用户身份来了。

我们用 kubectl --as xxx 可以执行一次身份伪装,这些用法上的说明就不展开了。

1.2.3 啥?k8s 没有 user account?

k8s 不保存 user account,其优点自然是可拓展性强……其他我编不出来了,那我就开始吐槽缺点吧。

  1. 构建在 k8s 之上的平台需要构建自己的账号体系。当然 k8s 的定位是 platform for platform 嘛,这么想这个也不是缺点,而是 feature
  2. 碍手碍脚的 service account:以 rancher 为例,rancher 管理了自己的账号,但 k8s 上还有一个 service account,其账号体系独立与上层应用。其权限管控脱离了 rancher 的管理。使得运维人员必须小心翼翼,处理需要在容器内部访问 apiserver 的那些服务,因为他们的行为永远不经过 rancher 的控制面。

这里吐槽的有点远了,等到介绍 rancher 的认证和鉴权的时候,会再讲一下。

1.2.4 authz: RBAC

在介绍鉴权之前,读者需要再次默念一句话:认证面向账号,鉴权面向身份。这一点在 k8s 有很好的体现。

k8s 默认采用 RBAC(Role Based Access Control)进行权限控制。在 k8s 的实现中,通过 RoleRoleBinding 实现。当然,还有 ClusterRoleClusterRoleBinding,但只是 scope 不同(是否局限在某个 namespace)中。其核心思想是:授予某个身份一组角色,每个角色定义了一组权限

再回想一下,k8s 不管理用户账号。假设我们十分原始,通过 openssl 来签发 x509 证书,在签发账号的时候,我们可以指定 -subj "/CN=jbeda/O=app1/O=app2",见 X509 客户证书

这里我们指定了这个账号的用户名和用户组。后续 k8s 认证之后,就会将账号处理为 username: jbeda; groups: [app1, app2] 这样的信息,并把它作为一种身份处理。举个例子,看一个 role binding 的字段:

给某个身份授权:

subjects:
- kind: User
  name: jane
  apiGroup: rbac.authorization.k8s.io
复制代码

或者给一个用户组(一组身份)授权:

subjects:
- kind: Group
  name: manager
  apiGroup: rbac.authorization.k8s.io
复制代码

这里读者看到的 kind: User 可能会让读者对本文产生诸多质疑。其实这里只是一些文字概念上的定义的区别。

在本文的语境中,用户指的是终端用户,账号分为用户账号和服务账号。所以这里用身份这个概念封装了 user name + user group,更有利于读者对于鉴权的理解。原因如下:

  1. 按照 k8s 的说法,RBAC 面向用户授权,但 k8s 又不管理用户,那用户从哪里来呢?这个反而是最矛盾的点。
  2. user 也可以填 system:serviceaccount:xxx:xxx,那 service account 也被当成一个用户的话,真实的终端用户,又该用什么词语来描述呢?
  3. 之前说过一个账号对应一个身份,“用户”通过认证,证明了自己的身份。在鉴权中,其实压根不需要关心用户的概念,只要关心这个身份。假设终端用户 A exec 到一个 pod 里,通过 service account token 访问 apiserver,你能说鉴权是对用户 A 的吗?因为此时 apiserver 看到的就是个 service account,不是终端用户了。

基于上面的原因,我依然希望在鉴权语境下,用身份这个词,来指代 k8s 的 User 的描述。

1.2.5 RBAC 的劣势:略显死板的规则

RBAC 的优势是简单、直接,定义好 role 和 binding,就可以进行最基本的权限控制了。

但是遗憾的是,在现实生活很多场景中,单纯的 RBAC 是满足不了需求的。假设学校引入了一套权限管理系统,有条规则:每晚 10 点之后,学生不允许出校门。显然,简单的 RBAC 无法处理这样的场景。

解决方案是 context-aware authorization,例如 ABAC。每一次的鉴权可以携带一个 context,定义一些额外信息,比如时间、地点等等。而 ABAC 的定义中,attribute 本身就可携带额外的条件信息。

比较遗憾的是,k8s 对 ABAC 的支持还不太够,使用 ABAC 鉴权中,其策略的定义仍然和 RBAC 可以无缝的转换,并不能真正发挥 ABAC 的优势。

1.2.6 可拓展性的源泉:authn/authz/admission webhook

k8s 支持自定义的认证、鉴权策略,并把他们都做了比较好的模块化:

基本上 k8s 就是用 webhook 的形式来做这些事情,定义好接口,大家去做实现就好了。如果要启用 webhook,有两个注意点需要读者关注:

  1. 如果存在 webhook,不管是认证还是鉴权,k8s 都会并发进行 webhook 的访问,只要内置和 webhook 任何一方通过,该请求均通过
  2. 注意流量的控制,毕竟每个请求都要走一遍 webhook

1.2.7 k8s authn & authz 的总结

从各个基础的名词的定义出发,这篇小文章零零碎碎涉及到了很多的小点,要理清它们之间的细微差别,也是个晦涩、枯燥的事情。

之后梳理了在 k8s 里认证与鉴权是如何做的,整理了它们的模式与特点,包括它不管理用户账号,但管理 service account、支持用户伪装、使用 RBAC 进行鉴权、可拓展强……通过对这些模式的一些解读,我们其实大概能知道 k8s 在认证和鉴权上的优劣势。

当然,很多点由于没直接讲 rancher,讲了个半吊子。所以下一篇就会从 rancher 入手,从这些优劣势出发,就能感受到 rancher 的设计补足了哪些能力,为什么要这样设计,也就能对 rancher 有更深刻的理解。

猜你喜欢

转载自juejin.im/post/7054119173441454093