From 8c5d36c69c26b65e592b46b852411a9399d4a215 Mon Sep 17 00:00:00 2001 From: YenchangChan Date: Tue, 4 Jun 2024 12:36:53 +0800 Subject: [PATCH] feat: user privilege management --- cmd/password/password.go | 16 +++---- common/jwt.go | 11 ----- common/user.go | 88 ++++++++++++++++++++++++++++++++++++++ controller/user.go | 16 ++----- server/enforce/enforce.go | 64 ++++++++++----------------- server/enforce/guest.go | 35 +++++++++++++++ server/enforce/ordinary.go | 24 +++++++++++ server/server.go | 2 +- 8 files changed, 183 insertions(+), 73 deletions(-) create mode 100644 common/user.go create mode 100644 server/enforce/guest.go create mode 100644 server/enforce/ordinary.go diff --git a/cmd/password/password.go b/cmd/password/password.go index f5d0a91..ecb0d94 100644 --- a/cmd/password/password.go +++ b/cmd/password/password.go @@ -12,6 +12,7 @@ import ( ) func main() { + common.LoadUsers(path.Join(common.GetWorkDirectory(), "conf")) fmt.Println(`Password must be at least 8 characters long. Password must contain at least three character categories among the following: * Uppercase characters (A-Z) @@ -19,11 +20,12 @@ Password must contain at least three character categories among the following: * Digits (0-9) * Special characters (~!@#$%^&*_-+=|\(){}[]:;"'<>,.?/)`) - fmt.Printf("\nEnter username(ckman/guest):") + fmt.Printf("\nEnter username:") var username string fmt.Scanf("%s", &username) - if !common.UsernameInvalid(username) { - fmt.Printf("invalid username, expect %s or %s\n", common.DefaultAdminName, common.DefaultGuestName) + userinfo, err := common.GetUserInfo(username) + if err != nil { + fmt.Printf("\nGet user info fail: %v\n", err) return } fmt.Printf("\nEnter password for [%s]: ", username) @@ -58,16 +60,14 @@ Password must contain at least three character categories among the following: return } - passwordFile := path.Join(common.GetWorkDirectory(), path.Join("conf", common.PasswordFile[username])) - fileFd, err := os.OpenFile(passwordFile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666) + fileFd, err := os.OpenFile(userinfo.UserFile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666) if err != nil { - fmt.Printf("\nOpen password file %s fail: %v\n", passwordFile, err) + fmt.Printf("\nOpen password file %s fail: %v\n", userinfo.UserFile, err) return } defer fileFd.Close() - if _, err := fileFd.Write([]byte(hash)); err != nil { - fmt.Printf("\nWrite password file %s fail: %v\n", passwordFile, err) + fmt.Printf("\nWrite password file %s fail: %v\n", userinfo.UserFile, err) return } diff --git a/common/jwt.go b/common/jwt.go index 4df5c25..6c634d7 100644 --- a/common/jwt.go +++ b/common/jwt.go @@ -6,20 +6,9 @@ import ( ) var ( - DefaultAdminName = "ckman" - DefaultGuestName = "guest" DefaultSigningKey = "change me" ) -var PasswordFile = map[string]string{ - DefaultAdminName: "password", - DefaultGuestName: "guestpassword", -} - -func UsernameInvalid(username string) bool { - return ArraySearch(username, []string{DefaultAdminName, DefaultGuestName}) -} - type JWT struct { SigningKey []byte } diff --git a/common/user.go b/common/user.go new file mode 100644 index 0000000..de866df --- /dev/null +++ b/common/user.go @@ -0,0 +1,88 @@ +package common + +import ( + "fmt" + "os" + "path" + "strings" + "sync" +) + +const ( + ADMIN string = "ckman" + GUEST string = "guest" + ORDINARY string = "ordinary" + + DefaultAdminName = "ckman" + InternalOrdinaryName = "ordinary" +) + +type UserInfo struct { + Policy string + Password string + UserFile string +} + +var UserMap map[string]UserInfo +var lock sync.Mutex + +func LoadUsers(configPath string) { + //usermap + lock.Lock() + defer lock.Unlock() + UserMap = make(map[string]UserInfo) + userPath := path.Join(configPath, "users") + entries, err := os.ReadDir(userPath) + if err == nil { + for _, entry := range entries { + if entry.IsDir() { + continue + } + + userfile := path.Join(userPath, entry.Name()) + userinfos := strings.Split(entry.Name(), ".") + if len(userinfos) != 2 { + continue + } + username := userinfos[0] + policy := userinfos[1] + if policy != GUEST && policy != ORDINARY { + continue + } + password, err := os.ReadFile(userfile) + if err == nil { + UserMap[username] = UserInfo{ + Policy: policy, + Password: string(password), + UserFile: userfile, + } + } + } + } + + passwordFile := path.Join(configPath, "password") + password, err := os.ReadFile(passwordFile) + if err != nil { + return + } + + UserMap[DefaultAdminName] = UserInfo{ + Policy: ADMIN, + Password: string(password), + UserFile: passwordFile, + } + + UserMap[InternalOrdinaryName] = UserInfo{ + Policy: ORDINARY, + //Password: "change me", // InternalOrdinaryName 专门给userToken方式的用户使用,无需密码 + } +} + +func GetUserInfo(name string) (UserInfo, error) { + lock.Lock() + defer lock.Unlock() + if userinfo, ok := UserMap[name]; ok { + return userinfo, nil + } + return UserInfo{}, fmt.Errorf("user %s doesn't exist", name) +} diff --git a/controller/user.go b/controller/user.go index 5446bb1..a9ef0a1 100644 --- a/controller/user.go +++ b/controller/user.go @@ -1,8 +1,6 @@ package controller import ( - "os" - "path" "path/filepath" "time" @@ -42,24 +40,18 @@ func NewUserController(config *config.CKManConfig, wrapfunc Wrapfunc) *UserContr func (controller *UserController) Login(c *gin.Context) { var req model.LoginReq c.Request.Header.Get("") + common.LoadUsers(filepath.Dir(controller.config.ConfigFile)) if err := model.DecodeRequestBody(c.Request, &req); err != nil { controller.wrapfunc(c, model.E_INVALID_PARAMS, err) return } - if !common.UsernameInvalid(req.Username) { - controller.wrapfunc(c, model.E_USER_VERIFY_FAIL, nil) - return - } - - passwordFile := path.Join(filepath.Dir(controller.config.ConfigFile), common.PasswordFile[req.Username]) - data, err := os.ReadFile(passwordFile) + userinfo, err := common.GetUserInfo(req.Username) if err != nil { - controller.wrapfunc(c, model.E_GET_USER_PASSWORD_FAIL, err) + controller.wrapfunc(c, model.E_USER_VERIFY_FAIL, err) return } - - if pass := common.ComparePassword(string(data), req.Password); !pass { + if pass := common.ComparePassword(userinfo.Password, req.Password); !pass { controller.wrapfunc(c, model.E_PASSWORD_VERIFY_FAIL, nil) return } diff --git a/server/enforce/enforce.go b/server/enforce/enforce.go index 8bb1600..ce557a6 100644 --- a/server/enforce/enforce.go +++ b/server/enforce/enforce.go @@ -2,12 +2,11 @@ package enforce import ( "strings" + + "github.com/housepower/ckman/common" ) const ( - ADMIN string = "ckman" - GUEST string = "guest" - POST string = "POST" GET string = "GET" PUT string = "PUT" @@ -15,7 +14,6 @@ const ( ) type Policy struct { - User string URL string Method string } @@ -26,12 +24,13 @@ type Model struct { } type Enforcer struct { - model Model - policies []Policy + model Model + guest []Policy + orinary []Policy } var DefaultModel = Model{ - Admin: ADMIN, + Admin: common.ADMIN, UrlPrefix: []string{ "/api/v1", "/api/v2", }, @@ -40,38 +39,9 @@ var e *Enforcer func init() { e = &Enforcer{ - model: DefaultModel, - policies: []Policy{ - {GUEST, "/ck/cluster", GET}, - {GUEST, "/ck/cluster/*", GET}, - {GUEST, "/ck/table/*", GET}, - {GUEST, "/ck/table/group_uniq_array/*", GET}, - {GUEST, "/ck/query/*", GET}, - {GUEST, "/ck/query_explain/*", GET}, - {GUEST, "/ck/query_history/*", GET}, - {GUEST, "/ck/table_lists/*", GET}, - {GUEST, "/ck/table_schema/*", GET}, - {GUEST, "/ck/get/*", GET}, - {GUEST, "/ck/partition/*", GET}, - {GUEST, "/ck/table_metric/*", GET}, - {GUEST, "/ck/table_merges/*", GET}, - {GUEST, "/ck/open_sessions/*", GET}, - {GUEST, "/ck/slow_sessions/*", GET}, - {GUEST, "/ck/ddl_queue/*", GET}, - {GUEST, "/ck/node/log/*", POST}, - {GUEST, "/ck/ping/*", POST}, - {GUEST, "/ck/config/*", GET}, - {GUEST, "/zk/status/*", GET}, - {GUEST, "/zk/replicated_table/*", GET}, - {GUEST, "/package", GET}, - {GUEST, "/metric/query/*", GET}, - {GUEST, "/metric/query_range/*", GET}, - {GUEST, "/version", GET}, - {GUEST, "/ui/schema", GET}, - {GUEST, "/task/*", GET}, - {GUEST, "/task/lists", GET}, - {GUEST, "/task/running", GET}, - }, + model: DefaultModel, + guest: GuestPolicies(), + orinary: OrdinaryPolicies(), } } @@ -95,8 +65,20 @@ func Enforce(username, url, method string) bool { return true } - for _, policy := range e.policies { - if policy.User == username && e.Match(policy.URL, url) && policy.Method == method { + userinfo, err := common.GetUserInfo(username) + if err != nil { + return false + } + var policies []Policy + switch userinfo.Policy { + case common.GUEST: + policies = e.guest + case common.ORDINARY: + policies = e.orinary + } + + for _, policy := range policies { + if e.Match(policy.URL, url) && policy.Method == method { return true } } diff --git a/server/enforce/guest.go b/server/enforce/guest.go new file mode 100644 index 0000000..e20d1a8 --- /dev/null +++ b/server/enforce/guest.go @@ -0,0 +1,35 @@ +package enforce + +func GuestPolicies() []Policy { + return []Policy{ + {"/ck/cluster", GET}, + {"/ck/cluster/*", GET}, + {"/ck/table/*", GET}, + {"/ck/table/group_uniq_array/*", GET}, + {"/ck/query/*", GET}, + {"/ck/query_explain/*", GET}, + {"/ck/query_history/*", GET}, + {"/ck/table_lists/*", GET}, + {"/ck/table_schema/*", GET}, + {"/ck/get/*", GET}, + {"/ck/partition/*", GET}, + {"/ck/table_metric/*", GET}, + {"/ck/table_merges/*", GET}, + {"/ck/open_sessions/*", GET}, + {"/ck/slow_sessions/*", GET}, + {"/ck/ddl_queue/*", GET}, + {"/ck/node/log/*", POST}, + {"/ck/ping/*", POST}, + {"/ck/config/*", GET}, + {"/zk/status/*", GET}, + {"/zk/replicated_table/*", GET}, + {"/package", GET}, + {"/metric/query/*", GET}, + {"/metric/query_range/*", GET}, + {"/version", GET}, + {"/ui/schema", GET}, + {"/task/*", GET}, + {"/task/lists", GET}, + {"/task/running", GET}, + } +} diff --git a/server/enforce/ordinary.go b/server/enforce/ordinary.go new file mode 100644 index 0000000..2955369 --- /dev/null +++ b/server/enforce/ordinary.go @@ -0,0 +1,24 @@ +package enforce + +func OrdinaryPolicies() []Policy { + var OrdinaryPolicies = []Policy{ + {"/ck/table/*", POST}, + {"/ck/dist_logic_table/*", POST}, + {"/ck/dist_logic_table/*", DELETE}, + {"/ck/table/*", PUT}, + {"/ck/truncate_table/*", DELETE}, + {"/ck/table/ttl/*", PUT}, + {"/ck/table/readonly/*", PUT}, + {"/ck/table/view/*", PUT}, + {"/ck/table/orderby/*", PUT}, + {"/ck/table/group_uniq_array/*", POST}, + {"/ck/table/group_uniq_array/*", DELETE}, + {"/ck/table/*", DELETE}, + {"/ck/table_all/*", DELETE}, + {"/ck/query_history/*", DELETE}, + {"/ck/open_sessions/*", PUT}, + {"/ck/purge_tables/*", POST}, + {"/ck/archive/*", POST}, + } + return append(OrdinaryPolicies, GuestPolicies()...) +} diff --git a/server/server.go b/server/server.go index 7fcc626..ed0a3c1 100644 --- a/server/server.go +++ b/server/server.go @@ -209,7 +209,7 @@ func ginJWTAuth() gin.HandlerFunc { return } //c.Set("username", userToken.UserId) - c.Set("username", common.DefaultAdminName) + c.Set("username", common.InternalOrdinaryName) return }