博客 / 詳情

返回

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 認證

  1. 當用户選擇 LDAP 登錄方式時,用户輸入用户名和密碼。首先會檢查集羣內是否存在該用户,並且該用户是否為“禁用”狀態。如果不存在或存在且為正常狀態,則開始進行 LDAP 認證
  2. KubeCube 作為 LDAP 客户端,獲取到用户的用户名和密碼,以管理員DN和管理員密碼為參數向 LDAP 服務器發送管理員綁定請求報文以獲得查詢權限。
  3. LDAP 服務器收到管理員綁定請求報文後,驗證管理員DN和管理員密碼是否正確。如果管理員DN和管理員密碼正確,則向 KubeCube 發送綁定成功的管理員綁定響應報文。
  4. KubeCube 收到綁定響應報文後,以用户輸入的用户名為參數構造過濾條件,向 LDAP 服務器發送用户DN查詢請求報文。例如:構造過濾條件為 CN=User2。
  5. LDAP 服務器收到用户DN查詢請求報文後,根據報文中的查詢起點、查詢範圍、以及過濾條件,對用户DN進行查找。如果查詢成功,則向 KubeCube 發送查詢成功的響應報文。查詢得到的用户DN可以是一個或多個。如果得到的用户不為一個,認為用户名或密碼錯誤,認證失敗。
  6. KubeCube 根據查詢得到的用户DN和用户輸入的密碼為參數,向 LDAP 服務器發送用户綁定請求報文。
  7. LDAP 服務器收到用户綁定請求報文後,檢查用户輸入的密碼是否正確。
  • 如果用户輸入的密碼正確,則向 KubeCube 發送綁定成功的綁定響應報文。
  • 如果用户輸入的密碼不正確,則向 KubeCube 發送綁定失敗的響應報文。KubeCube 以查詢到的下一個用户DN為參數,繼續向 LDAP 服務器發送綁定請求,直至有一個DN綁定成功。如果所有用户DN都綁定失敗,則 KubeCube 通知用户認證失敗。

認證成功後,和本地認證的邏輯相同:如果該用户在集羣中未存在,則根據用户名創建User cr; 並且根據該用户名生成對應的 Bearer Token 存儲到 Cookie 中,在下次請求時攜帶以識別用户身份。

OAuth2 認證

在 KubeCube 中 OAuth2 認證採用授權碼模式,因為該模式是功能最完整、流程最嚴密的授權模式。OAuth2 通常的認證流程為:

  1. 用户訪問客户端,後者將前者導向認證服務器。
  2. 用户選擇是否給予客户端授權。
  3. 假設用户給予授權,認證服務器將用户導向客户端事先指定的"重定向URI"(redirection URI),同時附上一個授權碼。
  4. 客户端收到授權碼,附上早先的"重定向URI",向認證服務器申請令牌。這一步是在客户端的後台的服務器上完成的,對用户不可見。
  5. 認證服務器核對了授權碼和重定向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-i...

深入解讀 KubeCube 多集羣管理

KubeCube 多級租户模型

KubeCube 開源:簡化 Kubernetes 落地的六大特性

網易數帆更多開源項目

作者簡介: 嘉慧,網易數帆高級工程師,KubeCube 社區核心成員

user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.