Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

*: support password validation options and variables #38953

Merged
merged 33 commits into from
Nov 24, 2022
Merged
Show file tree
Hide file tree
Changes from 32 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
1790641
Add system variables
CbcWestwolf Nov 7, 2022
9a3f177
TODO: checkDictionary
CbcWestwolf Nov 8, 2022
20ff355
Fix
CbcWestwolf Nov 8, 2022
1779510
TODO: add UT
CbcWestwolf Nov 8, 2022
983023a
Fix
CbcWestwolf Nov 8, 2022
21ad50c
Add basic UT
CbcWestwolf Nov 9, 2022
58460d7
Finish UT
CbcWestwolf Nov 9, 2022
44ec82e
Update UT
CbcWestwolf Nov 9, 2022
fb503f6
Merge branch 'master' of github.com:pingcap/tidb into validate_password
CbcWestwolf Nov 9, 2022
d05d9ca
TODO
CbcWestwolf Nov 11, 2022
37b9aab
TODO: add UT in builtin_encryption_vec.go and integration_test.go
CbcWestwolf Nov 14, 2022
e611e4a
Fix
CbcWestwolf Nov 14, 2022
0406cfb
Fix
CbcWestwolf Nov 14, 2022
7b83f74
Merge branch 'master' into validate_password
CbcWestwolf Nov 14, 2022
cec6fb1
Update
CbcWestwolf Nov 15, 2022
3194423
Fix
CbcWestwolf Nov 15, 2022
fe7812c
Merge branch 'master' into validate_password
CbcWestwolf Nov 15, 2022
abed795
Update
CbcWestwolf Nov 16, 2022
7a563ed
Remove comment
CbcWestwolf Nov 16, 2022
b35cd0e
create user must specify password
CbcWestwolf Nov 17, 2022
cd0c437
dictionary_file -> dictionary
CbcWestwolf Nov 17, 2022
4321cbc
Fix
CbcWestwolf Nov 17, 2022
adf75e6
Merge branch 'master' into validate_password
CbcWestwolf Nov 23, 2022
7811a1d
Merge branch 'master' of github.com:pingcap/tidb into validate_password
CbcWestwolf Nov 23, 2022
ca8fc6e
Update
CbcWestwolf Nov 23, 2022
e1024de
Fix
CbcWestwolf Nov 23, 2022
4667f3a
Fix
CbcWestwolf Nov 23, 2022
df65a25
Update executor/simple.go
CbcWestwolf Nov 24, 2022
106f7c2
Update
CbcWestwolf Nov 24, 2022
c0f2564
Merge branch 'validate_password' of github.com:CbcWestwolf/tidb into …
CbcWestwolf Nov 24, 2022
e97d7d3
Update
CbcWestwolf Nov 24, 2022
9b7e323
Update
CbcWestwolf Nov 24, 2022
cef0eb0
Merge branch 'master' into validate_password
ti-chi-bot Nov 24, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions errors.toml
Original file line number Diff line number Diff line change
Expand Up @@ -1451,6 +1451,11 @@ error = '''
SET PASSWORD has no significance for user '%-.48s'@'%-.255s' as authentication plugin does not support it.
'''

["executor:1819"]
error = '''
Your password does not satisfy the current policy requirements
'''

["executor:1827"]
error = '''
The password hash doesn't have the expected format. Check if the correct password algorithm is being used with the PASSWORD() function.
Expand Down
1 change: 1 addition & 0 deletions executor/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ go_library(
"//util/mathutil",
"//util/memory",
"//util/mvmap",
"//util/password-validation",
"//util/pdapi",
"//util/plancodec",
"//util/printer",
Expand Down
19 changes: 12 additions & 7 deletions executor/set_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -853,6 +853,14 @@ func TestSetVar(t *testing.T) {
tk.MustQuery("select @@global.tidb_opt_range_max_size").Check(testkit.Rows("1048576"))
tk.MustExec("set session tidb_opt_range_max_size = 2097152")
tk.MustQuery("select @@session.tidb_opt_range_max_size").Check(testkit.Rows("2097152"))

// test for password validation
tk.MustQuery("SELECT @@GLOBAL.validate_password.enable").Check(testkit.Rows("0"))
tk.MustQuery("SELECT @@GLOBAL.validate_password.length").Check(testkit.Rows("8"))
tk.MustExec("SET GLOBAL validate_password.length = 3")
tk.MustQuery("SELECT @@GLOBAL.validate_password.length").Check(testkit.Rows("4"))
tk.MustExec("SET GLOBAL validate_password.mixed_case_count = 2")
tk.MustQuery("SELECT @@GLOBAL.validate_password.length").Check(testkit.Rows("6"))
}

func TestGetSetNoopVars(t *testing.T) {
Expand Down Expand Up @@ -1407,14 +1415,11 @@ func TestValidateSetVar(t *testing.T) {
tk.MustExec("set @@innodb_lock_wait_timeout = 1073741825")
tk.MustQuery("show warnings").Check(testkit.RowsWithSep("|", "Warning|1292|Truncated incorrect innodb_lock_wait_timeout value: '1073741825'"))

tk.MustExec("set @@global.validate_password_number_count=-1")
tk.MustQuery("show warnings").Check(testkit.RowsWithSep("|", "Warning|1292|Truncated incorrect validate_password_number_count value: '-1'"))

tk.MustExec("set @@global.validate_password_length=-1")
tk.MustQuery("show warnings").Check(testkit.RowsWithSep("|", "Warning|1292|Truncated incorrect validate_password_length value: '-1'"))
tk.MustExec("set @@global.validate_password.number_count=-1")
tk.MustQuery("show warnings").Check(testkit.RowsWithSep("|", "Warning|1292|Truncated incorrect validate_password.number_count value: '-1'"))

tk.MustExec("set @@global.validate_password_length=8")
tk.MustQuery("show warnings").Check(testkit.Rows())
tk.MustExec("set @@global.validate_password.length=-1")
tk.MustQuery("show warnings").Check(testkit.RowsWithSep("|", "Warning|1292|Truncated incorrect validate_password.length value: '-1'"))

err = tk.ExecToErr("set @@tx_isolation=''")
require.True(t, terror.ErrorEqual(err, variable.ErrWrongValueForVar), fmt.Sprintf("err %v", err))
Expand Down
50 changes: 44 additions & 6 deletions executor/simple.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ import (
"github.com/pingcap/tidb/util/collate"
"github.com/pingcap/tidb/util/hack"
"github.com/pingcap/tidb/util/logutil"
pwdValidator "github.com/pingcap/tidb/util/password-validation"
"github.com/pingcap/tidb/util/sem"
"github.com/pingcap/tidb/util/sqlexec"
"github.com/pingcap/tidb/util/timeutil"
Expand Down Expand Up @@ -783,6 +784,23 @@ func (e *SimpleExec) executeRollback(s *ast.RollbackStmt) error {
return nil
}

func (e *SimpleExec) authUsingCleartextPwd(authOpt *ast.AuthOption, authPlugin string) bool {
if authOpt == nil || !authOpt.ByAuthString {
return false
}
return authPlugin == mysql.AuthNativePassword ||
authPlugin == mysql.AuthTiDBSM3Password ||
authPlugin == mysql.AuthCachingSha2Password
}

func (e *SimpleExec) isValidatePasswordEnabled() bool {
validatePwdEnable, err := e.ctx.GetSessionVars().GlobalVarsAccessor.GetGlobalSysVar(variable.ValidatePasswordEnable)
if err != nil {
return false
}
return variable.TiDBOptOn(validatePwdEnable)
}

func (e *SimpleExec) executeCreateUser(ctx context.Context, s *ast.CreateUserStmt) error {
internalCtx := kv.WithInternalSourceType(context.Background(), kv.InternalTxnPrivilege)
// Check `CREATE USER` privilege.
Expand Down Expand Up @@ -874,15 +892,25 @@ func (e *SimpleExec) executeCreateUser(ctx context.Context, s *ast.CreateUserStm
e.ctx.GetSessionVars().StmtCtx.AppendNote(err)
continue
}
authPlugin := mysql.AuthNativePassword
if spec.AuthOpt != nil && spec.AuthOpt.AuthPlugin != "" {
authPlugin = spec.AuthOpt.AuthPlugin
}
if e.isValidatePasswordEnabled() && !s.IsCreateRole {
if spec.AuthOpt == nil || !spec.AuthOpt.ByAuthString && spec.AuthOpt.HashString == "" {
return variable.ErrNotValidPassword.GenWithStackByArgs()
}
if e.authUsingCleartextPwd(spec.AuthOpt, authPlugin) {
if err := pwdValidator.ValidatePassword(e.ctx.GetSessionVars(), spec.AuthOpt.AuthString); err != nil {
return err
}
}
}
pwd, ok := spec.EncodedPassword()

if !ok {
return errors.Trace(ErrPasswordFormat)
}
authPlugin := mysql.AuthNativePassword
if spec.AuthOpt != nil && spec.AuthOpt.AuthPlugin != "" {
authPlugin = spec.AuthOpt.AuthPlugin
}

switch authPlugin {
case mysql.AuthNativePassword, mysql.AuthCachingSha2Password, mysql.AuthTiDBSM3Password, mysql.AuthSocket, mysql.AuthTiDBAuthToken:
Expand Down Expand Up @@ -1071,11 +1099,11 @@ func (e *SimpleExec) executeAlterUser(ctx context.Context, s *ast.AlterUserStmt)
var fields []alterField
if spec.AuthOpt != nil {
if spec.AuthOpt.AuthPlugin == "" {
authplugin, err := e.userAuthPlugin(spec.User.Username, spec.User.Hostname)
curAuthplugin, err := e.userAuthPlugin(spec.User.Username, spec.User.Hostname)
if err != nil {
return err
}
spec.AuthOpt.AuthPlugin = authplugin
spec.AuthOpt.AuthPlugin = curAuthplugin
}
switch spec.AuthOpt.AuthPlugin {
case mysql.AuthNativePassword, mysql.AuthCachingSha2Password, mysql.AuthTiDBSM3Password, mysql.AuthSocket, "":
Expand All @@ -1087,6 +1115,11 @@ func (e *SimpleExec) executeAlterUser(ctx context.Context, s *ast.AlterUserStmt)
default:
return ErrPluginIsNotLoaded.GenWithStackByArgs(spec.AuthOpt.AuthPlugin)
}
if e.isValidatePasswordEnabled() && e.authUsingCleartextPwd(spec.AuthOpt, spec.AuthOpt.AuthPlugin) {
if err := pwdValidator.ValidatePassword(e.ctx.GetSessionVars(), spec.AuthOpt.AuthString); err != nil {
return err
}
}
pwd, ok := spec.EncodedPassword()
if !ok {
return errors.Trace(ErrPasswordFormat)
Expand Down Expand Up @@ -1603,6 +1636,11 @@ func (e *SimpleExec) executeSetPwd(ctx context.Context, s *ast.SetPwdStmt) error
if err != nil {
return err
}
if e.isValidatePasswordEnabled() {
if err := pwdValidator.ValidatePassword(e.ctx.GetSessionVars(), s.Password); err != nil {
return err
}
}
var pwd string
switch authplugin {
case mysql.AuthCachingSha2Password, mysql.AuthTiDBSM3Password:
Expand Down
83 changes: 83 additions & 0 deletions executor/simple_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,3 +129,86 @@ func TestUserAttributes(t *testing.T) {
rootTK.MustExec("alter user usr1 comment 'comment1'")
rootTK.MustQuery("select user_attributes from mysql.user where user = 'usr1'").Check(testkit.Rows(`{"metadata": {"comment": "comment1"}}`))
}

func TestValidatePassword(t *testing.T) {
store, _ := testkit.CreateMockStoreAndDomain(t)
tk := testkit.NewTestKit(t, store)
subtk := testkit.NewTestKit(t, store)
err := tk.Session().Auth(&auth.UserIdentity{Username: "root", Hostname: "%"}, nil, nil)
require.NoError(t, err)
tk.MustExec("CREATE USER ''@'localhost'")
tk.MustExec("GRANT ALL PRIVILEGES ON mysql.* TO ''@'localhost';")
err = subtk.Session().Auth(&auth.UserIdentity{Hostname: "localhost"}, nil, nil)
require.NoError(t, err)

authPlugins := []string{mysql.AuthNativePassword, mysql.AuthCachingSha2Password, mysql.AuthTiDBSM3Password}
tk.MustQuery("SELECT @@global.validate_password.enable").Check(testkit.Rows("0"))
tk.MustExec("SET GLOBAL validate_password.enable = 1")
tk.MustQuery("SELECT @@global.validate_password.enable").Check(testkit.Rows("1"))

for _, authPlugin := range authPlugins {
tk.MustExec("DROP USER IF EXISTS testuser")
tk.MustExec(fmt.Sprintf("CREATE USER testuser IDENTIFIED WITH %s BY '!Abc12345678'", authPlugin))

tk.MustExec("SET GLOBAL validate_password.policy = 'LOW'")
// check user name
tk.MustQuery("SELECT @@global.validate_password.check_user_name").Check(testkit.Rows("1"))
tk.MustContainErrMsg("ALTER USER testuser IDENTIFIED BY '!Abcdroot1234'", "Password Contains User Name")
tk.MustContainErrMsg("ALTER USER testuser IDENTIFIED BY '!Abcdtoor1234'", "Password Contains Reversed User Name")
tk.MustExec("SET PASSWORD FOR 'testuser' = 'testuser'") // password the same as the user name, but run by root
tk.MustExec("ALTER USER testuser IDENTIFIED BY 'testuser'")
tk.MustExec("SET GLOBAL validate_password.check_user_name = 0")
tk.MustExec("ALTER USER testuser IDENTIFIED BY '!Abcdroot1234'")
tk.MustExec("ALTER USER testuser IDENTIFIED BY '!Abcdtoor1234'")
tk.MustExec("SET GLOBAL validate_password.check_user_name = 1")

// LOW: Length
tk.MustExec("SET GLOBAL validate_password.length = 8")
tk.MustQuery("SELECT @@global.validate_password.length").Check(testkit.Rows("8"))
tk.MustContainErrMsg("ALTER USER testuser IDENTIFIED BY '1234567'", "Require Password Length: 8")
tk.MustExec("SET GLOBAL validate_password.length = 12")
tk.MustContainErrMsg("ALTER USER testuser IDENTIFIED BY '!Abcdefg123'", "Require Password Length: 12")
tk.MustExec("ALTER USER testuser IDENTIFIED BY '!Abcdefg1234'")
tk.MustExec("SET GLOBAL validate_password.length = 8")

// MEDIUM: Length; numeric, lowercase/uppercase, and special characters
tk.MustExec("SET GLOBAL validate_password.policy = 'MEDIUM'")
tk.MustExec("ALTER USER testuser IDENTIFIED BY '!Abc1234567'")
tk.MustContainErrMsg("ALTER USER testuser IDENTIFIED BY '!ABC1234567'", "Require Password Lowercase Count: 1")
tk.MustContainErrMsg("ALTER USER testuser IDENTIFIED BY '!abc1234567'", "Require Password Uppercase Count: 1")
tk.MustContainErrMsg("ALTER USER testuser IDENTIFIED BY '!ABCDabcd'", "Require Password Digit Count: 1")
tk.MustContainErrMsg("ALTER USER testuser IDENTIFIED BY 'Abc1234567'", "Require Password Non-alphanumeric Count: 1")
tk.MustExec("SET GLOBAL validate_password.special_char_count = 0")
tk.MustExec("ALTER USER testuser IDENTIFIED BY 'Abc1234567'")
tk.MustExec("SET GLOBAL validate_password.special_char_count = 1")
tk.MustExec("SET GLOBAL validate_password.length = 3")
tk.MustQuery("SELECT @@GLOBAL.validate_password.length").Check(testkit.Rows("4"))

// STRONG: Length; numeric, lowercase/uppercase, and special characters; dictionary file
tk.MustExec("SET GLOBAL validate_password.policy = 'STRONG'")
tk.MustExec("ALTER USER testuser IDENTIFIED BY '!Abc1234567'")
tk.MustExec(fmt.Sprintf("SET GLOBAL validate_password.dictionary = '%s'", "1234;5678"))
tk.MustExec("ALTER USER testuser IDENTIFIED BY '!Abc123567'")
tk.MustExec("ALTER USER testuser IDENTIFIED BY '!Abc43218765'")
tk.MustContainErrMsg("ALTER USER testuser IDENTIFIED BY '!Abc1234567'", "Password contains word in the dictionary")
tk.MustExec("SET GLOBAL validate_password.dictionary = ''")
tk.MustExec("ALTER USER testuser IDENTIFIED BY '!Abc1234567'")

// "IDENTIFIED AS 'xxx'" is not affected by validation
tk.MustExec(fmt.Sprintf("ALTER USER testuser IDENTIFIED WITH '%s' AS ''", authPlugin))
}
tk.MustContainErrMsg("CREATE USER 'testuser1'@'localhost'", "Your password does not satisfy the current policy requirements")
tk.MustContainErrMsg("CREATE USER 'testuser1'@'localhost' IDENTIFIED WITH 'caching_sha2_password'", "Your password does not satisfy the current policy requirements")
tk.MustContainErrMsg("CREATE USER 'testuser1'@'localhost' IDENTIFIED WITH 'caching_sha2_password' AS ''", "Your password does not satisfy the current policy requirements")

// if the username is '', all password can pass the check_user_name
subtk.MustQuery("SELECT user(), current_user()").Check(testkit.Rows("@localhost @localhost"))
subtk.MustQuery("SELECT @@global.validate_password.check_user_name").Check(testkit.Rows("1"))
subtk.MustQuery("SELECT @@global.validate_password.enable").Check(testkit.Rows("1"))
subtk.MustExec("ALTER USER ''@'localhost' IDENTIFIED BY ''")
subtk.MustExec("ALTER USER ''@'localhost' IDENTIFIED BY 'abcd'")

// CREATE ROLE is not affected by password validation
tk.MustExec("SET GLOBAL validate_password.enable = 1")
tk.MustExec("CREATE ROLE role1")
}
1 change: 1 addition & 0 deletions expression/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ go_library(
"//util/mathutil",
"//util/mock",
"//util/parser",
"//util/password-validation",
"//util/plancodec",
"//util/printer",
"//util/sem",
Expand Down
66 changes: 64 additions & 2 deletions expression/builtin_encryption.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import (
"github.com/pingcap/tidb/types"
"github.com/pingcap/tidb/util/chunk"
"github.com/pingcap/tidb/util/encrypt"
pwdValidator "github.com/pingcap/tidb/util/password-validation"
"github.com/pingcap/tipb/go-tipb"
)

Expand Down Expand Up @@ -73,6 +74,7 @@ var (
_ builtinFunc = &builtinSHA2Sig{}
_ builtinFunc = &builtinUncompressSig{}
_ builtinFunc = &builtinUncompressedLengthSig{}
_ builtinFunc = &builtinValidatePasswordStrengthSig{}
)

// aesModeAttr indicates that the key length and iv attribute for specific block_encryption_mode.
Expand Down Expand Up @@ -728,7 +730,6 @@ func (c *sm3FunctionClass) getFunction(ctx sessionctx.Context, args []Expression
bf.tp.SetCollate(collate)
bf.tp.SetFlen(40)
sig := &builtinSM3Sig{bf}
//sig.setPbCode(tipb.ScalarFuncSig_SM3) // TODO
return sig, nil
}

Expand Down Expand Up @@ -1010,5 +1011,66 @@ type validatePasswordStrengthFunctionClass struct {
}

func (c *validatePasswordStrengthFunctionClass) getFunction(ctx sessionctx.Context, args []Expression) (builtinFunc, error) {
return nil, errFunctionNotExists.GenWithStackByArgs("FUNCTION", "VALIDATE_PASSWORD_STRENGTH")
if err := c.verifyArgs(args); err != nil {
return nil, err
}
bf, err := newBaseBuiltinFuncWithTp(ctx, c.funcName, args, types.ETInt, types.ETString)
if err != nil {
return nil, err
}
bf.tp.SetFlen(21)
sig := &builtinValidatePasswordStrengthSig{bf}
return sig, nil
}

type builtinValidatePasswordStrengthSig struct {
baseBuiltinFunc
}

func (b *builtinValidatePasswordStrengthSig) Clone() builtinFunc {
newSig := &builtinValidatePasswordStrengthSig{}
newSig.cloneFrom(&b.baseBuiltinFunc)
return newSig
}

// evalInt evals VALIDATE_PASSWORD_STRENGTH(str).
// See https://dev.mysql.com/doc/refman/8.0/en/encryption-functions.html#function_validate-password-strength
func (b *builtinValidatePasswordStrengthSig) evalInt(row chunk.Row) (int64, bool, error) {
globalVars := b.ctx.GetSessionVars().GlobalVarsAccessor
str, isNull, err := b.args[0].EvalString(b.ctx, row)
if err != nil || isNull {
return 0, true, err
} else if len([]rune(str)) < 4 {
return 0, false, nil
}
if validation, err := globalVars.GetGlobalSysVar(variable.ValidatePasswordEnable); err != nil {
return 0, true, err
} else if !variable.TiDBOptOn(validation) {
return 0, false, nil
}
return b.validateStr(str, &globalVars)
}

func (b *builtinValidatePasswordStrengthSig) validateStr(str string, globalVars *variable.GlobalVarAccessor) (int64, bool, error) {
if warn, err := pwdValidator.ValidateUserNameInPassword(str, b.ctx.GetSessionVars()); err != nil {
return 0, true, err
} else if len(warn) > 0 {
return 0, false, nil
}
if warn, err := pwdValidator.ValidatePasswordLowPolicy(str, globalVars); err != nil {
return 0, true, err
} else if len(warn) > 0 {
return 25, false, nil
}
if warn, err := pwdValidator.ValidatePasswordMediumPolicy(str, globalVars); err != nil {
return 0, true, err
} else if len(warn) > 0 {
return 50, false, nil
}
if ok, err := pwdValidator.ValidateDictionaryPassword(str, globalVars); err != nil {
return 0, true, err
} else if !ok {
return 75, false, nil
}
return 100, false, nil
}
Loading