mirror of
https://github.com/cubefs/cubefs.git
synced 2026-08-02 10:06:14 +00:00
516 lines
12 KiB
Go
516 lines
12 KiB
Go
package main
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"encoding/json"
|
|
"flag"
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"net/http"
|
|
"os"
|
|
"time"
|
|
|
|
"github.com/cubefs/cubefs/proto"
|
|
"github.com/cubefs/cubefs/util/config"
|
|
"github.com/cubefs/cubefs/util/cryptoutil"
|
|
"github.com/cubefs/cubefs/util/keystore"
|
|
)
|
|
|
|
// requst path
|
|
const (
|
|
GetTicket = "getticket"
|
|
CreateKey = "createkey"
|
|
DeleteKey = "deletekey"
|
|
GetKey = "getkey"
|
|
AddCaps = "addcaps"
|
|
DeleteCaps = "deletecaps"
|
|
AddRaftNode = "addraftnode"
|
|
RemoveRaftNode = "removeraftnode"
|
|
OSAddCaps = "osaddcaps"
|
|
OSDeleteCaps = "osdeletecaps"
|
|
OSGetCaps = "osgetcaps"
|
|
HTTP = "http://"
|
|
HTTPS = "https://"
|
|
)
|
|
|
|
const (
|
|
ID = "id"
|
|
Role = "role"
|
|
Caps = "caps"
|
|
AccessKey = "access_key"
|
|
AuthKey = "auth_key"
|
|
SessionKey = "session_key"
|
|
)
|
|
|
|
var action2PathMap = map[string]string{
|
|
GetTicket: proto.ClientGetTicket,
|
|
CreateKey: proto.AdminCreateKey,
|
|
DeleteKey: proto.AdminDeleteKey,
|
|
GetKey: proto.AdminGetKey,
|
|
AddCaps: proto.AdminAddCaps,
|
|
DeleteCaps: proto.AdminDeleteCaps,
|
|
AddRaftNode: proto.AdminAddRaftNode,
|
|
RemoveRaftNode: proto.AdminRemoveRaftNode,
|
|
OSAddCaps: proto.OSAddCaps,
|
|
OSDeleteCaps: proto.OSDeleteCaps,
|
|
OSGetCaps: proto.OSGetCaps,
|
|
}
|
|
|
|
var (
|
|
cflag string
|
|
flaginfo flagInfo
|
|
)
|
|
|
|
type ticketFlag struct {
|
|
key string
|
|
host string
|
|
output string
|
|
request string
|
|
service string
|
|
}
|
|
|
|
type apiFlag struct {
|
|
ticket string
|
|
host string
|
|
service string
|
|
request string
|
|
data string
|
|
output string
|
|
}
|
|
|
|
type flagInfo struct {
|
|
ticket ticketFlag
|
|
api apiFlag
|
|
https httpsSetting
|
|
}
|
|
|
|
type keyRing struct {
|
|
ID string `json:"id"`
|
|
Key []byte `json:"key"`
|
|
}
|
|
|
|
type ticketFile struct {
|
|
ID string `json:"id"`
|
|
Key string `json:"session_key"`
|
|
ServiceID string `json:"service_id"`
|
|
Ticket string `json:"ticket"`
|
|
}
|
|
|
|
type httpsSetting struct {
|
|
enable bool
|
|
cert []byte
|
|
}
|
|
|
|
func (m *ticketFile) dumpJSONFile(filename string) {
|
|
data, err := json.MarshalIndent(m, "", " ")
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
file, err := os.Create(filename)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
defer file.Close()
|
|
|
|
_, err = io.WriteString(file, string(data))
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
|
|
func sendReqX(target string, data interface{}, cert *[]byte) (res []byte, err error) {
|
|
var client *http.Client
|
|
target = HTTPS + target
|
|
client, err = cryptoutil.CreateClientX(cert)
|
|
if err != nil {
|
|
return
|
|
}
|
|
res, err = proto.SendData(client, target, data)
|
|
return
|
|
}
|
|
|
|
func sendReq(target string, data interface{}) (res []byte, err error) {
|
|
target = HTTP + target
|
|
client := &http.Client{}
|
|
res, err = proto.SendData(client, target, data)
|
|
return
|
|
}
|
|
|
|
func getTicketFromAuth(keyring *keyRing) (ticketfile ticketFile) {
|
|
var (
|
|
err error
|
|
ts int64
|
|
msgResp proto.AuthGetTicketResp
|
|
body []byte
|
|
)
|
|
|
|
// construct request body
|
|
message := proto.AuthGetTicketReq{
|
|
Type: proto.MsgAuthTicketReq,
|
|
ClientID: keyring.ID,
|
|
ServiceID: flaginfo.ticket.service,
|
|
}
|
|
|
|
if message.Verifier, ts, err = cryptoutil.GenVerifier(keyring.Key); err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
url := flaginfo.ticket.host + action2PathMap[flaginfo.ticket.request]
|
|
|
|
if flaginfo.https.enable {
|
|
body, err = sendReqX(url, message, &flaginfo.https.cert)
|
|
} else {
|
|
body, err = sendReq(url, message)
|
|
}
|
|
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
fmt.Printf("\n" + string(body) + "\n")
|
|
|
|
if msgResp, err = proto.ParseAuthGetTicketResp(body, keyring.Key); err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
if err = proto.VerifyTicketRespComm(&msgResp, proto.MsgAuthTicketReq, keyring.ID, flaginfo.ticket.service, ts); err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
ticketfile.Ticket = msgResp.Ticket
|
|
ticketfile.ServiceID = msgResp.ServiceID
|
|
ticketfile.Key = cryptoutil.Base64Encode(msgResp.SessionKey.Key)
|
|
ticketfile.ID = keyring.ID
|
|
|
|
return
|
|
}
|
|
|
|
func getTicket() {
|
|
cfg, err1 := config.LoadConfigFile(flaginfo.ticket.key)
|
|
if err1 != nil {
|
|
panic(err1)
|
|
}
|
|
key, err2 := cryptoutil.Base64Decode(cfg.GetString(AuthKey))
|
|
if err2 != nil {
|
|
panic(err2)
|
|
}
|
|
keyring := keyRing{
|
|
ID: cfg.GetString(ID),
|
|
Key: key,
|
|
}
|
|
|
|
ticketfile := getTicketFromAuth(&keyring)
|
|
ticketfile.dumpJSONFile(flaginfo.ticket.output)
|
|
}
|
|
|
|
func accessAuthServer() {
|
|
var (
|
|
msg proto.MsgType
|
|
sessionKey []byte
|
|
err error
|
|
message interface{}
|
|
ts int64
|
|
res string
|
|
body []byte
|
|
)
|
|
|
|
switch flaginfo.api.request {
|
|
case CreateKey:
|
|
msg = proto.MsgAuthCreateKeyReq
|
|
case DeleteKey:
|
|
msg = proto.MsgAuthDeleteKeyReq
|
|
case GetKey:
|
|
msg = proto.MsgAuthGetKeyReq
|
|
case AddCaps:
|
|
msg = proto.MsgAuthAddCapsReq
|
|
case DeleteCaps:
|
|
msg = proto.MsgAuthDeleteCapsReq
|
|
case AddRaftNode:
|
|
msg = proto.MsgAuthAddRaftNodeReq
|
|
case RemoveRaftNode:
|
|
msg = proto.MsgAuthRemoveRaftNodeReq
|
|
case OSAddCaps:
|
|
msg = proto.MsgAuthOSAddCapsReq
|
|
case OSDeleteCaps:
|
|
msg = proto.MsgAuthOSDeleteCapsReq
|
|
case OSGetCaps:
|
|
msg = proto.MsgAuthOSGetCapsReq
|
|
default:
|
|
panic(fmt.Errorf("wrong requst [%s]", flaginfo.api.request))
|
|
}
|
|
|
|
ticketCFG, err := config.LoadConfigFile(flaginfo.api.ticket)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
apiReq := &proto.APIAccessReq{
|
|
Type: msg,
|
|
ClientID: ticketCFG.GetString(ID),
|
|
ServiceID: proto.AuthServiceID,
|
|
}
|
|
|
|
if sessionKey, err = cryptoutil.Base64Decode(ticketCFG.GetString(SessionKey)); err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
if apiReq.Verifier, ts, err = cryptoutil.GenVerifier(sessionKey); err != nil {
|
|
panic(err)
|
|
}
|
|
apiReq.Ticket = ticketCFG.GetString("ticket")
|
|
|
|
dataCFG, err := config.LoadConfigFile(flaginfo.api.data)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
switch flaginfo.api.request {
|
|
case CreateKey:
|
|
message = proto.AuthAPIAccessReq{
|
|
APIReq: *apiReq,
|
|
KeyInfo: keystore.KeyInfo{
|
|
ID: dataCFG.GetString(ID),
|
|
Role: dataCFG.GetString(Role),
|
|
Caps: []byte(dataCFG.GetString(Caps)),
|
|
},
|
|
}
|
|
case DeleteKey:
|
|
fallthrough
|
|
case GetKey:
|
|
message = proto.AuthAPIAccessReq{
|
|
APIReq: *apiReq,
|
|
KeyInfo: keystore.KeyInfo{
|
|
ID: dataCFG.GetString(ID),
|
|
},
|
|
}
|
|
case AddCaps:
|
|
fallthrough
|
|
case DeleteCaps:
|
|
message = proto.AuthAPIAccessReq{
|
|
APIReq: *apiReq,
|
|
KeyInfo: keystore.KeyInfo{
|
|
ID: dataCFG.GetString(ID),
|
|
Caps: []byte(dataCFG.GetString(Caps)),
|
|
},
|
|
}
|
|
case AddRaftNode:
|
|
fallthrough
|
|
case RemoveRaftNode:
|
|
message = proto.AuthRaftNodeReq{
|
|
APIReq: *apiReq,
|
|
RaftNodeInfo: proto.AuthRaftNodeInfo{
|
|
ID: uint64(dataCFG.GetInt64(ID)),
|
|
Addr: dataCFG.GetString("addr"),
|
|
},
|
|
}
|
|
case OSAddCaps:
|
|
fallthrough
|
|
case OSDeleteCaps:
|
|
message = proto.AuthOSAccessKeyReq{
|
|
APIReq: *apiReq,
|
|
AKCaps: keystore.AccessKeyCaps{
|
|
AccessKey: dataCFG.GetString(AccessKey),
|
|
Caps: []byte(dataCFG.GetString(Caps)),
|
|
},
|
|
}
|
|
case OSGetCaps:
|
|
message = proto.AuthOSAccessKeyReq{
|
|
APIReq: *apiReq,
|
|
AKCaps: keystore.AccessKeyCaps{
|
|
AccessKey: dataCFG.GetString(AccessKey),
|
|
},
|
|
}
|
|
default:
|
|
panic(fmt.Errorf("wrong action [%s]", flaginfo.api.request))
|
|
}
|
|
|
|
url := flaginfo.api.host + action2PathMap[flaginfo.api.request]
|
|
|
|
if flaginfo.https.enable {
|
|
body, err = sendReqX(url, message, &flaginfo.https.cert)
|
|
} else {
|
|
body, err = sendReq(url, message)
|
|
}
|
|
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
fmt.Printf("\nbody: " + string(body) + "\n")
|
|
|
|
switch flaginfo.api.request {
|
|
case CreateKey:
|
|
fallthrough
|
|
case DeleteKey:
|
|
fallthrough
|
|
case GetKey:
|
|
fallthrough
|
|
case AddCaps:
|
|
fallthrough
|
|
case DeleteCaps:
|
|
var resp proto.AuthAPIAccessResp
|
|
if resp, err = proto.ParseAuthAPIAccessResp(body, sessionKey); err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
if err = proto.VerifyAPIRespComm(&resp.APIResp, msg, ticketCFG.GetString(ID), proto.AuthServiceID, ts); err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
if flaginfo.api.request == CreateKey {
|
|
if err = resp.KeyInfo.DumpJSONFile(flaginfo.api.output, resp.AuthIDKey); err != nil {
|
|
panic(err)
|
|
}
|
|
} else {
|
|
if res, err = resp.KeyInfo.DumpJSONStr(resp.AuthIDKey); err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
|
|
case AddRaftNode:
|
|
fallthrough
|
|
case RemoveRaftNode:
|
|
var resp proto.AuthRaftNodeResp
|
|
if resp, err = proto.ParseAuthRaftNodeResp(body, sessionKey); err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
if err = proto.VerifyAPIRespComm(&resp.APIResp, msg, ticketCFG.GetString(ID), proto.AuthServiceID, ts); err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
fmt.Printf(resp.Msg + "\n")
|
|
case OSAddCaps:
|
|
fallthrough
|
|
case OSDeleteCaps:
|
|
fallthrough
|
|
case OSGetCaps:
|
|
var resp proto.AuthOSAccessKeyResp
|
|
if resp, err = proto.ParseAuthOSAKResp(body, sessionKey); err != nil {
|
|
panic(err)
|
|
}
|
|
if err = proto.VerifyAPIRespComm(&resp.APIResp, msg, ticketCFG.GetString(ID), proto.AuthServiceID, ts); err != nil {
|
|
panic(err)
|
|
}
|
|
if res, err = resp.AKCaps.DumpJSONStr(); err != nil {
|
|
panic(err)
|
|
}
|
|
fmt.Printf(res + "\n")
|
|
default:
|
|
// do nothing
|
|
}
|
|
}
|
|
|
|
func accessAPI() {
|
|
switch flaginfo.api.service {
|
|
case proto.AuthServiceID:
|
|
accessAuthServer()
|
|
default:
|
|
panic(fmt.Errorf("server type error [%s]", flaginfo.api.service))
|
|
}
|
|
}
|
|
|
|
func loadCertfile(path string) (caCert []byte) {
|
|
var err error
|
|
caCert, err = os.ReadFile(path)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
return
|
|
}
|
|
|
|
func generateRandomBytes(n int) ([]byte, error) {
|
|
b := make([]byte, n)
|
|
_, err := rand.Read(b)
|
|
// Note that err == nil only if we read len(b) bytes.
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return b, nil
|
|
}
|
|
|
|
func main() {
|
|
ticketCmd := flag.NewFlagSet("ticket", flag.ExitOnError)
|
|
apiCmd := flag.NewFlagSet("api", flag.ExitOnError)
|
|
authkeyCmd := flag.NewFlagSet("authkey", flag.ExitOnError)
|
|
|
|
switch os.Args[1] {
|
|
case "ticket":
|
|
key := ticketCmd.String("keyfile", "keyring.json", "path to key file")
|
|
host := ticketCmd.String("host", "localhost:8080", "api host")
|
|
file := ticketCmd.String("output", "ticket.json", "output path to ticket file")
|
|
https := ticketCmd.Bool("https", false, "enable https")
|
|
certfile := ticketCmd.String("certfile", "server.crt", "path to cert file")
|
|
ticketCmd.Parse(os.Args[2:])
|
|
flaginfo.ticket.key = *key
|
|
flaginfo.ticket.host = *host
|
|
flaginfo.ticket.output = *file
|
|
flaginfo.https.enable = *https
|
|
if flaginfo.https.enable {
|
|
flaginfo.https.cert = loadCertfile(*certfile)
|
|
}
|
|
if len(ticketCmd.Args()) >= 2 {
|
|
flaginfo.ticket.request = ticketCmd.Args()[0]
|
|
flaginfo.ticket.service = ticketCmd.Args()[1]
|
|
if _, ok := action2PathMap[flaginfo.ticket.request]; !ok {
|
|
panic(fmt.Errorf("illegal parameter %s", flaginfo.ticket.request))
|
|
}
|
|
}
|
|
getTicket()
|
|
case "api":
|
|
ticket := apiCmd.String("ticketfile", "ticket.json", "path to ticket file")
|
|
host := apiCmd.String("host", "localhost:8080", "api host")
|
|
data := apiCmd.String("data", "data.json", "request data file")
|
|
output := apiCmd.String("output", "keyring.json", "output path to keyring file")
|
|
https := apiCmd.Bool("https", false, "enable https")
|
|
certfile := apiCmd.String("certfile", "server.crt", "path to cert file")
|
|
apiCmd.Parse(os.Args[2:])
|
|
flaginfo.api.ticket = *ticket
|
|
flaginfo.api.host = *host
|
|
flaginfo.api.data = *data
|
|
flaginfo.api.output = *output
|
|
flaginfo.https.enable = *https
|
|
if flaginfo.https.enable {
|
|
flaginfo.https.cert = loadCertfile(*certfile)
|
|
}
|
|
if len(apiCmd.Args()) >= 2 {
|
|
flaginfo.api.service = apiCmd.Args()[0]
|
|
flaginfo.api.request = apiCmd.Args()[1]
|
|
if _, ok := action2PathMap[flaginfo.api.request]; !ok {
|
|
panic(fmt.Errorf("illegal parameter %s", flaginfo.api.request))
|
|
}
|
|
} else {
|
|
panic(fmt.Errorf("requst parameter needed"))
|
|
}
|
|
accessAPI()
|
|
case "authkey":
|
|
len := authkeyCmd.Int("keylen", 32, "length of authkey")
|
|
output := [2]*string{
|
|
authkeyCmd.String("rootkey", "authroot.json", "output path to keyring file of auth root"),
|
|
authkeyCmd.String("servicekey", "authservice.json", "output path to keyring file of service"),
|
|
}
|
|
authkeyCmd.Parse(os.Args[2:])
|
|
for i := range output {
|
|
random, err := generateRandomBytes(*len)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
keyInfo := keystore.KeyInfo{
|
|
ID: "AuthService",
|
|
AuthKey: random,
|
|
Ts: time.Now().Unix(),
|
|
Role: "AuthService",
|
|
Caps: []byte(`{"*"}`),
|
|
}
|
|
keyInfo.DumpJSONFile(*output[i], "")
|
|
}
|
|
|
|
default:
|
|
fmt.Println("expected 'ticket', 'api' or 'authkey'subcommands")
|
|
os.Exit(1)
|
|
}
|
|
}
|