Skip to content

Commit

Permalink
feat: 密码安全新版本_授权规则创建账号不允许用内部账号名 TencentBlueKing#6680
Browse files Browse the repository at this point in the history
  • Loading branch information
fanfanyangyang committed Sep 12, 2024
1 parent 6c13511 commit d538d6b
Show file tree
Hide file tree
Showing 9 changed files with 214 additions and 138 deletions.
9 changes: 5 additions & 4 deletions dbm-services/common/go-pubpkg/errno/50000_dbpriv_code.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,9 @@ 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: "不允许使用内部账号名称"}
)
94 changes: 69 additions & 25 deletions dbm-services/mysql/db-priv/assests/migrate.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,42 +78,71 @@ 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}"
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}"
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}"
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}"
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 {
Component string
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"}})
Expand All @@ -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())
Expand All @@ -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
Expand Down
12 changes: 12 additions & 0 deletions dbm-services/mysql/db-priv/service/account.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package service

import (
"dbm-services/mysql/priv-service/util"
"encoding/hex"
"fmt"
"log/slog"
Expand Down Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
5 changes: 2 additions & 3 deletions dbm-services/mysql/db-priv/service/db_meta_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
122 changes: 72 additions & 50 deletions dbm-services/mysql/db-priv/service/generate_random_string.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,12 @@ import (
const lowercase = "abcdefghijklmnopqrstuvwxyz"
const uppercase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
const number = "0123456789"
const symbol = `!#%&()*+,-./;<=>?[]^_{|}~@:$` // 剔除 " ' ` \

const limitCnt = 4 // 连续4位的 字母顺序、数字顺序、特殊符号顺序、键盘顺序、重复的字母、数字、特殊字符
// 为密码池添加连续的字母序,数字序,特殊字符序和键盘序
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 {
Expand All @@ -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])
Expand Down Expand Up @@ -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
Expand All @@ -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, limitCnt) {
check.PasswordVerifyInfo.RepeatsValid = false
check.IsStrength = false
}
// 不能包含连续的某些字符(不区分大小写)
if FindLongestCommonSubstr(strLower, lowercase) >= limitCnt {
check.PasswordVerifyInfo.FollowLettersValid = false
check.IsStrength = false
}
if FindLongestCommonSubstr(strLower, number) >= limitCnt {
check.PasswordVerifyInfo.FollowNumbersValid = false
check.IsStrength = false
}
if FindLongestCommonSubstr(strLower, continuousSymbols) >= limitCnt {
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) >= limitCnt {
check.PasswordVerifyInfo.FollowKeyboardsValid = false
check.IsStrength = false
break
Expand All @@ -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)
Expand All @@ -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)) {
Expand Down
Loading

0 comments on commit d538d6b

Please sign in to comment.