From a52744c937adb1269f8565573306873270f4e95a Mon Sep 17 00:00:00 2001 From: xfwduke Date: Tue, 17 Dec 2024 10:35:50 +0800 Subject: [PATCH] =?UTF-8?q?feat(mysql):=20mysql=E6=9D=83=E9=99=90=E7=94=B3?= =?UTF-8?q?=E8=AF=B7=E4=BC=98=E5=8C=96=20#8650?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../db-priv/service/add_priv_base_func.go | 20 +- .../service/v2/add_priv/add_on_mysql.go | 20 +- .../db-priv/service/v2/add_priv/add_priv.go | 15 +- .../v2/add_priv/add_proxy_white_list.go | 110 +++--- .../v2/add_priv/fetch_target_dbmeta_info.go | 1 + .../db-priv/service/v2/add_priv/prepare.go | 13 +- .../db-priv/service/v2/add_priv/procedure.sql | 88 ++--- .../db-priv/service/v2/internal/tools.go | 6 +- .../db-priv/service/v2/query_priv/get_priv.go | 195 ----------- .../service/v2/query_priv/get_user_list.go | 175 ---------- .../db-priv/service/v2/query_priv/init.go | 94 ------ .../db-priv/service/v2/query_priv/validate.go | 17 - .../core/staticembed/default_sys_schema.sql | 313 +++++++++++++++++- .../scene/mysql/common/common_sub_flow.py | 2 +- ...orize_rule_v2.py => authorize_rules_v2.py} | 0 .../mysql/permission/test_authorize_rules.py | 4 +- .../builders/mysql/mysql_authorize_rules.py | 3 +- items-config.sql | 0 18 files changed, 478 insertions(+), 598 deletions(-) delete mode 100644 dbm-services/mysql/db-priv/service/v2/query_priv/get_priv.go delete mode 100644 dbm-services/mysql/db-priv/service/v2/query_priv/get_user_list.go delete mode 100644 dbm-services/mysql/db-priv/service/v2/query_priv/init.go delete mode 100644 dbm-services/mysql/db-priv/service/v2/query_priv/validate.go rename dbm-ui/backend/flow/plugins/components/collections/mysql/{authorize_rule_v2.py => authorize_rules_v2.py} (100%) delete mode 100644 items-config.sql diff --git a/dbm-services/mysql/db-priv/service/add_priv_base_func.go b/dbm-services/mysql/db-priv/service/add_priv_base_func.go index 3d1b256cbb..748f1397d2 100644 --- a/dbm-services/mysql/db-priv/service/add_priv_base_func.go +++ b/dbm-services/mysql/db-priv/service/add_priv_base_func.go @@ -207,8 +207,14 @@ func GenerateBackendSQL(account TbAccounts, rule TbAccountRules, ips []string, m } // 备库域名只授予查询类权限 if clusterType == tendbha && tendbhaMasterDomain == false { - sql = fmt.Sprintf("GRANT SELECT, SHOW VIEW ON `%s`.* TO '%s'@'%s' %s;", - rule.Dbname, account.User, ip, identifiedByPassword) + if rule.Dbname == "%" || rule.Dbname == "*" { + sql = fmt.Sprintf("GRANT SELECT, SHOW VIEW ON *.* TO '%s'@'%s' %s;", + account.User, ip, identifiedByPassword) + } else { + sql = fmt.Sprintf("GRANT SELECT, SHOW VIEW ON `%s`.* TO '%s'@'%s' %s;", + rule.Dbname, account.User, ip, identifiedByPassword) + } + sqlTemp = append(sqlTemp, sql) if containConnLogDBFlag { sql = fmt.Sprintf("%s '%s'@'%s' %s;", insertConnLogPriv, account.User, ip, identifiedByPassword) @@ -229,8 +235,14 @@ func GenerateBackendSQL(account TbAccounts, rule TbAccountRules, ips []string, m } if rule.DmlDdlPriv != "" { - sql = fmt.Sprintf("GRANT %s ON `%s`.* TO '%s'@'%s' %s;", - rule.DmlDdlPriv, rule.Dbname, account.User, ip, identifiedByPassword) + if rule.Dbname == "%" || rule.Dbname == "*" { + sql = fmt.Sprintf("GRANT %s ON *.* TO '%s'@'%s' %s;", + rule.DmlDdlPriv, account.User, ip, identifiedByPassword) + } else { + sql = fmt.Sprintf("GRANT %s ON `%s`.* TO '%s'@'%s' %s;", + rule.DmlDdlPriv, rule.Dbname, account.User, ip, identifiedByPassword) + } + sqlTemp = append(sqlTemp, sql) if needInsertConnLogFlag { sql = fmt.Sprintf("%s '%s'@'%s' %s;", insertConnLogPriv, account.User, ip, identifiedByPassword) diff --git a/dbm-services/mysql/db-priv/service/v2/add_priv/add_on_mysql.go b/dbm-services/mysql/db-priv/service/v2/add_priv/add_on_mysql.go index fccb844c1d..6368c65945 100644 --- a/dbm-services/mysql/db-priv/service/v2/add_priv/add_on_mysql.go +++ b/dbm-services/mysql/db-priv/service/v2/add_priv/add_on_mysql.go @@ -64,14 +64,15 @@ func (c *PrivTaskPara) addOneDtOnMySQL( dt *service.TbAccountRules, reports map[string][]string, ) error { - var ipStr string + var oneBatchClients []string for idx, ip := range clientIps { - // 限长 1500 代码会比较好些, 不往极限的 2000 搞 - ipStr = ipStr + "," + ip - if len(ipStr) > 1500 || idx == len(clientIps)-1 { - slog.Info("add one dt on mysql", slog.String("ipstr", ipStr)) + // 限长 100 代码会比较好些, 不往极限的 2000 搞 + oneBatchClients = append(oneBatchClients, ip) + if len(oneBatchClients) > 100 || idx == len(clientIps)-1 { + slog.Info("add one dt on mysql", slog.Any("one batch client", oneBatchClients)) + // 一次跑一批 client err := c.addOneDtOnMySQLForSplitClient( - strings.Trim(ipStr, ","), + strings.Join(oneBatchClients, ","), workingInstances, accountAndRuleDetails, psw, @@ -82,7 +83,7 @@ func (c *PrivTaskPara) addOneDtOnMySQL( slog.Error("add on mysql", slog.String("err", err.Error())) return err } - ipStr = "" + oneBatchClients = []string{} } } return nil @@ -152,6 +153,7 @@ func readOneDtRes(bkCloudId int64, res []*drs.OneAddressResult, reports map[stri slog.String("addr", r.Address), ) reports[r.Address] = []string{r.ErrorMsg} + continue } readOneAddrRes(bkCloudId, r, reports) } @@ -163,6 +165,10 @@ func readOneAddrRes(bkCloudId int64, r *drs.OneAddressResult, reports map[string return } + if _, ok := reports[r.Address]; !ok { + reports[r.Address] = make([]string, 0) + } + _, sqlStat, msgText, isException := internal.ParseMySQLErrStr(errMsg) if !isException { reports[r.Address] = append(reports[r.Address], msgText) diff --git a/dbm-services/mysql/db-priv/service/v2/add_priv/add_priv.go b/dbm-services/mysql/db-priv/service/v2/add_priv/add_priv.go index 26e0a547d8..38b6424182 100644 --- a/dbm-services/mysql/db-priv/service/v2/add_priv/add_priv.go +++ b/dbm-services/mysql/db-priv/service/v2/add_priv/add_priv.go @@ -6,6 +6,7 @@ import ( "dbm-services/mysql/priv-service/service/v2/internal" "encoding/json" "log/slog" + "strings" "time" "github.com/pkg/errors" @@ -17,7 +18,9 @@ func (c *PrivTaskPara) AddPriv(jsonPara, ticket string) (err error) { slog.String("jsonPara", jsonPara), ) - if c.ClusterType == internal.ClusterTypeSqlServerHA || c.ClusterType == internal.ClusterTypeSqlServer || c.ClusterType == internal.ClusterTypeSqlServerSingle { + if c.ClusterType == internal.ClusterTypeSqlServerHA || + c.ClusterType == internal.ClusterTypeSqlServer || + c.ClusterType == internal.ClusterTypeSqlServerSingle { return c.AddPrivForSqlserver(jsonPara) } @@ -39,6 +42,8 @@ func (c *PrivTaskPara) AddPriv(jsonPara, ticket string) (err error) { // targetInstance 传入的其实全是域名 c.TargetInstances = internal.UniqueStringSlice(c.TargetInstances) + slog.Info("add priv", slog.String("source ips", strings.Join(c.SourceIPs, ","))) + // 写审计日志 service.AddPrivLog( service.PrivLog{ @@ -56,6 +61,7 @@ func (c *PrivTaskPara) AddPriv(jsonPara, ticket string) (err error) { slog.Error("add priv", slog.String("err", err.Error())) return err } + slog.Info("add priv", slog.Any("target meta infos", targetMetaInfos)) /* TenDBSingle 授权是在存储实例操作 @@ -64,6 +70,7 @@ func (c *PrivTaskPara) AddPriv(jsonPara, ticket string) (err error) { */ // 开白名单 + // proxy 白名单是前置集中开, 所有出错了直接返回 if c.ClusterType == internal.ClusterTypeTenDBHA { err = c.addWhiteList(targetMetaInfos) if err != nil { @@ -80,7 +87,7 @@ func (c *PrivTaskPara) AddPriv(jsonPara, ticket string) (err error) { clientIps, workingMySQLInstances := c.prepareMySQLPayload(targetMetaInfos) slog.Info( "add priv", - slog.Any("clientIps", clientIps), + slog.String("clientIps", strings.Join(clientIps, ",")), slog.Any("workingMySQLInstances", workingMySQLInstances), ) @@ -96,6 +103,8 @@ func (c *PrivTaskPara) AddPriv(jsonPara, ticket string) (err error) { slog.String("accountAndRuleDetails", accountAndRuleDetails.String()), ) + // err 是调用函数出错, 直接报错返回 + // reports 是实施授权的报告 reports, err := c.addOnMySQL(clientIps, workingMySQLInstances, accountAndRuleDetails) if err != nil { slog.Error("add priv", slog.String("err", err.Error())) @@ -111,6 +120,6 @@ func (c *PrivTaskPara) AddPriv(jsonPara, ticket string) (err error) { return errors.New(string(b)) } - slog.Info("add priv finish", slog.Any("reports", reports)) + slog.Info("add priv finish") return nil } diff --git a/dbm-services/mysql/db-priv/service/v2/add_priv/add_proxy_white_list.go b/dbm-services/mysql/db-priv/service/v2/add_priv/add_proxy_white_list.go index 08a5d8e59b..93f43f07ec 100644 --- a/dbm-services/mysql/db-priv/service/v2/add_priv/add_proxy_white_list.go +++ b/dbm-services/mysql/db-priv/service/v2/add_priv/add_proxy_white_list.go @@ -33,71 +33,54 @@ func (c *PrivTaskPara) addWhiteList(targetMetaInfos []*service.Instance) (err er slog.Any("proxies", workingProxies), ) - // 确实有要操作的 proxy - if len(workingProxies) > 0 { - var cmds []string - for _, clientIp := range c.SourceIPs { - //clientIp 可能是 localhost, 要忽略 - if clientIp == "localhost" { - continue - } - cmds = append( - cmds, - fmt.Sprintf(`refresh_users('%s@%s', '+')`, - c.User, clientIp, - )) - } + if len(workingProxies) <= 0 { + return nil + } + + cmds := generateProxyCmds(c.SourceIPs, c.User) + slog.Info( + "add proxy white list", + slog.Any("cmds", cmds), + ) + + // drs 执行多个 sql 是循环一个一个来的 + // 所以批量发送是可以的, 只是这么搞 drs 负载估计要炸 + // 这里搞并发的意义不大 + var errCollect error + for bkCloudId, addresses := range workingProxies { slog.Info( "add proxy white list", - slog.Any("cmds", cmds), + slog.Int64("bk_cloud_id", bkCloudId), + slog.Any("addresses", addresses), + ) + drsRes, err := drs.RPCProxyAdmin( + bkCloudId, + addresses, + cmds, + false, + 0, ) + if err != nil { + slog.Error("add proxy white list", slog.String("err", err.Error())) + return err + } - // drs 执行多个 sql 是循环一个一个来的 - // 所以批量发送是可以的, 只是这么搞 drs 负载估计要炸 - // 这里搞并发的意义不大 - var errCollect error - for bkCloudId, addresses := range workingProxies { + // 错误要收集起来 + ec := collectErrors(drsRes) + if ec != nil { + slog.Error("add proxy white list", slog.String("err collection", ec.Error())) + errCollect = errors.Join(errCollect, ec) + } else { slog.Info( - "add proxy white list", + "add proxy white list success", slog.Int64("bk_cloud_id", bkCloudId), - slog.Any("addresses", addresses), ) - drsRes, err := drs.RPCProxyAdmin( - bkCloudId, - addresses, - cmds, - false, - 0, - ) - if err != nil { - slog.Error( - "add proxy white list", - slog.Int64("bk_cloud_id", bkCloudId), - slog.String("err", err.Error()), - ) - return err - } - - // 错误要收集起来 - ec := collectErrors(drsRes) - if ec != nil { - slog.Error( - "add proxy white list", - slog.Int64("bk_cloud_id", bkCloudId), - slog.String("err", ec.Error()), - ) - errCollect = errors.Join(errCollect, ec) - } else { - slog.Info( - "add proxy white list success", - slog.Int64("bk_cloud_id", bkCloudId), - ) - } - } - if errCollect != nil { - return errCollect } } + if errCollect != nil { + return errCollect + } + return nil } @@ -120,3 +103,18 @@ func collectErrors(res []*drs.OneAddressResult) (ec error) { } return ec } + +func generateProxyCmds(clientIps []string, username string) (cmds []string) { + for _, clientIp := range clientIps { + //clientIp 可能是 localhost, 要忽略 + if clientIp == "localhost" { + continue + } + cmds = append( + cmds, + fmt.Sprintf(`refresh_users('%s@%s', '+')`, + username, clientIp, + )) + } + return cmds +} diff --git a/dbm-services/mysql/db-priv/service/v2/add_priv/fetch_target_dbmeta_info.go b/dbm-services/mysql/db-priv/service/v2/add_priv/fetch_target_dbmeta_info.go index 8a4fbbceae..6cdacd6615 100644 --- a/dbm-services/mysql/db-priv/service/v2/add_priv/fetch_target_dbmeta_info.go +++ b/dbm-services/mysql/db-priv/service/v2/add_priv/fetch_target_dbmeta_info.go @@ -37,6 +37,7 @@ func (c *PrivTaskPara) fetchTargetDBMetaInfo() ([]*service.Instance, error) { ) return nil, err } + slog.Info("fetch target db meta info", slog.Any("result", result)) res := make([]*service.Instance, 0) err = json.Unmarshal(result.Data, &res) diff --git a/dbm-services/mysql/db-priv/service/v2/add_priv/prepare.go b/dbm-services/mysql/db-priv/service/v2/add_priv/prepare.go index e04c0fba41..7f81688b7f 100644 --- a/dbm-services/mysql/db-priv/service/v2/add_priv/prepare.go +++ b/dbm-services/mysql/db-priv/service/v2/add_priv/prepare.go @@ -5,6 +5,7 @@ import ( "dbm-services/mysql/priv-service/service/v2/internal" "fmt" "log/slog" + "strings" ) func (c *PrivTaskPara) prepareMySQLPayload(targetMetaInfos []*service.Instance) ( @@ -27,7 +28,7 @@ func (c *PrivTaskPara) prepareMySQLPayload(targetMetaInfos []*service.Instance) func (c *PrivTaskPara) prepareTenDBSingle(targetMetaInfos []*service.Instance) ( clientIps []string, workingMySQLInstances map[int64][]string) { - clientIps = make([]string, 0) + clientIps = c.SourceIPs workingMySQLInstances = make(map[int64][]string) for _, ele := range targetMetaInfos { @@ -58,13 +59,21 @@ func (c *PrivTaskPara) prepareTenDBHA(targetMetaInfos []*service.Instance) ( // 申请主域名权限要把来源替换为 proxy ip // 如果集群有 padding proxy 属性, 则是把 proxy ip 追加到 client ip 里 + slog.Info( + "prepare tendbha", + slog.String("bind to", ele.BindTo), + slog.Bool("padding proxy", ele.PaddingProxy), + ) if ele.BindTo == internal.MachineTypeProxy { if ele.PaddingProxy { clientIps = append(clientIps, proxyIps...) } else { clientIps = proxyIps } + } else { + clientIps = c.SourceIPs } + slog.Info("prepare tendbha", slog.String("clientIps", strings.Join(clientIps, ","))) // TenDBHA 要在所有存储实例执行授权 for _, s := range ele.Storages { if _, ok := workingMySQLInstances[ele.BkCloudId]; !ok { @@ -81,7 +90,7 @@ func (c *PrivTaskPara) prepareTenDBHA(targetMetaInfos []*service.Instance) ( } func (c *PrivTaskPara) prepareTenDBCluster(targetMetaInfos []*service.Instance) (clientIps []string, workingMySQLInstances map[int64][]string) { - clientIps = make([]string, 0) + clientIps = c.SourceIPs workingMySQLInstances = make(map[int64][]string) // 对应的 spider 上执行授权 diff --git a/dbm-services/mysql/db-priv/service/v2/add_priv/procedure.sql b/dbm-services/mysql/db-priv/service/v2/add_priv/procedure.sql index a84deeb561..909e695fd6 100644 --- a/dbm-services/mysql/db-priv/service/v2/add_priv/procedure.sql +++ b/dbm-services/mysql/db-priv/service/v2/add_priv/procedure.sql @@ -1,11 +1,12 @@ +SET SESSION sql_log_bin = 0; -- 授权, 检查 ERROR_MSG 来判断是否成功 -- ERROR STATE CODE -- 32401 参数验证错误 -- 32402 冲突检测错误 --- ToDo 中控兼容 -DROP PROCEDURE IF EXISTS dba_grant; +-- 中控不会转发授权语句, 当普通 MySQL 就好 +DROP PROCEDURE IF EXISTS infodba_schema.dba_grant; DELIMITER // -CREATE PROCEDURE dba_grant( +CREATE PROCEDURE infodba_schema.dba_grant( IN username VARCHAR(128), IN ip_list VARCHAR(3000), -- 但是限制最大只能传入 2000 IN db_list VARCHAR(3000), -- 但是限制最大只能传入 2000 @@ -25,7 +26,9 @@ BEGIN SIGNAL SQLSTATE '32401' SET MESSAGE_TEXT = @msg; END IF; - SET SESSION binlog_format = 'STATEMENT'; + -- 不同版本授权语句不兼容, 所以干脆不写 + SET SESSION sql_log_bin = 0; + -- 初始化结果表 CALL init_report_table(); @@ -41,7 +44,7 @@ BEGIN -- 先做检查 SET @is_check_failed = 0; - CALL check_all(@uuid, @@bind_address, @grant_time, username, ip_list, db_list, long_psw, short_psw, @is_check_failed); + CALL check_all(@uuid, @grant_time, username, ip_list, db_list, long_psw, short_psw, @is_check_failed); IF @is_check_failed = 1 THEN SIGNAL SQLSTATE '32402' SET MESSAGE_TEXT = @uuid; @@ -54,18 +57,22 @@ BEGIN -- 如果涉及新增账号, 只使用新版本密码 CALL dba_grant_one_ip(username, @ip, db_list, long_psw, priv_str, global_priv_str); END WHILE; + + FLUSH PRIVILEGES; END// DELIMITER ; -DROP PROCEDURE IF EXISTS init_report_table; +DROP PROCEDURE IF EXISTS infodba_schema.init_report_table; +DROP TABLE IF EXISTS infodba_schema.dba_grant_result; DELIMITER // -CREATE PROCEDURE init_report_table() +CREATE PROCEDURE infodba_schema.init_report_table() SQL SECURITY INVOKER BEGIN - SET SESSION binlog_format = 'STATEMENT'; - CREATE TABLE IF NOT EXISTS dba_grant_result( + -- 不同版本授权语句不兼容, 所以干脆不写 + SET SESSION sql_log_bin = 0; + + CREATE TABLE IF NOT EXISTS infodba_schema.dba_grant_result( id VARCHAR(64), - db_ip VARCHAR(32), grant_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, username VARCHAR(128), client_ip VARCHAR(32), @@ -75,17 +82,16 @@ BEGIN priv VARCHAR(4096), global_priv VARCHAR(4096), msg VARCHAR(4096) - ); + ) ENGINE = InnoDB; DELETE FROM dba_grant_result WHERE grant_time < DATE_SUB(NOW(), INTERVAL 1 DAY); END// DELIMITER ; -- 检查入口 -DROP PROCEDURE IF EXISTS check_all; +DROP PROCEDURE IF EXISTS infodba_schema.check_all; DELIMITER // -CREATE PROCEDURE check_all( +CREATE PROCEDURE infodba_schema.check_all( IN uuid VARCHAR(64), - IN db_ip VARCHAR(32), IN grant_time TIMESTAMP, IN username VARCHAR(128), IN ip_list VARCHAR(3000), @@ -97,17 +103,16 @@ CREATE PROCEDURE check_all( SQL SECURITY INVOKER BEGIN -- 全量检查入口 - CALL check_password(uuid, db_ip, grant_time, username, ip_list, long_psw, short_psw, is_check_failed); - CALL check_db_conflict(uuid, db_ip, grant_time, username, ip_list, db_list, is_check_failed); + CALL check_password(uuid, grant_time, username, ip_list, long_psw, short_psw, is_check_failed); + CALL check_db_conflict(uuid, grant_time, username, ip_list, db_list, is_check_failed); END // DELIMITER ; -- 密码一致性检查 -DROP PROCEDURE IF EXISTS check_password; +DROP PROCEDURE IF EXISTS infodba_schema.check_password; DELIMITER // -CREATE PROCEDURE check_password( +CREATE PROCEDURE infodba_schema.check_password( IN uuid VARCHAR(64), - IN db_ip VARCHAR(32), IN grant_time TIMESTAMP, IN username VARCHAR(128), IN ip_list VARCHAR(3000), @@ -117,7 +122,9 @@ CREATE PROCEDURE check_password( ) SQL SECURITY INVOKER BEGIN - SET SESSION binlog_format = 'STATEMENT'; + -- 不同版本授权语句不兼容, 所以干脆不写 + SET SESSION sql_log_bin = 0; + WHILE (LOCATE(',', ip_list) > 0) DO SET @ip = TRIM(SUBSTRING(ip_list, 1, LOCATE(',', ip_list) - 1)); @@ -135,8 +142,8 @@ BEGIN IF NOT @psw_match THEN SET is_check_failed = is_check_failed OR 1; - INSERT INTO dba_grant_result(id, db_ip, grant_time, username, client_ip, long_psw, short_psw, msg) - VALUES (uuid, db_ip, grant_time, username, @ip, long_psw, short_psw, 'password not match'); + INSERT INTO dba_grant_result(id, grant_time, username, client_ip, long_psw, short_psw, msg) + VALUES (uuid, grant_time, username, @ip, long_psw, short_psw, 'password not match'); END IF; END IF; END WHILE; @@ -144,11 +151,10 @@ END // DELIMITER ; -- 库模式冲突检查入口 -DROP PROCEDURE IF EXISTS check_db_conflict; +DROP PROCEDURE IF EXISTS infodba_schema.check_db_conflict; DELIMITER // -CREATE PROCEDURE check_db_conflict( +CREATE PROCEDURE infodba_schema.check_db_conflict( IN uuid VARCHAR(64), - IN db_ip VARCHAR(32), IN grant_time TIMESTAMP, IN username VARCHAR(128), IN ip_list VARCHAR(3000), @@ -164,18 +170,17 @@ BEGIN SELECT EXISTS(SELECT 1 FROM mysql.db WHERE user = username AND host = @ip) INTO @db_priv_applied; IF @db_priv_applied THEN - CALL check_db_conflict_one_ip(uuid, db_ip, grant_time, username, @ip, db_list, is_check_failed); + CALL check_db_conflict_one_ip(uuid, grant_time, username, @ip, db_list, is_check_failed); END IF; END WHILE; END // DELIMITER ; -- 单 IP 库模式冲突检查 -DROP PROCEDURE IF EXISTS check_db_conflict_one_ip; +DROP PROCEDURE IF EXISTS infodba_schema.check_db_conflict_one_ip; DELIMITER // -CREATE PROCEDURE check_db_conflict_one_ip( +CREATE PROCEDURE infodba_schema.check_db_conflict_one_ip( IN uuid VARCHAR(64), - IN db_ip VARCHAR(32), IN grant_time TIMESTAMP, IN username VARCHAR(128), IN ip VARCHAR(15), @@ -189,7 +194,8 @@ BEGIN DECLARE db_cursor CURSOR FOR SELECT db FROM mysql.db WHERE user = username AND host = @ip; DECLARE CONTINUE HANDLER FOR NOT FOUND SET cursor_done = TRUE; - SET SESSION binlog_format = 'STATEMENT'; + -- 不同版本授权语句不兼容, 所以干脆不写 + SET SESSION sql_log_bin = 0; OPEN db_cursor; fetch_loop: LOOP @@ -208,8 +214,8 @@ BEGIN -- 申请库名和已有库名不等并且能模式匹配 IF @dbname <> applied_dbname AND (@dbname LIKE applied_dbname OR applied_dbname LIKE @dbname) THEN SET is_check_failed = is_check_failed OR 1; - INSERT INTO dba_grant_result(id, db_ip, grant_time, username, client_ip, dbname, long_psw, short_psw, msg) - VALUES (uuid, db_ip, grant_time, username, @ip, @dbname, long_psw, short_psw, CONCAT("conflict with applied db [", applied_dbname, "]")); + INSERT INTO dba_grant_result(id, grant_time, username, client_ip, dbname, long_psw, short_psw, msg) + VALUES (uuid, grant_time, username, @ip, @dbname, long_psw, short_psw, CONCAT("conflict with applied db [", applied_dbname, "]")); END IF; END WHILE; @@ -219,9 +225,9 @@ END // DELIMITER ; -- 单 IP 授权 -DROP PROCEDURE IF EXISTS dba_grant_one_ip; +DROP PROCEDURE IF EXISTS infodba_schema.dba_grant_one_ip; DELIMITER // -CREATE PROCEDURE dba_grant_one_ip( +CREATE PROCEDURE infodba_schema.dba_grant_one_ip( IN username VARCHAR(128), IN ip VARCHAR(15), IN db_list VARCHAR(3000), @@ -231,6 +237,9 @@ CREATE PROCEDURE dba_grant_one_ip( ) SQL SECURITY INVOKER BEGIN + -- 不同版本授权语句不兼容, 所以干脆不写 + SET SESSION sql_log_bin = 0; + SELECT EXISTS(SELECT 1 FROM mysql.user WHERE user = username AND host = ip) INTO @user_host_exists; IF NOT @user_host_exists THEN -- 用户不存在, 直接创建, 只使用新版本密码, 传入的密码是密文 @@ -239,7 +248,7 @@ BEGIN PREPARE stmt FROM @create_user_sql; EXECUTE stmt; ELSE - SET @create_user_sql = CONCAT("CREATE USER IF NOT EXISTS'", username, "'@'", ip, "' IDENTIFIED WITH mysql_native_password AS '", psw, "'"); + SET @create_user_sql = CONCAT("CREATE USER IF NOT EXISTS '", username, "'@'", ip, "' IDENTIFIED WITH mysql_native_password AS '", psw, "'"); PREPARE stmt FROM @create_user_sql; EXECUTE stmt; END IF; @@ -255,9 +264,9 @@ END// DELIMITER ; -- 单 IP 单 DB 授权 -DROP PROCEDURE IF EXISTS dba_grant_one_ip_db; +DROP PROCEDURE IF EXISTS infodba_schema.dba_grant_one_ip_db; DELIMITER // -CREATE PROCEDURE dba_grant_one_ip_db( +CREATE PROCEDURE infodba_schema.dba_grant_one_ip_db( IN username VARCHAR(128), IN ip VARCHAR(15), IN dbname VARCHAR(64), @@ -272,6 +281,9 @@ BEGIN DECLARE db_cursor CURSOR FOR SELECT db FROM mysql.db WHERE user = username AND host = ip; DECLARE CONTINUE HANDLER FOR NOT FOUND SET cursor_done = TRUE; + -- 不同版本授权语句不兼容, 所以干脆不写 + SET SESSION sql_log_bin = 0; + SET priv_str = TRIM(BOTH ',' FROM TRIM(priv_str)); SET global_priv_str = TRIM(BOTH ',' FROM TRIM(global_priv_str)); @@ -295,5 +307,3 @@ BEGIN END IF; END// DELIMITER ; - - diff --git a/dbm-services/mysql/db-priv/service/v2/internal/tools.go b/dbm-services/mysql/db-priv/service/v2/internal/tools.go index 640ce444a3..63e2597088 100644 --- a/dbm-services/mysql/db-priv/service/v2/internal/tools.go +++ b/dbm-services/mysql/db-priv/service/v2/internal/tools.go @@ -3,6 +3,7 @@ package internal import ( "regexp" "strconv" + "strings" "golang.org/x/exp/maps" ) @@ -10,7 +11,10 @@ import ( func UniqueStringSlice(s []string) []string { t := make(map[string]int) for _, v := range s { - t[v] = 1 + vv := strings.TrimSpace(v) + if vv != "" { + t[vv] = 1 + } } return maps.Keys(t) } diff --git a/dbm-services/mysql/db-priv/service/v2/query_priv/get_priv.go b/dbm-services/mysql/db-priv/service/v2/query_priv/get_priv.go deleted file mode 100644 index b9e3804b9a..0000000000 --- a/dbm-services/mysql/db-priv/service/v2/query_priv/get_priv.go +++ /dev/null @@ -1,195 +0,0 @@ -// Package query_priv TODO -package query_priv - -import ( - "fmt" - "slices" - "strings" - - "dbm-services/mysql/priv-service/service" - v2 "dbm-services/mysql/priv-service/service/v2/internal" -) - -// GetPriv TODO -// func (m *GetPrivPara) GetPriv() error { -// err := m.validate() -// if err != nil { -// return err -// } - -// if len(m.Users) == 0 { -// return errno.ErrUserIsEmpty -// } -// userStr := strings.Join(m.Users, `','`) - -// var errChan = make(chan error) -// var retChan = make(chan []string) - -// go func() { -// wg := sync.WaitGroup{} - -// for _, item := range m.ImmuteDomains { -// wg.Add(1) - -// err := m.limiter.Wait(context.Background()) -// if err != nil { -// errChan <- err -// wg.Done() -// return -// } - -// go func(item string) { -// defer func() { -// wg.Done() -// }() - -// domainInfo, err := service.GetCluster(*m.ClusterType, service.Domain{EntryName: item}) -// if err != nil { -// errChan <- err -// return -// } - -// switch domainInfo.ClusterType { -// case v2.ClusterTypeTenDBSingle: -// privsFromTenDBSingle(&domainInfo, m.Users, m.Ips, m.Dbs) -// case v2.ClusterTypeTenDBHA: -// privsFromTenDBHA(&domainInfo, m.Users, m.Ips, m.Dbs) -// case v2.ClusterTypeTenDBCluster: -// default: -// errChan <- fmt.Errorf("unknown cluster type: %s", domainInfo.ClusterType) -// return -// } -// }(item) -// } -// }() -// } - -func privsFromTenDBSingle(domainInfo *service.Instance, users, clientIps, dbs []string) ([]service.GrantInfo, error) { - backendAddr := fmt.Sprintf("%s:%d", domainInfo.Storages[0].IP, domainInfo.Storages[0].Port) - - userList, _, matchHosts, err := service.MysqlUserList( - backendAddr, - domainInfo.BkCloudId, - clientIps, - users, - domainInfo.ImmuteDomain, - ) - if err != nil { - return nil, err - } - if len(matchHosts) == 0 { - return nil, nil - } - - userGrants, err := service.GetRemotePrivilege( - backendAddr, - matchHosts, - domainInfo.BkCloudId, - v2.MachineTypeBackend, - strings.Join(users, `','`), - true, - ) - if err != nil { - return nil, err - } - if len(userGrants) == 0 { - return nil, nil - } - - splitUserGrants := service.SplitGrantSql(userGrants, dbs, false) - return service.CombineUserWithGrant(userList, splitUserGrants, false), nil -} - -func privsFromTenDBHA(domainInfo *service.Instance, users, clientIps, dbs []string) ([]service.GrantInfo, error) { - if domainInfo.EntryRole == v2.EntryRoleMasterEntry && !domainInfo.PaddingProxy { - return privsFromTenDBHAMaster(domainInfo, users, clientIps, dbs) - } else { - return privsFromTenDBHASlave(domainInfo, users, clientIps, dbs) - } -} - -func privsFromTenDBHAMaster(domainInfo *service.Instance, users, clientIps, dbs []string) ([]service.GrantInfo, error) { - idx := slices.IndexFunc(domainInfo.Storages, func(s service.Storage) bool { - return s.InstanceRole == v2.InstanceRoleBackendMaster - }) - backendAddr := fmt.Sprintf("%s:%d", domainInfo.Storages[idx].IP, domainInfo.Storages[idx].Port) - - userGrants, err := service.GetRemotePrivilege( - backendAddr, - domainInfo.Proxies[0].IP, - domainInfo.BkCloudId, - v2.MachineTypeBackend, - strings.Join(users, `','`), - true, - ) - if err != nil { - return nil, err - } - - splitUserGrants := service.SplitGrantSql(userGrants, dbs, true) - - proxyAddr := fmt.Sprintf("%s:%d", domainInfo.Proxies[0].IP, domainInfo.Proxies[0].Port) - whiteList, _, _, err := service.ProxyWhiteList( - proxyAddr, - domainInfo.BkCloudId, - clientIps, - users, - domainInfo.ImmuteDomain, - ) - if err != nil { - return nil, err - } - - return service.CombineUserWithGrant(whiteList, splitUserGrants, true), nil -} - -func privsFromTenDBHASlave(domainInfo *service.Instance, users []string, clientIps []string, - dbs []string) ([]service.GrantInfo, error) { - idx := slices.IndexFunc(domainInfo.Storages, func(s service.Storage) bool { - // ToDo 这里其实有点问题, 如果有多个 slave, 应该返回哪一个? - return s.InstanceRole == v2.InstanceRoleBackendSlave - }) - backendAddr := fmt.Sprintf("%s:%d", domainInfo.Storages[idx].IP, domainInfo.Storages[idx].Port) - - userList, _, matchHosts, err := service.MysqlUserList( - backendAddr, - domainInfo.BkCloudId, - clientIps, - users, - domainInfo.ImmuteDomain, - ) - if err != nil { - return nil, err - } - - if len(matchHosts) == 0 { - return nil, nil - } - - userGrants, err := service.GetRemotePrivilege( - backendAddr, - matchHosts, - domainInfo.BkCloudId, - v2.MachineTypeBackend, - strings.Join(users, `','`), - true, - ) - if err != nil { - return nil, err - } - if len(userGrants) == 0 { - return nil, nil - } - - splitUserGrants := service.SplitGrantSql(userGrants, dbs, false) - return service.CombineUserWithGrant(userList, splitUserGrants, false), nil -} - -func privsFromTenDBCluster() { - -} - -// func privsFromMySQL(addr string, isMasterDomain bool, domainInfo *service.Instance, users, clientIps, -// dbs []string) ([]service.GrantInfo, error) { - -// } diff --git a/dbm-services/mysql/db-priv/service/v2/query_priv/get_user_list.go b/dbm-services/mysql/db-priv/service/v2/query_priv/get_user_list.go deleted file mode 100644 index e5b1babc67..0000000000 --- a/dbm-services/mysql/db-priv/service/v2/query_priv/get_user_list.go +++ /dev/null @@ -1,175 +0,0 @@ -package query_priv - -import ( - "context" - "dbm-services/common/go-pubpkg/errno" - "dbm-services/mysql/priv-service/service" - v2 "dbm-services/mysql/priv-service/service/v2/internal" - "fmt" - "slices" - "strings" - "sync" -) - -func (m *GetPrivPara) GetUserList() ([]string, int, error) { - err := m.validate() - if err != nil { - return nil, 0, err - } - - var errChan = make(chan error) - var retChan = make(chan []string) - - go func() { - wg := sync.WaitGroup{} - - for _, item := range m.ImmuteDomains { - wg.Add(1) - - err := m.limiter.Wait(context.Background()) - if err != nil { - errChan <- err - wg.Done() - return - } - - go func(item string) { - defer func() { - wg.Done() - }() - - domainInfo, err := service.GetCluster(*m.ClusterType, service.Domain{EntryName: item}) - if err != nil { - errChan <- err - return - } - - var users []string - switch domainInfo.ClusterType { - case v2.ClusterTypeTenDBSingle: - users, err = usersFromTenDBSingle(&domainInfo, m.Ips) - case v2.ClusterTypeTenDBHA: - users, err = usersFromTenDBHA(&domainInfo, m.Ips) - case v2.ClusterTypeTenDBCluster: - users, err = usersFromTenDBCluster(&domainInfo, m.Ips) - default: - errChan <- fmt.Errorf("unknown cluster type: %s", domainInfo.ClusterType) - return - } - - if err != nil { - errChan <- err - return - } - - retChan <- users - return - }(item) - } - - wg.Wait() - }() - - var userList []string - var errList []string - count := 0 - for { - select { - case err := <-errChan: - count++ - errList = append(errList, err.Error()) - case users := <-retChan: - count++ - userList = append(userList, users...) - } - if count == len(m.ImmuteDomains) { - break - } - } - - close(errChan) - close(retChan) - - if len(errList) > 0 { - return userList, len(userList), errno.QueryPrivilegesFail.Add("\n" + strings.Join(errList, "\n")) - } - return userList, count, nil -} - -func usersFromTenDBHA(domainInfo *service.Instance, clientIps []string) ([]string, error) { - if domainInfo.EntryRole == v2.EntryRoleMasterEntry && !domainInfo.PaddingProxy { - // ToDo Proxies 可能为空 - queryAddr := fmt.Sprintf("%s:%d", domainInfo.Proxies[0].IP, domainInfo.Proxies[0].Port) - _, users, _, err := service.ProxyWhiteList( - queryAddr, - domainInfo.BkCloudId, - clientIps, - nil, - "", - ) - if err != nil { - return nil, err - } - return users, nil - } else { - instanceRole := v2.InstanceRoleBackendSlave - if domainInfo.EntryRole == v2.EntryRoleMasterEntry { - instanceRole = v2.InstanceRoleBackendMaster - } - - idx := slices.IndexFunc(domainInfo.Storages, func(s service.Storage) bool { - return s.InstanceRole == instanceRole - }) - // ToDo idx 可能 < 0 - queryAddr := fmt.Sprintf("%s:%d", domainInfo.Storages[idx].IP, domainInfo.Storages[idx].Port) - _, users, _, err := service.MysqlUserList( - queryAddr, - domainInfo.BkCloudId, - clientIps, - nil, - "", - ) - if err != nil { - return nil, err - } - return users, nil - } -} - -func usersFromTenDBSingle(domainInfo *service.Instance, clientIps []string) ([]string, error) { - idx := slices.IndexFunc(domainInfo.Storages, func(s service.Storage) bool { - return s.InstanceRole == v2.InstanceRoleOrphan - }) - queryAddr := fmt.Sprintf("%s:%d", domainInfo.Storages[idx].IP, domainInfo.Storages[idx].Port) - _, users, _, err := service.MysqlUserList( - queryAddr, - domainInfo.BkCloudId, - clientIps, - nil, - "", - ) - if err != nil { - return nil, err - } - return users, nil -} - -func usersFromTenDBCluster(domainInfo *service.Instance, clientIps []string) ([]string, error) { - var queryAddr string - if domainInfo.EntryRole == v2.EntryRoleMasterEntry { - queryAddr = fmt.Sprintf(domainInfo.SpiderMaster[0].IP, domainInfo.SpiderMaster[0].Port) - } else { - queryAddr = fmt.Sprintf(domainInfo.SpiderSlave[0].IP, domainInfo.SpiderSlave[0].Port) - } - - _, users, _, err := service.MysqlUserList( - queryAddr, - domainInfo.BkCloudId, - clientIps, - nil, - "") - if err != nil { - return nil, err - } - return users, nil -} diff --git a/dbm-services/mysql/db-priv/service/v2/query_priv/init.go b/dbm-services/mysql/db-priv/service/v2/query_priv/init.go deleted file mode 100644 index 91968170de..0000000000 --- a/dbm-services/mysql/db-priv/service/v2/query_priv/init.go +++ /dev/null @@ -1,94 +0,0 @@ -package query_priv - -import "golang.org/x/time/rate" - -// GetPrivPara 查询权限的入参 -type GetPrivPara struct { - Ips []string `json:"ips"` - ImmuteDomains []string `json:"immute_domains"` - Users []string `json:"users"` - Dbs []string `json:"dbs"` - ClusterType *string `json:"cluster_type"` - Format string `json:"format"` - limiter *rate.Limiter -} - -func NewGetPrivPara(limiter *rate.Limiter) *GetPrivPara { - - return &GetPrivPara{ - limiter: limiter, - } -} - -// GrantInfo 查询到的权限信息 -type GrantInfo struct { - Ip string `json:"ip"` - MatchIp string `json:"match_ip"` - ImmuteDomain string `json:"immute_domain"` - User string `json:"user"` - Privs []DbPriv `json:"privs"` -} - -// DbPriv db上的权限信息 -type DbPriv struct { - MatchDb string `json:"match_db"` // 实例中匹配的db - Db string `json:"db"` // 目标查询的db - Priv string `json:"priv"` // 权限 -} - -// RelatedIp 以client ip聚合展示 -type RelatedIp struct { - Ip string `json:"ip"` // client ip - Dbs []RelatedDb `json:"dbs"` // 目标db列表,以及匹配哪些权限 -} - -type RelatedDb struct { - Db string `json:"db"` // 目标db - Domains []RelatedDomain `json:"domains"` // 目标域名列表,以及匹配哪些权限 -} -type RelatedDomain struct { - ImmuteDomain string `json:"immute_domain"` // 目标域名 - Users []RelatedUser `json:"users"` // 目标db列表,以及匹配哪些权限 -} - -type RelatedUser struct { - User string `json:"user"` // 目标user(即匹配的user) - MatchIps []RelatedMatchIp `json:"match_ips"` // 匹配的ip列表 -} - -type RelatedMatchIp struct { - MatchIp string `json:"match_ip"` // 匹配的ip - MatchDbs []RelatedMatchDb `json:"match_dbs"` // 匹配的db列表 -} - -type RelatedMatchDb struct { - MatchDb string `json:"match_db"` // 匹配的db - Priv string `json:"priv"` // 权限 -} - -// RelatedDomain2 以域名聚合展示 -type RelatedDomain2 struct { - ImmuteDomain string `json:"immute_domain"` // 目标域名 - Users []RelatedUser2 `json:"users"` // 目标db列表,以及匹配哪些权限 -} - -type RelatedUser2 struct { - User string `json:"user"` // 目标user(即匹配的user) - MatchIps []RelatedMatchIp2 `json:"match_ips"` // 匹配的ip列表 -} - -type RelatedMatchIp2 struct { - MatchIp string `json:"match_ip"` // 匹配的ip - MatchDbs []RelatedMatchDb2 `json:"match_dbs"` // 匹配的db列表 -} - -type RelatedMatchDb2 struct { - MatchDb string `json:"match_db"` // 匹配的db - Priv string `json:"priv"` // 权限 - IpDbs []RelatedIpDb `json:"ip_dbs"` // client ip 和 目标db -} - -type RelatedIpDb struct { - Ip string `json:"ip"` // client ip - Db string `json:"db"` // 目标db -} diff --git a/dbm-services/mysql/db-priv/service/v2/query_priv/validate.go b/dbm-services/mysql/db-priv/service/v2/query_priv/validate.go deleted file mode 100644 index 89f0e3d3b9..0000000000 --- a/dbm-services/mysql/db-priv/service/v2/query_priv/validate.go +++ /dev/null @@ -1,17 +0,0 @@ -package query_priv - -import "dbm-services/common/go-pubpkg/errno" - -// CheckPara 查询权限入参检查 -func (m *GetPrivPara) validate() error { - if m.ClusterType == nil { - return errno.ClusterTypeIsEmpty - } - if len(m.Ips) == 0 { - return errno.IpRequired - } - if len(m.ImmuteDomains) == 0 { - return errno.DomainRequired - } - return nil -} diff --git a/dbm-services/mysql/db-tools/dbactuator/pkg/core/staticembed/default_sys_schema.sql b/dbm-services/mysql/db-tools/dbactuator/pkg/core/staticembed/default_sys_schema.sql index 6120adc12a..7cf0d9e975 100644 --- a/dbm-services/mysql/db-tools/dbactuator/pkg/core/staticembed/default_sys_schema.sql +++ b/dbm-services/mysql/db-tools/dbactuator/pkg/core/staticembed/default_sys_schema.sql @@ -116,4 +116,315 @@ CREATE TABLE IF NOT EXISTS infodba_schema.proxy_user_list( ) ENGINE=InnoDB; flush privileges; -flush logs; \ No newline at end of file +flush logs; + + +SET SESSION sql_log_bin = 0; +-- 授权, 检查 ERROR_MSG 来判断是否成功 +-- ERROR STATE CODE +-- 32401 参数验证错误 +-- 32402 冲突检测错误 +-- 中控不会转发授权语句, 当普通 MySQL 就好 +DROP PROCEDURE IF EXISTS infodba_schema.dba_grant; +DELIMITER // +CREATE PROCEDURE infodba_schema.dba_grant( + IN username VARCHAR(128), + IN ip_list VARCHAR(3000), -- 但是限制最大只能传入 2000 + IN db_list VARCHAR(3000), -- 但是限制最大只能传入 2000 + IN long_psw VARCHAR(128), -- 密码是密文 + IN short_psw VARCHAR(32), -- 密码是密文 + IN priv_str VARCHAR(4096), + IN global_priv_str VARCHAR(4096) +) +SQL SECURITY INVOKER +BEGIN + IF LENGTH(ip_list) >= 2000 OR LENGTH(db_list) >= 2000 THEN + SIGNAL SQLSTATE '32401' SET MESSAGE_TEXT = "input ip_list or db_list too long, max length is 2000"; + END IF; + + IF NOT(long_psw LIKE '*%' AND LENGTH(long_psw) = 41) THEN + SET @msg = CONCAT('bad password: ', long_psw); + SIGNAL SQLSTATE '32401' SET MESSAGE_TEXT = @msg; + END IF; + + -- 不同版本授权语句不兼容, 所以干脆不写 + SET SESSION sql_log_bin = 0; + + -- 初始化结果表 + CALL init_report_table(); + + -- 初始化结果标识 + SET @uuid = UUID(); + SET @grant_time = NOW(); + + SET ip_list = TRIM(BOTH ',' FROM ip_list); + SET ip_list = CONCAT(ip_list, ","); + + SET db_list = TRIM(BOTH ',' FROM db_list); + SET db_list = CONCAT(db_list, ","); + + -- 先做检查 + SET @is_check_failed = 0; + CALL check_all(@uuid, @grant_time, username, ip_list, db_list, long_psw, short_psw, @is_check_failed); + + IF @is_check_failed = 1 THEN + SIGNAL SQLSTATE '32402' SET MESSAGE_TEXT = @uuid; + END IF; + + WHILE (LOCATE(',', ip_list) > 0) + DO + SET @ip = TRIM(SUBSTRING(ip_list, 1, LOCATE(',', ip_list) - 1)); + SET ip_list = SUBSTRING(ip_list, LOCATE(',', ip_list) + 1); + -- 如果涉及新增账号, 只使用新版本密码 + CALL dba_grant_one_ip(username, @ip, db_list, long_psw, priv_str, global_priv_str); + END WHILE; + + FLUSH PRIVILEGES; +END// +DELIMITER ; + +DROP PROCEDURE IF EXISTS infodba_schema.init_report_table; +DROP TABLE IF EXISTS infodba_schema.dba_grant_result; +DELIMITER // +CREATE PROCEDURE infodba_schema.init_report_table() +SQL SECURITY INVOKER +BEGIN + -- 不同版本授权语句不兼容, 所以干脆不写 + SET SESSION sql_log_bin = 0; + + CREATE TABLE IF NOT EXISTS infodba_schema.dba_grant_result( + id VARCHAR(64), + grant_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + username VARCHAR(128), + client_ip VARCHAR(32), + dbname VARCHAR(64), + long_psw VARCHAR(128), + short_psw VARCHAR(32), + priv VARCHAR(4096), + global_priv VARCHAR(4096), + msg VARCHAR(4096) + ) ENGINE = InnoDB; + DELETE FROM dba_grant_result WHERE grant_time < DATE_SUB(NOW(), INTERVAL 1 DAY); +END// +DELIMITER ; + +-- 检查入口 +DROP PROCEDURE IF EXISTS infodba_schema.check_all; +DELIMITER // +CREATE PROCEDURE infodba_schema.check_all( + IN uuid VARCHAR(64), + IN grant_time TIMESTAMP, + IN username VARCHAR(128), + IN ip_list VARCHAR(3000), + IN db_list VARCHAR(3000), + IN long_psw VARCHAR(128), + IN short_psw VARCHAR(32), + OUT is_check_failed INT +) +SQL SECURITY INVOKER +BEGIN + -- 全量检查入口 + CALL check_password(uuid, grant_time, username, ip_list, long_psw, short_psw, is_check_failed); + CALL check_db_conflict(uuid, grant_time, username, ip_list, db_list, is_check_failed); +END // +DELIMITER ; + +-- 密码一致性检查 +DROP PROCEDURE IF EXISTS infodba_schema.check_password; +DELIMITER // +CREATE PROCEDURE infodba_schema.check_password( + IN uuid VARCHAR(64), + IN grant_time TIMESTAMP, + IN username VARCHAR(128), + IN ip_list VARCHAR(3000), + IN long_psw VARCHAR(128), + IN short_psw VARCHAR(32), + OUT is_check_failed INT +) +SQL SECURITY INVOKER +BEGIN + -- 不同版本授权语句不兼容, 所以干脆不写 + SET SESSION sql_log_bin = 0; + + WHILE (LOCATE(',', ip_list) > 0) + DO + SET @ip = TRIM(SUBSTRING(ip_list, 1, LOCATE(',', ip_list) - 1)); + SET ip_list = SUBSTRING(ip_list, LOCATE(',', ip_list) + 1); + + SELECT EXISTS(SELECT 1 FROM mysql.user WHERE user = username AND host = @ip) INTO @user_host_exists; + IF @user_host_exists THEN + -- 用户存在, 检查密码 + -- 5.6 及之前还有 old_password 函数, 也就是 < 5.7 可能有 old_password + IF SUBSTRING_INDEX(@@version, ".", 2) < 5.7 THEN + SELECT password = long_psw OR password = short_psw INTO @psw_match FROM mysql.user WHERE user = username AND host = @ip; + ELSE + SELECT authentication_string = long_psw INTO @psw_match FROM mysql.user WHERE user = username AND host =@ip; + END IF; + + IF NOT @psw_match THEN + SET is_check_failed = is_check_failed OR 1; + INSERT INTO dba_grant_result(id, grant_time, username, client_ip, long_psw, short_psw, msg) + VALUES (uuid, grant_time, username, @ip, long_psw, short_psw, 'password not match'); + END IF; + END IF; + END WHILE; +END // +DELIMITER ; + +-- 库模式冲突检查入口 +DROP PROCEDURE IF EXISTS infodba_schema.check_db_conflict; +DELIMITER // +CREATE PROCEDURE infodba_schema.check_db_conflict( + IN uuid VARCHAR(64), + IN grant_time TIMESTAMP, + IN username VARCHAR(128), + IN ip_list VARCHAR(3000), + IN db_list VARCHAR(3000), + OUT is_check_failed INT +) +SQL SECURITY INVOKER +BEGIN + WHILE (LOCATE(',', ip_list) > 0) + DO + SET @ip = TRIM(SUBSTRING(ip_list, 1, LOCATE(',', ip_list) - 1)); + SET ip_list = SUBSTRING(ip_list, LOCATE(',', ip_list) + 1); + + SELECT EXISTS(SELECT 1 FROM mysql.db WHERE user = username AND host = @ip) INTO @db_priv_applied; + IF @db_priv_applied THEN + CALL check_db_conflict_one_ip(uuid, grant_time, username, @ip, db_list, is_check_failed); + END IF; + END WHILE; +END // +DELIMITER ; + +-- 单 IP 库模式冲突检查 +DROP PROCEDURE IF EXISTS infodba_schema.check_db_conflict_one_ip; +DELIMITER // +CREATE PROCEDURE infodba_schema.check_db_conflict_one_ip( + IN uuid VARCHAR(64), + IN grant_time TIMESTAMP, + IN username VARCHAR(128), + IN ip VARCHAR(15), + IN db_list VARCHAR(3000), + OUT is_check_failed INT +) +SQL SECURITY INVOKER +BEGIN + DECLARE applied_dbname VARCHAR(64); + DECLARE cursor_done INT DEFAULT FALSE; + DECLARE db_cursor CURSOR FOR SELECT db FROM mysql.db WHERE user = username AND host = @ip; + DECLARE CONTINUE HANDLER FOR NOT FOUND SET cursor_done = TRUE; + + -- 不同版本授权语句不兼容, 所以干脆不写 + SET SESSION sql_log_bin = 0; + + OPEN db_cursor; + fetch_loop: LOOP + FETCH db_cursor INTO applied_dbname; + IF cursor_done THEN + LEAVE fetch_loop; + END IF; + + SET @loop_db_list = db_list; + + WHILE (LOCATE(',', @loop_db_list) > 0) + DO + SET @dbname = TRIM(SUBSTRING(@loop_db_list, 1, LOCATE(',', @loop_db_list) - 1)); + SET @loop_db_list = SUBSTRING(@loop_db_list, LOCATE(',', @loop_db_list) + 1); + + -- 申请库名和已有库名不等并且能模式匹配 + IF @dbname <> applied_dbname AND (@dbname LIKE applied_dbname OR applied_dbname LIKE @dbname) THEN + SET is_check_failed = is_check_failed OR 1; + INSERT INTO dba_grant_result(id, grant_time, username, client_ip, dbname, long_psw, short_psw, msg) + VALUES (uuid, grant_time, username, @ip, @dbname, long_psw, short_psw, CONCAT("conflict with applied db [", applied_dbname, "]")); + END IF; + END WHILE; + + END LOOP fetch_loop; + CLOSE db_cursor; +END // +DELIMITER ; + +-- 单 IP 授权 +DROP PROCEDURE IF EXISTS infodba_schema.dba_grant_one_ip; +DELIMITER // +CREATE PROCEDURE infodba_schema.dba_grant_one_ip( + IN username VARCHAR(128), + IN ip VARCHAR(15), + IN db_list VARCHAR(3000), + IN psw VARCHAR(128), + IN priv_str VARCHAR(4096), + IN global_priv_str VARCHAR(4096) +) +SQL SECURITY INVOKER +BEGIN + -- 不同版本授权语句不兼容, 所以干脆不写 + SET SESSION sql_log_bin = 0; + + SELECT EXISTS(SELECT 1 FROM mysql.user WHERE user = username AND host = ip) INTO @user_host_exists; + IF NOT @user_host_exists THEN + -- 用户不存在, 直接创建, 只使用新版本密码, 传入的密码是密文 + IF SUBSTRING_INDEX(@@version, ".", 2) < 5.7 THEN + SET @create_user_sql = CONCAT("GRANT USAGE ON *.* TO '", username, "'@'", ip, "' IDENTIFIED BY PASSWORD '", psw, "'"); + PREPARE stmt FROM @create_user_sql; + EXECUTE stmt; + ELSE + SET @create_user_sql = CONCAT("CREATE USER IF NOT EXISTS '", username, "'@'", ip, "' IDENTIFIED WITH mysql_native_password AS '", psw, "'"); + PREPARE stmt FROM @create_user_sql; + EXECUTE stmt; + END IF; + END IF; + + WHILE (LOCATE(',', db_list) > 0) + DO + SET @db = TRIM(SUBSTRING(db_list, 1, LOCATE(',', db_list) - 1)); + SET db_list = SUBSTRING(db_list, LOCATE(',', db_list) + 1); + CALL dba_grant_one_ip_db(username, ip, @db, psw, priv_str, global_priv_str); + END WHILE; +END// +DELIMITER ; + +-- 单 IP 单 DB 授权 +DROP PROCEDURE IF EXISTS infodba_schema.dba_grant_one_ip_db; +DELIMITER // +CREATE PROCEDURE infodba_schema.dba_grant_one_ip_db( + IN username VARCHAR(128), + IN ip VARCHAR(15), + IN dbname VARCHAR(64), + IN psw VARCHAR(128), + IN priv_str VARCHAR(4096), + IN global_priv_str VARCHAR(4096) +) +SQL SECURITY INVOKER +BEGIN + DECLARE exists_db VARCHAR(64); + DECLARE cursor_done INT DEFAULT FALSE; + DECLARE db_cursor CURSOR FOR SELECT db FROM mysql.db WHERE user = username AND host = ip; + DECLARE CONTINUE HANDLER FOR NOT FOUND SET cursor_done = TRUE; + + -- 不同版本授权语句不兼容, 所以干脆不写 + SET SESSION sql_log_bin = 0; + + SET priv_str = TRIM(BOTH ',' FROM TRIM(priv_str)); + SET global_priv_str = TRIM(BOTH ',' FROM TRIM(global_priv_str)); + + -- 全局权限 + IF global_priv_str IS NOT NULL AND global_priv_str <> '' THEN + SET @global_grant_sql = CONCAT("GRANT ", global_priv_str, " ON *.* TO '", username, "'@'", ip, "'"); + PREPARE stmt FROM @global_grant_sql; + EXECUTE stmt; + END IF; + + -- 非全局权限 + IF priv_str IS NOT NULL AND priv_str <> '' THEN + SET @grant_sql = ""; + if dbname = '*' OR dbname = '%' THEN + SET @grant_sql = CONCAT("GRANT ", priv_str, " ON *.* TO '", username, "'@'", ip, "'"); + ELSE + SET @grant_sql = CONCAT("GRANT ", priv_str, " ON `", dbname, "`.* TO '", username, "'@'", ip, "'"); + END IF; + PREPARE stmt FROM @grant_sql; + EXECUTE stmt; + END IF; +END// +DELIMITER ; diff --git a/dbm-ui/backend/flow/engine/bamboo/scene/mysql/common/common_sub_flow.py b/dbm-ui/backend/flow/engine/bamboo/scene/mysql/common/common_sub_flow.py index b3e2ee57ac..84da2620a2 100644 --- a/dbm-ui/backend/flow/engine/bamboo/scene/mysql/common/common_sub_flow.py +++ b/dbm-ui/backend/flow/engine/bamboo/scene/mysql/common/common_sub_flow.py @@ -36,8 +36,8 @@ InstallNodemanPluginServiceComponent, ) from backend.flow.plugins.components.collections.common.sa_idle_check import CheckMachineIdleComponent -from backend.flow.plugins.components.collections.mysql.authorize_rule_v2 import AuthorizeRulesV2Component from backend.flow.plugins.components.collections.mysql.authorize_rules import AuthorizeRulesComponent +from backend.flow.plugins.components.collections.mysql.authorize_rules_v2 import AuthorizeRulesV2Component from backend.flow.plugins.components.collections.mysql.check_client_connections import CheckClientConnComponent from backend.flow.plugins.components.collections.mysql.clone_rules import CloneRulesComponent from backend.flow.plugins.components.collections.mysql.exec_actuator_script import ExecuteDBActuatorScriptComponent diff --git a/dbm-ui/backend/flow/plugins/components/collections/mysql/authorize_rule_v2.py b/dbm-ui/backend/flow/plugins/components/collections/mysql/authorize_rules_v2.py similarity index 100% rename from dbm-ui/backend/flow/plugins/components/collections/mysql/authorize_rule_v2.py rename to dbm-ui/backend/flow/plugins/components/collections/mysql/authorize_rules_v2.py diff --git a/dbm-ui/backend/tests/flow/components/collections/mysql/permission/test_authorize_rules.py b/dbm-ui/backend/tests/flow/components/collections/mysql/permission/test_authorize_rules.py index 950a4ec84b..c79580d1ea 100644 --- a/dbm-ui/backend/tests/flow/components/collections/mysql/permission/test_authorize_rules.py +++ b/dbm-ui/backend/tests/flow/components/collections/mysql/permission/test_authorize_rules.py @@ -16,7 +16,7 @@ from django.test import TestCase from pipeline.component_framework.component import Component -from backend.flow.plugins.components.collections.mysql.authorize_rules import AuthorizeRulesComponent +from backend.flow.plugins.components.collections.mysql.authorize_rules_v2 import AuthorizeRulesV2Component from backend.tests.flow.components.collections.base import BaseComponentPatcher as Patcher from backend.tests.flow.components.collections.mysql.utils import MySQLComponentBaseTest from backend.tests.mock_data.components.mysql_priv_manager import DBPrivManagerApiMock @@ -36,7 +36,7 @@ def _set_excepted_outputs(cls) -> None: cls.excepted_outputs = {} def component_cls(self) -> Type[Component]: - return AuthorizeRulesComponent + return AuthorizeRulesV2Component def tearDown(self) -> Union[Any, NoReturn]: pass diff --git a/dbm-ui/backend/ticket/builders/mysql/mysql_authorize_rules.py b/dbm-ui/backend/ticket/builders/mysql/mysql_authorize_rules.py index a684a2bb6b..6b0b53a361 100644 --- a/dbm-ui/backend/ticket/builders/mysql/mysql_authorize_rules.py +++ b/dbm-ui/backend/ticket/builders/mysql/mysql_authorize_rules.py @@ -78,7 +78,8 @@ class MySQLExcelAuthorizeRulesSerializer(serializers.Serializer): class MySQLAuthorizeRulesFlowParamBuilder(builders.FlowParamBuilder): - controller = MySQLController.mysql_authorize_rules + # controller = MySQLController.mysql_authorize_rules + controller = MySQLController.mysql_authorize_rules_v2 def post_callback(self): excel_url = ( diff --git a/items-config.sql b/items-config.sql deleted file mode 100644 index e69de29bb2..0000000000