Skip to content

Commit b186620

Browse files
author
Joao Gramacho
committed
WL#10957: Binary log encryption at rest (Step 2)
This patch introduces a new option 'binlog_encryption' that allows to enable and disable the generation of encrypted binary and relay log files. It also introduces a new dynamic privilege 'BINLOG_ENCRYPTION_ADMIN'. A user must have this privilege to change the option in a client session. When enabling the option for the first time for a server instance, the server shall generate a new binlog master key that will be used to encrypt the encrypted binary and relay log files passwords. @ share/errmsg-utf8.txt Introduced the following errors: - ER[_SERVER]_RPL_ENCRYPTION_FAILED_TO_ROTATE_LOGS; - ER[_SERVER]_RPL_ENCRYPTION_KEY_EXISTS_UNEXPECTED; - ER[_SERVER]_RPL_ENCRYPTION_FAILED_TO_GENERATE_KEY; - ER[_SERVER]_RPL_ENCRYPTION_FAILED_TO_STORE_KEY; - ER[_SERVER]_RPL_ENCRYPTION_FAILED_TO_REMOVE_KEY; - ER[_SERVER]_RPL_ENCRYPTION_MASTER_KEY_RECOVERY_FAILED; - ER_RPL_ENCRYPTION_UNABLE_TO_CHANGE_OPTION; - ER_SERVER_RPL_ENCRYPTION_UNABLE_TO_INITIALIZE; @ sql/basic_istream.cc Did minor refactoring. @ sql/binlog.cc Binlog_ofile can now create new encrypted binary or relay log files depending on binlog_encryption option. Made read_gtids_and_update_trx_parser_from_relaylog to not ignore errors when trying to decrypt relay log files. @ sql/binlog_istream.cc Did minor refactoring. @ sql/binlog_ostream.{h|cc} Introduced the new 'open' and 'get_header_size' functions to Binlog_encryption_ostream. @ sql/binlog_reader.h Did minor refactoring. @ sql/mysqld.cc Moved server UUID initialization to an early stage and added a call to initialize binlog encryption infrastructure right after it, as it must be initialized before generating any new binary or relay log file. @ sql/rpl_log_encryption.{h|cc} Extended Rpl_encryption class to handle enabling and disabling the binlog_encryption option, master key generation and recovery. Extended Rpl_encryption_header and child to generate encryption headers for new files and to serialize them into a down stream. Extended and fixed code comments. @ sql/rpl_rli.{h|cc} Add a new parameter to rli_init_info() to make it skip reading from any existing relay log files. @ sql/rpl_slave.{h|cc} Add a new parameter to load_mi_and_rli_from_repositories() to make it skip reading from any existing relay log files. Refactored the generic error message reported by the applier thread when facing errors reading events from relay log files to also mention possible issues with keyring/encryption. Made change_master() to call load_mi_and_rli_from_repositories() telling it to skip reading from relay log files when they are expected to be purged in a later stage. @ sql/set_var.h Refactored a comment. @ sql/sys_vars.{h|cc} Added a new Sys_var_binlog_encryption to handle binlog_encryption option. @ sql/auth/dynamic_privileges_impl.cc Introuduced the new BINLOG_ENCRYPTION_ADMIN dynamic privilege. Test cases ========== @ binlog.binlog_encryption_random_access Tests random access to an encrypted binary log file. @ rpl.rpl_encryption Test many scenarios of enabling the option and errors a slave can face if the keyring is uninstalled while the binlog_encryption option is ON. @ rpl.rpl_encryption_master_key_generation_recovery Test possible failures and how the server recover from issues while generating a new master key (enabling the option for the first time in a given server instance). Some other test cases had to be re-recorded or fixed because of the new option or the new dynamic privilege.
1 parent 66d0aa2 commit b186620

39 files changed

+2286
-204
lines changed

mysql-test/include/rpl_get_end_of_relay_log.inc

+7
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,13 @@ EOF
6464
--let $relay_log_size= `SELECT TRIM(SUBSTR('$size_and_file', 1, 10))`
6565
--let $relay_log_file= `SELECT TRIM(SUBSTR('$size_and_file', 11))`
6666

67+
# Take into account the encryption header if necessary
68+
--let $_binlog_encryption=`SELECT @@GLOBAL.binlog_encryption`
69+
if ($_binlog_encryption)
70+
{
71+
--let $relay_log_size=`SELECT $relay_log_size - 512`
72+
}
73+
6774
if ($rpl_debug)
6875
{
6976
--echo relay_log_file=$relay_log_file relay_log_size=$relay_log_size
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
# ==== Purpose ====
2+
#
3+
# Retrieve a given replication log file encryption key id.
4+
#
5+
# ==== Usage ====
6+
#
7+
# --let $rpl_log_file= <BINARY OR RELAY LOG FILE>
8+
# [--let $rpl_debug= 0]
9+
# --source include/rpl_get_log_encryption_key_id.inc
10+
#
11+
# Parameters:
12+
#
13+
# $rpl_log_file
14+
# The file to be inspected.
15+
#
16+
# $rpl_debug=1
17+
# Print extra debugging information.
18+
#
19+
# Output variable:
20+
#
21+
# $rpl_encryption_key_id will be set with:
22+
#
23+
# - Error: Error fetching the info.
24+
#
25+
# - None: The file is not encrypted.
26+
#
27+
# - MySQLReplicationKey_<UUID>_<SEQNO>: a replication encryption key.
28+
#
29+
30+
--let $_rgeki_suffix= `SELECT UUID()`
31+
--let _RPL_RESULT_FILE= $MYSQLTEST_VARDIR/tmp/_rgeki_$_rgeki_suffix
32+
--let _RPL_LOG_FILE= $rpl_log_file
33+
--let _RPL_DEBUG= $rpl_debug
34+
--let $rpl_encryption_key_id=Error
35+
36+
# Write file to make mysql-test-run.pl start up the server again
37+
# Because mysqltest is such a wonderful language, we use perl instead.
38+
perl;
39+
my $log_file= $ENV{'_RPL_LOG_FILE'};
40+
my $result_file= $ENV{'_RPL_RESULT_FILE'};
41+
if ($ENV{'_RPL_DEBUG'})
42+
{
43+
print "# debug: log_file='$log_file'\n";
44+
}
45+
46+
# Open the file in raw mode
47+
open LFILE, '<:raw', $log_file or die "Error opening $log_file: $!";
48+
open RFILE, "> $result_file" or die "Error opening $result_file: $!";
49+
50+
# Read binlog magic
51+
my $bytes_read = read LFILE, my $magic, 4;
52+
die 'Got $bytes_read but expected 4' unless $bytes_read == 4;
53+
54+
if ($ENV{'_RPL_DEBUG'})
55+
{
56+
print "# debug: magic='$magic'\n";
57+
}
58+
59+
my $plain_magic = "\xfe\x62\x69\x6e";
60+
my $encrypted_magic = "\xfd\x62\x69\x6e";
61+
62+
if ($magic eq $plain_magic) {
63+
# Ordinary binary log
64+
if ($ENV{'_RPL_DEBUG'})
65+
{
66+
print "# debug: ordinary log file\n";
67+
}
68+
print RFILE "--let \$rpl_encryption_key_id=None\n" or die "Error writing to $result_file: $!";
69+
} elsif ($magic eq $encrypted_magic) {
70+
# Encrypted binary log
71+
if ($ENV{'_RPL_DEBUG'})
72+
{
73+
print "# debug: encrypted log file\n";
74+
}
75+
$bytes_read = read LFILE, my $version, 1;
76+
die 'Got $bytes_read but expected 1' unless $bytes_read == 1;
77+
if ($version cmp "\x01")
78+
{
79+
die 'Unexpected replication encryption header version';
80+
}
81+
$bytes_read = read LFILE, my $field_type, 1;
82+
die 'Got $bytes_read but expected 1' unless $bytes_read == 1;
83+
if ($field_type cmp "\x01")
84+
{
85+
die 'Unexpected field type';
86+
}
87+
$bytes_read = read LFILE, my $length, 1;
88+
die 'Got $bytes_read but expected 1' unless $bytes_read == 1;
89+
$length = ord($length);
90+
$bytes_read = read LFILE, my $key_id, $length;
91+
die 'Got $bytes_read but expected $length' unless $bytes_read == $length;
92+
print RFILE "--let \$rpl_encryption_key_id=$key_id\n" or die "Error writing to $result_file: $!";
93+
}
94+
else {
95+
die 'Not a binary log file.';
96+
}
97+
98+
close LFILE or die "Error closing $log_file: $!";
99+
close RFILE or die "Error closing $result_file: $!";
100+
101+
EOF
102+
103+
--source $_RPL_RESULT_FILE
104+
--remove_file $_RPL_RESULT_FILE
105+
--let _RPL_LOG_FILE=
106+
--let _RPL_DEBUG=
107+
--let _RPL_RESULT_FILE=

mysql-test/include/rpl_start_server.inc

+5
Original file line numberDiff line numberDiff line change
@@ -132,3 +132,8 @@ if (!$rpl_server_error)
132132
--let $include_filename= rpl_start_server.inc [$_rpl_start_server_info]
133133
--source include/end_include_file.inc
134134
}
135+
136+
if ($rpl_server_error)
137+
{
138+
--dec $_include_file_depth
139+
}
+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# show binary logs
22

33
# mask out the binlog position
4-
-- replace_column 2 #
4+
-- replace_column 2 # 3 #
55
show binary logs;
+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# show master logs
22

3-
# mask out the binlog position
4-
-- replace_column 2 #
3+
# mask out the binlog position and the encrypted columns
4+
-- replace_column 2 # 3 #
55
query_vertical show master logs;

mysql-test/r/mysqld--help-notwin.result

+2
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ The following options may be given as the first argument:
6969
--binlog-do-db=name Tells the master it should log updates for the specified
7070
database, and exclude all others not explicitly
7171
mentioned.
72+
--binlog-encryption Enable/disable binary and relay logs encryption.
7273
--binlog-error-action=name
7374
When statements cannot be written to the binary log due
7475
to a fatal error, the server can either ignore the error
@@ -1335,6 +1336,7 @@ bind-address *
13351336
binlog-cache-size 32768
13361337
binlog-checksum CRC32
13371338
binlog-direct-non-transactional-updates FALSE
1339+
binlog-encryption FALSE
13381340
binlog-error-action ABORT_SERVER
13391341
binlog-expire-logs-seconds 2592000
13401342
binlog-format ROW

mysql-test/r/mysqld--help-win.result

+2
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ The following options may be given as the first argument:
6969
--binlog-do-db=name Tells the master it should log updates for the specified
7070
database, and exclude all others not explicitly
7171
mentioned.
72+
--binlog-encryption Enable/disable binary and relay logs encryption.
7273
--binlog-error-action=name
7374
When statements cannot be written to the binary log due
7475
to a fatal error, the server can either ignore the error
@@ -1343,6 +1344,7 @@ bind-address *
13431344
binlog-cache-size 32768
13441345
binlog-checksum CRC32
13451346
binlog-direct-non-transactional-updates FALSE
1347+
binlog-encryption FALSE
13461348
binlog-error-action ABORT_SERVER
13471349
binlog-expire-logs-seconds 2592000
13481350
binlog-format ROW
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
CALL mtr.add_suppression('Unsafe statement written to the binary log using statement format');
2+
CREATE TABLE t1 (c1 INT PRIMARY KEY, c2 TEXT, pos INT);
3+
# Inserting 100 random transaction
4+
# Asserting we can show binlog events from each transaction
5+
DROP TABLE t1;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
--early-plugin-load="keyring_file=$KEYRING_PLUGIN"
2+
--keyring_file_data=$MYSQL_TMP_DIR/keyring_data
3+
--binlog_encryption=ON
4+
$KEYRING_PLUGIN_OPT
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
# ==== Purpose ====
2+
#
3+
# This test will generate a random workload in the server and will perform
4+
# many SHOW BINLOG EVENTS pointing to the positions the transactions start
5+
# in the encrypted binary log file.
6+
#
7+
# The server shall be able to display the requested events properly, by doing
8+
# random access to the encrypted binary log file.
9+
#
10+
# ==== Related Bugs and Worklogs ====
11+
#
12+
# WL#10957: Binary log encryption at rest
13+
#
14+
15+
--source include/have_log_bin.inc
16+
17+
# Suppression of error messages
18+
CALL mtr.add_suppression('Unsafe statement written to the binary log using statement format');
19+
--let $binlog_file=query_get_value(SHOW MASTER STATUS, File, 1)
20+
21+
CREATE TABLE t1 (c1 INT PRIMARY KEY, c2 TEXT, pos INT);
22+
23+
--let $max_trx=100
24+
--echo # Inserting $max_trx random transaction
25+
--let $trx=$max_trx
26+
--disable_query_log
27+
--disable_warnings
28+
while ($trx)
29+
{
30+
--let $pos=query_get_value(SHOW MASTER STATUS, Position, 1)
31+
BEGIN;
32+
--eval INSERT INTO t1 (c1, c2, pos) VALUES ($trx, REPEAT('a', RAND() * 8192), $pos)
33+
COMMIT;
34+
--dec $trx
35+
}
36+
--enable_query_log
37+
--enable_warnings
38+
39+
--let $trx=$max_trx
40+
--echo # Asserting we can show binlog events from each transaction
41+
--disable_query_log
42+
while ($trx)
43+
{
44+
--let $pos=`SELECT pos FROM t1 WHERE c1 = $trx`
45+
--let $command=SHOW BINLOG EVENTS IN '$binlog_file' FROM $pos LIMIT 1
46+
--let $showed_pos=query_get_value($command, Pos, 1)
47+
if ($showed_pos != $pos)
48+
{
49+
--enable_query_log
50+
--echo requested_pos=$pos
51+
--eval $command
52+
--die requested_pos shall be equal to Pos
53+
}
54+
--dec $trx
55+
}
56+
--enable_query_log
57+
58+
# Cleanup
59+
DROP TABLE t1;

mysql-test/suite/binlog_nogtid/r/binlog_persist_only_variables.result

+7-5
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,13 @@ VARIABLE_NAME LIKE '%slave%') AND
2222
'innodb_master_thread_disabled_debug'))
2323
ORDER BY VARIABLE_NAME;
2424

25-
include/assert.inc ['Expect 81 variables in the table.']
25+
include/assert.inc ['Expect 82 variables in the table.']
2626

2727
# Test SET PERSIST_ONLY
2828
SET PERSIST_ONLY binlog_cache_size = @@GLOBAL.binlog_cache_size;
2929
SET PERSIST_ONLY binlog_checksum = @@GLOBAL.binlog_checksum;
3030
SET PERSIST_ONLY binlog_direct_non_transactional_updates = @@GLOBAL.binlog_direct_non_transactional_updates;
31+
SET PERSIST_ONLY binlog_encryption = @@GLOBAL.binlog_encryption;
3132
SET PERSIST_ONLY binlog_error_action = @@GLOBAL.binlog_error_action;
3233
SET PERSIST_ONLY binlog_expire_logs_seconds = @@GLOBAL.binlog_expire_logs_seconds;
3334
SET PERSIST_ONLY binlog_format = @@GLOBAL.binlog_format;
@@ -121,23 +122,24 @@ SET PERSIST_ONLY sync_master_info = @@GLOBAL.sync_master_info;
121122
SET PERSIST_ONLY sync_relay_log = @@GLOBAL.sync_relay_log;
122123
SET PERSIST_ONLY sync_relay_log_info = @@GLOBAL.sync_relay_log_info;
123124

124-
include/assert.inc ['Expect 70 persisted variables in persisted_variables table.']
125+
include/assert.inc ['Expect 71 persisted variables in persisted_variables table.']
125126

126127
############################################################
127128
# 2. Restart server, it must preserve the persisted variable
128129
# settings. Verify persisted configuration.
129130
# restart
130131

131-
include/assert.inc ['Expect 70 persisted variables in persisted_variables table.']
132-
include/assert.inc ['Expect 70 persisted variables shown as PERSISTED in variables_info table.']
133-
include/assert.inc ['Expect 70 persisted variables with matching peristed and global values.']
132+
include/assert.inc ['Expect 71 persisted variables in persisted_variables table.']
133+
include/assert.inc ['Expect 71 persisted variables shown as PERSISTED in variables_info table.']
134+
include/assert.inc ['Expect 70 persisted variables with matching persisted and global values.']
134135

135136
############################################################
136137
# 3. Test RESET PERSIST. Verify persisted variable settings
137138
# are removed.
138139
RESET PERSIST binlog_cache_size;
139140
RESET PERSIST binlog_checksum;
140141
RESET PERSIST binlog_direct_non_transactional_updates;
142+
RESET PERSIST binlog_encryption;
141143
RESET PERSIST binlog_error_action;
142144
RESET PERSIST binlog_expire_logs_seconds;
143145
RESET PERSIST binlog_format;

mysql-test/suite/binlog_nogtid/r/binlog_persist_variables.result

+7-5
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,13 @@ VARIABLE_NAME LIKE '%slave%') AND
2222
'innodb_master_thread_disabled_debug'))
2323
ORDER BY VARIABLE_NAME;
2424

25-
include/assert.inc ['Expect 81 variables in the table.']
25+
include/assert.inc ['Expect 82 variables in the table.']
2626

2727
# Test SET PERSIST
2828
SET PERSIST binlog_cache_size = @@GLOBAL.binlog_cache_size;
2929
SET PERSIST binlog_checksum = @@GLOBAL.binlog_checksum;
3030
SET PERSIST binlog_direct_non_transactional_updates = @@GLOBAL.binlog_direct_non_transactional_updates;
31+
SET PERSIST binlog_encryption = @@GLOBAL.binlog_encryption;
3132
SET PERSIST binlog_error_action = @@GLOBAL.binlog_error_action;
3233
SET PERSIST binlog_expire_logs_seconds = @@GLOBAL.binlog_expire_logs_seconds;
3334
SET PERSIST binlog_format = @@GLOBAL.binlog_format;
@@ -127,23 +128,24 @@ SET PERSIST sync_master_info = @@GLOBAL.sync_master_info;
127128
SET PERSIST sync_relay_log = @@GLOBAL.sync_relay_log;
128129
SET PERSIST sync_relay_log_info = @@GLOBAL.sync_relay_log_info;
129130

130-
include/assert.inc ['Expect 64 persisted variables in persisted_variables table.']
131+
include/assert.inc ['Expect 65 persisted variables in persisted_variables table.']
131132

132133
############################################################
133134
# 2. Restart server, it must preserve the persisted variable
134135
# settings. Verify persisted configuration.
135136
# restart
136137

137-
include/assert.inc ['Expect 64 persisted variables in persisted_variables table.']
138-
include/assert.inc ['Expect 64 persisted variables shown as PERSISTED in variables_info table.']
139-
include/assert.inc ['Expect 64 persisted variables with matching peristed and global values.']
138+
include/assert.inc ['Expect 65 persisted variables in persisted_variables table.']
139+
include/assert.inc ['Expect 65 persisted variables shown as PERSISTED in variables_info table.']
140+
include/assert.inc ['Expect 65 persisted variables with matching persisted and global values.']
140141

141142
############################################################
142143
# 3. Test RESET PERSIST IF EXISTS. Verify persisted variable
143144
# settings are removed.
144145
RESET PERSIST IF EXISTS binlog_cache_size;
145146
RESET PERSIST IF EXISTS binlog_checksum;
146147
RESET PERSIST IF EXISTS binlog_direct_non_transactional_updates;
148+
RESET PERSIST IF EXISTS binlog_encryption;
147149
RESET PERSIST IF EXISTS binlog_error_action;
148150
RESET PERSIST IF EXISTS binlog_expire_logs_seconds;
149151
RESET PERSIST IF EXISTS binlog_format;

mysql-test/suite/binlog_nogtid/t/binlog_persist_only_variables.test

+12-9
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,8 @@ INSERT INTO rplvars (varname, varvalue)
5757
# If this count differs, it means a variable has been added or removed.
5858
# In that case, this testcase needs to be updated accordingly.
5959
--echo
60-
--let $assert_text= 'Expect 81 variables in the table.'
61-
--let $assert_cond= [SELECT COUNT(*) as count FROM rplvars, count, 1] = 81
60+
--let $assert_text= 'Expect 82 variables in the table.'
61+
--let $assert_cond= [SELECT COUNT(*) as count FROM rplvars, count, 1] = 82
6262
--source include/assert.inc
6363

6464
--echo
@@ -88,8 +88,8 @@ while ( $varid <= $countvars )
8888
}
8989

9090
--echo
91-
--let $assert_text= 'Expect 70 persisted variables in persisted_variables table.'
92-
--let $assert_cond= [SELECT COUNT(*) as count FROM performance_schema.persisted_variables, count, 1] = 70
91+
--let $assert_text= 'Expect 71 persisted variables in persisted_variables table.'
92+
--let $assert_cond= [SELECT COUNT(*) as count FROM performance_schema.persisted_variables, count, 1] = 71
9393
--source include/assert.inc
9494

9595

@@ -101,15 +101,18 @@ while ( $varid <= $countvars )
101101
--source include/wait_until_connected_again.inc
102102

103103
--echo
104-
--let $assert_text= 'Expect 70 persisted variables in persisted_variables table.'
105-
--let $assert_cond= [SELECT COUNT(*) as count FROM performance_schema.persisted_variables, count, 1] = 70
104+
--let $assert_text= 'Expect 71 persisted variables in persisted_variables table.'
105+
--let $assert_cond= [SELECT COUNT(*) as count FROM performance_schema.persisted_variables, count, 1] = 71
106106
--source include/assert.inc
107107

108-
--let $assert_text= 'Expect 70 persisted variables shown as PERSISTED in variables_info table.'
109-
--let $assert_cond= [SELECT COUNT(*) as count FROM performance_schema.variables_info WHERE variable_source="PERSISTED", count, 1] = 70
108+
--let $assert_text= 'Expect 71 persisted variables shown as PERSISTED in variables_info table.'
109+
--let $assert_cond= [SELECT COUNT(*) as count FROM performance_schema.variables_info WHERE variable_source="PERSISTED", count, 1] = 71
110110
--source include/assert.inc
111111

112-
--let $assert_text= 'Expect 70 persisted variables with matching peristed and global values.'
112+
# WL#10957 added binlog_encryption and as it is persisted as read only and
113+
# appended to command line parameter, its value is not converted from 0 to OFF.
114+
# The values at persisted_variables and global_variables tables differ: 0 != OFF.
115+
--let $assert_text= 'Expect 70 persisted variables with matching persisted and global values.'
113116
--let $assert_cond= [SELECT COUNT(*) as count FROM performance_schema.variables_info vi JOIN performance_schema.persisted_variables pv JOIN performance_schema.global_variables gv ON vi.variable_name=pv.variable_name AND vi.variable_name=gv.variable_name AND pv.variable_value=gv.variable_value WHERE vi.variable_source="PERSISTED", count, 1] = 70
114117
--source include/assert.inc
115118

0 commit comments

Comments
 (0)