mirror of
https://github.com/MariaDB/server.git
synced 2025-01-15 19:42:28 +01:00
MDEV-7598 Lock user after too many password errors
This commit is contained in:
parent
30da40bb8c
commit
83c81d8991
17 changed files with 305 additions and 0 deletions
13
mysql-test/include/have_auth_named_pipe.inc
Normal file
13
mysql-test/include/have_auth_named_pipe.inc
Normal file
|
@ -0,0 +1,13 @@
|
|||
--source include/not_embedded.inc
|
||||
|
||||
if (!$AUTH_NAMED_PIPE_SO) {
|
||||
skip No auth_named_pipe plugin;
|
||||
}
|
||||
|
||||
if (!$USERNAME) {
|
||||
skip USER variable is undefined;
|
||||
}
|
||||
|
||||
if (`SELECT count(*) <> 0 FROM mysql.user WHERE user = '$USERNAME'`) {
|
||||
skip %USERNAME%=$USER which exists in mysql.user;
|
||||
}
|
45
mysql-test/main/max_password_errors.result
Normal file
45
mysql-test/main/max_password_errors.result
Normal file
|
@ -0,0 +1,45 @@
|
|||
set @old_max_password_errors=@@max_password_errors;
|
||||
set global max_password_errors=2;
|
||||
create user u identified by 'good_pass';
|
||||
connect(localhost,u,bas_pass,test,MASTER_PORT,MASTER_SOCKET);
|
||||
connect con1, localhost, u, bas_pass;
|
||||
ERROR 28000: Access denied for user 'u'@'localhost' (using password: YES)
|
||||
connect(localhost,u,bad_pass,test,MASTER_PORT,MASTER_SOCKET);
|
||||
connect con1, localhost, u, bad_pass;
|
||||
ERROR 28000: Access denied for user 'u'@'localhost' (using password: YES)
|
||||
connect(localhost,u,good_pass,test,MASTER_PORT,MASTER_SOCKET);
|
||||
connect con1, localhost, u, good_pass;
|
||||
ERROR HY000: User is blocked because of too many credential errors; unblock with 'FLUSH PRIVILEGES'
|
||||
connect(localhost,u,bad_pass,test,MASTER_PORT,MASTER_SOCKET);
|
||||
connect con1, localhost, u, bad_pass;
|
||||
ERROR HY000: User is blocked because of too many credential errors; unblock with 'FLUSH PRIVILEGES'
|
||||
FLUSH PRIVILEGES;
|
||||
connect con1, localhost, u, good_pass;
|
||||
disconnect con1;
|
||||
connect(localhost,u,bad_pass,test,MASTER_PORT,MASTER_SOCKET);
|
||||
connect con1, localhost, u, bad_pass;
|
||||
ERROR 28000: Access denied for user 'u'@'localhost' (using password: YES)
|
||||
connect con1, localhost, u, good_pass;
|
||||
disconnect con1;
|
||||
connect(localhost,u,bad_pass,test,MASTER_PORT,MASTER_SOCKET);
|
||||
connect con1, localhost, u, bad_pass;
|
||||
ERROR 28000: Access denied for user 'u'@'localhost' (using password: YES)
|
||||
connect con1, localhost, u, good_pass;
|
||||
ERROR 28000: Access denied for user 'u'@'localhost' (using password: YES)
|
||||
ERROR 28000: Access denied for user 'u'@'localhost' (using password: YES)
|
||||
ERROR HY000: User is blocked because of too many credential errors; unblock with 'FLUSH PRIVILEGES'
|
||||
disconnect con1;
|
||||
connection default;
|
||||
FLUSH PRIVILEGES;
|
||||
connect(localhost,root,bas_pass,test,MASTER_PORT,MASTER_SOCKET);
|
||||
connect con1, localhost, root, bas_pass;
|
||||
ERROR 28000: Access denied for user 'root'@'localhost' (using password: YES)
|
||||
connect(localhost,root,bad_pass,test,MASTER_PORT,MASTER_SOCKET);
|
||||
connect con1, localhost, root, bad_pass;
|
||||
ERROR 28000: Access denied for user 'root'@'localhost' (using password: YES)
|
||||
connect con1, localhost, u, good_pass;
|
||||
disconnect con1;
|
||||
connection default;
|
||||
DROP USER u;
|
||||
FLUSH PRIVILEGES;
|
||||
set global max_password_errors=@old_max_password_errors;
|
64
mysql-test/main/max_password_errors.test
Normal file
64
mysql-test/main/max_password_errors.test
Normal file
|
@ -0,0 +1,64 @@
|
|||
--source include/not_embedded.inc
|
||||
set @old_max_password_errors=@@max_password_errors;
|
||||
set global max_password_errors=2;
|
||||
create user u identified by 'good_pass';
|
||||
|
||||
# Test that user is blocked after 'max_password_errors' bad passwords
|
||||
--replace_result $MASTER_MYSOCK MASTER_SOCKET $MASTER_MYPORT MASTER_PORT
|
||||
error ER_ACCESS_DENIED_ERROR;
|
||||
connect(con1, localhost, u, bas_pass);
|
||||
--replace_result $MASTER_MYSOCK MASTER_SOCKET $MASTER_MYPORT MASTER_PORT
|
||||
error ER_ACCESS_DENIED_ERROR;
|
||||
connect (con1, localhost, u, bad_pass);
|
||||
--replace_result $MASTER_MYSOCK MASTER_SOCKET $MASTER_MYPORT MASTER_PORT
|
||||
error ER_USER_IS_BLOCKED;
|
||||
connect(con1, localhost, u, good_pass);
|
||||
--replace_result $MASTER_MYSOCK MASTER_SOCKET $MASTER_MYPORT MASTER_PORT
|
||||
error ER_USER_IS_BLOCKED;
|
||||
connect(con1, localhost, u, bad_pass);
|
||||
|
||||
|
||||
# Test that FLUSH PRIVILEGES clears the error
|
||||
FLUSH PRIVILEGES;
|
||||
connect (con1, localhost, u, good_pass);
|
||||
disconnect con1;
|
||||
|
||||
# Test that good login clears the error
|
||||
--replace_result $MASTER_MYSOCK MASTER_SOCKET $MASTER_MYPORT MASTER_PORT
|
||||
error ER_ACCESS_DENIED_ERROR;
|
||||
connect (con1, localhost, u, bad_pass);
|
||||
connect (con1, localhost, u, good_pass);
|
||||
disconnect con1;
|
||||
--replace_result $MASTER_MYSOCK MASTER_SOCKET $MASTER_MYPORT MASTER_PORT
|
||||
error ER_ACCESS_DENIED_ERROR;
|
||||
connect (con1, localhost, u, bad_pass);
|
||||
connect (con1, localhost, u, good_pass);
|
||||
|
||||
# Test the behavior of change_user
|
||||
--replace_result $MASTER_MYSOCK MASTER_SOCKET $MASTER_MYPORT MASTER_PORT
|
||||
error ER_ACCESS_DENIED_ERROR;
|
||||
change_user u,bad_pass;
|
||||
--replace_result $MASTER_MYSOCK MASTER_SOCKET $MASTER_MYPORT MASTER_PORT
|
||||
error ER_ACCESS_DENIED_ERROR;
|
||||
change_user u,bad_pass;
|
||||
--replace_result $MASTER_MYSOCK MASTER_SOCKET $MASTER_MYPORT MASTER_PORT
|
||||
error ER_USER_IS_BLOCKED;
|
||||
change_user u,good_pass;
|
||||
disconnect con1;
|
||||
|
||||
connection default;
|
||||
FLUSH PRIVILEGES;
|
||||
|
||||
#Test that root@localhost is not blocked, with password errors
|
||||
--replace_result $MASTER_MYSOCK MASTER_SOCKET $MASTER_MYPORT MASTER_PORT
|
||||
error ER_ACCESS_DENIED_ERROR;
|
||||
connect(con1, localhost, root, bas_pass);
|
||||
--replace_result $MASTER_MYSOCK MASTER_SOCKET $MASTER_MYPORT MASTER_PORT
|
||||
error ER_ACCESS_DENIED_ERROR;
|
||||
connect (con1, localhost, root, bad_pass);
|
||||
connect (con1, localhost, u, good_pass);
|
||||
disconnect con1;
|
||||
connection default;
|
||||
DROP USER u;
|
||||
FLUSH PRIVILEGES;
|
||||
set global max_password_errors=@old_max_password_errors;
|
|
@ -546,6 +546,10 @@ The following specify which files/extra groups are read (specified before remain
|
|||
The maximum BLOB length to send to server from
|
||||
mysql_send_long_data API. Deprecated option; use
|
||||
max_allowed_packet instead.
|
||||
--max-password-errors=#
|
||||
If there is more than this number of failed connect
|
||||
attempts due to invalid password, user will be blocked
|
||||
from further connections until FLUSH_PRIVILEGES.
|
||||
--max-prepared-stmt-count=#
|
||||
Maximum number of prepared statements in the server
|
||||
--max-recursive-iterations[=#]
|
||||
|
@ -1518,6 +1522,7 @@ max-heap-table-size 16777216
|
|||
max-join-size 18446744073709551615
|
||||
max-length-for-sort-data 1024
|
||||
max-long-data-size 16777216
|
||||
max-password-errors 18446744073709551615
|
||||
max-prepared-stmt-count 16382
|
||||
max-recursive-iterations 18446744073709551615
|
||||
max-relay-log-size 1073741824
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
set @old_max_password_errors=@@max_password_errors;
|
||||
create user nosuchuser identified with 'named_pipe';
|
||||
set global max_password_errors=1;
|
||||
connect(localhost,nosuchuser,,test,MASTER_PORT,MASTER_SOCKET);
|
||||
connect pipe_con,localhost,nosuchuser,,,,,PIPE;
|
||||
ERROR 28000: Access denied for user 'nosuchuser'@'localhost'
|
||||
connect(localhost,nosuchuser,,test,MASTER_PORT,MASTER_SOCKET);
|
||||
connect pipe_con,localhost,nosuchuser,,,,,PIPE;
|
||||
ERROR 28000: Access denied for user 'nosuchuser'@'localhost'
|
||||
DROP USER nosuchuser;
|
||||
FLUSH PRIVILEGES;
|
||||
set global max_password_errors=@old_max_password_errors;
|
|
@ -0,0 +1,12 @@
|
|||
set @old_max_password_errors=@@max_password_errors;
|
||||
create user nosuchuser identified with 'unix_socket';
|
||||
set global max_password_errors=1;
|
||||
connect(localhost,nosuchuser,,test,MASTER_PORT,MASTER_SOCKET);
|
||||
connect pipe_con,localhost,nosuchuser;
|
||||
ERROR 28000: Access denied for user 'nosuchuser'@'localhost'
|
||||
connect(localhost,nosuchuser,,test,MASTER_PORT,MASTER_SOCKET);
|
||||
connect pipe_con,localhost,nosuchuser;
|
||||
ERROR 28000: Access denied for user 'nosuchuser'@'localhost'
|
||||
DROP USER nosuchuser;
|
||||
FLUSH PRIVILEGES;
|
||||
set global max_password_errors=@old_max_password_errors;
|
|
@ -0,0 +1 @@
|
|||
--loose-enable-named-pipe --plugin-load=$AUTH_NAMED_PIPE_SO
|
|
@ -0,0 +1,22 @@
|
|||
# Tests that max_password_errors has no effect on login errors with
|
||||
# passwordless plugins (Windows version / auth_named_pipe)
|
||||
|
||||
--source include/not_embedded.inc
|
||||
--source include/have_auth_named_pipe.inc
|
||||
if (`SELECT '$USERNAME' = 'nosuchuser'`) {
|
||||
skip skipped for nosuchuser;
|
||||
}
|
||||
set @old_max_password_errors=@@max_password_errors;
|
||||
create user nosuchuser identified with 'named_pipe';
|
||||
|
||||
set global max_password_errors=1;
|
||||
--replace_result $MASTER_MYSOCK MASTER_SOCKET $MASTER_MYPORT MASTER_PORT
|
||||
error ER_ACCESS_DENIED_NO_PASSWORD_ERROR;
|
||||
connect(pipe_con,localhost,nosuchuser,,,,,PIPE);
|
||||
--replace_result $MASTER_MYSOCK MASTER_SOCKET $MASTER_MYPORT MASTER_PORT
|
||||
error ER_ACCESS_DENIED_NO_PASSWORD_ERROR;
|
||||
connect(pipe_con,localhost,nosuchuser,,,,,PIPE);
|
||||
|
||||
DROP USER nosuchuser;
|
||||
FLUSH PRIVILEGES;
|
||||
set global max_password_errors=@old_max_password_errors;
|
|
@ -0,0 +1 @@
|
|||
--loose-enable-named-pipe --plugin-load=$AUTH_SOCKET_SO
|
|
@ -0,0 +1,23 @@
|
|||
# Tests that max_password_errors has no effect on login errors with
|
||||
# passwordless plugins (Unix version / auth_unix_socket)
|
||||
|
||||
--source include/not_embedded.inc
|
||||
--source include/have_unix_socket.inc
|
||||
|
||||
if (`SELECT '$USER' = 'nosuchuser'`) {
|
||||
skip USER is nosuchuser;
|
||||
}
|
||||
set @old_max_password_errors=@@max_password_errors;
|
||||
create user nosuchuser identified with 'unix_socket';
|
||||
|
||||
set global max_password_errors=1;
|
||||
--replace_result $MASTER_MYSOCK MASTER_SOCKET $MASTER_MYPORT MASTER_PORT
|
||||
error ER_ACCESS_DENIED_NO_PASSWORD_ERROR;
|
||||
connect(pipe_con,localhost,nosuchuser);
|
||||
--replace_result $MASTER_MYSOCK MASTER_SOCKET $MASTER_MYPORT MASTER_PORT
|
||||
error ER_ACCESS_DENIED_NO_PASSWORD_ERROR;
|
||||
connect(pipe_con,localhost,nosuchuser);
|
||||
|
||||
DROP USER nosuchuser;
|
||||
FLUSH PRIVILEGES;
|
||||
set global max_password_errors=@old_max_password_errors;
|
|
@ -2210,6 +2210,20 @@ NUMERIC_BLOCK_SIZE 1
|
|||
ENUM_VALUE_LIST NULL
|
||||
READ_ONLY YES
|
||||
COMMAND_LINE_ARGUMENT REQUIRED
|
||||
VARIABLE_NAME MAX_PASSWORD_ERRORS
|
||||
SESSION_VALUE NULL
|
||||
GLOBAL_VALUE 4294967295
|
||||
GLOBAL_VALUE_ORIGIN COMPILE-TIME
|
||||
DEFAULT_VALUE 4294967295
|
||||
VARIABLE_SCOPE GLOBAL
|
||||
VARIABLE_TYPE INT UNSIGNED
|
||||
VARIABLE_COMMENT If there is more than this number of failed connect attempts due to invalid password, user will be blocked from further connections until FLUSH_PRIVILEGES.
|
||||
NUMERIC_MIN_VALUE 1
|
||||
NUMERIC_MAX_VALUE 4294967295
|
||||
NUMERIC_BLOCK_SIZE 1
|
||||
ENUM_VALUE_LIST NULL
|
||||
READ_ONLY NO
|
||||
COMMAND_LINE_ARGUMENT REQUIRED
|
||||
VARIABLE_NAME MAX_PREPARED_STMT_COUNT
|
||||
SESSION_VALUE NULL
|
||||
GLOBAL_VALUE 16382
|
||||
|
|
|
@ -2420,6 +2420,20 @@ NUMERIC_BLOCK_SIZE 1
|
|||
ENUM_VALUE_LIST NULL
|
||||
READ_ONLY YES
|
||||
COMMAND_LINE_ARGUMENT REQUIRED
|
||||
VARIABLE_NAME MAX_PASSWORD_ERRORS
|
||||
SESSION_VALUE NULL
|
||||
GLOBAL_VALUE 4294967295
|
||||
GLOBAL_VALUE_ORIGIN COMPILE-TIME
|
||||
DEFAULT_VALUE 4294967295
|
||||
VARIABLE_SCOPE GLOBAL
|
||||
VARIABLE_TYPE INT UNSIGNED
|
||||
VARIABLE_COMMENT If there is more than this number of failed connect attempts due to invalid password, user will be blocked from further connections until FLUSH_PRIVILEGES.
|
||||
NUMERIC_MIN_VALUE 1
|
||||
NUMERIC_MAX_VALUE 4294967295
|
||||
NUMERIC_BLOCK_SIZE 1
|
||||
ENUM_VALUE_LIST NULL
|
||||
READ_ONLY NO
|
||||
COMMAND_LINE_ARGUMENT REQUIRED
|
||||
VARIABLE_NAME MAX_PREPARED_STMT_COUNT
|
||||
SESSION_VALUE NULL
|
||||
GLOBAL_VALUE 16382
|
||||
|
|
|
@ -513,6 +513,7 @@ ulong specialflag=0;
|
|||
ulong binlog_cache_use= 0, binlog_cache_disk_use= 0;
|
||||
ulong binlog_stmt_cache_use= 0, binlog_stmt_cache_disk_use= 0;
|
||||
ulong max_connections, max_connect_errors;
|
||||
uint max_password_errors;
|
||||
ulong extra_max_connections;
|
||||
uint max_digest_length= 0;
|
||||
ulong slave_retried_transactions;
|
||||
|
|
|
@ -241,6 +241,7 @@ extern ulong slow_launch_threads, slow_launch_time;
|
|||
extern MYSQL_PLUGIN_IMPORT ulong max_connections;
|
||||
extern uint max_digest_length;
|
||||
extern ulong max_connect_errors, connect_timeout;
|
||||
extern uint max_password_errors;
|
||||
extern my_bool slave_allow_batching;
|
||||
extern my_bool allow_slave_start;
|
||||
extern LEX_CSTRING reason_slave_blocked;
|
||||
|
|
|
@ -7931,3 +7931,5 @@ ER_BACKUP_STAGE_FAILED
|
|||
eng "Backup stage '%s' failed"
|
||||
ER_BACKUP_UNKNOWN_STAGE
|
||||
eng "Unknown backup stage: '%s'. Stage should be one of START, FLUSH, BLOCK_DDL, BLOCK_COMMIT or END"
|
||||
ER_USER_IS_BLOCKED
|
||||
eng "User is blocked because of too many credential errors; unblock with 'FLUSH PRIVILEGES'"
|
||||
|
|
|
@ -147,6 +147,7 @@ public:
|
|||
size_t hostname_length;
|
||||
USER_RESOURCES user_resource;
|
||||
enum SSL_type ssl_type;
|
||||
uint password_errors;
|
||||
const char *ssl_cipher, *x509_issuer, *x509_subject;
|
||||
LEX_CSTRING plugin;
|
||||
LEX_CSTRING auth_string;
|
||||
|
@ -12325,6 +12326,23 @@ static bool send_plugin_request_packet(MPVIO_EXT *mpvio,
|
|||
}
|
||||
|
||||
#ifndef NO_EMBEDDED_ACCESS_CHECKS
|
||||
|
||||
/**
|
||||
Safeguard to avoid blocking the root, when max_password_errors
|
||||
limit is reached.
|
||||
|
||||
Currently, we allow password errors for superuser on localhost.
|
||||
|
||||
@return true, if password errors should be ignored, and user should not be locked.
|
||||
*/
|
||||
static bool ignore_max_password_errors(const ACL_USER *acl_user)
|
||||
{
|
||||
const char *host= acl_user->host.hostname;
|
||||
return (acl_user->access & SUPER_ACL)
|
||||
&& (!strcasecmp(host, "localhost") ||
|
||||
!strcmp(host, "127.0.0.1") ||
|
||||
!strcmp(host, "::1"));
|
||||
}
|
||||
/**
|
||||
Finds acl entry in user database for authentication purposes.
|
||||
|
||||
|
@ -12343,6 +12361,16 @@ static bool find_mpvio_user(MPVIO_EXT *mpvio)
|
|||
mysql_mutex_lock(&acl_cache->lock);
|
||||
|
||||
ACL_USER *user= find_user_or_anon(sctx->host, sctx->user, sctx->ip);
|
||||
|
||||
if (user && user->password_errors >= max_password_errors && !ignore_max_password_errors(user))
|
||||
{
|
||||
mysql_mutex_unlock(&acl_cache->lock);
|
||||
my_error(ER_USER_IS_BLOCKED, MYF(0));
|
||||
general_log_print(mpvio->auth_info.thd, COM_CONNECT,
|
||||
ER_THD(mpvio->auth_info.thd, ER_USER_IS_BLOCKED));
|
||||
DBUG_RETURN(1);
|
||||
}
|
||||
|
||||
if (user)
|
||||
mpvio->acl_user= user->copy(mpvio->auth_info.thd->mem_root);
|
||||
|
||||
|
@ -13188,6 +13216,38 @@ static int do_auth_once(THD *thd, const LEX_CSTRING *auth_plugin_name,
|
|||
return res;
|
||||
}
|
||||
|
||||
enum PASSWD_ERROR_ACTION
|
||||
{
|
||||
PASSWD_ERROR_CLEAR,
|
||||
PASSWD_ERROR_INCREMENT
|
||||
};
|
||||
|
||||
/* Increment, or clear password errors for a user. */
|
||||
static void handle_password_errors(const char *user, const char *hostname, PASSWD_ERROR_ACTION action)
|
||||
{
|
||||
#ifndef NO_EMBEDDED_ACCESS_CHECKS
|
||||
mysql_mutex_assert_not_owner(&acl_cache->lock);
|
||||
mysql_mutex_lock(&acl_cache->lock);
|
||||
ACL_USER *u = find_user_exact(hostname, user);
|
||||
if (u)
|
||||
{
|
||||
switch(action)
|
||||
{
|
||||
case PASSWD_ERROR_INCREMENT:
|
||||
u->password_errors++;
|
||||
break;
|
||||
case PASSWD_ERROR_CLEAR:
|
||||
u->password_errors= 0;
|
||||
break;
|
||||
default:
|
||||
DBUG_ASSERT(0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
mysql_mutex_unlock(&acl_cache->lock);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Perform the handshake, authorize the client and update thd sctx variables.
|
||||
|
@ -13307,6 +13367,8 @@ bool acl_authenticate(THD *thd, uint com_change_user_pkt_len)
|
|||
break;
|
||||
case CR_AUTH_USER_CREDENTIALS:
|
||||
errors.m_authentication= 1;
|
||||
if (thd->password && !mpvio.make_it_fail)
|
||||
handle_password_errors(acl_user->user.str, acl_user->host.hostname, PASSWD_ERROR_INCREMENT);
|
||||
break;
|
||||
case CR_ERROR:
|
||||
default:
|
||||
|
@ -13321,6 +13383,11 @@ bool acl_authenticate(THD *thd, uint com_change_user_pkt_len)
|
|||
}
|
||||
|
||||
sctx->proxy_user[0]= 0;
|
||||
if (thd->password && acl_user->password_errors)
|
||||
{
|
||||
/* Login succeeded, clear password errors.*/
|
||||
handle_password_errors(acl_user->user.str, acl_user->host.hostname, PASSWD_ERROR_CLEAR);
|
||||
}
|
||||
|
||||
if (initialized) // if not --skip-grant-tables
|
||||
{
|
||||
|
|
|
@ -1515,6 +1515,14 @@ static Sys_var_ulong Sys_max_connect_errors(
|
|||
VALID_RANGE(1, UINT_MAX), DEFAULT(MAX_CONNECT_ERRORS),
|
||||
BLOCK_SIZE(1));
|
||||
|
||||
static Sys_var_uint Sys_max_password_errors(
|
||||
"max_password_errors",
|
||||
"If there is more than this number of failed connect attempts "
|
||||
"due to invalid password, user will be blocked from further connections until FLUSH_PRIVILEGES.",
|
||||
GLOBAL_VAR(max_password_errors), CMD_LINE(REQUIRED_ARG),
|
||||
VALID_RANGE(1, UINT_MAX), DEFAULT(UINT_MAX),
|
||||
BLOCK_SIZE(1));
|
||||
|
||||
static Sys_var_uint Sys_max_digest_length(
|
||||
"max_digest_length", "Maximum length considered for digest text.",
|
||||
READ_ONLY GLOBAL_VAR(max_digest_length),
|
||||
|
|
Loading…
Reference in a new issue