ckman
是一款由擎創科技數據庫團隊主導開發的,用來管理和監控ClickHouse
的可視化開源工具。它將繁瑣的ClickHouse
集羣的配置和監控工做變的簡單化、直觀化。github
地址爲ckman, 感興趣的同窗能夠今後處下載源代碼。html
雖然ckman
提供了web
界面,可是對於部分企業來講,可能某些API
的調用,直接使用網頁端並非那麼的通用,而是更多的指望將其整合到本身的產品中去。好比經過本身的產品頁面,就能夠直接調用ckman
的API
,將ckman
做爲一個server
端,從而很方便地進行集羣管理,數據庫表的管理以及數據監控。node
爲了安全考慮,ckman
加入了https
支持以及token
進行鑑權。token
的加入,雖然大大提升了數據鏈路的安全性,可是對於開發來講,卻提供了必定難度(由於咱們雖然可以經過登陸的接口獲取到token
是什麼,可是一旦ckman
重啓或者token
過時,就會致使token
失效)。git
正是由於這種狀況,ckman
在token
以外,提供了另外一種訪問方案。只要請求在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 }
其中,咱們只須要關心Duration
和Timestamp
字段便可。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}}
這與咱們使用網頁端訪問的結果一致:
若是咱們使用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... 進行查看。界面以下所示:
本文簡單介紹瞭如何在實際開發中使用代碼操做ckman
,能夠經過配置RSA
公鑰和私鑰的方式跳過token
鑑權,這樣有助於將ckman
的調用與企業本身的產品相結合,使得ckman
不只僅是一款獨立的管理工具,更是扮演了一個server
服務端的角色,從而大大擴展了ckman
的使用場景。