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  }