github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/pkg/lorry/engines/mysql/user.go (about) 1 /* 2 Copyright (C) 2022-2023 ApeCloud Co., Ltd 3 4 This file is part of KubeBlocks project 5 6 This program is free software: you can redistribute it and/or modify 7 it under the terms of the GNU Affero General Public License as published by 8 the Free Software Foundation, either version 3 of the License, or 9 (at your option) any later version. 10 11 This program is distributed in the hope that it will be useful 12 but WITHOUT ANY WARRANTY; without even the implied warranty of 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 GNU Affero General Public License for more details. 15 16 You should have received a copy of the GNU Affero General Public License 17 along with this program. If not, see <http://www.gnu.org/licenses/>. 18 */ 19 20 package mysql 21 22 import ( 23 "context" 24 "fmt" 25 "strings" 26 27 "golang.org/x/exp/slices" 28 29 "github.com/1aal/kubeblocks/pkg/lorry/engines/models" 30 ) 31 32 const ( 33 superUserPriv = "SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, RELOAD, SHUTDOWN, PROCESS, FILE, REFERENCES, INDEX, ALTER, SHOW DATABASES, SUPER, CREATE TEMPORARY TABLES, LOCK TABLES, EXECUTE, REPLICATION SLAVE, REPLICATION CLIENT, CREATE VIEW, SHOW VIEW, CREATE ROUTINE, ALTER ROUTINE, CREATE USER, EVENT, TRIGGER, CREATE TABLESPACE, CREATE ROLE, DROP ROLE ON *.*" 34 readWritePriv = "SELECT, INSERT, UPDATE, DELETE ON *.*" 35 readOnlyRPriv = "SELECT ON *.*" 36 noPriv = "USAGE ON *.*" 37 38 listUserSQL = "SELECT user AS userName, CASE password_expired WHEN 'N' THEN 'F' ELSE 'T' END as expired FROM mysql.user WHERE host = '%' and user <> 'root' and user not like 'kb%';" 39 showGrantSQL = "SHOW GRANTS FOR '%s'@'%%';" 40 getUserSQL = ` 41 SELECT user AS userName, CASE password_expired WHEN 'N' THEN 'F' ELSE 'T' END as expired 42 FROM mysql.user 43 WHERE host = '%%' and user <> 'root' and user not like 'kb%%' and user ='%s';" 44 ` 45 createUserSQL = "CREATE USER '%s'@'%%' IDENTIFIED BY '%s';" 46 deleteUserSQL = "DROP USER IF EXISTS '%s'@'%%';" 47 grantSQL = "GRANT %s TO '%s'@'%%';" 48 revokeSQL = "REVOKE %s FROM '%s'@'%%';" 49 listSystemAccountsSQL = "SELECT user AS userName FROM mysql.user WHERE host = '%' and user like 'kb%';" 50 ) 51 52 func (mgr *Manager) ListUsers(ctx context.Context) ([]models.UserInfo, error) { 53 users := []models.UserInfo{} 54 55 err := QueryRowsMap(mgr.DB, listUserSQL, func(rMap RowMap) error { 56 user := models.UserInfo{ 57 UserName: rMap.GetString("userName"), 58 Expired: rMap.GetString("expired"), 59 } 60 users = append(users, user) 61 return nil 62 }) 63 if err != nil { 64 mgr.Logger.Error(err, "error executing %s") 65 return nil, err 66 } 67 return users, nil 68 } 69 70 func (mgr *Manager) ListSystemAccounts(ctx context.Context) ([]models.UserInfo, error) { 71 users := []models.UserInfo{} 72 73 err := QueryRowsMap(mgr.DB, listSystemAccountsSQL, func(rMap RowMap) error { 74 user := models.UserInfo{ 75 UserName: rMap.GetString("userName"), 76 } 77 users = append(users, user) 78 return nil 79 }) 80 if err != nil { 81 mgr.Logger.Error(err, "error executing %s") 82 return nil, err 83 } 84 return users, nil 85 } 86 87 func (mgr *Manager) DescribeUser(ctx context.Context, userName string) (*models.UserInfo, error) { 88 user := &models.UserInfo{} 89 // only keep one role name of the highest privilege 90 userRoles := make([]models.RoleType, 0) 91 92 sql := fmt.Sprintf(showGrantSQL, userName) 93 94 err := QueryRowsMap(mgr.DB, sql, func(rMap RowMap) error { 95 for k, v := range rMap { 96 if user.UserName == "" { 97 user.UserName = strings.TrimPrefix(strings.TrimSuffix(k, "@%"), "Grants for ") 98 } 99 mysqlRoleType := priv2Role(strings.TrimPrefix(v.String, "GRANT ")) 100 userRoles = append(userRoles, mysqlRoleType) 101 } 102 103 return nil 104 }) 105 if err != nil { 106 mgr.Logger.Error(err, "execute sql failed", "sql", sql) 107 return nil, err 108 } 109 110 slices.SortFunc(userRoles, models.SortRoleByWeight) 111 if len(userRoles) > 0 { 112 user.RoleName = (string)(userRoles[0]) 113 } 114 return user, nil 115 } 116 117 func (mgr *Manager) CreateUser(ctx context.Context, userName, password string) error { 118 sql := fmt.Sprintf(createUserSQL, userName, password) 119 120 _, err := mgr.Exec(ctx, sql) 121 if err != nil { 122 mgr.Logger.Error(err, "execute sql failed", "sql", sql) 123 return err 124 } 125 126 return nil 127 } 128 129 func (mgr *Manager) DeleteUser(ctx context.Context, userName string) error { 130 sql := fmt.Sprintf(deleteUserSQL, userName) 131 132 _, err := mgr.Exec(ctx, sql) 133 if err != nil { 134 mgr.Logger.Error(err, "execute sql failed", "sql", sql) 135 return err 136 } 137 138 return nil 139 } 140 141 func (mgr *Manager) GrantUserRole(ctx context.Context, userName, roleName string) error { 142 // render sql stmts 143 roleDesc, _ := role2Priv(roleName) 144 // update privilege 145 sql := fmt.Sprintf(grantSQL, roleDesc, userName) 146 _, err := mgr.Exec(ctx, sql) 147 if err != nil { 148 mgr.Logger.Error(err, "execute sql failed", "sql", sql) 149 return err 150 } 151 152 return nil 153 } 154 155 func (mgr *Manager) RevokeUserRole(ctx context.Context, userName, roleName string) error { 156 // render sql stmts 157 roleDesc, _ := role2Priv(roleName) 158 // update privilege 159 sql := fmt.Sprintf(revokeSQL, roleDesc, userName) 160 _, err := mgr.Exec(ctx, sql) 161 if err != nil { 162 mgr.Logger.Error(err, "execute sql failed", "sql", sql) 163 return err 164 } 165 166 return nil 167 } 168 169 func role2Priv(roleName string) (string, error) { 170 roleType := models.String2RoleType(roleName) 171 switch roleType { 172 case models.SuperUserRole: 173 return superUserPriv, nil 174 case models.ReadWriteRole: 175 return readWritePriv, nil 176 case models.ReadOnlyRole: 177 return readOnlyRPriv, nil 178 } 179 return "", fmt.Errorf("role name: %s is not supported", roleName) 180 } 181 182 func priv2Role(priv string) models.RoleType { 183 if strings.HasPrefix(priv, readOnlyRPriv) { 184 return models.ReadOnlyRole 185 } 186 if strings.HasPrefix(priv, readWritePriv) { 187 return models.ReadWriteRole 188 } 189 if strings.HasPrefix(priv, superUserPriv) { 190 return models.SuperUserRole 191 } 192 if strings.HasPrefix(priv, noPriv) { 193 return models.NoPrivileges 194 } 195 return models.CustomizedRole 196 }