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 cb5a5d83e0..03ccd4fb5d 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 65a275c261..86c62e52cd 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 @@ -133,47 +133,30 @@ func GetUserGantSql(needShowCreateUser bool, userHost, address string, bkCloudId sql string grants []string ) + var sqls []string if needShowCreateUser { sql = fmt.Sprintf("show create user %s;", userHost) - res, err := GetGrantResponse(sql, address, bkCloudId) - if err != nil { - return grants, err - } - grants = append(grants, res...) - } - sql = fmt.Sprintf("show grants for %s ", userHost) - res, err := GetGrantResponse(sql, address, bkCloudId) - if err != nil { - return grants, err + sqls = append(sqls, sql) } - grants = append(grants, res...) - if len(grants) == 0 { - return grants, fmt.Errorf("show grants in %s fail,query return nothing", userHost) - } - return grants, nil -} - -// GetGrantResponse 执行sql语句,获取结果 -func GetGrantResponse(sql, address string, bkCloudId int64) ([]string, error) { - var grants []string - queryRequest := QueryRequest{[]string{address}, []string{sql}, true, 60, bkCloudId} + sqls = append(sqls, fmt.Sprintf("show grants for %s ", userHost)) + queryRequest := QueryRequest{[]string{address}, sqls, true, 60, bkCloudId} reps, err := OneAddressExecuteSql(queryRequest) if err != nil { - return grants, fmt.Errorf("execute (%s) fail, error:%s", sql, err.Error()) + return grants, fmt.Errorf("execute (%s) fail, error:%s", sqls, err.Error()) } - - if len(reps.CmdResults[0].TableData) > 0 { - for _, item := range reps.CmdResults[0].TableData { + for _, data := range reps.CmdResults { + for _, item := range data.TableData { for _, grant := range item { if grant != nil { grants = append(grants, grant.(string)) } else { - return grants, fmt.Errorf("execute (%s), content of return is null", sql) + return grants, fmt.Errorf("execute (%s), content of return is null", sqls) } } } - } else { - return grants, nil + } + if len(grants) == 0 { + return grants, fmt.Errorf("show grants in %s fail,query return nothing", userHost) } return grants, nil } 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..654bac5ed4 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) @@ -99,13 +99,26 @@ func (m *GetPrivPara) GetUserList() ([]string, int, error) { if len(errMsg.errs) > 0 { return userList.l, count, errno.QueryPrivilegesFail.Add("\n" + strings.Join(errMsg.errs, "\n")) } - return userList.l, len(userList.l), nil + var uniqUser = make(map[string]struct{}) + var users []string + for _, user := range userList.l { + if _, isExists := uniqUser[user]; isExists == false { + uniqUser[user] = struct{}{} + users = append(users, user) + } + } + return users, len(users), nil } // GetPriv 获取权限 func (m *GetPrivPara) GetPriv() ([]RelatedIp, []RelatedDomain2, int, []GrantInfo, []string, []string, error) { + type PrivMu struct { + mu sync.RWMutex + resources []GrantInfo + } + var ( - all []GrantInfo + all PrivMu count int errMsg Err ) @@ -118,10 +131,15 @@ func (m *GetPrivPara) GetPriv() ([]RelatedIp, []RelatedDomain2, int, []GrantInfo } users := strings.Join(m.Users, "','") wg := sync.WaitGroup{} - limit := rate.Every(time.Millisecond * 50) // QPS:20 - burst := 20 // 桶容量 20 + limit := rate.Every(time.Millisecond * 200) // 并发查询集群数量 QPS 5 + burst := 5 // 桶容量 5 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 +154,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 @@ -156,6 +169,7 @@ func (m *GetPrivPara) GetPriv() ([]RelatedIp, []RelatedDomain2, int, []GrantInfo for _, storage := range instance.Storages { if storage.InstanceRole == backendMaster { address := fmt.Sprintf("%s:%d", storage.IP, storage.Port) + slog.Info("msg", "backend", address) userGrants, err = GetRemotePrivilege(address, proxy.IP, instance.BkCloudId, machineTypeBackend, users, true) if err != nil { @@ -168,6 +182,7 @@ func (m *GetPrivPara) GetPriv() ([]RelatedIp, []RelatedDomain2, int, []GrantInfo } } address := fmt.Sprintf("%s:%d", proxy.IP, proxy.AdminPort) + slog.Info("msg", "proxy", address) // 在proxy查询查询user@ip result, _, _, err = ProxyWhiteList(address, instance.BkCloudId, m.Ips, m.Users, item) if err != nil { @@ -196,9 +211,11 @@ func (m *GetPrivPara) GetPriv() ([]RelatedIp, []RelatedDomain2, int, []GrantInfo } } else { AddError(&errMsg, item, fmt.Errorf("wrong cluster type %s", instance.ClusterType)) + return } if address == "" { AddError(&errMsg, item, fmt.Errorf("no instance found")) + return } // 在后端mysql中获取匹配的user@host列表 result, _, matchHosts, err = MysqlUserList(address, instance.BkCloudId, m.Ips, m.Users, item) @@ -228,20 +245,25 @@ func (m *GetPrivPara) GetPriv() ([]RelatedIp, []RelatedDomain2, int, []GrantInfo // mysql中的账号与权限相结合 result = CombineUserWithGrant(result, dbpriv, tendbhaMasterDomain) } - all = append(all, result...) + slog.Info("msg", "SplitGrantSql", dbpriv) + slog.Info("msg", "CombineUserWithGrant", result) + all.mu.Lock() + all.resources = append(all.resources, result...) + all.mu.Unlock() }(item) } wg.Wait() if len(errMsg.errs) > 0 { return nil, nil, count, nil, nil, nil, errno.QueryPrivilegesFail.Add("\n" + strings.Join(errMsg.errs, "\n")) } + slog.Info("msg", "all.resources", all.resources) // 以访问源的ip等维度聚合展示 if m.Format == "ip" { - formatted, hasPriv, noPriv := FormatRelatedIp(all, m.Ips) - return formatted, nil, len(formatted), all, hasPriv, noPriv, nil + formatted, hasPriv, noPriv := FormatRelatedIp(all.resources, m.Ips) + return formatted, nil, len(formatted), all.resources, hasPriv, noPriv, nil } else if m.Format == "cluster" { - formatted, hasPriv, noPriv := FormatRelatedCluster(all, m.Ips, m.ImmuteDomains) - return nil, formatted, len(formatted), all, hasPriv, noPriv, nil + formatted, hasPriv, noPriv := FormatRelatedCluster(all.resources, m.Ips, m.ImmuteDomains) + return nil, formatted, len(formatted), all.resources, hasPriv, noPriv, nil } else { return nil, nil, 0, nil, nil, nil, fmt.Errorf("not supported display format") } @@ -497,12 +519,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 +540,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 +548,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 +556,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 +603,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 +651,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 +661,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 +669,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"` }