KubeCube 用户管理与身份认证
前言
KubeCube (https://kubecube.io) 是由网易数帆近期开源的一个轻量化的企业级容器平台,为企业提供 kubernetes 资源可视化管理以及统一的多集群多租户管理功能。KubeCube 社区将通过系列技术文章解读 KubeCube 的设计特点和技术实现,帮助开发者和用户更快地理解和上手 KubeCube。本文是第三篇,重点介绍 KubeCube 中用户管理与身份认证的实现方案。
用户管理
所有 Kubernetes 集群都有两类用户:由 Kubernetes 管理的服务账号和普通用户。
Kubernetes 假定普通用户是由一个与集群无关的服务通过以下方式之一进行管理的:
- 负责分发私钥的管理员
- 类似 Keystone 或者 Google Accounts 这类用户数据库
- 包含用户名和密码列表的文件
有鉴于此,Kubernetes 并不包含用来代表普通用户账号的对象。 普通用户的信息无法通过 API 调用添加到集群中。
根据 Kubernetes 官方所述,Kubernetes 本身并不直接提供用户管理的特性,不支持普通用户对象,更不存储普通用户的任何信息。如果需要创建一个用户,需要为该用户创建私钥和证书,通过证书进行身份认证。并且,由于不存储用户信息,集群管理员无法集中管理用户,对其他用户无感知。因此,KubeCube 首先重新定义了用户这一概念,即提供了 User 这一资源类型,存储用户信息,进行用户管理,同时方便后续的身份认证和权限校验等。
apiVersion: user.kubecube.io/v1 kind: User metadata: name: 登录账号,用户唯一标识,用户自定义,不可重复,不可修改 spec: password: 密码,必填,系统会将密码进行md5加盐加密后保存 displayName: 用户名 email: 邮箱 phone: 电话 language: 语言:en/ch loginType: 用户登录方式:normal/ldap/github/... state: 用户状态:normal/forbidden status: lastLoginTime: 上次登录时间 lastLoginIp: 上次登录IP
用户可以由管理员在前端页面手动创建,也可以在使用外部认证第一次登录时系统自动创建。因此,用户在注册方式上可以分为系统普通注册用户和第三方授权登录用户。但对于这两种创建方式,都是对应在管控集群创建相应的 User cr。然后 Warden 的资源同步管理器会将该 cr 从管控集群同步到计算集群,以便于后续多集群统一认证。
这样,在用户管理页面,只需要查询管控集群内的 User 资源,即可实现用户的集中管理。并且,可以轻松地添加用户、查询用户以及对用户元信息的修改。
身份认证
在 KubeCube 中,支持本地认证和外部认证。本地认证是指,在 KubeCube 中创建普通用户,用户再使用其创建时注册的用户名密码进行登录和认证。而外部认证,是指无需创建用户,通过第三方的认证平台认证用户身份,从而访问 KubeCube。下面将分别介绍这两种认证方式的实现。
本地认证
在 KubeCube 中,主要是通过 JWT(JSON Web Token)进行用户的身份认证的。
在使用本地认证登录时,用户需要输入用户名和密码。KubeCube 会根据用户名在集群中查询 User cr 并比较密码,如果查询到 User 并且密码一致,视为登录成功。KubeCube 在更新用户登录状态后,会根据用户名生成 JWT 并拼接成 Bearer Token,存储在 cookie 中返回。
用户登录时校验用户名密码成功后的代码如下:
// generate token and return authJwtImpl := jwt.GetAuthJwtImpl() token, err := authJwtImpl.GenerateToken(&v1beta1.UserInfo{Username: name}) if err != nil { response.FailReturn(c, errcode.AuthenticateError) return } bearerToken := jwt.BearerTokenPrefix + " " + token c.SetCookie(constants.AuthorizationHeader, bearerToken, int(authJwtImpl.TokenExpireDuration), "/", "", false, true) user.Spec.Password = "" response.SuccessReturn(c, user) return
用户成功登录后,后续的每次请求,前端都会通过 cookie 带上该 JWT进行请求,后端认证中间件再对该 JWT 进行校验。如果有效,则会生成新的 token 返回,循环上述过程。这样,即使 KubeCube 中生成 JWT 的默认有效时间为1小时,只要用户持续访问,JWT 便会不断刷新使用户始终处于登录状态。
认证中间件的部分代码如下:
func Auth() gin.HandlerFunc { return func(c *gin.Context) { if !withinWhiteList(c.Request.URL, c.Request.Method, whiteList) { authJwtImpl := jwt.GetAuthJwtImpl() userToken, err := token.GetTokenFromReq(c.Request) if err != nil { response.FailReturn(c, errcode.AuthenticateError) return } newToken, respInfo := authJwtImpl.RefreshToken(userToken) if respInfo != nil { response.FailReturn(c, errcode.AuthenticateError) return } v := jwt.BearerTokenPrefix + " " + newToken c.Request.Header.Set(constants.AuthorizationHeader, v) c.SetCookie(constants.AuthorizationHeader, v, int(authJwtImpl.TokenExpireDuration), "/", "", false, true) c.Next() } } }
外部认证
外部认证的实现目前主要分为3种,分别为通用认证、LDAP 认证和 OAuth2 认证。
通用认证
为了方便用户可以对接一套自己的认证系统,KubeCube 中支持了一种通用认证方式。用户可以通过开启通用认证方式以及配置认证系统的地址,使用户在每一次访问 KubeCube 时,都会去自己的认证系统中进行认证。认证通过后,需要返回给 KubeCube 该用户的用户名,KubeCube 依然会根据该用户名生成对应的 Bearer Token 放在 header 中,以进行后续的权限校验等。主要的逻辑代码实现如下:
func Auth() gin.HandlerFunc { return func(c *gin.Context) { if !withinWhiteList(c.Request.URL, c.Request.Method, whiteList) { authJwtImpl := jwt.GetAuthJwtImpl() if generic.Config.GenericAuthIsEnable { h := generic.GetProvider() user, err := h.Authenticate(c.Request.Header) if err != nil || user == nil { clog.Error("generic auth error: %v", err) response.FailReturn(c, errcode.AuthenticateError) return } newToken, error := authJwtImpl.GenerateToken(&v1beta1.UserInfo{Username: user.GetUserName()}) if error != nil { response.FailReturn(c, errcode.AuthenticateError) return } b := jwt.BearerTokenPrefix + " " + newToken c.Request.Header.Set(constants.AuthorizationHeader, b) } c.Next() } } }
LDAP 认证
当用户选择 LDAP 登录方式时,用户输入用户名和密码。首先会检查集群内是否存在该用户,并且该用户是否为“禁用”状态。如果不存在或存在且为正常状态,则开始进行 LDAP 认证:
-
KubeCube 作为 LDAP 客户端,获取到用户的用户名和密码,以管理员DN和管理员密码为参数向 LDAP 服务器发送管理员绑定请求报文以获得查询权限。
-
LDAP 服务器收到管理员绑定请求报文后,验证管理员DN和管理员密码是否正确。如果管理员DN和管理员密码正确,则向 KubeCube 发送绑定成功的管理员绑定响应报文。
-
KubeCube 收到绑定响应报文后,以用户输入的用户名为参数构造过滤条件,向 LDAP 服务器发送用户DN查询请求报文。例如:构造过滤条件为 CN=User2。
-
LDAP 服务器收到用户DN查询请求报文后,根据报文中的查询起点、查询范围、以及过滤条件,对用户DN进行查找。如果查询成功,则向 KubeCube 发送查询成功的响应报文。查询得到的用户DN可以是一个或多个。如果得到的用户不为一个,认为用户名或密码错误,认证失败。
-
KubeCube 根据查询得到的用户DN和用户输入的密码为参数,向 LDAP 服务器发送用户绑定请求报文。
-
LDAP 服务器收到用户绑定请求报文后,检查用户输入的密码是否正确。
- 如果用户输入的密码正确,则向 KubeCube 发送绑定成功的绑定响应报文。
- 如果用户输入的密码不正确,则向 KubeCube 发送绑定失败的响应报文。KubeCube 以查询到的下一个用户DN为参数,继续向 LDAP 服务器发送绑定请求,直至有一个DN绑定成功。如果所有用户DN都绑定失败,则 KubeCube 通知用户认证失败。
认证成功后,和本地认证的逻辑相同:如果该用户在集群中未存在,则根据用户名创建User cr; 并且根据该用户名生成对应的 Bearer Token 存储到 Cookie 中,在下次请求时携带以识别用户身份。
OAuth2 认证
在 KubeCube 中 OAuth2 认证采用授权码模式,因为该模式是功能最完整、流程最严密的授权模式。OAuth2 通常的认证流程为:
- 用户访问客户端,后者将前者导向认证服务器。
- 用户选择是否给予客户端授权。
- 假设用户给予授权,认证服务器将用户导向客户端事先指定的"重定向URI"(redirection URI),同时附上一个授权码。
- 客户端收到授权码,附上早先的"重定向URI",向认证服务器申请令牌。这一步是在客户端的后台的服务器上完成的,对用户不可见。
- 认证服务器核对了授权码和重定向URI,确认无误后,向客户端发送访问令牌(access token)和更新令牌(refresh token)。
在 KubeCube 的实现中,以 GitHub 登录为例:
- 用户在登录时选择 GitHub 认证登录,前端将请求转发给 GitHub;
- GitHub 询问用户是否同意授权给 KubeCube;
- 如果用户同意,GitHub 就会重定向回KubeCube(
/oauth/redirect
),同时发回一个授权码(code
); - KubeCube 使用授权码,向 GitHub 请求令牌(
access_token
); - GitHub 返回令牌(
access_token
); - KubeCube 使用令牌(
access_token
),向 GitHub 请求用户信息数据; - 查询集群,如果该用户不存在,则根据用户信息创建 User cr;
- 根据用户名生成访问集群的 Bearer Token,并返回认证成功;
- 前端将 Bearer Token 存储到 Cookie 中,在下次请求时携带。
OpenAPI 认证
基于以上的设计方案,可以轻松的推断出,OpenAPI的认证实现,也是通过 JWT 完成。User 和每组 AK、SK进行绑定,通过AK、SK查询到对应的 User,再通过该 User.Name 生成 Bearer Token 返回。在下次请求时,用户需要在 Cookie 或 header 中携带该 Token,KubeCube 认证中间件就可以通过该 Token 解析出用户身份,从而完成认证。
集群认证
在中间件完成身份认证后会 refresh token,但是如果直接在请求头中携带该 token 请求 kube-apiserver 来完成集群认证,则需要在部署 KubeCube 时修改 kube-apiserver 的认证后端,即修改 kube-apiserver 的配置。这会对原生的 kubernetes 集群造成侵入,大大增加 KubeCube 的部署成本和运维成本。因此,我们需要建立另一模块来帮助完成集群认证——auth-proxy。
用户对 KubeCube 进行访问请求 kubernetes 资源时,在通过认证中间件进入到透传接口后,会走到 auth-proxy 模块;auth-proxy 将 request 中的 Bearer Token 解析为对应的 User;再使用 User impersonation 的方式,将 request 代理发送至 kube-apiserver,即使用 “admin” 用户伪装成当前用户来请求 kube-apiserver, 从而“跳过”认证,并且有利于后续鉴权。
结语
KubeCube 的用户管理系统主要基于 User CRD 实现;认证系统支持了本地和外部两种认证方式,本地认证基于 JWT 实现,外部认证在第三方认证平台认证通过后同样需要在集群内创建一个 User cr,以进行后续的用户管理、权限绑定等。对于集群认证,主要使用了 Kubernetes 提供的 Impersonation 方法“跳过认证”。整体设计和实现相对简单,秉承了 KubeCube 轻量化的设计理念。
更多信息请参阅:
-
KubeCube 官网:https://www.kubecube.io/
-
KubeCube 源码:https://github.com/kubecube-io/kubecube
作者简介: 嘉慧,网易数帆高级工程师,KubeCube 社区核心成员

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
Rust 社区求变,PHP 大旗不倒?
上月底发生了两件开源语言相关的大事。 一是 JetBrains 牵头成立 PHP 基金会,希望能确保语言的长寿和繁荣。二是 Rust 审核团队集体辞职,理由是 Rust 核心团队造成了“寡头政治”。 近年来,由于各个大厂的押注,Rust 风头正盛,但是由于其过高的学习成本、替换工程巨大等等原因,实际上是叫好不叫座。再加之最近社区陷入混乱,不免让人怀疑 Rust 是否真有其背后的“金主爸爸”们说的那么好?反观 PHP,虽然常常被唱衰,但使用率基本稳定在前十,而且随着新版本的发布、基金会的成立,反倒是一派欣欣向荣之象。 而单从社区治理的角度来看,两个语言社区的风格与发展也是截然不同。本文分成上下 2 篇,分别聊聊 Rust 和 PHP 的社区发展。 Rust 救急 2006 年,编程语言界正是各个语言割据争霸的时候,也有许多工程师抱着“自己写一个编程语言”的梦想进入战场。Rust 之父Graydon Hoare 正是其中一位。 Graydon Hoare 作为职业编程语言工程师,工作日常是给其他语言开发编译器和工具集,参与过 GCC、Clang、Swift、Tracemonkey 等许多编...
- 下一篇
纵深与横向,亚马逊云科技如何扩大自己的云服务版图?
亚马逊云科技 reInvent 2021 刚刚结束,今年的大会以“探路者”为主题,系统性地回顾了亚马逊云科技15 年来在云计算领域的探索之路,同时发布了多项创新成果。 我们把本次大会的内容重点分为两个方向来看,可以一窥这家云服务巨头在云计算领域纵横多年的发展基石。 突破云服务纵深极限 15 年以来,亚马逊云科技(Amazon Web Services)一直以技术创新、服务丰富、应用广泛而享誉业界。亚马逊云科技一直不断扩展其服务组合以支持几乎云上任意工作负载,目前提供超过 200 项全功能的服务,涵盖计算、存储、数据库、网络、数据分析、机器学习与人工智能、物联网、移动、安全、混合云、虚拟现实与增强现实、媒体,以及应用开发、部署与管理等方面。在这些丰富的云服务实例背后,离不开亚马逊云科技多年来对用户需求的敏锐发掘,以及其在软件和硬件方面的持续创新和大力投入。 拥抱开源持续创新 云计算的诞生源于互联网基础设施的进化,分布式系统模型的诞生为算力带来了前所未有的可伸缩性,也为风靡全球的 SaaS、IaaS 等云服务提供了技术基础。而这些技术核心的基础软件,都离不开来自开源社区的贡献。 从 200...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- Windows10,CentOS7,CentOS8安装MongoDB4.0.16
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果
- CentOS关闭SELinux安全模块
- CentOS8,CentOS7,CentOS6编译安装Redis5.0.7
- SpringBoot2整合Redis,开启缓存,提高访问速度
- CentOS7安装Docker,走上虚拟化容器引擎之路
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- SpringBoot2配置默认Tomcat设置,开启更多高级功能
- Eclipse初始化配置,告别卡顿、闪退、编译时间过长
- Windows10,CentOS7,CentOS8安装Nodejs环境