如何在產品中集成ckman

前言

ckman是一款由擎創科技數據庫團隊主導開發的,用來管理和監控ClickHouse的可視化開源工具。它將繁瑣的ClickHouse集羣的配置和監控工做變的簡單化、直觀化。github地址爲ckman, 感興趣的同窗能夠今後處下載源代碼。html

雖然ckman提供了web界面,可是對於部分企業來講,可能某些API的調用,直接使用網頁端並非那麼的通用,而是更多的指望將其整合到本身的產品中去。好比經過本身的產品頁面,就能夠直接調用ckmanAPI,將ckman做爲一個server端,從而很方便地進行集羣管理,數據庫表的管理以及數據監控。node

爲了安全考慮,ckman加入了https支持以及token進行鑑權。token的加入,雖然大大提升了數據鏈路的安全性,可是對於開發來講,卻提供了必定難度(由於咱們雖然可以經過登陸的接口獲取到token是什麼,可是一旦ckman重啓或者token過時,就會致使token失效)。git

正是由於這種狀況,ckmantoken以外,提供了另外一種訪問方案。只要請求在header中攜帶userToken的字段,且知足ckman規定的默認格式,就能夠跳過token的鑑權機制,從而保證不會有token無效或過時自動跳轉到登陸頁面的問題。github

那麼userToken是如何保證鏈路安全的呢?web

在這裏,ckman使用了RSA加密的技術。用戶能夠本身提供公鑰和私鑰,將userToken的內容使用私鑰加密後,放入請求的header中,在ckman接收到請求的時候,一旦識別到header中有userToken的字段,就會使用對應的公鑰去解密,從而創建有效的鏈接。算法

而咱們僅僅要作的,就是將公鑰配置到ckman的配置文件中便可。數據庫

server:
  id: 1
  port: 8808
  https: false
  pprof: true
  session_timeout: 3600
  public_key:  #該字段配置公鑰

那麼,userToken的默認格式是什麼樣的呢?編程

ckman提供的userToken是一個包含了時間戳、超時時間等字段的一個類(結構體),其結構以下所示:json

type UserTokenModel struct {
    Duration           int64
    RandomPaddingValue string
    UserId             int64
    Timestamp          int64
}

其中,咱們只須要關心DurationTimestamp字段便可。Duration指的是超時時間,ckman會將接收到請求的當前時間與userToken中攜帶的Timestamp時間進行比對,一旦時間間隔超過Duration,則認爲超時。所以,這個超時時間是由client本身去控制的,自由度比較大。segmentfault

代碼演示

因爲ckman是使用go語言開發的,因此我在這裏也是用go進行演示。(事實上,只要符合以上規範,使用任何編程語言都能達到一樣的效果)。

首先,咱們在ckman的配置文件中配置公鑰,關於如何生成RSA公鑰,網上教程有不少,這裏就很少贅述了。

ps:示例代碼中的公鑰和私鑰來自github.com/housepower/ckman/common/rsa_test.go)

server:
  id: 1
  port: 8808
  https: false
  pprof: true
  session_timeout: 3600
  public_key: MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDNw9cbOh1JRVNf/pQiRRMoa4TSmgZeq9zyK+Z5qE0Ak1XcmFzRg1m667ZAgfl/gEiwMGtbKyiPBGeHP5Gw3z5ENIHg7WGKTE0yRM/U/FMnktjly2xzjf7HUl/IA7PFYq5KBVBNPhjwzuFxpmsJL+fhhuYB75uDL0axYwcm7WHdewIDAQAB

而後啓動ckman,保持服務運行。

接下來就能夠編寫client端的代碼了。

爲了方便,我在這裏直接引用ckman的包,只須要在控制檯運行go get github.com/housepower/ckman 便可。固然若是以爲引入ckman的包太大,你也能夠本身實現一套rsa經過私鑰加密的算法,可參考github.com/housepower/ckman/common/rsa.go

代碼示例以下:

package main

import (
    "encoding/json"
    "fmt"
    "io/ioutil"
    "net/http"
    "time"

    "github.com/housepower/ckman/common"
)

const PRI_KEY string = "MIICXgIBAAKBgQDNw9cbOh1JRVNf/pQiRRMoa4TSmgZeq9zyK+Z5qE0Ak1XcmFzRg1m667ZAgfl/gEiwMGtbKyiPBGeHP5Gw3z5ENIHg7WGKTE0yRM/U/FMnktjly2xzjf7HUl/IA7PFYq5KBVBNPhjwzuFxpmsJL+fhhuYB75uDL0axYwcm7WHdewIDAQABAoGBAIKxMz1t6hAR4mUEc95YdVSlBhYmEomrK4j97UO0bERDULPuanYAscuRz46lf21Gc+TEvEuJ3BcKux8id00aXpcbbNhqIDyUMvET4MjdisgXhxay/dzc6jRBYQdhMrLT0NYfQSbULdXA3CGQhti4nChazn708ag6slvjGtsC4O9BAkEA9c/ZmbKisBb3GweWP/IhYB+GO5Qsby0KkF582NgnGIjnpGirniO2jyNSXO72QerTfG4JXqofGkH7AmlO0bkX0QJBANZLFBzoRIJr8x32dsKnd/V/7k2OgNbrUGwFJrJOGCSClPF7yM3xjN0lg3EjKW4AZP75pr//vOLOYTQDHyeNv4sCQQCoUlzyJ2XJ6N/q7WYQgbAjD1MuxwcqVhBuzZT2NAWJgm4EofwqvM/M8mX651NPzgploT/fR+UmaNoGS7BCYlmRAkAFAY3/uuFW1qTAT3CozXa88ncjsq+J1cd0Lo6f3bksqSxHk+e1/+2VgPnYG8Us/69cUYK2u4ezGLUmnOgOaX5PAkEA6wwIjYGDQRYIEVD4oJyNtdL7FFso63lon3LMySxLgi/KZGS4N8+FYJQVIzWWCrdk3Z1mXw4wuOQkE4pDy8xx+w=="

func main() {
    //構建userToken結構體
    userTokenModel := common.UserTokenModel{
        Duration:           3600,                        //超時時間,這裏設的是1個小時
        RandomPaddingValue: "ckmandemo",                 //隨機值,eoi產品中使用的,ckman中用不到
        UserId:             123456,                      //用戶id,eoi產品中能夠設置不一樣的用戶權限,ckman中用不到
        Timestamp:          time.Now().UnixNano() / 1e6, //當前時間,單位爲毫秒
    }

    //將userToken結構體使用私鑰加密
    uenc, _ := json.Marshal(userTokenModel)
    var rsaEncrypt common.RSAEncryption
    userToken, err := rsaEncrypt.Encode(uenc, PRI_KEY)
    if err != nil {
        fmt.Println("rsa encode error:", err)
        return
    }

    //建立http客戶端
    client := http.Client{}

    //示例中咱們調用/api/v1/ck/get 接口去獲取ipv4集羣的節點狀態
    url := "http://localhost:8808/api/v1/ck/get/ipv4"
    req, err := http.NewRequest("GET", url, nil)
    if err != nil {
        fmt.Println("http request failed:", err)
        return
    }

    req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
    req.Header.Set("Content-type", "application/json")
    req.Header.Add("userToken", string(userToken)) //將userToken添加到header中,能夠看到,這裏並無登陸,也沒有在header中添加token
    response, err := client.Do(req)
    if err != nil {
        fmt.Println("http error:", err)
        return
    }
    if response.StatusCode == 200 {
        defer response.Body.Close()
        body, err := ioutil.ReadAll(response.Body)
        if err != nil {
            fmt.Println("response error:", err)
            return
        }
        //打印返回結果
        fmt.Println(string(body))
    } else {
        fmt.Println("response:", response.Status)
        return
    }
}

運行結果以下所示:

chenyc@YenchangChan:ckmanDemo$go run main.go 
{"retCode":0,"retMsg":"ok","entity":{"status":"green","version":"21.3.9.83","nodes":[{"ip":"192.168.21.51","hostname":"node1","status":"green","shardNumber":1,"replicaNumber":1,"disk":"36.95GB/49.98GB"},{"ip":"192.168.21.52","hostname":"node2","status":"green","shardNumber":1,"replicaNumber":2,"disk":"28.15GB/49.98GB"},{"ip":"192.168.21.53","hostname":"node3","status":"green","shardNumber":2,"replicaNumber":1,"disk":"28.71GB/49.98GB"},{"ip":"192.168.21.54","hostname":"node4","status":"green","shardNumber":2,"replicaNumber":2,"disk":"23.27GB/49.98GB"}],"mode":"deploy","needPassword":false}}

這與咱們使用網頁端訪問的結果一致:
image.png
若是咱們使用F12打開調試模式,會發現該接口返回的JSON與代碼輸出的JSON結果是一致的。

上面簡單演示了GET請求,若是是POST或者其餘請求,也是同樣的操做,這裏就不作演示了。關於ckman對外提供的接口,能夠參考swagger文檔。

開啓swagger文檔須要在配置文件增長swagger_enable配置項:

server:
  id: 1
  port: 8808
  https: false
  pprof: true
  swagger_enable: true    #配置此項便可使用swagger文檔
  session_timeout: 3600
  public_key: MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDNw9cbOh1JRVNf/pQiRRMoa4TSmgZeq9zyK+Z5qE0Ak1XcmFzRg1m667ZAgfl/gEiwMGtbKyiPBGeHP5Gw3z5ENIHg7WGKTE0yRM/U/FMnktjly2xzjf7HUl/IA7PFYq5KBVBNPhjwzuFxpmsJL+fhhuYB75uDL0axYwcm7WHdewIDAQAB

重啓後可登陸http://localhost:8808/swagger... 進行查看。界面以下所示:
image.png

總結

本文簡單介紹瞭如何在實際開發中使用代碼操做ckman,能夠經過配置RSA公鑰和私鑰的方式跳過token鑑權,這樣有助於將ckman的調用與企業本身的產品相結合,使得ckman不只僅是一款獨立的管理工具,更是扮演了一個server服務端的角色,從而大大擴展了ckman的使用場景。

相關文章
相關標籤/搜索