Skip to content

Commit 6fe865a

Browse files
authored
Merge pull request #948 from signal18/user-gui
Implement roles for Users. Auto create user in api-credentials if not exists
2 parents 0da231c + 29891ce commit 6fe865a

File tree

13 files changed

+315
-66
lines changed

13 files changed

+315
-66
lines changed

cluster/cluster.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ type Cluster struct {
127127
LogSlack *log.Logger `json:"-"`
128128
JobResults *config.TasksMap `json:"jobResults"`
129129
Grants map[string]string `json:"-"`
130+
Roles map[string]string `json:"-"`
130131
tlog *s18log.TermLog `json:"-"`
131132
htlog *s18log.HttpLog `json:"-"`
132133
SQLGeneralLog s18log.HttpLog `json:"sqlGeneralLog"`
@@ -371,6 +372,7 @@ func (cluster *Cluster) InitFromConf() {
371372
cluster.DiskType = cluster.Conf.GetDiskType()
372373
cluster.VMType = cluster.Conf.GetVMType()
373374
cluster.Grants = cluster.Conf.GetGrantType()
375+
cluster.Roles = cluster.Conf.GetRoleType()
374376

375377
cluster.QueryRules = make(map[uint32]config.QueryRule)
376378
cluster.Schedule = make(map[string]cron.Entry)

cluster/cluster_acl.go

Lines changed: 70 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ package cluster
88

99
import (
1010
"fmt"
11+
"slices"
1112
"strings"
1213

1314
"github.com/signal18/replication-manager/config"
@@ -21,9 +22,42 @@ type APIUser struct {
2122
Password string `json:"-"`
2223
GitToken string `json:"-"`
2324
GitUser string `json:"-"`
25+
Roles map[string]bool `json:"roles"`
2426
Grants map[string]bool `json:"grants"`
2527
}
2628

29+
func (cluster *Cluster) SetNewUserGrants(u *APIUser, grant string) {
30+
acls := strings.Split(grant, " ")
31+
for key, value := range cluster.Grants {
32+
found := false
33+
for _, acl := range acls {
34+
if strings.HasPrefix(key, acl) && acl != "" {
35+
found = true
36+
break
37+
}
38+
}
39+
u.Grants[value] = found
40+
}
41+
}
42+
43+
func (cluster *Cluster) SetNewUserRoles(u *APIUser, roles string) {
44+
list := strings.Split(roles, " ")
45+
46+
if u.Grants[config.GrantGlobalGrant] && roles == "" {
47+
u.Roles[config.RoleSysOps] = true
48+
u.Roles[config.RoleDBOps] = true
49+
return
50+
}
51+
52+
for key, value := range cluster.Roles {
53+
found := false
54+
if slices.Contains(list, key) {
55+
found = true
56+
}
57+
u.Roles[value] = found
58+
}
59+
}
60+
2761
func (u *APIUser) Granted(grant string) error {
2862
if value, ok := u.Grants[grant]; ok {
2963
if !value {
@@ -59,19 +93,34 @@ func (cluster *Cluster) GetAPIUser(strUser string, strPassword string) (APIUser,
5993
return APIUser{}, fmt.Errorf("user not found")
6094
}
6195

96+
func (cluster *Cluster) SaveUserAcls(user string) string {
97+
var aEnabledAcls []string
98+
for grant, value := range cluster.APIUsers[user].Grants {
99+
if value {
100+
aEnabledAcls = append(aEnabledAcls, grant)
101+
}
102+
}
103+
return strings.Join(aEnabledAcls, " ")
104+
}
105+
106+
func (cluster *Cluster) SaveUserRoles(user string) string {
107+
var aEnabledRoles []string
108+
for grant, value := range cluster.APIUsers[user].Roles {
109+
if value {
110+
aEnabledRoles = append(aEnabledRoles, grant)
111+
}
112+
}
113+
return strings.Join(aEnabledRoles, " ")
114+
}
115+
62116
func (cluster *Cluster) SaveAcls() {
63117
credentials := strings.Split(cluster.Conf.GetDecryptedValue("api-credentials")+","+cluster.Conf.GetDecryptedValue("api-credentials-external"), ",")
64118
var aUserAcls []string
65119
for _, credential := range credentials {
66120
user, _ := misc.SplitPair(credential)
67-
var aEnabledAcls []string
68-
for grant, value := range cluster.APIUsers[user].Grants {
69-
if value {
70-
aEnabledAcls = append(aEnabledAcls, grant)
71-
}
72-
}
73-
enabledAclsCredential := user + ":" + strings.Join(aEnabledAcls, " ")
74-
aUserAcls = append(aUserAcls, enabledAclsCredential)
121+
enabledAcls := cluster.SaveUserAcls(user)
122+
enabledRoles := cluster.SaveUserRoles(user)
123+
aUserAcls = append(aUserAcls, user+":"+enabledAcls+":"+enabledRoles)
75124
}
76125
cluster.Conf.APIUsersACLAllow = strings.Join(aUserAcls, ",")
77126
cluster.Conf.APIUsersACLDiscard = ""
@@ -92,28 +141,26 @@ func (cluster *Cluster) LoadAPIUsers() error {
92141
// fmt.Printf(cluster.Conf.Secrets["api-credentials"].Value + "," + cluster.Conf.Secrets["api-credentials-external"].Value)
93142
meUsers := make(map[string]APIUser)
94143
for _, credential := range credentials {
144+
// Assign User Credentials
95145
var newapiuser APIUser
96-
97146
newapiuser.User, newapiuser.Password = misc.SplitPair(credential)
98147
newapiuser.Password = cluster.Conf.GetDecryptedPassword("api-credentials", newapiuser.Password)
99-
usersAllowACL := strings.Split(cluster.Conf.APIUsersACLAllow, ",")
100148
newapiuser.Grants = make(map[string]bool)
149+
newapiuser.Roles = make(map[string]bool)
150+
151+
// Assign Roles and ACLs
152+
usersAllowACL := strings.Split(cluster.Conf.APIUsersACLAllow, ",")
101153
for _, userACL := range usersAllowACL {
102-
useracl, listacls := misc.SplitPair(userACL)
103-
acls := strings.Split(listacls, " ")
104-
if useracl == newapiuser.User {
105-
for key, value := range cluster.Grants {
106-
found := false
107-
for _, acl := range acls {
108-
if strings.HasPrefix(key, acl) && acl != "" {
109-
found = true
110-
break
111-
}
112-
}
113-
newapiuser.Grants[value] = found
114-
}
154+
useracl, listacls, listroles, listcluster := misc.SplitAcls(userACL)
155+
cluster_acls := strings.Split(listcluster, " ")
156+
157+
// For compatibility allow empty cluster list ACL
158+
if useracl == newapiuser.User && (listcluster == "" || slices.Contains(cluster_acls, cluster.Name)) {
159+
cluster.SetNewUserGrants(&newapiuser, listacls)
160+
cluster.SetNewUserRoles(&newapiuser, listroles)
115161
}
116162
}
163+
117164
usersDiscardACL := strings.Split(cluster.Conf.APIUsersACLDiscard, ",")
118165
for _, userACL := range usersDiscardACL {
119166
useracl, listacls := misc.SplitPair(userACL)

cluster/cluster_add.go

Lines changed: 8 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -158,51 +158,38 @@ func (cluster *Cluster) AddSeededProxy(prx string, srv string, port string, user
158158
type UserForm struct {
159159
Username string `json:"username"`
160160
Password string `json:"password"`
161+
Roles string `json:"roles"`
161162
Grants string `json:"grants"`
162163
}
163164

164165
func (cluster *Cluster) AddUser(userform UserForm) error {
165166
user := userform.Username
167+
roles := userform.Roles
166168
grants := userform.Grants
167169
pass, _ := cluster.GeneratePassword()
168170
if _, ok := cluster.APIUsers[user]; ok {
169171
cluster.LogModulePrintf(cluster.Conf.Verbose, config.ConstLogModGeneral, config.LvlErr, "User %s already exist ", user)
170172
} else {
171173
if cluster.Conf.GetDecryptedValue("api-credentials-external") == "" {
172-
cluster.Conf.APIUsersExternal = user + ":" + pass
173-
174+
cluster.Conf.APIUsersExternal = user + ":" + pass + ":" + roles
174175
} else {
175-
cluster.Conf.APIUsersExternal = cluster.Conf.GetDecryptedValue("api-credentials-external") + "," + user + ":" + pass
176+
cluster.Conf.APIUsersExternal = cluster.Conf.GetDecryptedValue("api-credentials-external") + "," + user + ":" + pass + ":" + roles
176177
}
177178
var new_secret config.Secret
178179
new_secret.Value = cluster.Conf.APIUsersExternal
179180
new_secret.OldValue = cluster.Conf.GetDecryptedValue("api-credentials-external")
180181
cluster.Conf.Secrets["api-credentials-external"] = new_secret
182+
183+
// Assign ACL before reloading
184+
cluster.Conf.APIUsersACLAllow = cluster.Conf.APIUsersACLAllow + "," + user + ":" + grants + ":" + roles + ":" + cluster.Name
185+
181186
cluster.LoadAPIUsers()
182-
cluster.AddUserGrants(user, grants)
183187
cluster.Save()
184188
}
185189

186190
return nil
187191
}
188192

189-
func (cluster *Cluster) AddUserGrants(user string, grants string) {
190-
191-
acls := strings.Split(grants, " ")
192-
for key, value := range cluster.Grants {
193-
found := false
194-
for _, acl := range acls {
195-
if strings.HasPrefix(key, acl) && acl != "" {
196-
found = true
197-
break
198-
}
199-
}
200-
cluster.APIUsers[user].Grants[value] = found
201-
}
202-
203-
cluster.SaveAcls()
204-
}
205-
206193
func (cluster *Cluster) AddShardingHostGroup(proxy *MariadbShardProxy) error {
207194
if cluster.Conf.ClusterHead != "" {
208195
return nil

config/config.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -878,6 +878,20 @@ type Grant struct {
878878
Enable bool `json:"enable"`
879879
}
880880

881+
type Role struct {
882+
Role string `json:"role"`
883+
Enable bool `json:"enable"`
884+
}
885+
886+
const (
887+
RoleSysOps string = "sysops"
888+
RoleDBOps string = "dbops"
889+
RoleExtSysOps string = "extsysops"
890+
RoleExtDBOps string = "extdbops"
891+
RoleSponsor string = "sponsor"
892+
RoleVisitor string = "visitor"
893+
)
894+
881895
const (
882896
GrantDBStart string = "db-start"
883897
GrantDBStop string = "db-stop"
@@ -1889,6 +1903,17 @@ func (conf *Config) GetGrantType() map[string]string {
18891903
}
18901904
}
18911905

1906+
func (conf *Config) GetRoleType() map[string]string {
1907+
return map[string]string{
1908+
RoleSysOps: RoleSysOps,
1909+
RoleDBOps: RoleDBOps,
1910+
RoleExtSysOps: RoleExtSysOps,
1911+
RoleExtDBOps: RoleExtDBOps,
1912+
RoleSponsor: RoleSponsor,
1913+
RoleVisitor: RoleVisitor,
1914+
}
1915+
}
1916+
18921917
func (conf *Config) GetDockerRepos(file string, is_not_embed bool) ([]DockerRepo, error) {
18931918
var repos DockerRepos
18941919
var byteValue []byte

server/api.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import (
3939
"github.com/gorilla/mux"
4040
"github.com/signal18/replication-manager/cert"
4141
"github.com/signal18/replication-manager/cluster"
42+
"github.com/signal18/replication-manager/config"
4243
"github.com/signal18/replication-manager/regtest"
4344
"github.com/signal18/replication-manager/share"
4445
"github.com/signal18/replication-manager/utils/githelper"
@@ -360,6 +361,31 @@ func (repman *ReplicationManager) IsValidClusterACL(r *http.Request, cluster *cl
360361
return false, ""
361362
}
362363

364+
func (repman *ReplicationManager) GetUserFromRequest(r *http.Request) string {
365+
366+
token, err := request.ParseFromRequest(r, request.AuthorizationHeaderExtractor, func(token *jwt.Token) (interface{}, error) {
367+
vk, _ := jwt.ParseRSAPublicKeyFromPEM(verificationKey)
368+
return vk, nil
369+
})
370+
371+
if err == nil {
372+
claims := token.Claims.(jwt.MapClaims)
373+
userinfo := claims["CustomUserInfo"]
374+
mycutinfo := userinfo.(map[string]interface{})
375+
meuser := mycutinfo["Name"].(string)
376+
_, ok := mycutinfo["profile"]
377+
378+
if ok {
379+
if strings.Contains(mycutinfo["profile"].(string), repman.Conf.OAuthProvider) /*&& strings.Contains(mycutinfo["email_verified"]*/ {
380+
return mycutinfo["email"].(string)
381+
}
382+
}
383+
return meuser
384+
}
385+
386+
return ""
387+
}
388+
363389
func (repman *ReplicationManager) loginHandler(w http.ResponseWriter, r *http.Request) {
364390
w.Header().Set("Access-Control-Allow-Origin", "*")
365391
var user userCredentials
@@ -753,8 +779,33 @@ func (repman *ReplicationManager) jsonResponse(apiresponse interface{}, w http.R
753779
func (repman *ReplicationManager) handlerMuxClusterAdd(w http.ResponseWriter, r *http.Request) {
754780
w.Header().Set("Access-Control-Allow-Origin", "*")
755781
vars := mux.Vars(r)
782+
783+
username := repman.GetUserFromRequest(r)
784+
if username == "" {
785+
http.Error(w, "User is not valid", http.StatusInternalServerError)
786+
return
787+
}
788+
756789
repman.AddCluster(vars["clusterName"], "")
790+
// Create user and grant for new cluster
791+
cl := repman.getClusterByName(vars["clusterName"])
792+
if cl != nil {
793+
if u, ok := cl.APIUsers[username]; !ok {
794+
// Create user and grant for new cluster
795+
userform := cluster.UserForm{
796+
Username: username,
797+
Roles: strings.Join(([]string{config.RoleSponsor, config.RoleDBOps, config.RoleSysOps}), " "),
798+
Grants: "cluster db proxy prov",
799+
}
757800

801+
cl.AddUser(userform)
802+
} else {
803+
// Update grant for new cluster
804+
cl.SetNewUserGrants(&u, "cluster db proxy prov")
805+
cl.SetNewUserRoles(&u, strings.Join(([]string{config.RoleSponsor, config.RoleDBOps, config.RoleSysOps}), " "))
806+
cl.APIUsers[u.User] = u
807+
}
808+
}
758809
}
759810

760811
func (repman *ReplicationManager) handlerMuxClusterDelete(w http.ResponseWriter, r *http.Request) {

server/server.go

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ type ReplicationManager struct {
9393
ServicePlans []config.ServicePlan `json:"servicePlans"`
9494
ServiceOrchestrators []config.ConfigVariableType `json:"serviceOrchestrators"`
9595
ServiceAcl []config.Grant `json:"serviceAcl"`
96+
ServiceRoles []config.Role `json:"serviceRoles"`
9697
ServiceRepos []config.DockerRepo `json:"serviceRepos"`
9798
ServiceTarballs []config.Tarball `json:"serviceTarballs"`
9899
ServiceFS map[string]bool `json:"serviceFS"`
@@ -1755,6 +1756,7 @@ func (repman *ReplicationManager) Run() error {
17551756
repman.InitServicePlans()
17561757
repman.ServiceOrchestrators = repman.Conf.GetOrchestratorsProv()
17571758
repman.InitGrants()
1759+
repman.InitRoles()
17581760
repman.ServiceRepos, err = repman.Conf.GetDockerRepos(repman.Conf.ShareDir+"/repo/repos.json", repman.Conf.Test)
17591761
if err != nil {
17601762
repman.Logrus.WithError(err).Errorf("Initialization docker repo failed: %s %s", repman.Conf.ShareDir+"/repo/repos.json", err)
@@ -2292,10 +2294,14 @@ func (a GrantSorter) Len() int { return len(a) }
22922294
func (a GrantSorter) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
22932295
func (a GrantSorter) Less(i, j int) bool { return a[i].Grant < a[j].Grant }
22942296

2295-
func (repman *ReplicationManager) InitGrants() error {
2297+
type RoleSorter []config.Role
22962298

2297-
acls := []config.Grant{}
2299+
func (a RoleSorter) Len() int { return len(a) }
2300+
func (a RoleSorter) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
2301+
func (a RoleSorter) Less(i, j int) bool { return a[i].Role < a[j].Role }
22982302

2303+
func (repman *ReplicationManager) InitGrants() error {
2304+
acls := []config.Grant{}
22992305
for _, value := range repman.Conf.GetGrantType() {
23002306
var acl config.Grant
23012307
acl.Grant = value
@@ -2306,6 +2312,18 @@ func (repman *ReplicationManager) InitGrants() error {
23062312
return nil
23072313
}
23082314

2315+
func (repman *ReplicationManager) InitRoles() error {
2316+
roles := []config.Role{}
2317+
for _, value := range repman.Conf.GetRoleType() {
2318+
var acl config.Role
2319+
acl.Role = value
2320+
roles = append(roles, acl)
2321+
}
2322+
repman.ServiceRoles = roles
2323+
sort.Sort(RoleSorter(repman.ServiceRoles))
2324+
return nil
2325+
}
2326+
23092327
func IsDefault(p string, v *viper.Viper) bool {
23102328

23112329
return false

0 commit comments

Comments
 (0)