diff options
author | Brandon Nesterenko <brandon.nesterenko@mariadb.com> | 2022-04-26 19:51:42 -0600 |
---|---|---|
committer | Brandon Nesterenko <brandon.nesterenko@mariadb.com> | 2022-05-02 16:26:50 -0600 |
commit | a25fc247fe030ad4f2f98427cb1d90e37c5908c4 (patch) | |
tree | 75de696ffb557635ab4ed3767f852d38e0650297 | |
parent | 388032e99057449219d4a943b4407e36c42ec4af (diff) | |
download | mariadb-git-bb-10.2-MDEV-28294.tar.gz |
MDEV-28294: set default role bypasses Replicate_Wild_Ignore_Table: mysql.%bb-10.2-MDEV-28294
Problem:
========
When replicating SET DEFAULT ROLE, the pre-update check (i.e. that
in set_var_default_role::check()) tries to validate the existence of
the given rules/user even when the targeted tables are ignored. When
previously issued CREATE USER/ROLE commands are ignored by the
replica because of the replication filtering rules, this results in
an error because the targeted data does not exist.
Solution:
========
Before checking that the given roles/user exist of a SET DEFAULT
ROLE command, first ensure that the mysql.user and
mysql.roles_mapping tables are not excluded by replication filters.
Reviewed By:
============
Andrei Elkin <andrei.elkin@mariadb.com>
Sergei Golubchik <serg@mariadb.com>
-rw-r--r-- | mysql-test/suite/rpl/r/rpl_filter_set_var_missing_data.result | 55 | ||||
-rw-r--r-- | mysql-test/suite/rpl/t/rpl_filter_set_var_missing_data.test | 83 | ||||
-rw-r--r-- | sql/sql_acl.cc | 50 |
3 files changed, 176 insertions, 12 deletions
diff --git a/mysql-test/suite/rpl/r/rpl_filter_set_var_missing_data.result b/mysql-test/suite/rpl/r/rpl_filter_set_var_missing_data.result new file mode 100644 index 00000000000..e232edae1ed --- /dev/null +++ b/mysql-test/suite/rpl/r/rpl_filter_set_var_missing_data.result @@ -0,0 +1,55 @@ +include/master-slave.inc +[connection master] +# +# Set replica to ignore system mysql tables +connection slave; +include/stop_slave.inc +SET @@GLOBAL.replicate_wild_ignore_table="mysql.%"; +include/start_slave.inc +# +# Execute grant-based commands on primary which modify mysql system +# tables +connection master; +CREATE ROLE journalist; +CREATE USER testuser@localhost IDENTIFIED by ''; +GRANT journalist to testuser@localhost; +# +# Execute SET commands which use the previous user/role data +SET DEFAULT ROLE journalist for testuser@localhost; +SET PASSWORD for testuser@localhost= PASSWORD('123'); +include/save_master_gtid.inc +# +# Verify primary's grant tables have the correct user/role data +select count(*)=1 from mysql.user where User='testuser'; +count(*)=1 +1 +select count(*)=1 from mysql.roles_mapping where User='testuser'; +count(*)=1 +1 +# +# Ensure that the replica receives all of the primary's events without +# error +connection slave; +include/sync_with_master_gtid.inc +Last_SQL_Error = +Last_SQL_Errno = 0 +# +# Verify that the replica did not execute the master's commands +select count(*)=0 from mysql.user where User='testuser'; +count(*)=0 +1 +select count(*)=0 from mysql.roles_mapping where User='testuser'; +count(*)=0 +1 +# +# Clean up +connection master; +DROP ROLE journalist; +DROP USER testuser@localhost; +include/save_master_gtid.inc +connection slave; +include/sync_with_master_gtid.inc +include/stop_slave.inc +SET @@GLOBAL.replicate_wild_ignore_table=""; +include/start_slave.inc +include/rpl_end.inc diff --git a/mysql-test/suite/rpl/t/rpl_filter_set_var_missing_data.test b/mysql-test/suite/rpl/t/rpl_filter_set_var_missing_data.test new file mode 100644 index 00000000000..25efb6ed662 --- /dev/null +++ b/mysql-test/suite/rpl/t/rpl_filter_set_var_missing_data.test @@ -0,0 +1,83 @@ +# +# Purpose: +# This test ensures that the SET DEFAULT ROLE and SET PASSWORD commands can +# be ignored by replica filter rules. MDEV-28294 exposed a bug in which +# SET DEFAULT ROLE would check for the existence of the given roles/user even +# when the targeted tables are ignored, resulting in errors if the targeted +# data does not exist. More specifically, when previously issued +# CREATE USER/ROLE commands are ignored by the replica because of the +# replication filtering rules, SET DEFAULT ROLE would result in an error +# because the targeted data does not exist. +# +# Methodology: +# Using a replica configured with replicate_wild_ignore_table="mysql.%", +# execute SET DEFAULT ROLE and SET PASSWORD on the primary and ensure that the +# replica neither errors nor executes the commands which the primary sends. +# +# References: +# MDEV-28294: set default role bypasses Replicate_Wild_Ignore_Table: mysql.% +# + +source include/master-slave.inc; +source include/have_binlog_format_mixed.inc; + +--echo # +--echo # Set replica to ignore system mysql tables +connection slave; +let $old_filter= query_get_value(SHOW SLAVE STATUS, Replicate_Wild_Ignore_Table, 1); +source include/stop_slave.inc; +SET @@GLOBAL.replicate_wild_ignore_table="mysql.%"; +source include/start_slave.inc; + +--echo # +--echo # Execute grant-based commands on primary which modify mysql system +--echo # tables +connection master; +CREATE ROLE journalist; +CREATE USER testuser@localhost IDENTIFIED by ''; +GRANT journalist to testuser@localhost; + +--echo # +--echo # Execute SET commands which use the previous user/role data +SET DEFAULT ROLE journalist for testuser@localhost; +SET PASSWORD for testuser@localhost= PASSWORD('123'); +--source include/save_master_gtid.inc + +--echo # +--echo # Verify primary's grant tables have the correct user/role data +select count(*)=1 from mysql.user where User='testuser'; +select count(*)=1 from mysql.roles_mapping where User='testuser'; + +--echo # +--echo # Ensure that the replica receives all of the primary's events without +--echo # error +connection slave; +--source include/sync_with_master_gtid.inc +let $error= query_get_value(SHOW SLAVE STATUS, Last_SQL_Error, 1); +--echo Last_SQL_Error = $error +let $errno= query_get_value(SHOW SLAVE STATUS, Last_SQL_Errno, 1); +--echo Last_SQL_Errno = $errno + +--echo # +--echo # Verify that the replica did not execute the master's commands +select count(*)=0 from mysql.user where User='testuser'; +select count(*)=0 from mysql.roles_mapping where User='testuser'; + +--echo # +--echo # Clean up + +# The master has to drop the role/user combination while the slave still has +# its filters active; otherwise, the slave would try to drop users/roles that +# were never replicated. +--connection master +DROP ROLE journalist; +DROP USER testuser@localhost; +--source include/save_master_gtid.inc + +--connection slave +--source include/sync_with_master_gtid.inc +source include/stop_slave.inc; +--eval SET @@GLOBAL.replicate_wild_ignore_table="$old_filter" +source include/start_slave.inc; + +--source include/rpl_end.inc diff --git a/sql/sql_acl.cc b/sql/sql_acl.cc index f62dd5471eb..e9ba96b4a05 100644 --- a/sql/sql_acl.cc +++ b/sql/sql_acl.cc @@ -1346,18 +1346,8 @@ class Grant_tables DBUG_ENTER("Grant_tables::open_and_lock"); DBUG_ASSERT(first_table_in_list); #ifdef HAVE_REPLICATION - if (first_table_in_list->tl.lock_type >= TL_WRITE_ALLOW_WRITE && - thd->slave_thread && !thd->spcont) - { - /* - GRANT and REVOKE are applied the slave in/exclusion rules as they are - some kind of updates to the mysql.% tables. - */ - Rpl_filter *rpl_filter= thd->system_thread_info.rpl_sql_info->rpl_filter; - if (rpl_filter->is_on() && - !rpl_filter->tables_ok(0, &first_table_in_list->tl)) - DBUG_RETURN(1); - } + if (int ignore_ret= rpl_ignore_tables(thd)) + DBUG_RETURN(ignore_ret); #endif if (open_and_lock_tables(thd, &first_table_in_list->tl, FALSE, MYSQL_LOCK_IGNORE_TIMEOUT)) @@ -1387,6 +1377,32 @@ class Grant_tables DBUG_RETURN(0); } +#ifdef HAVE_REPLICATION + /* Checks if the tables targeted by a grant command should be ignored because + of the configured replication filters + + @retval 1 Tables are excluded for replication + @retval 0 tables are included for replication + */ + int rpl_ignore_tables(THD *thd) + { + DBUG_ENTER("Grant_tables::rpl_ignore_tables"); + if (first_table_in_list->tl.lock_type >= TL_WRITE_ALLOW_WRITE && + thd->slave_thread && !thd->spcont) + { + /* + GRANT and REVOKE are applied the slave in/exclusion rules as they are + some kind of updates to the mysql.% tables. + */ + Rpl_filter *rpl_filter= thd->system_thread_info.rpl_sql_info->rpl_filter; + if (rpl_filter->is_on() && + !rpl_filter->tables_ok(0, &first_table_in_list->tl)) + DBUG_RETURN(1); + } + DBUG_RETURN(0); + } +#endif + inline const User_table& user_table() const { return m_user_table; @@ -3562,6 +3578,16 @@ int acl_check_set_default_role(THD *thd, const char *host, const char *user, const char *role) { DBUG_ENTER("acl_check_set_default_role"); +#ifdef HAVE_REPLICATION + /* + If the roles_mapping table is excluded by the replication filter, we return + successful without validating the user/role data because the command will + be ignored in a later call to `acl_set_default_role()` for a graceful exit. + */ + Grant_tables tables(Table_roles_mapping, TL_WRITE); + if (tables.rpl_ignore_tables(thd)) + DBUG_RETURN(0); +#endif DBUG_RETURN(check_alter_user(thd, host, user) || check_user_can_set_role(thd, user, host, NULL, role, NULL)); } |