From cb5608536bf017ae57cab6a8f2e2300a49122e1b Mon Sep 17 00:00:00 2001 From: fanfanyangyang Date: Thu, 5 Sep 2024 19:15:12 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AF=86=E7=A0=81=E5=AE=89=E5=85=A8?= =?UTF-8?q?=E6=96=B0=E7=89=88=E6=9C=AC=5F=E6=8E=88=E6=9D=83=E8=A7=84?= =?UTF-8?q?=E5=88=99=E5=88=9B=E5=BB=BA=E8=B4=A6=E5=8F=B7=E4=B8=8D=E5=85=81?= =?UTF-8?q?=E8=AE=B8=E7=94=A8=E5=86=85=E9=83=A8=E8=B4=A6=E5=8F=B7=E5=90=8D?= =?UTF-8?q?=5Fmysql=E6=94=AF=E6=8C=81replication=5Fslave=E7=9A=84=E7=89=B9?= =?UTF-8?q?=E6=AE=8A=E6=8E=88=E6=9D=83=E6=96=B9=E5=BC=8F=20#6680?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../go-pubpkg/errno/50000_dbpriv_code.go | 10 +- dbm-services/mysql/db-priv/assests/migrate.go | 94 ++++++++++---- dbm-services/mysql/db-priv/service/account.go | 12 ++ .../mysql/db-priv/service/add_priv.go | 27 +++- .../db-priv/service/add_priv_base_func.go | 11 +- .../service/admin_password_base_func.go | 1 + .../service/clone_instance_priv_base_func.go | 95 +++++++------- .../mysql/db-priv/service/db_meta_service.go | 5 +- .../db-priv/service/generate_random_string.go | 120 +++++++++++------- .../mysql/db-priv/service/query_priv.go | 54 ++++---- .../mysql/db-priv/service/security_rule.go | 14 +- .../db-priv/service/security_rule_object.go | 42 +++--- 12 files changed, 292 insertions(+), 193 deletions(-) diff --git a/dbm-services/common/go-pubpkg/errno/50000_dbpriv_code.go b/dbm-services/common/go-pubpkg/errno/50000_dbpriv_code.go index 202c57cc96..850f883040 100644 --- a/dbm-services/common/go-pubpkg/errno/50000_dbpriv_code.go +++ b/dbm-services/common/go-pubpkg/errno/50000_dbpriv_code.go @@ -92,8 +92,10 @@ var ( CNMessage: "检查没有通过"} MigrateFail = Errno{Code: 51038, Message: "migrate fail", CNMessage: "迁移失败"} - PortRequired = Errno{Code: 51039, Message: "port is required", CNMessage: "port不能为空"} - IpRequired = Errno{Code: 51040, Message: "ip is required", CNMessage: "ip列表不能为空"} - DomainRequired = Errno{Code: 51041, Message: "domain is required", CNMessage: "域名列表不能为空"} - QueryPrivilegesFail = Errno{Code: 51042, Message: "query privileges fail", CNMessage: "查询权限失败"} + PortRequired = Errno{Code: 51039, Message: "port is required", CNMessage: "port不能为空"} + IpRequired = Errno{Code: 51040, Message: "ip is required", CNMessage: "ip列表不能为空"} + DomainRequired = Errno{Code: 51041, Message: "domain is required", CNMessage: "域名列表不能为空"} + QueryPrivilegesFail = Errno{Code: 51042, Message: "query privileges fail", CNMessage: "查询权限失败"} + InternalAccountNameNotAllowed = Errno{Code: 51043, Message: "internal account name is not allowed", + CNMessage: "不允许使用内部账号名称"} ) diff --git a/dbm-services/mysql/db-priv/assests/migrate.go b/dbm-services/mysql/db-priv/assests/migrate.go index e923c0ff74..23624a9a04 100644 --- a/dbm-services/mysql/db-priv/assests/migrate.go +++ b/dbm-services/mysql/db-priv/assests/migrate.go @@ -78,34 +78,63 @@ func DoMigrateFromEmbed() error { } func DoMigratePlatformPassword() error { - // 初始化安全规则 - // 通用的安全规则 - passwordSecurityRule := &service.SecurityRulePara{Name: "password", - Rule: "{\"max_length\":12,\"min_length\":8,\"include_rule\":{\"numbers\":true,\"symbols\":true,\"lowercase\":true,\"uppercase\":true},\"exclude_continuous_rule\":{\"limit\":4,\"letters\":false,\"numbers\":false,\"symbols\":false,\"keyboards\":false,\"repeats\":false}}", Operator: "admin"} - b, _ := json.Marshal(passwordSecurityRule) - errOuter := passwordSecurityRule.AddSecurityRule(string(b), "add_security_rule") - if errOuter != nil { - no, _ := errno.DecodeErr(errOuter) - if no != errno.RuleExisted.Code { - return errOuter + // 兼容历史版本,避免未同步调整,引起旧规则无法使用 + oldRules := []string{"password", "mongo_password", "redis_password", "simple_password"} + bigDataRule := "{\"max_length\":32,\"min_length\":6,\"include_rule\":{\"numbers\":true,\"symbols\":false,\"lowercase\":true,\"uppercase\":true},\"number_of_types\":2,\"symbols_allowed\":\"\",\"weak_password\":false,\"repeats\":4}" + mysqlSqlserverRule := "{\"max_length\":32,\"min_length\":6,\"include_rule\":{\"numbers\":true,\"symbols\":true,\"lowercase\":true,\"uppercase\":true},\"number_of_types\":2,\"symbols_allowed\":\"!#%&()*+,-./;<=>?[]^_{|}~@:$\",\"weak_password\":false,\"repeats\":4}" + redisRule := "{\"max_length\":32,\"min_length\":8,\"include_rule\":{\"numbers\":true,\"symbols\":true,\"lowercase\":true,\"uppercase\":true},\"number_of_types\":2,\"symbols_allowed\":\"#@%=+-;\",\"weak_password\":false,\"repeats\":4}" + mongodbRule := "{\"max_length\":32,\"min_length\":8,\"include_rule\":{\"numbers\":true,\"symbols\":true,\"lowercase\":true,\"uppercase\":true},\"number_of_types\":2,\"symbols_allowed\":\"#%=+-;\",\"weak_password\":false,\"repeats\":4}" + for _, old := range oldRules { + SecurityRulePara := &service.SecurityRulePara{Name: old} + rule, err := SecurityRulePara.GetSecurityRule() + if err != nil { + continue + } + var v2 string + switch old { + case "password": + v2 = mysqlSqlserverRule + case "mongo_password": + v2 = mongodbRule + case "redis_password": + v2 = redisRule + case "simple_password": + v2 = bigDataRule + default: + continue + } + SecurityRulePara = &service.SecurityRulePara{Id: rule.Id, Rule: v2, Operator: "deprecated"} + b, _ := json.Marshal(SecurityRulePara) + err = SecurityRulePara.ModifySecurityRule(string(b), "modify_security_rule") + if err != nil { + slog.Error("modify_security_rule", "name", old, "error", err) } } - // 不需要符号、固定长度的安全规则 - NoSymbols := []string{"mongo_password", "redis_password", "simple_password"} - // mongodb专用的安全规则 - for _, name := range NoSymbols { - passwordSecurityRule = &service.SecurityRulePara{Name: name, - Rule: "{\"max_length\":16,\"min_length\":16,\"include_rule\":{\"numbers\":true,\"symbols\":false,\"lowercase\":true,\"uppercase\":true},\"exclude_continuous_rule\":{\"limit\":4,\"letters\":false,\"numbers\":false,\"symbols\":false,\"keyboards\":false,\"repeats\":false}}", Operator: "admin"} - b, _ = json.Marshal(passwordSecurityRule) - errOuter = passwordSecurityRule.AddSecurityRule(string(b), "add_security_rule") - if errOuter != nil { - no, _ := errno.DecodeErr(errOuter) - if no != errno.RuleExisted.Code { - return errOuter - } + // 初始化新版本安全规则 + // 密码服务V2版本,各个组件有独立的安全规则 + bigData := []string{"es_password", "kafka_password", "hdfs_password", "pulsar_password", "influxdb_password"} + mysqlSqlserver := []string{"mysql_password", "tendbcluster_password", "sqlserver_password"} + for _, name := range bigData { + err := AddRule(name, bigDataRule) + if err != nil { + return err } } + for _, name := range mysqlSqlserver { + err := AddRule(name, mysqlSqlserverRule) + if err != nil { + return err + } + } + err := AddRule("redis_password_v2", redisRule) + if err != nil { + return err + } + err = AddRule("mongodb_password", mongodbRule) + if err != nil { + return err + } // 初始化平台密码,随机密码 type ComponentPlatformUser struct { @@ -113,7 +142,7 @@ func DoMigratePlatformPassword() error { Usernames []string } - // 平台密码初始化,不存在新增 + // 平台密码初始化,不存在则新增 var users []ComponentPlatformUser users = append(users, ComponentPlatformUser{Component: "mysql", Usernames: []string{ "dba_bak_all_sel", "MONITOR", "MONITOR_ALL", "mysql", "repl", "yw", "partition_yw"}}) @@ -140,7 +169,7 @@ func DoMigratePlatformPassword() error { Instances: []service.Address{{"0.0.0.0", &defaultInt, &defaultInt}}, InitPlatform: true, SecurityRuleName: "redis_password"} } - b, _ = json.Marshal(*insertPara) + b, _ := json.Marshal(*insertPara) err = insertPara.ModifyPassword(string(b), "modify_password") if err != nil { return fmt.Errorf("%s error: %s", "init platform password, modify password", err.Error()) @@ -151,6 +180,21 @@ func DoMigratePlatformPassword() error { return nil } +func AddRule(name string, content string) error { + rule := &service.SecurityRulePara{Name: name, + Rule: content, Operator: "admin"} + b, _ := json.Marshal(rule) + err := rule.AddSecurityRule(string(b), "add_security_rule") + if err != nil { + no, _ := errno.DecodeErr(err) + if no != errno.RuleExisted.Code { + slog.Error("add_rule", "name", name, "error", err) + return err + } + } + return nil +} + func ModifyPrivsGlobalToGlobalNon() error { var rules []*service.TbAccountRules err := service.DB.Self.Model(&service.TbAccountRules{}).Scan(&rules).Error diff --git a/dbm-services/mysql/db-priv/service/account.go b/dbm-services/mysql/db-priv/service/account.go index 9fa8e6c7c1..7d7c558350 100644 --- a/dbm-services/mysql/db-priv/service/account.go +++ b/dbm-services/mysql/db-priv/service/account.go @@ -1,6 +1,7 @@ package service import ( + "dbm-services/mysql/priv-service/util" "encoding/hex" "fmt" "log/slog" @@ -47,6 +48,17 @@ func (m *AccountPara) AddAccount(jsonPara string, ticket string) (TbAccounts, er if count != 0 { return detail, errno.AccountExisted.AddBefore(m.User) } + innerAccount := make(map[string][]string) + innerAccount[sqlserver] = []string{"mssql_exporter", "dbm_admin", "sa", "sqlserver"} + innerAccount[mongodb] = []string{"dba", "apppdba", "monitor", "appmonitor"} + innerAccount[mysql] = []string{"gcs_admin", "gcs_dba", "monitor", "gm", "admin", "repl", "dba_bak_all_sel", + "yw", "partition_yw", "spider", "mysql.session", "mysql.sys", "gcs_spider", "sync"} + innerAccount[tendbcluster] = innerAccount[mysql] + if !m.MigrateFlag { + if util.HasElem(strings.ToLower(m.User), innerAccount[*m.ClusterType]) { + return detail, errno.InternalAccountNameNotAllowed + } + } psw = m.Psw // 从旧系统迁移的,不检查是否帐号和密码不同 if psw == m.User && !m.MigrateFlag { diff --git a/dbm-services/mysql/db-priv/service/add_priv.go b/dbm-services/mysql/db-priv/service/add_priv.go index fc2e2f6d1e..7ce2c06a29 100644 --- a/dbm-services/mysql/db-priv/service/add_priv.go +++ b/dbm-services/mysql/db-priv/service/add_priv.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "log/slog" + "regexp" "strings" "sync" "time" @@ -80,12 +81,27 @@ func (m *PrivTaskPara) AddPriv(jsonPara string, ticket string) error { limit := rate.Every(time.Millisecond * 100) // QPS:10 burst := 10 // 桶容量 10 limiter := rate.NewLimiter(limit, burst) - for _, rule := range m.AccoutRules { // 添加权限,for acccountRuleList;for instanceList; do create a routine + for _, rule := range m.AccoutRules { // 添加权限,for accountRuleList;for instanceList; do create a routine account, accountRule, outerErr := GetAccountRuleInfo(m.BkBizId, m.ClusterType, m.User, rule.Dbname) if outerErr != nil { AddErrorOnly(&errMsg, outerErr) continue } + repl := accountRule + var replFlag bool + // replication slave、replication client 权限在tendbha集群下,单独处理,授权方式与其他权限不同 + for _, priv := range []string{"replication slave", "replication client"} { + if strings.Contains(accountRule.GlobalPriv, priv) { + replFlag = true + accountRule.GlobalPriv = strings.Replace(accountRule.GlobalPriv, priv, "", -1) + repl.GlobalPriv = fmt.Sprintf("%s,%s", repl.GlobalPriv, priv) + } + } + if replFlag { + repl.GlobalPriv = strings.Trim(repl.GlobalPriv, ",") + accountRule.GlobalPriv = strings.Trim(accountRule.GlobalPriv, ",") + accountRule.GlobalPriv = regexp.MustCompile(`,+`).ReplaceAllString(accountRule.GlobalPriv, ",") + } for _, dns := range m.TargetInstances { errLimiter := limiter.Wait(context.Background()) if errLimiter != nil { @@ -140,6 +156,15 @@ func (m *PrivTaskPara) AddPriv(jsonPara string, ticket string) error { if err != nil { errMsgInner = append(errMsgInner, err.Error()) } + if replFlag { + // 在mysql实例上授权 + err = ImportBackendPrivilege(account, repl, address, proxyIPs, m.SourceIPs, + instance.ClusterType, tendbhaMasterDomain, instance.BkCloudId, false, + true) + if err != nil { + errMsgInner = append(errMsgInner, err.Error()) + } + } } if len(errMsgInner) > 0 { AddErrorOnly(&errMsg, errors.New(failInfo+sep+strings.Join(errMsgInner, sep))) 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 2c380aee6b..3d1b256cbb 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 @@ -214,10 +214,13 @@ func GenerateBackendSQL(account TbAccounts, rule TbAccountRules, ips []string, m sql = fmt.Sprintf("%s '%s'@'%s' %s;", insertConnLogPriv, account.User, ip, identifiedByPassword) sqlTemp = append(sqlTemp, sql) } - if strings.Contains(strings.ToLower(rule.GlobalPriv), "show databases") { - sql = fmt.Sprintf(`GRANT SHOW DATABASES ON *.* TO '%s'@'%s' %s;`, - account.User, ip, identifiedByPassword) - sqlTemp = append(sqlTemp, sql) + slaveAllowedPriv := []string{"show databases", "replication slave", "replication client"} + for _, priv := range slaveAllowedPriv { + if strings.Contains(strings.ToLower(rule.GlobalPriv), priv) { + sql = fmt.Sprintf(`GRANT %s ON *.* TO '%s'@'%s' %s;`, + priv, account.User, ip, identifiedByPassword) + sqlTemp = append(sqlTemp, sql) + } } result.mu.Lock() result.backendSQL = append(result.backendSQL, sqlTemp...) diff --git a/dbm-services/mysql/db-priv/service/admin_password_base_func.go b/dbm-services/mysql/db-priv/service/admin_password_base_func.go index e8e1cc5b86..80f652d5dc 100644 --- a/dbm-services/mysql/db-priv/service/admin_password_base_func.go +++ b/dbm-services/mysql/db-priv/service/admin_password_base_func.go @@ -27,6 +27,7 @@ func CheckOrGetPassword(psw string, security SecurityRule) (string, error) { password = psw check := CheckPassword(security, []byte(psw)) if !check.IsStrength { + slog.Error("msg", "check", check) return "", errno.NotMeetComplexity } } else { diff --git a/dbm-services/mysql/db-priv/service/clone_instance_priv_base_func.go b/dbm-services/mysql/db-priv/service/clone_instance_priv_base_func.go index 23a557f2a7..27ee5dceeb 100644 --- a/dbm-services/mysql/db-priv/service/clone_instance_priv_base_func.go +++ b/dbm-services/mysql/db-priv/service/clone_instance_priv_base_func.go @@ -100,10 +100,7 @@ func GetRemotePrivilege(address string, host string, bkCloudId int64, instanceTy wg.Done() // <-tokenBucket }() - var Grants []string - var err error - slog.Info("msg", "userHost", userHost) - err = GetUserGantSql(needShowCreateUser, userHost, address, &Grants, bkCloudId) + Grants, err := GetUserGantSql(needShowCreateUser, userHost, address, bkCloudId) if err != nil { errorChan <- err return @@ -131,56 +128,54 @@ func GetRemotePrivilege(address string, host string, bkCloudId int64, instanceTy } // GetUserGantSql 查询用户创建以及授权语句 -func GetUserGantSql(needShowCreateUser bool, userHost, address string, grants *[]string, bkCloudId int64) error { +func GetUserGantSql(needShowCreateUser bool, userHost, address string, bkCloudId int64) ([]string, error) { var ( - sql string - err error - hasValue bool + sql string + grants []string ) if needShowCreateUser { sql = fmt.Sprintf("show create user %s;", userHost) - err, hasValue = GetGrantResponse(sql, address, grants, bkCloudId) + res, err := GetGrantResponse(sql, address, bkCloudId) if err != nil { - return err - } else if !hasValue { - return fmt.Errorf("execute (%s) return nothing", sql) + return grants, err } + grants = append(grants, res...) } sql = fmt.Sprintf("show grants for %s ", userHost) - err, _ = GetGrantResponse(sql, address, grants, bkCloudId) + res, err := GetGrantResponse(sql, address, bkCloudId) if err != nil { - return err + return grants, err } - if len(*grants) == 0 { - return fmt.Errorf("show grants in %s fail,query return nothing", userHost) + grants = append(grants, res...) + if len(grants) == 0 { + return grants, fmt.Errorf("show grants in %s fail,query return nothing", userHost) } - return nil + return grants, nil } // GetGrantResponse 执行sql语句,获取结果 -func GetGrantResponse(sql, address string, grants *[]string, bkCloudId int64) (error, bool) { - hasValue := false +func GetGrantResponse(sql, address string, bkCloudId int64) ([]string, error) { + var grants []string queryRequest := QueryRequest{[]string{address}, []string{sql}, true, 60, bkCloudId} reps, err := OneAddressExecuteSql(queryRequest) if err != nil { - return fmt.Errorf("execute (%s) fail, error:%s", sql, err.Error()), hasValue + return grants, fmt.Errorf("execute (%s) fail, error:%s", sql, err.Error()) } if len(reps.CmdResults[0].TableData) > 0 { for _, item := range reps.CmdResults[0].TableData { for _, grant := range item { if grant != nil { - *grants = append(*grants, grant.(string)) + grants = append(grants, grant.(string)) } else { - return fmt.Errorf("execute (%s), content of return is null", sql), hasValue + return grants, fmt.Errorf("execute (%s), content of return is null", sql) } } } } else { - return nil, hasValue + return grants, nil } - hasValue = true - return nil, hasValue + return grants, nil } // DealWithPrivileges 处理授权语句,做版本兼容 @@ -251,11 +246,12 @@ func (m *CloneInstancePrivPara) DealWithPrivileges(userGrants []UserGrant, insta } row.Grants = tmp } - errInner := DiffVersionConvert(&row.Grants, mysql80Tomysql57, mysql57Tomysql56, mysql5Tomysql8, mysql8) + tmp, errInner := DiffVersionConvert(row.Grants, mysql80Tomysql57, mysql57Tomysql56, mysql5Tomysql8, mysql8) if errInner != nil { errorChan <- errInner return } + row.Grants = tmp newUserGrants.mu.Lock() newUserGrants.Data = append(newUserGrants.Data, row) newUserGrants.mu.Unlock() @@ -275,7 +271,7 @@ func (m *CloneInstancePrivPara) DealWithPrivileges(userGrants []UserGrant, insta } // DiffVersionConvert 跨版本克隆权限对授权语句变形,做兼容 -func DiffVersionConvert(grants *[]string, mysql80Tomysql57, mysql57Tomysql56, mysql5Tomysql8, mysql8 bool) error { +func DiffVersionConvert(grants []string, mysql80Tomysql57, mysql57Tomysql56, mysql5Tomysql8, mysql8 bool) ([]string, error) { var err error var tmp []string regForCreateUser := regexp.MustCompile(`(?i)^\s*CREATE USER `) // CREATE USER变为CREATE USER IF NOT EXISTS @@ -286,12 +282,12 @@ func DiffVersionConvert(grants *[]string, mysql80Tomysql57, mysql57Tomysql56, my switch { case mysql80Tomysql57: - err = PrivMysql80ToMysql57(grants) + tmp, err = PrivMysql80ToMysql57(grants) if err != nil { - return err + return tmp, err } case mysql57Tomysql56: - for _, str := range *grants { + for _, str := range grants { if regForPasswordExpired.MatchString(str) { str = regForPasswordExpired.ReplaceAllString(str, ``) } @@ -303,16 +299,16 @@ func DiffVersionConvert(grants *[]string, mysql80Tomysql57, mysql57Tomysql56, my } tmp = append(tmp, str) } - *grants = tmp + return tmp, nil case mysql5Tomysql8: - err = PrivMysql5ToMysql8(grants) + tmp, err = PrivMysql5ToMysql8(grants) if err != nil { - return err + return tmp, err } case mysql8: - PrivMysql8(grants) + tmp = PrivMysql8(grants) default: - for _, str := range *grants { + for _, str := range grants { if regForCreateUser.MatchString(str) { str = regForCreateUser.ReplaceAllString(str, `CREATE USER /*!50706 IF NOT EXISTS */ `) } @@ -321,19 +317,18 @@ func DiffVersionConvert(grants *[]string, mysql80Tomysql57, mysql57Tomysql56, my } tmp = append(tmp, str) } - *grants = tmp } - return nil + return tmp, nil } // PrivMysql8 剔除txsql 8.0包含的动态权限 -func PrivMysql8(grants *[]string) { +func PrivMysql8(grants []string) []string { var tmp []string regForCreateUser := regexp.MustCompile(`(?i)^\s*CREATE USER `) // CREATE USER变为CREATE USER IF NOT EXISTS regForConnLog := regexp.MustCompile("`test`.`conn_log`") // `test`.`conn_log`变为 infodba_schema.conn_log // 8.0.30-txsql 包含的动态权限,但是开源版本不支持 var onlyForTxsql = []string{"READ_MASK"} - for _, item := range *grants { + for _, item := range grants { dynamicGrantsFlag := false for _, priv := range onlyForTxsql { if regexp.MustCompile(priv).MatchString(item) { @@ -353,16 +348,16 @@ func PrivMysql8(grants *[]string) { } tmp = append(tmp, item) } - *grants = tmp + return tmp } // PrivMysql5ToMysql8 Mysql5授权语句向Mysql8兼容 -func PrivMysql5ToMysql8(grants *[]string) error { +func PrivMysql5ToMysql8(grants []string) ([]string, error) { var tmp []string regForCreateUser := regexp.MustCompile(`(?i)^\s*CREATE USER `) // CREATE USER变为CREATE USER IF NOT EXISTS regForConnLog := regexp.MustCompile("`test`.`conn_log`") // `test`.`conn_log`变为 infodba_schema.conn_log regForPlainText := regexp.MustCompile(`(?i)\s+IDENTIFIED\s+BY\s+`) - for _, item := range *grants { + for _, item := range grants { if regForCreateUser.MatchString(item) { item = regForCreateUser.ReplaceAllString(item, `CREATE USER /*!50706 IF NOT EXISTS */ `) } @@ -373,7 +368,7 @@ func PrivMysql5ToMysql8(grants *[]string) error { sqlParser := parser.New() stmtNodes, warns, err := sqlParser.Parse(item, "", "") if err != nil { - return fmt.Errorf("parse sql failed, sql:%s, error:%s", item, err.Error()) + return tmp, fmt.Errorf("parse sql failed, sql:%s, error:%s", item, err.Error()) } if len(warns) > 0 { slog.Warn("some warnings happend", warns) @@ -382,7 +377,7 @@ func PrivMysql5ToMysql8(grants *[]string) error { v := visitor{} stmtNode.Accept(&v) if !v.legal { - return fmt.Errorf("parse pass,but sql format error,sql:%s", item) + return tmp, fmt.Errorf("parse pass,but sql format error,sql:%s", item) } // statement which have password need to CREATE USER if v.legal && len(v.secPassword) > 0 { @@ -405,12 +400,11 @@ func PrivMysql5ToMysql8(grants *[]string) error { tmp = append(tmp, item) } } - *grants = tmp - return nil + return tmp, nil } // PrivMysql80ToMysql57 Mysql8.0授权语句向Mysql5.7兼容 -func PrivMysql80ToMysql57(grants *[]string) error { +func PrivMysql80ToMysql57(grants []string) ([]string, error) { var tmp []string var dynamicGrantsForMySQL8 = []string{ "APPLICATION_PASSWORD_ADMIN", @@ -448,7 +442,7 @@ func PrivMysql80ToMysql57(grants *[]string) error { ) // 排除8.0使用caching_sha2_password作为密码验证方式 regForConnLog := regexp.MustCompile("`test`.`conn_log`") // `test`.`conn_log`变为 infodba_schema.conn_log - for _, item := range *grants { + for _, item := range grants { if regForPasswordOption.MatchString(item) { item = regForPasswordOption.ReplaceAllString(item, ``) } @@ -493,7 +487,7 @@ func PrivMysql80ToMysql57(grants *[]string) error { } } if regForPasswordPlugin.MatchString(item) { - return fmt.Errorf("using caching_sha2_password, sql: %s", item) + return tmp, fmt.Errorf("using caching_sha2_password, sql: %s", item) } if regForCreateUser.MatchString(item) { item = regForCreateUser.ReplaceAllString(item, `CREATE USER /*!50706 IF NOT EXISTS */ `) @@ -503,8 +497,7 @@ func PrivMysql80ToMysql57(grants *[]string) error { } tmp = append(tmp, item) } - *grants = tmp - return nil + return tmp, nil } // ValidateInstancePair 验证实例是否存在 diff --git a/dbm-services/mysql/db-priv/service/db_meta_service.go b/dbm-services/mysql/db-priv/service/db_meta_service.go index 596dc4fb2e..6118ff2a39 100644 --- a/dbm-services/mysql/db-priv/service/db_meta_service.go +++ b/dbm-services/mysql/db-priv/service/db_meta_service.go @@ -28,11 +28,10 @@ const sqlserverSingle string = "sqlserver_single" const backendMaster string = "backend_master" const orphan string = "orphan" const sqlserverSysDB string = "Monitor" +const mongodb string = "mongodb" -// GetAllClustersInfo TODO +// GetAllClustersInfo 获取业务下所有集群信息 /* -GetAllClustersInfo 获取业务下所有集群信息 - [{ "db_module_id": 126, "bk_biz_id": "3", diff --git a/dbm-services/mysql/db-priv/service/generate_random_string.go b/dbm-services/mysql/db-priv/service/generate_random_string.go index 555041aea0..4f473686e7 100644 --- a/dbm-services/mysql/db-priv/service/generate_random_string.go +++ b/dbm-services/mysql/db-priv/service/generate_random_string.go @@ -13,13 +13,12 @@ import ( const lowercase = "abcdefghijklmnopqrstuvwxyz" const uppercase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" const number = "0123456789" -const symbol = `!#%&()*+,-./;<=>?[]^_{|}~@:$` // 剔除 " ' ` \ // 为密码池添加连续的字母序,数字序,特殊字符序和键盘序 const continuousSymbols = "~!@#$%^&*()_+" var continuousKeyboardCol = []string{"1qaz", "2wsx", "3edc", "4rfv", "5tgb", "6yhn", "7ujm", "8ik,", "9ol.", "0p;/"} -var continuousKeyboardRow = []string{"qwertyuiop[]\\", "asdfghjkl;'", "zxcvbnm,./"} +var continuousKeyboardRow = []string{"qwertyuiop[]\\", "asdfghjkl;'", "zxcvbnm,./", "1234567890-="} // GenerateRandomStringPara 生成随机字符串的函数的入参 type GenerateRandomStringPara struct { @@ -34,14 +33,11 @@ type CheckPasswordPara struct { func GenerateRandomString(security SecurityRule) (string, error) { var length int - if security.MaxLength == security.MinLength { - length = security.MaxLength - } else { - length = rand.Intn(security.MaxLength-security.MinLength) + security.MinLength - } + length = security.MaxLength var str []byte var vrange string rand.Seed(time.Now().UnixNano()) + symbol := security.SymbolsAllowed if security.IncludeRule.Lowercase { index := rand.Intn(len(lowercase)) str = append(str, lowercase[index]) @@ -93,8 +89,8 @@ func GenerateRandomString(security SecurityRule) (string, error) { func CheckPassword(security SecurityRule, password []byte) CheckPasswordComplexity { check := CheckPasswordComplexity{IsStrength: true, PasswordVerifyInfo: PasswordVerifyInfo{true, - true, true, true, true, true, - true, true, true, true, true}} + true, "", true, true, true, + true, true, true, true}} str := string(password) strLower := strings.ToLower(str) // check 默认每个检查项是true,检查不通过是false @@ -106,49 +102,74 @@ func CheckPassword(security SecurityRule, password []byte) CheckPasswordComplexi check.PasswordVerifyInfo.MinLengthValid = false check.IsStrength = false } - // 必须包含某些字符 - if security.IncludeRule.Lowercase && !CheckStringChar(password, lowercase) { - check.PasswordVerifyInfo.LowercaseValid = false - check.IsStrength = false - } - if security.IncludeRule.Uppercase && !CheckStringChar(password, uppercase) { - check.PasswordVerifyInfo.UppercaseValid = false - check.IsStrength = false + // 包含的字符类型 + var numberTypes int + // 允许的字符范围 + var allCharAllowed string + if security.IncludeRule.Lowercase { + allCharAllowed = fmt.Sprintf("%s%s", allCharAllowed, lowercase) + if CheckStringChar(password, lowercase) { + numberTypes++ + } } - if security.IncludeRule.Numbers && !CheckStringChar(password, number) { - check.PasswordVerifyInfo.NumbersValid = false - check.IsStrength = false + if security.IncludeRule.Uppercase { + allCharAllowed = fmt.Sprintf("%s%s", allCharAllowed, uppercase) + if CheckStringChar(password, uppercase) { + numberTypes++ + } } - if security.IncludeRule.Symbols && !CheckStringChar(password, symbol) { - check.PasswordVerifyInfo.SymbolsValid = false - check.IsStrength = false + if security.IncludeRule.Numbers { + allCharAllowed = fmt.Sprintf("%s%s", allCharAllowed, number) + if CheckStringChar(password, number) { + numberTypes++ + } } - - excludeContinuous := security.ExcludeContinuousRule - // 不能密码连续重复出现某字符 - if excludeContinuous.Repeats && !CheckContinuousRepeats( - str, excludeContinuous.Limit) { - check.PasswordVerifyInfo.RepeatsValid = false - check.IsStrength = false + if security.IncludeRule.Symbols { + allCharAllowed = fmt.Sprintf("%s%s", allCharAllowed, security.SymbolsAllowed) + if CheckStringChar(password, security.SymbolsAllowed) { + numberTypes++ + } } - // 不能包含连续的某些字符(不区分大小写) - if excludeContinuous.Letters && FindLongestCommonSubstr(strLower, lowercase) >= excludeContinuous.Limit { - check.PasswordVerifyInfo.FollowLettersValid = false + if numberTypes < security.NumberOfTypes { + check.PasswordVerifyInfo.NumberOfTypesValid = false check.IsStrength = false } - if excludeContinuous.Numbers && FindLongestCommonSubstr(strLower, number) >= excludeContinuous.Limit { - check.PasswordVerifyInfo.FollowNumbersValid = false - check.IsStrength = false + // 超过范围的字符 + var outOfRange string + for _, item := range password { + s := string(item) + if !strings.Contains(allCharAllowed, s) { + outOfRange = fmt.Sprintf("%s%s", outOfRange, s) + } } - if excludeContinuous.Symbols && FindLongestCommonSubstr(strLower, continuousSymbols) >= excludeContinuous.Limit { - check.PasswordVerifyInfo.FollowSymbolsValid = false + if outOfRange != "" { + check.PasswordVerifyInfo.AllowedValid = false + check.PasswordVerifyInfo.OutOfRange = outOfRange check.IsStrength = false } - // 不能连续的键盘序 - if excludeContinuous.Keyboards { + if security.WeakPassword { + // 不能密码连续重复出现某字符 + if !CheckContinuousRepeats(str, security.Repeats) { + check.PasswordVerifyInfo.RepeatsValid = false + check.IsStrength = false + } + // 不能包含连续的某些字符(不区分大小写) + if FindLongestCommonSubstr(strLower, lowercase) >= security.Repeats { + check.PasswordVerifyInfo.FollowLettersValid = false + check.IsStrength = false + } + if FindLongestCommonSubstr(strLower, number) >= security.Repeats { + check.PasswordVerifyInfo.FollowNumbersValid = false + check.IsStrength = false + } + if FindLongestCommonSubstr(strLower, continuousSymbols) >= security.Repeats { + check.PasswordVerifyInfo.FollowSymbolsValid = false + check.IsStrength = false + } + // 不能连续的键盘序 keyboard := append(continuousKeyboardRow, continuousKeyboardCol...) for _, v := range keyboard { - if FindLongestCommonSubstr(strLower, v) >= excludeContinuous.Limit { + if FindLongestCommonSubstr(strLower, v) >= security.Repeats { check.PasswordVerifyInfo.FollowKeyboardsValid = false check.IsStrength = false break @@ -168,7 +189,7 @@ func RandShuffle(slice *[]byte) { // FindLongestCommonSubstr 找出最长的公共字符串子串以及其长度 func FindLongestCommonSubstr(s1, s2 string) int { - var max int + var vmax int temp := make([][]int, len(s1)+1) for i := range temp { temp[i] = make([]int, len(s2)+1) @@ -177,35 +198,36 @@ func FindLongestCommonSubstr(s1, s2 string) int { for j := 0; j < len(s2); j++ { if s1[i] == s2[j] { temp[i+1][j+1] = temp[i][j] + 1 - if temp[i+1][j+1] > max { - max = temp[i+1][j+1] + if temp[i+1][j+1] > vmax { + vmax = temp[i+1][j+1] } } } } - return max + return vmax } // CheckContinuousRepeats 字符连续重复出现的次数 func CheckContinuousRepeats(str string, repeats int) bool { str = strings.ToLower(str) - var max, j int + var vmax, j int cnt := 1 for i := 1; i < len(str); i++ { if str[i] == str[j] { cnt++ } if str[i] != str[j] || i == len(str)-1 { - if cnt > max { - max = cnt + if cnt > vmax { + vmax = cnt } j = i cnt = 1 } } - return max < repeats + return vmax < repeats } +// CheckStringChar 字符串是否包含字符数组中的某个字符 func CheckStringChar(str []byte, charRange string) bool { for _, item := range str { if strings.Contains(charRange, string(item)) { diff --git a/dbm-services/mysql/db-priv/service/query_priv.go b/dbm-services/mysql/db-priv/service/query_priv.go index 72b885892e..479d70c633 100644 --- a/dbm-services/mysql/db-priv/service/query_priv.go +++ b/dbm-services/mysql/db-priv/service/query_priv.go @@ -28,17 +28,17 @@ func (m *GetPrivPara) GetUserList() ([]string, int, error) { burst := 20 // 桶容量 20 limiter := rate.NewLimiter(limit, burst) for _, item := range m.ImmuteDomains { + errLimiter := limiter.Wait(context.Background()) + if errLimiter != nil { + AddError(&errMsg, item, errLimiter) + continue + } wg.Add(1) go func(item string) { defer func() { wg.Done() }() var users []string - err := limiter.Wait(context.Background()) - if err != nil { - AddError(&errMsg, item, err) - return - } instance, err := GetCluster(*m.ClusterType, Domain{EntryName: item}) if err != nil { AddError(&errMsg, item, err) @@ -122,6 +122,11 @@ func (m *GetPrivPara) GetPriv() ([]RelatedIp, []RelatedDomain2, int, []GrantInfo burst := 20 // 桶容量 20 limiter := rate.NewLimiter(limit, burst) for _, item := range m.ImmuteDomains { + errLimiter := limiter.Wait(context.Background()) + if errLimiter != nil { + AddError(&errMsg, item, errLimiter) + continue + } wg.Add(1) go func(item string) { defer func() { @@ -136,11 +141,6 @@ func (m *GetPrivPara) GetPriv() ([]RelatedIp, []RelatedDomain2, int, []GrantInfo var userGrants []UserGrant var result []GrantInfo var matchHosts string - err = limiter.Wait(context.Background()) - if err != nil { - AddError(&errMsg, item, err) - return - } dbpriv := make(map[string][]DbPriv) if instance.ClusterType == tendbha && instance.BindTo == machineTypeProxy && !instance.PaddingProxy { tendbhaMasterDomain = true @@ -497,12 +497,12 @@ func FormatRelatedIp(source []GrantInfo, ips []string) ([]RelatedIp, []string, [ }() // 对所有的权限归类,记录每个权限细则的信息:查询ip、查询db、域名、用户、命中ip for _, priv := range grant.Privs { - IpDbDomainUserMatchIp := fmt.Sprintf("%s|%s|%s|%s|%s", grant.Ip, priv.Db, + IpDbDomainUserMatchIp := fmt.Sprintf("|%s|%s|%s|%s|%s|", grant.Ip, priv.Db, grant.ImmuteDomain, grant.User, grant.MatchIp) - IpDbDomainUser := fmt.Sprintf("%s|%s|%s|%s", grant.Ip, priv.Db, + IpDbDomainUser := fmt.Sprintf("|%s|%s|%s|%s|", grant.Ip, priv.Db, grant.ImmuteDomain, grant.User) - IpDbDomain := fmt.Sprintf("%s|%s|%s", grant.Ip, priv.Db, grant.ImmuteDomain) - IpDb := fmt.Sprintf("%s|%s", grant.Ip, priv.Db) + IpDbDomain := fmt.Sprintf("|%s|%s|%s|", grant.Ip, priv.Db, grant.ImmuteDomain) + IpDb := fmt.Sprintf("|%s|%s|", grant.Ip, priv.Db) Ip := grant.Ip UniqIpDbDomainUserMatchIp[IpDbDomainUserMatchIp] = append(UniqIpDbDomainUserMatchIp[IpDbDomainUserMatchIp], RelatedMatchDb{priv.MatchDb, priv.Priv}) @@ -518,7 +518,7 @@ func FormatRelatedIp(source []GrantInfo, ips []string) ([]RelatedIp, []string, [ for user := range UniqIpDbDomainUser { for k, v := range UniqIpDbDomainUserMatchIp { if strings.Contains(k, user) { - matchIp := strings.Split(k, "|")[4] + matchIp := strings.Split(k, "|")[5] UniqIpDbDomainUser[user] = append(UniqIpDbDomainUser[user], RelatedMatchIp{matchIp, v}) } } @@ -526,7 +526,7 @@ func FormatRelatedIp(source []GrantInfo, ips []string) ([]RelatedIp, []string, [ for domain := range UniqIpDbDomain { for k, v := range UniqIpDbDomainUser { if strings.Contains(k, domain) { - user := strings.Split(k, "|")[3] + user := strings.Split(k, "|")[4] UniqIpDbDomain[domain] = append(UniqIpDbDomain[domain], RelatedUser{user, v}) } } @@ -534,15 +534,15 @@ func FormatRelatedIp(source []GrantInfo, ips []string) ([]RelatedIp, []string, [ for db := range UniqIpDb { for k, v := range UniqIpDbDomain { if strings.Contains(k, db) { - domain := strings.Split(k, "|")[2] + domain := strings.Split(k, "|")[3] UniqIpDb[db] = append(UniqIpDb[db], RelatedDomain{domain, v}) } } } for ip := range UniqIp { for k, v := range UniqIpDb { - if strings.Contains(k, ip) { - db := strings.Split(k, "|")[1] + if strings.Contains(k, fmt.Sprintf("|%s|", ip)) { + db := strings.Split(k, "|")[2] UniqIp[ip] = append(UniqIp[ip], RelatedDb{db, v}) } } @@ -581,10 +581,10 @@ func FormatRelatedCluster(source []GrantInfo, ips []string, domains []string) ([ }() // 对所有的权限归类,记录每个权限细则的信息:域名、用户、命中ip、命中的db、权限细则 for _, priv := range grant.Privs { - DomainUserMatchIpMatchDbPriv := fmt.Sprintf("%s|%s|%s|%s|%s", grant.ImmuteDomain, + DomainUserMatchIpMatchDbPriv := fmt.Sprintf("|%s|%s|%s|%s|%s|", grant.ImmuteDomain, grant.User, grant.MatchIp, priv.MatchDb, priv.Priv) - DomainUserMatchIp := fmt.Sprintf("%s|%s|%s", grant.ImmuteDomain, grant.User, grant.MatchIp) - DomainUser := fmt.Sprintf("%s|%s", grant.ImmuteDomain, grant.User) + DomainUserMatchIp := fmt.Sprintf("|%s|%s|%s|", grant.ImmuteDomain, grant.User, grant.MatchIp) + DomainUser := fmt.Sprintf("|%s|%s|", grant.ImmuteDomain, grant.User) vDomain := grant.ImmuteDomain UniqDomainUserMatchIpMatchDbPrivTemp[DomainUserMatchIpMatchDbPriv] = append(UniqDomainUserMatchIpMatchDbPrivTemp[DomainUserMatchIpMatchDbPriv], @@ -629,8 +629,8 @@ func FormatRelatedCluster(source []GrantInfo, ips []string, domains []string) ([ for k, v := range UniqDomainUserMatchIpMatchDbPriv { if strings.Contains(k, matchip) { splits := strings.Split(k, "|") - matchDB := splits[3] - priv := splits[4] + matchDB := splits[4] + priv := splits[5] UniqDomainUserMatchIp[matchip] = append(UniqDomainUserMatchIp[matchip], RelatedMatchDb2{matchDB, priv, v}) } @@ -639,7 +639,7 @@ func FormatRelatedCluster(source []GrantInfo, ips []string, domains []string) ([ for user := range UniqDomainUser { for k, v := range UniqDomainUserMatchIp { if strings.Contains(k, user) { - matchip := strings.Split(k, "|")[2] + matchip := strings.Split(k, "|")[3] UniqDomainUser[user] = append(UniqDomainUser[user], RelatedMatchIp2{matchip, v}) } @@ -647,8 +647,8 @@ func FormatRelatedCluster(source []GrantInfo, ips []string, domains []string) ([ } for domain := range UniqDomain { for k, v := range UniqDomainUser { - if strings.Contains(k, domain) { - user := strings.Split(k, "|")[1] + if strings.Contains(k, fmt.Sprintf("|%s|", domain)) { + user := strings.Split(k, "|")[2] UniqDomain[domain] = append(UniqDomain[domain], RelatedUser2{user, v}) } diff --git a/dbm-services/mysql/db-priv/service/security_rule.go b/dbm-services/mysql/db-priv/service/security_rule.go index 4b91194dd8..71678aa2de 100644 --- a/dbm-services/mysql/db-priv/service/security_rule.go +++ b/dbm-services/mysql/db-priv/service/security_rule.go @@ -2,23 +2,25 @@ package service import ( "dbm-services/common/go-pubpkg/errno" + "dbm-services/mysql/priv-service/util" "fmt" "time" ) // ModifySecurityRule 修改安全规则 func (m *SecurityRulePara) ModifySecurityRule(jsonPara string, ticket string) error { + // 根据规则的id,修改规则内容 if m.Id == 0 { return errno.RuleIdNull } updateTime := time.Now() - rule := TbSecurityRules{Name: m.Name, Rule: m.Rule, Operator: m.Operator, UpdateTime: updateTime} + rule := TbSecurityRules{Rule: m.Rule, Operator: m.Operator, UpdateTime: updateTime} id := TbSecurityRules{Id: m.Id} result := DB.Self.Model(&id).Update(&rule) if result.Error != nil { return result.Error } - // 是否更新 + // 是否更新成功 if result.RowsAffected == 0 { return errno.RuleNotExisted } @@ -54,6 +56,7 @@ func (m *SecurityRulePara) AddSecurityRule(jsonPara string, ticket string) error // GetSecurityRule 查询安全规则 func (m *SecurityRulePara) GetSecurityRule() (*TbSecurityRules, error) { var rules []*TbSecurityRules + // 根据规则名称查询 if m.Name == "" { return nil, errno.RuleNameNull } @@ -78,8 +81,11 @@ func (m *SecurityRulePara) DeleteSecurityRule(jsonPara string, ticket string) er if err != nil { return err } - // 不允许删除密码规则,随机化默认使用的规则 - if len(rules) > 0 && rules[0].Name == "password" { + system := []string{"mysql_password", "tendbcluster_password", "redis_password_v2", "es_password", + "kafka_password", "hdfs_password", "pulsar_password", "influxdb_password", + "sqlserver_password", "mongodb_password"} + // 不允许删除各个组件默认使用的密码规则 + if len(rules) > 0 && util.HasElem(rules[0].Name, system) { return fmt.Errorf("system config can not be delete") } // 根据id删除 diff --git a/dbm-services/mysql/db-priv/service/security_rule_object.go b/dbm-services/mysql/db-priv/service/security_rule_object.go index 36814bceaf..ef72e9d6a0 100644 --- a/dbm-services/mysql/db-priv/service/security_rule_object.go +++ b/dbm-services/mysql/db-priv/service/security_rule_object.go @@ -23,20 +23,13 @@ type TbSecurityRules struct { // SecurityRule 安全规则 type SecurityRule struct { - ExcludeContinuousRule ExcludeContinuousRule `json:"exclude_continuous_rule"` // 连续字符的规则 - IncludeRule IncludeRule `json:"include_rule"` // 密码中必须包含某些字符 - MaxLength int `json:"max_length"` // 密码的最大长度 - MinLength int `json:"min_length"` // 密码的最小长度 -} - -// ExcludeContinuousRule 密码不允许连续N位出现 -type ExcludeContinuousRule struct { - Limit int `json:"limit"` //连续N位 - Letters bool `json:"letters"` //字母顺序 - Numbers bool `json:"numbers"` //数字顺序 - Symbols bool `json:"symbols"` //特殊符号顺序 - Keyboards bool `json:"keyboards"` //键盘顺序 - Repeats bool `json:"repeats"` //重复的字母、数字、特殊字符 + MaxLength int `json:"max_length"` // 密码的最大长度 + MinLength int `json:"min_length"` // 密码的最小长度 + IncludeRule IncludeRule `json:"include_rule"` // 密码组成 + NumberOfTypes int `json:"number_of_types"` // 包含任意多少种类型 + SymbolsAllowed string `json:"symbols_allowed"` // 指定特殊字符 + WeakPassword bool `json:"weak_password"` // 弱密码检查,连续N位的字母顺序、数字顺序、特殊符号顺序、键盘顺序、重复的字母、数字、特殊字符 + Repeats int `json:"repeats"` // 连续N位 } type IncludeRule struct { @@ -52,15 +45,14 @@ type CheckPasswordComplexity struct { } type PasswordVerifyInfo struct { - LowercaseValid bool `json:"lowercase_valid"` - UppercaseValid bool `json:"uppercase_valid"` - NumbersValid bool `json:"numbers_valid"` - SymbolsValid bool `json:"symbols_valid"` - RepeatsValid bool `json:"repeats_valid"` - FollowLettersValid bool `json:"follow_letters_valid"` - FollowSymbolsValid bool `json:"follow_symbols_valid"` - FollowKeyboardsValid bool `json:"follow_keyboards_valid"` - FollowNumbersValid bool `json:"follow_numbers_valid"` - MinLengthValid bool `json:"min_length_valid"` - MaxLengthValid bool `json:"max_length_valid"` + NumberOfTypesValid bool `json:"number_of_types_valid"` // ”包含任意多少种类型“的检查结果 + AllowedValid bool `json:"allowed_valid"` // 是否在允许的字符列表内 + OutOfRange string `json:"out_of_range"` // 不在允许列表的字符 + RepeatsValid bool `json:"repeats_valid"` + FollowLettersValid bool `json:"follow_letters_valid"` + FollowSymbolsValid bool `json:"follow_symbols_valid"` + FollowKeyboardsValid bool `json:"follow_keyboards_valid"` + FollowNumbersValid bool `json:"follow_numbers_valid"` + MinLengthValid bool `json:"min_length_valid"` + MaxLengthValid bool `json:"max_length_valid"` }