Skip to content

Commit 089f123

Browse files
author
Daogang Qu
committed
WL#12079: Encrypt binary log caches at rest
Use AES with CTR mode to encrypt temporary files created when binary log caches spill to disk when binlog_encryption is on. Client sessions will have their binary log caches unencrypted when binlog_encryption is off and client sessions will have their binary log caches encrypted when binlog_encryption is on. Every time a transaction commits, it requests its binlog cache to be reset. On this reset operation, the binlog cache shall check `binlog_encryption` value to enable or disable its encryption, and close the ciphers and open them again using new password and IV when binlog_encryption is on. RB: 21577
1 parent 287f8e5 commit 089f123

25 files changed

+813
-86
lines changed

client/CMakeLists.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ SET(MYSQLBINLOG_LIB_SOURCES
8686
${CMAKE_SOURCE_DIR}/sql/basic_istream.cc
8787
${CMAKE_SOURCE_DIR}/sql/binlog_istream.cc
8888
${CMAKE_SOURCE_DIR}/sql/binlog_reader.cc
89-
${CMAKE_SOURCE_DIR}/sql/rpl_cipher.cc
89+
${CMAKE_SOURCE_DIR}/sql/stream_cipher.cc
9090
${CMAKE_SOURCE_DIR}/sql/rpl_log_encryption.cc
9191
)
9292
ADD_LIBRARY(mysqlbinlog_lib STATIC ${MYSQLBINLOG_LIB_SOURCES})

include/my_sys.h

+70
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@
6464
#include "mysql/components/services/psi_memory_bits.h"
6565
#include "mysql/components/services/psi_stage_bits.h"
6666
#include "mysql/psi/psi_base.h"
67+
#include "sql/stream_cipher.h"
6768

6869
struct CHARSET_INFO;
6970
struct MY_CHARSET_LOADER;
@@ -472,6 +473,10 @@ struct IO_CACHE /* Used when cacheing files */
472473
somewhere else
473474
*/
474475
bool alloced_buffer{false};
476+
// This is an encryptor for encrypting the temporary file of the IO cache.
477+
Stream_cipher *m_encryptor = nullptr;
478+
// This is a decryptor for decrypting the temporary file of the IO cache.
479+
Stream_cipher *m_decryptor = nullptr;
475480
};
476481

477482
typedef int (*qsort2_cmp)(const void *, const void *, const void *);
@@ -975,4 +980,69 @@ extern MYSQL_FILE *mysql_stdin;
975980
@} (end of group MYSYS)
976981
*/
977982

983+
// True if the temporary file of binlog cache is encrypted.
984+
#ifndef DBUG_OFF
985+
extern bool binlog_cache_temporary_file_is_encrypted;
986+
#endif
987+
988+
/**
989+
This is a wrapper around mysql_file_seek. Seek to a position in the
990+
temporary file of a binlog cache, and set the encryption/decryption
991+
stream offset if binlog_encryption is on.
992+
993+
@param cache The handler of a binlog cache to seek.
994+
@param pos The expected position (absolute or relative)
995+
@param whence A direction parameter and one of
996+
{SEEK_SET, SEEK_CUR, SEEK_END}
997+
@param flags The bitmap of different flags
998+
MY_WME | MY_FAE | MY_NABP | MY_FNABP |
999+
MY_DONT_CHECK_FILESIZE and so on.
1000+
1001+
@retval The new position in the file, or MY_FILEPOS_ERROR on error.
1002+
*/
1003+
my_off_t mysql_encryption_file_seek(IO_CACHE *cache, my_off_t pos, int whence,
1004+
myf flags);
1005+
/**
1006+
This is a wrapper around mysql_file_read. Read data from the temporary
1007+
file of a binlog cache, and take care of decrypting the data if
1008+
binlog_encryption is on.
1009+
1010+
1011+
@param cache The handler of a binlog cache to read.
1012+
@param[out] buffer The memory buffer to write to.
1013+
@param count The length of data in the temporary file to be read in bytes.
1014+
@param flags The bitmap of different flags
1015+
MY_WME | MY_FAE | MY_NABP | MY_FNABP |
1016+
MY_DONT_CHECK_FILESIZE and so on.
1017+
1018+
@retval The length of bytes to be read, or MY_FILE_ERROR on error.
1019+
*/
1020+
size_t mysql_encryption_file_read(IO_CACHE *cache, uchar *buffer, size_t count,
1021+
myf flags);
1022+
/**
1023+
This is a wrapper around mysql_file_write. Write data in buffer to the
1024+
temporary file of a binlog cache, and take care of encrypting the data
1025+
if binlog_encryption is on.
1026+
1027+
@param cache The handler of a binlog cache to write.
1028+
@param buffer The memory buffer to write from.
1029+
@param count The length of data in buffer to be written in bytes.
1030+
@param flags The bitmap of different flags
1031+
MY_WME | MY_FAE | MY_NABP | MY_FNABP |
1032+
MY_DONT_CHECK_FILESIZE and so on
1033+
1034+
if (flags & (MY_NABP | MY_FNABP)) {
1035+
@retval 0 if count == 0
1036+
@retval 0 success
1037+
@retval MY_FILE_ERROR error
1038+
} else {
1039+
@retval 0 if count == 0
1040+
@retval The number of bytes written on success.
1041+
@retval MY_FILE_ERROR error
1042+
@retval The actual number of bytes written on partial success (if
1043+
less than count bytes were written).
1044+
}
1045+
*/
1046+
size_t mysql_encryption_file_write(IO_CACHE *cache, const uchar *buffer,
1047+
size_t count, myf flags);
9781048
#endif /* _my_sys_h */
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
CREATE TABLE t1 (c1 TEXT);
2+
# Adding debug point 'verify_mysql_encryption_file_write_bytes' to @@GLOBAL.debug
3+
INSERT INTO t1 VALUES (REPEAT('123', 16384.0));
4+
# Removing debug point 'verify_mysql_encryption_file_write_bytes' from @@GLOBAL.debug
5+
# Adding debug point 'simulate_binlog_cache_temp_file_encrypt_fail' to @@GLOBAL.debug
6+
INSERT INTO t1 VALUES (REPEAT('456', 16384.0));
7+
ERROR HY000: Error writing file 'binlog' ((errno: #)
8+
# Removing debug point 'simulate_binlog_cache_temp_file_encrypt_fail' from @@GLOBAL.debug
9+
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,49 @@
1+
# ==== Purpose ====
2+
#
3+
# Verify that the correct number of bytes are written by
4+
# mysql_encryption_file_write(...) if both MY_NABP and
5+
# MY_FNABP are not set.
6+
#
7+
# ==== Implementation ====
8+
#
9+
# 1. Start the master with binlog_encryption on.
10+
# 2. Create a table t1 with a TEXT column.
11+
# 3. Execute a trx to insert a big text into the table to make
12+
# binlog cache spill to disk and ensure that the correct
13+
# number of bytes are written by mysql_encryption_file_write(...).
14+
# 4. Verify that an error 'ER_ERROR_ON_WRITE' is reported if there
15+
# is a failure on encrypting binlog cache temporary file.
16+
# ==== References ====
17+
#
18+
# Wl#12079 Encrypt binary log caches at rest
19+
20+
# This test script will be run only in non GR set up.
21+
--source include/not_group_replication_plugin.inc
22+
--source include/have_binlog_format_row.inc
23+
# Restrict the test runs to only debug builds, since we set DEBUG point in the test.
24+
--source include/have_debug.inc
25+
26+
--let $data_size= `select 0.5 * @@global.binlog_cache_size`
27+
28+
CREATE TABLE t1 (c1 TEXT);
29+
30+
--let $debug_point= verify_mysql_encryption_file_write_bytes
31+
--source include/add_debug_point.inc
32+
33+
eval INSERT INTO t1 VALUES (REPEAT('123', $data_size));
34+
35+
--let $debug_point= verify_mysql_encryption_file_write_bytes
36+
--source include/remove_debug_point.inc
37+
38+
39+
--let $debug_point= simulate_binlog_cache_temp_file_encrypt_fail
40+
--source include/add_debug_point.inc
41+
42+
--replace_regex /(errno: .*)/(errno: #)/
43+
--error ER_ERROR_ON_WRITE
44+
eval INSERT INTO t1 VALUES (REPEAT('456', $data_size));
45+
46+
--let $debug_point= simulate_binlog_cache_temp_file_encrypt_fail
47+
--source include/remove_debug_point.inc
48+
49+
DROP TABLE t1;

mysql-test/suite/group_replication/r/gr_key_rotation.result

+11
Original file line numberDiff line numberDiff line change
@@ -73,10 +73,21 @@ include/diff_tables.inc [server1:t1, server2:t1]
7373
# check that t2 exists and has same values in both servers
7474
include/diff_tables.inc [server1:t2, server2:t2]
7575
# check that t3 exists and has same values in both servers
76+
[connection server1]
77+
SET GLOBAL binlog_encryption=ON;
78+
CREATE TABLE t4 (c1 TEXT,c2 INT AUTO_INCREMENT PRIMARY KEY);
79+
# Adding debug point 'ensure_binlog_cache_temporary_file_is_encrypted' to @@GLOBAL.debug
80+
INSERT INTO t4(c1) VALUES (REPEAT('123', 16384.0));
81+
# Removing debug point 'ensure_binlog_cache_temporary_file_is_encrypted' from @@GLOBAL.debug
82+
SET GLOBAL binlog_encryption = OFF;
83+
include/rpl_sync.inc
84+
# check that t4 exists and has same values in both servers
85+
[connection server2]
7686
UNINSTALL PLUGIN keyring_file;
7787
[connection server1]
7888
UNINSTALL PLUGIN keyring_file;
7989
DROP TABLE t2;
8090
DROP TABLE t3;
8191
DROP TABLE t1;
92+
DROP TABLE t4;
8293
include/group_replication_end.inc

mysql-test/suite/group_replication/t/gr_key_rotation.test

+42-2
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,20 @@
1414
# 2.3 Check that M2 is in ERROR state.
1515
# 2.4 Stop GR, Install keyring plugin and start GR on M2.
1616
# 2.5 Check that t3 exists and has same values on both the servers.
17-
# 3. Clean Up.
17+
# 3. Verify that the temporary file of binlog cache is encrypted when
18+
# the binlog cache spills to disk if binlog_encryption is on.
19+
# 3.1 Enable binlog_encryption on M1.
20+
# 3.2 Create a table t4 with TEXT column.
21+
# 3.3 Execute a trx to insert a big text into the table to make
22+
# binlog cache spill to disk and ensure that the temporary
23+
# file of binlog cache is encrypted.
24+
# 3.4 Check that t4 exists and has same values on both the servers.
25+
# 4. Clean Up.
1826
###############################################################################
1927

2028
--source include/big_test.inc
29+
# Restrict the test runs to only debug builds, since we set DEBUG point in the test.
30+
--source include/have_debug.inc
2131
# Ensure that plugin is installed.
2232
--source include/have_group_replication_plugin_base.inc
2333

@@ -176,9 +186,38 @@ DELETE FROM t1 WHERE c1=1;
176186
--echo # check that t3 exists and has same values in both servers
177187
--let $diff_tables=server1:t3, server2:t3
178188

179-
# Clean Up
189+
# Scenario 3
190+
191+
# 3.1 Enable binlog_encryption on M1.
192+
--let $rpl_connection_name= server1
193+
--source include/rpl_connection.inc
194+
195+
SET GLOBAL binlog_encryption=ON;
196+
197+
# 3.2 Create a table t4 with TEXT column.
198+
--let $data_size= `select 0.5 * @@global.binlog_cache_size`
199+
200+
CREATE TABLE t4 (c1 TEXT,c2 INT AUTO_INCREMENT PRIMARY KEY);
201+
202+
# 3.3 Execute a trx to insert a big text into the table to make binlog cache spill to disk
203+
--let $debug_point= ensure_binlog_cache_temporary_file_is_encrypted
204+
--source include/add_debug_point.inc
205+
206+
eval INSERT INTO t4(c1) VALUES (REPEAT('123', $data_size));
207+
208+
--let $debug_point= ensure_binlog_cache_temporary_file_is_encrypted
209+
--source include/remove_debug_point.inc
210+
211+
SET GLOBAL binlog_encryption = OFF;
212+
213+
# 3.4 Check that t4 exists and has same values on both the servers.
214+
--source include/rpl_sync.inc
215+
--echo # check that t4 exists and has same values in both servers
216+
--let $diff_tables=server1:t4, server2:t4
180217

181218
# Uninstall keyring plugin and remove dummy keyring file on server2
219+
--let $rpl_connection_name= server2
220+
--source include/rpl_connection.inc
182221
UNINSTALL PLUGIN keyring_file;
183222
--remove_file $MYSQL_TMP_DIR/mydummy_key2
184223

@@ -191,6 +230,7 @@ UNINSTALL PLUGIN keyring_file;
191230
DROP TABLE t2;
192231
DROP TABLE t3;
193232
DROP TABLE t1;
233+
DROP TABLE t4;
194234

195235
--source include/force_restart.inc
196236
--source include/group_replication_end.inc
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
include/master-slave.inc
2+
Warnings:
3+
Note #### Sending passwords in plain text without SSL/TLS is extremely insecure.
4+
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.
5+
[connection master]
6+
[connection slave]
7+
[connection master]
8+
# Part 1 - binlog_encryption = OFF
9+
CREATE TABLE t1 (c1 INT PRIMARY KEY AUTO_INCREMENT, c2 TEXT);
10+
# Inserting 50 transactions with save points
11+
SHOW BINLOG EVENTS shall not fail on the un-encrypted binlog file
12+
SHOW BINLOG EVENTS IN 'PLAIN_MASTER_FILE';
13+
# Restarting the server with "--binlog_encryption=ON" and keyring
14+
include/rpl_restart_server.inc [server_number=1]
15+
include/assert.inc [binlog_encryption option shall be ON]
16+
include/assert.inc [Binary log is encrypted using 1st master key]
17+
# Part 2 - binlog_encryption = ON
18+
# Inserting 50 transactions with save points
19+
SHOW BINLOG EVENTS shall not fail on the encrypted binlog file
20+
SHOW BINLOG EVENTS IN 'ENCRYPTED_MASTER_FILE';
21+
# Restarting the server without "--binlog_encryption=ON"
22+
include/rpl_restart_server.inc [server_number=1]
23+
[connection slave]
24+
include/start_slave.inc
25+
[connection master]
26+
include/sync_slave_sql_with_master.inc
27+
include/diff_tables.inc [master:t1, slave:t1]
28+
[connection master]
29+
UNINSTALL PLUGIN keyring_file;
30+
DROP TABLE t1;
31+
include/rpl_end.inc
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
include/master-slave.inc
2+
Warnings:
3+
Note #### Sending passwords in plain text without SSL/TLS is extremely insecure.
4+
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.
5+
[connection master]
6+
CREATE TABLE t1 (c1 TEXT) ENGINE=INNODB;
7+
CREATE TABLE t2 (c2 TEXT) ENGINE=MYISAM;
8+
# Adding debug point 'ensure_binlog_cache_temporary_file_is_encrypted' to @@GLOBAL.debug
9+
INSERT INTO t1 VALUES (REPEAT('123', 16384.0));
10+
INSERT INTO t2 VALUES (REPEAT('123', 16384.0));
11+
# Removing debug point 'ensure_binlog_cache_temporary_file_is_encrypted' from @@GLOBAL.debug
12+
SET GLOBAL binlog_encryption=OFF;
13+
# Adding debug point 'ensure_binlog_cache_temp_file_encryption_is_disabled' to @@GLOBAL.debug
14+
INSERT INTO t1 VALUES (REPEAT('off', 16384.0));
15+
INSERT INTO t2 VALUES (REPEAT('off', 16384.0));
16+
# Removing debug point 'ensure_binlog_cache_temp_file_encryption_is_disabled' from @@GLOBAL.debug
17+
SET GLOBAL binlog_encryption=ON;
18+
# Adding debug point 'ensure_binlog_cache_temporary_file_is_encrypted' to @@GLOBAL.debug
19+
INSERT INTO t1 VALUES (REPEAT('on1', 16384.0));
20+
INSERT INTO t2 VALUES (REPEAT('on1', 16384.0));
21+
# Removing debug point 'ensure_binlog_cache_temporary_file_is_encrypted' from @@GLOBAL.debug
22+
# Adding debug point 'ensure_binlog_cache_is_reset' to @@GLOBAL.debug
23+
INSERT INTO t1 VALUES ("567");
24+
BEGIN;
25+
INSERT INTO t1 VALUES ("789");
26+
ROLLBACK;
27+
INSERT INTO t2 VALUES ("567");
28+
# Removing debug point 'ensure_binlog_cache_is_reset' from @@GLOBAL.debug
29+
include/sync_slave_sql_with_master.inc
30+
include/diff_tables.inc [master:t1,slave:t1]
31+
[connection master]
32+
DROP TABLE t1;
33+
DROP TABLE t2;
34+
include/rpl_end.inc

0 commit comments

Comments
 (0)