Skip to content

Commit

Permalink
Permissions for replay binlog for a read only database (facebook#1091)
Browse files Browse the repository at this point in the history
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:

Extend repl_slave + admin_port to set gtid_next when repaly binlog
let admin_port + repl_slave bypass read only database
let rpl_repl_slave_acl can use binlog

Reference Patch: facebook@8e4765845c5
Reference Patch: facebook@e6a4851729b
Reference Patch: facebook@cfd9b938f57

Pull Request resolved: facebook#1091

Reviewed By: lth

Differential Revision: D19525120

Pulled By: lth

fbshipit-source-id: c2c496f
  • Loading branch information
inikep committed Sep 7, 2020
1 parent 932c1bb commit aceffec
Show file tree
Hide file tree
Showing 5 changed files with 273 additions and 1 deletion.
20 changes: 20 additions & 0 deletions mysql-test/suite/binlog_gtid/r/rpl_repl_slave_set_gtid_next.result
Original file line number Diff line number Diff line change
@@ -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;
81 changes: 81 additions & 0 deletions mysql-test/suite/binlog_gtid/t/rpl_repl_slave_set_gtid_next.test
Original file line number Diff line number Diff line change
@@ -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;
64 changes: 64 additions & 0 deletions mysql-test/suite/rpl_gtid/r/rpl_repl_slave_acl.result
Original file line number Diff line number Diff line change
@@ -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, REPLICATION_APPLIER 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, REPLICATION_APPLIER, 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
103 changes: 103 additions & 0 deletions mysql-test/suite/rpl_gtid/t/rpl_repl_slave_acl.test
Original file line number Diff line number Diff line change
@@ -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, REPLICATION_APPLIER ON *.* FROM 'olm'@'localhost';

CREATE USER 'normal'@'localhost' IDENTIFIED BY 'password';
GRANT ALL PRIVILEGES ON *.* TO 'normal'@'localhost';
REVOKE SUPER, CONNECTION_ADMIN, REPLICATION_APPLIER, 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
6 changes: 5 additions & 1 deletion sql/sql_db.cc
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,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.
Expand All @@ -229,7 +232,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);
}

Expand Down

0 comments on commit aceffec

Please sign in to comment.