如何在产品中集成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的使用场景。

相关文章
相关标签/搜索