From b72408cf2c0285da8c939cca42e0d3b19374b007 Mon Sep 17 00:00:00 2001 From: Przemyslaw Skibinski Date: Tue, 7 Jan 2020 11:42:57 +0100 Subject: [PATCH] Permissions for replay binlog for a read only database Summary: Extend BINLOG_ADMIN to work with `ALTER DATABASE test read_only=True` so BINLOG_ADMIN will be equal to `REPLICATION SLAVE+ADMIN PORT` in 5.6. This patch replaces the following 5.6 commits: commit 2eeee2cad2786227213f248158738eb8470cba0f Author: Zhicheng Zhu Date: Tue Oct 29 10:20:47 2019 -0700 Extend repal_slave + admin_port to set gtid_next when repaly binlog Summary: Currently 8.0 mysqlbinlog will auto print set @session.next_gtid = 'automatic'. This will cause olm fix fail in 5.6. Extending the repal_slave + admin_port to unblock olm. Originally Reviewed By: yoshinorim fbshipit-source-id: 34f0a79ef47 commit 30f5d8f273609dd95245f0a6f3cf36fe8dda3726 Author: Zhicheng Zhu Date: Fri Sep 13 12:51:10 2019 -0700 let admin_port + repl_slave bypass read only database Summary: As the title said, this is the diff for letting admin_port + repl_slave can replay readonly database. Originally Reviewed By: yoshinorim fbshipit-source-id: b8fce06bcb0 commit 02d7ee4db7a16df45faf244c6854b3a51fb194a5 Author: Zhicheng Zhu Date: Mon Sep 9 16:03:33 2019 -0700 let rpl_repl_slave_acl can use binlog Summary: Context: If we use SUPER_USER priv to replay binlog and write metadata to dst replicaset, SUPER_USER writing to read-only master can cause GTID not generated and therefore these events will not be replicated to slaves and will be ignored by promotion, causing a silent dataloss Fix: To fix this we extend REPL_SLAVE_ACL so that binlog can bypass it and run like SUPER_USER but without causing GTID not generated. Originally Reviewed By: yoshinorim fbshipit-source-id: 1a67821abfd --- .../r/rpl_repl_slave_set_gtid_next.result | 20 ++++ .../t/rpl_repl_slave_set_gtid_next.test | 81 ++++++++++++++ .../rpl_gtid/r/rpl_repl_slave_acl.result | 64 +++++++++++ .../suite/rpl_gtid/t/rpl_repl_slave_acl.test | 103 ++++++++++++++++++ sql/sql_db.cc | 6 +- 5 files changed, 273 insertions(+), 1 deletion(-) create mode 100644 mysql-test/suite/binlog_gtid/r/rpl_repl_slave_set_gtid_next.result create mode 100644 mysql-test/suite/binlog_gtid/t/rpl_repl_slave_set_gtid_next.test create mode 100644 mysql-test/suite/rpl_gtid/r/rpl_repl_slave_acl.result create mode 100644 mysql-test/suite/rpl_gtid/t/rpl_repl_slave_acl.test diff --git a/mysql-test/suite/binlog_gtid/r/rpl_repl_slave_set_gtid_next.result b/mysql-test/suite/binlog_gtid/r/rpl_repl_slave_set_gtid_next.result new file mode 100644 index 000000000000..a9a73de6a5b9 --- /dev/null +++ b/mysql-test/suite/binlog_gtid/r/rpl_repl_slave_set_gtid_next.result @@ -0,0 +1,20 @@ +create user 'olm'@'localhost' IDENTIFIED BY 'password'; +grant all privileges on *.* to 'olm'@'localhost'; +revoke super on *.* from 'olm'@'localhost'; +Warnings: +Warning 1287 The SUPER privilege identifier is deprecated +create user 'normal'@'localhost' IDENTIFIED BY 'password'; +grant all privileges on *.* to 'normal'@'localhost'; +revoke super on *.* from 'normal'@'localhost'; +Warnings: +Warning 1287 The SUPER privilege identifier is deprecated +revoke BINLOG_ADMIN on *.* from 'normal'@'localhost'; +alter database test read_only=True; +# Without BINLOG_ADMIN, non-super user can't execute set gtid_next when replaying binlog +select * from test.tbl; +ERROR 42S02: Table 'test.tbl' doesn't exist +# With BINLOG_ADMIN, non-super user can execute set gtid_next when replaying binlog +include/assert.inc [tbl should have 1 row] +DROP user 'olm'@'localhost'; +DROP user 'normal'@'localhost'; +DROP table tbl; diff --git a/mysql-test/suite/binlog_gtid/t/rpl_repl_slave_set_gtid_next.test b/mysql-test/suite/binlog_gtid/t/rpl_repl_slave_set_gtid_next.test new file mode 100644 index 000000000000..b2064d6b4e59 --- /dev/null +++ b/mysql-test/suite/binlog_gtid/t/rpl_repl_slave_set_gtid_next.test @@ -0,0 +1,81 @@ +source include/assert_gtid_mode_on.inc; + +# Replay an 8.0.X mysqlbinlog stream +write_file $MYSQLTEST_VARDIR/tmp/binlog_output; +/*!50530 SET @@SESSION.PSEUDO_SLAVE_MODE=1*/; +/*!50003 SET @OLD_COMPLETION_TYPE=@@COMPLETION_TYPE,COMPLETION_TYPE=0*/; +DELIMITER /*!*/; +# at 4 +#191029 14:39:57 server id 1 end_log_pos 120 Start: binlog v 4, server v 5.6.35-fb-debug-log created 191029 14:39:57 at startup +# Warning: this binlog is either in use or was not closed properly. +ROLLBACK/*!*/; +BINLOG ' +rbG4XQ8BAAAAdAAAAHgAAAABAAQANS42LjM1LWZiLWRlYnVnLWxvZwAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAACtsbhdEzgNAAgAEgAEBAQEEgAAXAAEGggAAAAICAgCAAAACgoKGRkAANOj +IFM= +'/*!*/; +/*!50616 SET @@SESSION.GTID_NEXT='AUTOMATIC'*//*!*/; +# at 120 +# at 147 +# at 191 +#191029 14:40:59 server id 1 end_log_pos 297 Query thread_id=3 exec_time=0 error_code=0 +use `test`/*!*/; +SET TIMESTAMP=1572385259/*!*/; +SET @@session.pseudo_thread_id=3/*!*/; +SET @@session.foreign_key_checks=1, @@session.sql_auto_is_null=0, @@session.unique_checks=1, @@session.autocommit=1/*!*/; +SET @@session.sql_mode=1073741824/*!*/; +SET @@session.auto_increment_increment=1, @@session.auto_increment_offset=1/*!*/; +/*!\C latin1 *//*!*/; +SET @@session.character_set_client=8,@@session.collation_connection=8,@@session.collation_server=8/*!*/; +SET @@session.lc_time_names=0/*!*/; +SET @@session.collation_database=DEFAULT/*!*/; +create table tbl (a int primary key) +/*!*/; +# at 297 +# at 341 +#191029 14:42:37 server id 1 end_log_pos 416 Query thread_id=3 exec_time=0 error_code=0 +SET TIMESTAMP=1572385357/*!*/; +BEGIN +/*!*/; +# at 416 +#191029 14:42:37 server id 1 end_log_pos 511 Query thread_id=3 exec_time=0 error_code=0 +SET TIMESTAMP=1572385357/*!*/; +insert into tbl values(1) +/*!*/; +# at 511 +#191029 14:42:37 server id 1 end_log_pos 538 Xid = 17 +COMMIT/*!*/; +DELIMITER ; +# End of log file +/*!50003 SET COMPLETION_TYPE=@OLD_COMPLETION_TYPE*/; +/*!50530 SET @@SESSION.PSEUDO_SLAVE_MODE=0*/; +EOF + +create user 'olm'@'localhost' IDENTIFIED BY 'password'; +grant all privileges on *.* to 'olm'@'localhost'; +revoke super on *.* from 'olm'@'localhost'; + +create user 'normal'@'localhost' IDENTIFIED BY 'password'; +grant all privileges on *.* to 'normal'@'localhost'; +revoke super on *.* from 'normal'@'localhost'; +revoke BINLOG_ADMIN on *.* from 'normal'@'localhost'; + +alter database test read_only=True; + +--echo # Without BINLOG_ADMIN, non-super user can't execute set gtid_next when replaying binlog +--disable_abort_on_error +--exec $MYSQL --binary-mode -P $MASTER_MYPORT -u normal -h 127.0.0.1 -D test --password='password' < $MYSQLTEST_VARDIR/tmp/binlog_output +--enable_abort_on_error +--error ER_NO_SUCH_TABLE +select * from test.tbl; + +--echo # With BINLOG_ADMIN, non-super user can execute set gtid_next when replaying binlog +--exec $MYSQL --binary-mode -P $MASTER_MYPORT -u olm -h 127.0.0.1 -D test --password='password' < $MYSQLTEST_VARDIR/tmp/binlog_output +--let $assert_text = tbl should have 1 row +--let $assert_cond = [SELECT COUNT(*) from tbl] = 1 +--source include/assert.inc + +--remove_file $MYSQLTEST_VARDIR/tmp/binlog_output +DROP user 'olm'@'localhost'; +DROP user 'normal'@'localhost'; +DROP table tbl; diff --git a/mysql-test/suite/rpl_gtid/r/rpl_repl_slave_acl.result b/mysql-test/suite/rpl_gtid/r/rpl_repl_slave_acl.result new file mode 100644 index 000000000000..2d9cd33a91c4 --- /dev/null +++ b/mysql-test/suite/rpl_gtid/r/rpl_repl_slave_acl.result @@ -0,0 +1,64 @@ +include/master-slave.inc +Warnings: +Note #### Sending passwords in plain text without SSL/TLS is extremely insecure. +Note #### Storing MySQL user name or password information in the master info repository is not secure and is therefore not recommended. Please consider using the USER and PASSWORD connection options for START SLAVE; see the 'START SLAVE Syntax' in the MySQL Manual for more information. +[connection master] +CREATE TABLE tbl (id INT PRIMARY KEY, value INT); +INSERT INTO tbl VALUES (1, 1); +UPDATE tbl SET value=value+1 WHERE id=1; +INSERT INTO tbl VALUES (2, 10); +FLUSH LOGS; +include/assert.inc [tbl should have 2 rows] +DROP TABLE tbl; +include/sync_slave_sql_with_master.inc +[connection master] +CREATE USER 'olm'@'localhost' IDENTIFIED BY 'password'; +GRANT ALL PRIVILEGES ON *.* TO 'olm'@'localhost'; +REVOKE SUPER, CONNECTION_ADMIN ON *.* FROM 'olm'@'localhost'; +Warnings: +Warning 1287 The SUPER privilege identifier is deprecated +CREATE USER 'normal'@'localhost' IDENTIFIED BY 'password'; +GRANT ALL PRIVILEGES ON *.* TO 'normal'@'localhost'; +REVOKE SUPER, CONNECTION_ADMIN, BINLOG_ADMIN ON *.* FROM 'normal'@'localhost'; +Warnings: +Warning 1287 The SUPER privilege identifier is deprecated +--------------------------------- +1. Tests with read/write database +--------------------------------- +# User without BINLOG_ADMIN privilege can't replay binlog +SELECT * FROM tbl; +ERROR 42S02: Table 'test.tbl' doesn't exist +# User with BINLOG_ADMIN privilege can replay binlog +include/assert.inc [tbl should have 2 rows] +DROP TABLE tbl; +-------------------------------------------------- +2. Tests with "ALTER DATABASE test read_only=True" +-------------------------------------------------- +ALTER DATABASE test read_only=True; +# User without BINLOG_ADMIN privilege can't replay binlog for a read only database instance +SELECT * FROM tbl; +ERROR 42S02: Table 'test.tbl' doesn't exist +# User with BINLOG_ADMIN privilege can replay binlog for a read only database instance +include/assert.inc [tbl should have 2 rows] +DROP TABLE tbl; +ALTER DATABASE test read_only=False; +-------------------------------------- +3. Tests with "SET GLOBAL read_only=1" +-------------------------------------- +SET GLOBAL read_only=1; +# User with BINLOG_ADMIN but without CONNECTION_ADMIN privilege can't replay binlog when the server uses a "read-only" mode +SELECT * FROM tbl; +ERROR 42S02: Table 'test.tbl' doesn't exist +GRANT CONNECTION_ADMIN ON *.* TO 'olm'@'localhost'; +# User who has BINLOG_ADMIN and CONNECTION_ADMIN privilege can replay binlog when the server uses a "read-only" mode +include/assert.inc [tbl should have 2 rows] +SET GLOBAL read_only=0; +# With BINLOG_ADMIN privilege, reply binlog should generate gtid for read_only database +include/sync_slave_sql_with_master.inc +[connection slave] +include/assert.inc [tbl should have 2 rows] +[connection master] +DROP user 'normal'@'localhost'; +DROP user 'olm'@'localhost'; +DROP TABLE tbl; +include/rpl_end.inc diff --git a/mysql-test/suite/rpl_gtid/t/rpl_repl_slave_acl.test b/mysql-test/suite/rpl_gtid/t/rpl_repl_slave_acl.test new file mode 100644 index 000000000000..f5e42be79cbf --- /dev/null +++ b/mysql-test/suite/rpl_gtid/t/rpl_repl_slave_acl.test @@ -0,0 +1,103 @@ +--source include/assert_gtid_mode_on.inc +--source include/master-slave.inc + +CREATE TABLE tbl (id INT PRIMARY KEY, value INT); +INSERT INTO tbl VALUES (1, 1); +UPDATE tbl SET value=value+1 WHERE id=1; +INSERT INTO tbl VALUES (2, 10); + +let $MYSQLD_DATADIR= `SELECT @@datadir;`; +let $BINLOG_NAME = query_get_value(show master status, File, 1); +--copy_file $MYSQLD_DATADIR/$BINLOG_NAME $MYSQLD_DATADIR/master-bin.saved + +FLUSH LOGS; +--let $assert_text = tbl should have 2 rows +--let $assert_cond = [SELECT COUNT(*) from tbl] = 2 +--source include/assert.inc +DROP TABLE tbl; +--source include/sync_slave_sql_with_master.inc + +--source include/rpl_connection_master.inc +CREATE USER 'olm'@'localhost' IDENTIFIED BY 'password'; +GRANT ALL PRIVILEGES ON *.* TO 'olm'@'localhost'; +REVOKE SUPER, CONNECTION_ADMIN ON *.* FROM 'olm'@'localhost'; + +CREATE USER 'normal'@'localhost' IDENTIFIED BY 'password'; +GRANT ALL PRIVILEGES ON *.* TO 'normal'@'localhost'; +REVOKE SUPER, CONNECTION_ADMIN, BINLOG_ADMIN ON *.* FROM 'normal'@'localhost'; + + +--echo --------------------------------- +--echo 1. Tests with read/write database +--echo --------------------------------- +--echo # User without BINLOG_ADMIN privilege can't replay binlog +--disable_abort_on_error +--exec $MYSQL_BINLOG $MYSQLD_DATADIR/master-bin.saved --skip-gtids --skip-empty-trans --database test | $MYSQL --user='normal' --password='password' --port=$MASTER_MYPORT --host=127.0.0.1 +--enable_abort_on_error +# Table 'test.tbl' doesn't exist +--error ER_NO_SUCH_TABLE +SELECT * FROM tbl; + +--echo # User with BINLOG_ADMIN privilege can replay binlog +--exec $MYSQL_BINLOG $MYSQLD_DATADIR/master-bin.saved --skip-gtids --skip-empty-trans --database test | $MYSQL --user='olm' --password='password' --port=$MASTER_MYPORT --host=127.0.0.1 +--let $assert_text = tbl should have 2 rows +--let $assert_cond = [SELECT COUNT(*) from tbl] = 2 +--source include/assert.inc +DROP TABLE tbl; + + +--echo -------------------------------------------------- +--echo 2. Tests with "ALTER DATABASE test read_only=True" +--echo -------------------------------------------------- +ALTER DATABASE test read_only=True; +--echo # User without BINLOG_ADMIN privilege can't replay binlog for a read only database instance +--disable_abort_on_error +--exec $MYSQL_BINLOG $MYSQLD_DATADIR/master-bin.saved --skip-gtids --skip-empty-trans --database test | $MYSQL --user='normal' --password='password' --port=$MASTER_MYPORT --host=127.0.0.1 +--enable_abort_on_error +# Table 'test.tbl' doesn't exist +--error ER_NO_SUCH_TABLE +SELECT * FROM tbl; + +--echo # User with BINLOG_ADMIN privilege can replay binlog for a read only database instance +--exec $MYSQL_BINLOG $MYSQLD_DATADIR/master-bin.saved --skip-gtids --skip-empty-trans --database test | $MYSQL --user='olm' --password='password' --port=$MASTER_MYPORT --host=127.0.0.1 +--let $assert_text = tbl should have 2 rows +--let $assert_cond = [SELECT COUNT(*) from tbl] = 2 +--source include/assert.inc +DROP TABLE tbl; +ALTER DATABASE test read_only=False; + + +--echo -------------------------------------- +--echo 3. Tests with "SET GLOBAL read_only=1" +--echo -------------------------------------- +SET GLOBAL read_only=1; +--echo # User with BINLOG_ADMIN but without CONNECTION_ADMIN privilege can't replay binlog when the server uses a "read-only" mode +--disable_abort_on_error +--exec $MYSQL_BINLOG $MYSQLD_DATADIR/master-bin.saved --skip-gtids --skip-empty-trans --database test | $MYSQL --user='olm' --password='password' --port=$MASTER_MYPORT --host=127.0.0.1 +--enable_abort_on_error +# Table 'test.tbl' doesn't exist +--error ER_NO_SUCH_TABLE +SELECT * FROM tbl; + +GRANT CONNECTION_ADMIN ON *.* TO 'olm'@'localhost'; +--echo # User who has BINLOG_ADMIN and CONNECTION_ADMIN privilege can replay binlog when the server uses a "read-only" mode +--exec $MYSQL_BINLOG $MYSQLD_DATADIR/master-bin.saved --skip-gtids --skip-empty-trans --database test | $MYSQL --user='olm' --password='password' --port=$MASTER_MYPORT --host=127.0.0.1 +--let $assert_text = tbl should have 2 rows +--let $assert_cond = [SELECT COUNT(*) from tbl] = 2 +--source include/assert.inc +SET GLOBAL read_only=0; + + +--echo # With BINLOG_ADMIN privilege, reply binlog should generate gtid for read_only database +--source include/sync_slave_sql_with_master.inc +--source include/rpl_connection_slave.inc +--let $assert_text = tbl should have 2 rows +--let $assert_cond = [SELECT COUNT(*) from tbl] = 2 +--source include/assert.inc + +--source include/rpl_connection_master.inc +DROP user 'normal'@'localhost'; +DROP user 'olm'@'localhost'; +DROP TABLE tbl; +--remove_file $MYSQLD_DATADIR/master-bin.saved +--source include/rpl_end.inc diff --git a/sql/sql_db.cc b/sql/sql_db.cc index 5c65b82fe5a1..a12c37177f4e 100644 --- a/sql/sql_db.cc +++ b/sql/sql_db.cc @@ -208,6 +208,9 @@ static bool fill_db_read_only_from_dd(THD *thd, const char *db, bool is_thd_db_read_only_by_name(THD *thd, const char *db) { DBUG_ENTER("is_thd_db_read_only_by_name"); bool super = thd->m_main_security_ctx.check_access(SUPER_ACL); + bool binlog_admin = + thd->m_main_security_ctx.has_global_grant(STRING_WITH_LEN("BINLOG_ADMIN")) + .first; enum enum_db_read_only flag = DB_READ_ONLY_NULL; // Check cached info in THD first. @@ -228,7 +231,8 @@ bool is_thd_db_read_only_by_name(THD *thd, const char *db) { DBUG_ASSERT(flag >= DB_READ_ONLY_NO && flag <= DB_READ_ONLY_SUPER); - if (flag == DB_READ_ONLY_SUPER || (flag == DB_READ_ONLY_YES && !super)) { + if (flag == DB_READ_ONLY_SUPER || + (flag == DB_READ_ONLY_YES && !super && !binlog_admin)) { DBUG_RETURN(true); }