Skip to content

Commit

Permalink
PS-5476: Report error when redo log encryption is requested without a…
Browse files Browse the repository at this point in the history
… keyring

Issue: when the server is started without a keyring, the redo log encryption
checks were never performed, redo log encryption wasn't initialized, but the
setting was left on, misleading the user. Later operations which triggered
this check could possibly fail.

Issue #2: redo log encryption didn't work properly when it was turned on using
the command line: in some cases, the redo log remained unencrypted (until
another operation triggered the redo log encryption setup methods to be called
again)

Both issues were caused by the refactoring of the periodic, once every
second encryption checks in upstream, in PS-5189.

This commit:

* changes the code so redo log encryption routines are called at least twice
during every startup with the encryption settings on. This fixes issue #2.
* changes the code so redo log encryption routines are called at least once
during every startup, even without a keyring, or a read-only server: this fixes
issues #1
* adds a test ensuring that in an invalid configuration (without a keyring) the
the server remains running, but the redo log encryption setting correctly displays the
OFF status
* adds a test ensuring that when the redo log is correctly configured (using the
dynamic variable or a command line parameter) the log is encrypted, and no unencrypted
data is present in it.
* backports additional redo log related bugfixes/refactorings from 8.0
(92198f4). The original commit contains fixes
for both the redo and the undo log, the backport only includes the redo log related
parts refactored to match our changes.
  • Loading branch information
dutow committed May 28, 2019
1 parent a339a99 commit c9f151e
Show file tree
Hide file tree
Showing 21 changed files with 365 additions and 154 deletions.
2 changes: 1 addition & 1 deletion mysql-test/include/log_encrypt_3.inc
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ let $restart_parameters = restart: --early-plugin-load="keyring_file=$KEYRING_PL
--source include/restart_mysqld_no_echo.inc

SELECT @@global.innodb_redo_log_encrypt;
SET GLOBAL innodb_redo_log_encrypt = 1;
eval SET GLOBAL innodb_redo_log_encrypt = $redo_log_encrypt_mode;
SELECT @@global.innodb_redo_log_encrypt;

CREATE TABLE tde_db.t1 (a BIGINT PRIMARY KEY, b LONGBLOB) ENGINE=InnoDB;
Expand Down
34 changes: 34 additions & 0 deletions mysql-test/include/percona_log_encrypt_content.inc
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# InnoDB transparent tablespace data encryption
# This test case will verify that no unencrypted data is in the logs

--source include/no_valgrind_without_big.inc
--source include/have_innodb.inc
--source include/not_embedded.inc

# Test: command line parameter

--let ABORT_ON=FOUND
--source include/percona_log_encrypt_content_test.inc

# Test: variable

# Restart the server with keyring loaded
--let restart_parameters="restart:$KEYRING_PARAMS"
--source include/restart_mysqld_no_echo.inc

--eval SET GLOBAL innodb_redo_log_encrypt=$LOG_ENCRYPT_TYPE

--source include/percona_log_encrypt_content_test.inc

# Restart the server with keyring loaded
--let restart_parameters="restart:$KEYRING_PARAMS"
--source include/restart_mysqld_no_echo.inc
SET GLOBAL innodb_redo_log_encrypt=OFF;

--let ABORT_ON=NOT_FOUND
--source include/percona_log_encrypt_content_test.inc

# Cleanup
--eval SET GLOBAL innodb_redo_log_encrypt=$LOG_ENCRYPT_TYPE
--remove_file $MYSQL_TMP_DIR/mysecret_keyring_content

14 changes: 14 additions & 0 deletions mysql-test/include/percona_log_encrypt_content_test.inc
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@

CREATE TABLE t1(c1 INT, c2 char(20)) ENGINE = InnoDB;

INSERT INTO t1 VALUES(0, "asdfghjkl");
INSERT INTO t1 VALUES(1, "qwertyuio");
INSERT INTO t1 VALUES(2, "zxcvbnm");

# Check file content
--let $MYSQLD_DATADIR= `select @@datadir`
--let SEARCH_PATTERN= asdfghjkl
-- let SEARCH_FILE= $MYSQLD_DATADIR/ib_logfile0
-- source include/search_pattern_in_file.inc

DROP TABLE t1;
2 changes: 1 addition & 1 deletion mysql-test/suite/innodb/r/log_encrypt_3_mk.result
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ UNINSTALL PLUGIN keyring_file;
SELECT @@global.innodb_redo_log_encrypt;
@@global.innodb_redo_log_encrypt
master_key
SET GLOBAL innodb_redo_log_encrypt = 1;
SET GLOBAL innodb_redo_log_encrypt = MASTER_KEY;
SELECT @@global.innodb_redo_log_encrypt;
@@global.innodb_redo_log_encrypt
master_key
Expand Down
4 changes: 2 additions & 2 deletions mysql-test/suite/innodb/r/log_encrypt_3_rk.result
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,10 @@ UNINSTALL PLUGIN keyring_file;
SELECT @@global.innodb_redo_log_encrypt;
@@global.innodb_redo_log_encrypt
keyring_key
SET GLOBAL innodb_redo_log_encrypt = 1;
SET GLOBAL innodb_redo_log_encrypt = KEYRING_KEY;
SELECT @@global.innodb_redo_log_encrypt;
@@global.innodb_redo_log_encrypt
master_key
keyring_key
CREATE TABLE tde_db.t1 (a BIGINT PRIMARY KEY, b LONGBLOB) ENGINE=InnoDB;
INSERT INTO t1 (a, b) VALUES (1, REPEAT('a', 6*512*512));
SELECT a,LEFT(b,10) FROM tde_db.t1;
Expand Down
18 changes: 18 additions & 0 deletions mysql-test/suite/innodb/r/percona_log_encrypt_content_mk.result
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
CREATE TABLE t1(c1 INT, c2 char(20)) ENGINE = InnoDB;
INSERT INTO t1 VALUES(0, "asdfghjkl");
INSERT INTO t1 VALUES(1, "qwertyuio");
INSERT INTO t1 VALUES(2, "zxcvbnm");
DROP TABLE t1;
SET GLOBAL innodb_redo_log_encrypt=MASTER_KEY;
CREATE TABLE t1(c1 INT, c2 char(20)) ENGINE = InnoDB;
INSERT INTO t1 VALUES(0, "asdfghjkl");
INSERT INTO t1 VALUES(1, "qwertyuio");
INSERT INTO t1 VALUES(2, "zxcvbnm");
DROP TABLE t1;
SET GLOBAL innodb_redo_log_encrypt=OFF;
CREATE TABLE t1(c1 INT, c2 char(20)) ENGINE = InnoDB;
INSERT INTO t1 VALUES(0, "asdfghjkl");
INSERT INTO t1 VALUES(1, "qwertyuio");
INSERT INTO t1 VALUES(2, "zxcvbnm");
DROP TABLE t1;
SET GLOBAL innodb_redo_log_encrypt=MASTER_KEY;
18 changes: 18 additions & 0 deletions mysql-test/suite/innodb/r/percona_log_encrypt_content_rk.result
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
CREATE TABLE t1(c1 INT, c2 char(20)) ENGINE = InnoDB;
INSERT INTO t1 VALUES(0, "asdfghjkl");
INSERT INTO t1 VALUES(1, "qwertyuio");
INSERT INTO t1 VALUES(2, "zxcvbnm");
DROP TABLE t1;
SET GLOBAL innodb_redo_log_encrypt=KEYRING_KEY;
CREATE TABLE t1(c1 INT, c2 char(20)) ENGINE = InnoDB;
INSERT INTO t1 VALUES(0, "asdfghjkl");
INSERT INTO t1 VALUES(1, "qwertyuio");
INSERT INTO t1 VALUES(2, "zxcvbnm");
DROP TABLE t1;
SET GLOBAL innodb_redo_log_encrypt=OFF;
CREATE TABLE t1(c1 INT, c2 char(20)) ENGINE = InnoDB;
INSERT INTO t1 VALUES(0, "asdfghjkl");
INSERT INTO t1 VALUES(1, "qwertyuio");
INSERT INTO t1 VALUES(2, "zxcvbnm");
DROP TABLE t1;
SET GLOBAL innodb_redo_log_encrypt=KEYRING_KEY;
7 changes: 7 additions & 0 deletions mysql-test/suite/innodb/r/percona_log_encrypt_failure.result
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
call mtr.add_suppression("Encryption can't find master key, please check the keyring plugin is loaded.");
call mtr.add_suppression("Can't set redo log tablespace to be encrypted.");
select @@innodb_redo_log_encrypt;
@@innodb_redo_log_encrypt
off
Pattern "Can't set redo log tablespace to be encrypted." found
Pattern "Encryption can't find master key, please check the keyring plugin is loaded." found
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
$KEYRING_PLUGIN_OPT
$KEYRING_PLUGIN_EARLY_LOAD
--loose-keyring_file_data=$MYSQL_TMP_DIR/mysecret_keyring_content
--innodb_redo_log_encrypt=MASTER_KEY
2 changes: 2 additions & 0 deletions mysql-test/suite/innodb/t/percona_log_encrypt_content_mk.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
--let LOG_ENCRYPT_TYPE=MASTER_KEY
--source include/percona_log_encrypt_content.inc
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
$KEYRING_PLUGIN_OPT
$KEYRING_PLUGIN_EARLY_LOAD
--loose-keyring_file_data=$MYSQL_TMP_DIR/mysecret_keyring_content
--innodb_redo_log_encrypt=KEYRING_KEY
2 changes: 2 additions & 0 deletions mysql-test/suite/innodb/t/percona_log_encrypt_content_rk.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
--let LOG_ENCRYPT_TYPE=KEYRING_KEY
--source include/percona_log_encrypt_content.inc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
--innodb-redo-log-encrypt=MASTER_KEY
11 changes: 11 additions & 0 deletions mysql-test/suite/innodb/t/percona_log_encrypt_failure.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
call mtr.add_suppression("Encryption can't find master key, please check the keyring plugin is loaded.");
call mtr.add_suppression("Can't set redo log tablespace to be encrypted.");
select @@innodb_redo_log_encrypt;


--let SEARCH_FILE= $MYSQLTEST_VARDIR/log/mysqld.1.err
--let SEARCH_PATTERN=Can't set redo log tablespace to be encrypted.
--source include/search_pattern.inc
--let SEARCH_PATTERN=Encryption can't find master key, please check the keyring plugin is loaded.
--source include/search_pattern.inc

Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,10 @@ set global innodb_redo_log_encrypt='MASTER_KEY';
set global innodb_redo_log_encrypt='KEYRING_KEY';
select * from performance_schema.global_variables where variable_name='innodb_redo_log_encrypt';
VARIABLE_NAME VARIABLE_VALUE
innodb_redo_log_encrypt keyring_key
innodb_redo_log_encrypt off
select * from performance_schema.session_variables where variable_name='innodb_redo_log_encrypt';
VARIABLE_NAME VARIABLE_VALUE
innodb_redo_log_encrypt keyring_key
innodb_redo_log_encrypt off
set @@global.innodb_redo_log_encrypt=0;
select @@global.innodb_redo_log_encrypt;
@@global.innodb_redo_log_encrypt
Expand Down
87 changes: 71 additions & 16 deletions storage/innobase/handler/ha_innodb.cc
Original file line number Diff line number Diff line change
Expand Up @@ -3910,29 +3910,36 @@ static bool innobase_is_supported_system_table(const char *db,
/* mutex protecting the master_key_id */
ib_mutex_t master_key_id_mutex;

void srv_enable_undo_encryption_if_set();

/** Fix the empty UUID of tablespaces like system, temp etc by generating
a new master key and do key rotation. These tablespaces if encrypted
during startup, will be encrypted with tablespace key which has empty UUID
@return false on success, true on failure */
bool
innobase_fix_tablespaces_empty_uuid()
{
if (Encryption::master_key_id == 0) {
/* We have to call srv_enable_redo_encryption during every
startup, to report errors generated in this function correctly.
Without this call here, some illegal configurations, such as
enabling encryption without a keyring are silently accepted,
and result in errors later during the server run.
These functions are also called later, when the
master key is correctly set up, later in this function. */
if(srv_enable_redo_encryption()) {
srv_redo_log_encrypt = REDO_LOG_ENCRYPT_OFF;
} else {
redo_rotate_default_key();
}
}

/* If we are in read only mode, we cannot do rotation but it
is OK */
if (srv_read_only_mode) {
return(false);
}

if (Encryption::master_key_id == 0 && srv_redo_log_encrypt == REDO_LOG_ENCRYPT_RK) {
/* redo log can be encrypted with keyring_key, which has to be
initialized even when nothing's encrypted with master_key - in
which case, master_key_id == 0, so this condition succeeds.
We call this function here, because otherwise, if the redo log
is encrypted with master_key, log encryption has to be initialized
after Encryption::create_master_key. */
log_enable_encryption_if_set();
}

/* We only need to handle the case when an encrypted tablespace
is created at startup. If it is 0, there is no encrypted tablespace,
If it is > 1, it means we already have fixed the UUID */
Expand Down Expand Up @@ -3963,7 +3970,11 @@ innobase_fix_tablespaces_empty_uuid()
return(true);
}

log_enable_encryption_if_set();
if (srv_enable_redo_encryption()) {
srv_redo_log_encrypt = REDO_LOG_ENCRYPT_OFF;
} else {
redo_rotate_default_key();
}

/** Check if sys, temp need rotation to fix the empty uuid */
/* Also, in future, it is possible to fix empty uuid for redo & undo
Expand Down Expand Up @@ -21437,12 +21448,36 @@ innodb_temp_tablespace_encryption_update(
@param[in] save immediate result from check function */
static
void
innodb_redo_encryption_update(
update_innodb_redo_log_encrypt(
THD* thd,
struct st_mysql_sys_var* var,
void* var_ptr,
const void* save)
{
const ulong target = *static_cast<const ulong *>(save);

if (srv_redo_log_encrypt == target) {
/* No change */
return;
}

if (target == REDO_LOG_ENCRYPT_OFF) {
srv_redo_log_encrypt = REDO_LOG_ENCRYPT_OFF;
return;
}

if (srv_redo_log_encrypt != REDO_LOG_ENCRYPT_OFF
&& srv_redo_log_encrypt != target) {
push_warning_printf(
thd, Sql_condition::SL_WARNING,
ER_WRONG_ARGUMENTS,
" Redo log encryption mode"
" can't be switched without stopping the server and"
" recreating the redo logs.");
return;
}


if (srv_read_only_mode) {
push_warning_printf(
thd, Sql_condition::SL_WARNING,
Expand All @@ -21452,10 +21487,30 @@ innodb_redo_encryption_update(
return;
}

*static_cast<ulong*>(var_ptr) =
*static_cast<const ulong*>(save);
byte iv[ENCRYPTION_KEY_LEN];
Encryption::random_value(iv);

log_enable_encryption_if_set();
if (target == REDO_LOG_ENCRYPT_MK) {
ut_ad(strlen(server_uuid) > 0);
if(srv_enable_redo_encryption_mk()) {
return;
}

srv_redo_log_encrypt = target;
return;
}

if (target == REDO_LOG_ENCRYPT_RK) {
ut_ad(strlen(server_uuid) > 0);
if(srv_enable_redo_encryption_rk()) {
return;
}

srv_redo_log_encrypt = target;
return;
}

ut_ad(0);
}

static SHOW_VAR innodb_status_variables_export[]= {
Expand Down Expand Up @@ -22521,7 +22576,7 @@ static MYSQL_SYSVAR_ENUM(default_row_format, innodb_default_row_format,
static MYSQL_SYSVAR_ENUM(redo_log_encrypt, srv_redo_log_encrypt,
PLUGIN_VAR_OPCMDARG,
"Enable or disable Encryption of REDO tablespace. Possible values: OFF, MASTER_KEY, KEYRING_KEY.",
NULL, innodb_redo_encryption_update, REDO_LOG_ENCRYPT_OFF, &redo_log_encrypt_typelib);
NULL, update_innodb_redo_log_encrypt, REDO_LOG_ENCRYPT_OFF, &redo_log_encrypt_typelib);

#ifdef UNIV_DEBUG
static MYSQL_SYSVAR_UINT(trx_rseg_n_slots_debug, trx_rseg_n_slots_debug,
Expand Down
13 changes: 11 additions & 2 deletions storage/innobase/include/log0log.h
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,14 @@ Closes the log.
lsn_t
log_close(void);

enum redo_log_encrypt_enum {
REDO_LOG_ENCRYPT_OFF = 0,
REDO_LOG_ENCRYPT_MK = 1,
REDO_LOG_ENCRYPT_RK = 2,
};

void redo_rotate_default_key();

/** Write the encryption info into the log file header(the 3rd block).
It just need to flush the file header block with current master key.
@param[in] key encryption key
Expand All @@ -151,7 +159,8 @@ It just need to flush the file header block with current master key.
bool
log_write_encryption(
byte* key,
byte* iv);
byte* iv,
redo_log_encrypt_enum redo_log_encrypt);

/** Rotate the redo log encryption
* It will re-encrypt the redo log encryption metadata and write it to
Expand All @@ -162,7 +171,7 @@ log_rotate_encryption();

/** Enables redo log encryption. */
void
log_enable_encryption_if_set();
redo_rotate_default_master_key();
/************************************************************//**
Gets the current lsn.
@return current lsn */
Expand Down
19 changes: 13 additions & 6 deletions storage/innobase/include/srv0srv.h
Original file line number Diff line number Diff line change
Expand Up @@ -355,12 +355,6 @@ extern ulong srv_n_log_files;

extern ulong srv_redo_log_encrypt;

enum redo_log_encrypt_enum {
REDO_LOG_ENCRYPT_OFF = 0,
REDO_LOG_ENCRYPT_MK = 1,
REDO_LOG_ENCRYPT_RK = 2,
};

/** At startup, this is the current redo log file size.
During startup, if this is different from srv_log_file_size_requested
(innodb_log_file_size), the redo log will be rebuilt and this size
Expand Down Expand Up @@ -1053,6 +1047,19 @@ bool
srv_is_undo_tablespace(
ulint space_id);

/** Enables master key redo encryption.
Doesn't depend on the srv_redo_log_encrypt variable, used by
SET innodb_redo_log_encrypt = MK. */
bool srv_enable_redo_encryption_mk();

/** Enables keyring key redo encryption.
Doesn't depend on the srv_redo_log_encrypt variable, used by
SET innodb_redo_log_encrypt = RK. */
bool srv_enable_redo_encryption_rk();

/** Enables redo log encryption based on srv_redo_log_encrypt. */
bool srv_enable_redo_encryption();

#ifdef UNIV_DEBUG
/** Disables master thread. It's used by:
SET GLOBAL innodb_master_thread_disabled_debug = 1 (0).
Expand Down
Loading

0 comments on commit c9f151e

Please sign in to comment.