Commit ada1074b authored by Thirunarayanan Balathandayuthapani's avatar Thirunarayanan Balathandayuthapani Committed by Marko Mäkelä

MDEV-14398 innodb_encryption_rotate_key_age=0 causes innodb_encrypt_tables to be ignored

The statement

SET GLOBAL innodb_encryption_rotate_key_age=0;

would have the unwanted side effect that ENCRYPTION=DEFAULT tablespaces
would no longer be encrypted or decrypted according to the setting of
innodb_encrypt_tables.

We implement a trigger, so that whenever one of the following is executed:

SET GLOBAL innodb_encrypt_tables=OFF;
SET GLOBAL innodb_encrypt_tables=ON;
SET GLOBAL innodb_encrypt_tables=FORCE;

all wrong-state ENCRYPTION=DEFAULT tablespaces will be added to
fil_system_t::rotation_list, so that the encryption will be added
or removed.

Note: This will *NOT* happen automatically after a server restart.
Before reading the first page of a data file, InnoDB cannot know
the encryption status of the data file. The statement
SET GLOBAL innodb_encrypt_tables will have the side effect that
all not-yet-read InnoDB data files will be accessed in order to
determine the encryption status.

innodb_encrypt_tables_validate(): Stop disallowing
SET GLOBAL innodb_encrypt_tables when innodb_encryption_rotate_key_age=0.
This reverts part of commit 50eb40a2
that addressed MDEV-11738 and MDEV-11581.

fil_system_t::read_page0(): Trigger a call to fil_node_t::read_page0().
Refactored from fil_space_get_space().

fil_crypt_rotation_list_fill(): If innodb_encryption_rotate_key_age=0,
initialize fil_system->rotation_list. This is invoked both on
SET GLOBAL innodb_encrypt_tables and
on SET GLOBAL innodb_encryption_rotate_key_age=0.

fil_space_set_crypt_data(): Remove.

fil_parse_write_crypt_data(): Simplify the logic.

This is joint work with Marko Mäkelä.
parent 2370eeb0
......@@ -37,10 +37,6 @@ NAME ENCRYPTION_SCHEME CURRENT_KEY_ID
enctests/t7 0 1
enctests/t8 0 1
enctests/t9 0 1
SET GLOBAL innodb_encrypt_tables=OFF;
ERROR 42000: Variable 'innodb_encrypt_tables' can't be set to the value of 'OFF'
SET GLOBAL innodb_encrypt_tables=ON;
ERROR 42000: Variable 'innodb_encrypt_tables' can't be set to the value of 'ON'
# t1 default on expecting NOT FOUND
NOT FOUND /secred/ in t1.ibd
# t2 default on expecting NOT FOUND
......
CREATE TABLE t1 (f1 INT, f2 VARCHAR(256))engine=innodb;
INSERT INTO t1 VALUES(1, 'MariaDB'), (2, 'Robot'), (3, 'Science');
INSERT INTO t1 SELECT * FROM t1;
CREATE TABLE t2(f1 INT, f2 VARCHAR(256))engine=innodb;
INSERT INTO t2 SELECT * FROM t1;
CREATE TABLE t3(f1 INT, f2 VARCHAR(256))engine=innodb encrypted=yes;
INSERT INTO t3 SELECT * FROM t1;
# Restart the server with encryption
# Wait until encryption threads have encrypted all tablespaces
SELECT NAME FROM INFORMATION_SCHEMA.INNODB_TABLESPACES_ENCRYPTION WHERE MIN_KEY_VERSION = 0;
NAME
SELECT NAME FROM INFORMATION_SCHEMA.INNODB_TABLESPACES_ENCRYPTION WHERE MIN_KEY_VERSION <> 0;
NAME
innodb_system
mysql/innodb_index_stats
mysql/innodb_table_stats
test/t1
test/t2
test/t3
# Restart the server with innodb_encryption_rotate_key_age= 0
create table t4 (f1 int not null)engine=innodb encrypted=NO;
# Wait until encryption threads have encrypted all tablespaces
SELECT NAME FROM INFORMATION_SCHEMA.INNODB_TABLESPACES_ENCRYPTION WHERE MIN_KEY_VERSION = 0;
NAME
test/t4
SELECT NAME FROM INFORMATION_SCHEMA.INNODB_TABLESPACES_ENCRYPTION WHERE MIN_KEY_VERSION <> 0;
NAME
innodb_system
mysql/innodb_index_stats
mysql/innodb_table_stats
test/t1
test/t2
test/t3
# Disable encryption when innodb_encryption_rotate_key_age is 0
set global innodb_encrypt_tables = OFF;
# Wait until encryption threads to decrypt all unencrypted tablespaces
SELECT NAME FROM INFORMATION_SCHEMA.INNODB_TABLESPACES_ENCRYPTION WHERE MIN_KEY_VERSION = 0;
NAME
innodb_system
mysql/innodb_index_stats
mysql/innodb_table_stats
test/t1
test/t2
test/t4
# Display only encrypted create tables (t3)
SELECT NAME FROM INFORMATION_SCHEMA.INNODB_TABLESPACES_ENCRYPTION WHERE MIN_KEY_VERSION <> 0;
NAME
test/t3
# Enable encryption when innodb_encryption_rotate_key_age is 0
set global innodb_encrypt_tables = ON;
# Wait until encryption threads to encrypt all unencrypted tablespaces
SELECT NAME FROM INFORMATION_SCHEMA.INNODB_TABLESPACES_ENCRYPTION WHERE MIN_KEY_VERSION = 0;
NAME
test/t4
# Display only unencrypted create tables (t4)
SELECT NAME FROM INFORMATION_SCHEMA.INNODB_TABLESPACES_ENCRYPTION WHERE MIN_KEY_VERSION <> 0;
NAME
innodb_system
mysql/innodb_index_stats
mysql/innodb_table_stats
test/t1
test/t2
test/t3
SELECT NAME FROM INFORMATION_SCHEMA.INNODB_TABLESPACES_ENCRYPTION WHERE MIN_KEY_VERSION = 0;
NAME
test/t4
SELECT NAME FROM INFORMATION_SCHEMA.INNODB_TABLESPACES_ENCRYPTION WHERE MIN_KEY_VERSION <> 0;
NAME
innodb_system
mysql/innodb_index_stats
mysql/innodb_table_stats
test/t1
test/t2
test/t3
DROP TABLE t4, t3, t2, t1;
......@@ -43,11 +43,6 @@ SELECT NAME,ENCRYPTION_SCHEME,CURRENT_KEY_ID FROM INFORMATION_SCHEMA.INNODB_TABL
--echo # should list tables t7-t9
SELECT NAME,ENCRYPTION_SCHEME,CURRENT_KEY_ID FROM INFORMATION_SCHEMA.INNODB_TABLESPACES_ENCRYPTION WHERE MIN_KEY_VERSION = 0 and NAME LIKE 'enctests%';
--error ER_WRONG_VALUE_FOR_VAR
SET GLOBAL innodb_encrypt_tables=OFF;
--error ER_WRONG_VALUE_FOR_VAR
SET GLOBAL innodb_encrypt_tables=ON;
--let $MYSQLD_DATADIR=`select @@datadir`
-- source include/shutdown_mysqld.inc
......
--innodb-tablespaces-encryption
--innodb_encrypt_tables=ON
-- source include/have_innodb.inc
-- source include/not_embedded.inc
-- source include/have_example_key_management_plugin.inc
CREATE TABLE t1 (f1 INT, f2 VARCHAR(256))engine=innodb;
INSERT INTO t1 VALUES(1, 'MariaDB'), (2, 'Robot'), (3, 'Science');
INSERT INTO t1 SELECT * FROM t1;
CREATE TABLE t2(f1 INT, f2 VARCHAR(256))engine=innodb;
INSERT INTO t2 SELECT * FROM t1;
CREATE TABLE t3(f1 INT, f2 VARCHAR(256))engine=innodb encrypted=yes;
INSERT INTO t3 SELECT * FROM t1;
--echo # Restart the server with encryption
let $restart_parameters= --innodb_encryption_threads=5 --innodb_encryption_rotate_key_age=16384;
--source include/restart_mysqld.inc
--echo # Wait until encryption threads have encrypted all tablespaces
--let $tables_count= `select count(*) + 1 from information_schema.tables where engine = 'InnoDB'`
--let $wait_timeout= 600
--let $wait_condition=SELECT COUNT(*) >= $tables_count FROM INFORMATION_SCHEMA.INNODB_TABLESPACES_ENCRYPTION WHERE MIN_KEY_VERSION <> 0;
--source include/wait_condition.inc
SELECT NAME FROM INFORMATION_SCHEMA.INNODB_TABLESPACES_ENCRYPTION WHERE MIN_KEY_VERSION = 0;
SELECT NAME FROM INFORMATION_SCHEMA.INNODB_TABLESPACES_ENCRYPTION WHERE MIN_KEY_VERSION <> 0;
--echo # Restart the server with innodb_encryption_rotate_key_age= 0
let $restart_parameters= --innodb_encryption_threads=1 --innodb_encryption_rotate_key_age=0;
--source include/restart_mysqld.inc
create table t4 (f1 int not null)engine=innodb encrypted=NO;
--echo # Wait until encryption threads have encrypted all tablespaces
--let $tables_count= `select count(*) from information_schema.tables where engine = 'InnoDB'`
--let $wait_timeout= 600
--let $wait_condition=SELECT COUNT(*) >= $tables_count FROM INFORMATION_SCHEMA.INNODB_TABLESPACES_ENCRYPTION WHERE MIN_KEY_VERSION <> 0;
--source include/wait_condition.inc
SELECT NAME FROM INFORMATION_SCHEMA.INNODB_TABLESPACES_ENCRYPTION WHERE MIN_KEY_VERSION = 0;
SELECT NAME FROM INFORMATION_SCHEMA.INNODB_TABLESPACES_ENCRYPTION WHERE MIN_KEY_VERSION <> 0;
--echo # Disable encryption when innodb_encryption_rotate_key_age is 0
set global innodb_encrypt_tables = OFF;
--echo # Wait until encryption threads to decrypt all unencrypted tablespaces
--let $tables_count= `select count(*) from information_schema.tables where engine = 'InnoDB'`
--let $wait_timeout= 600
--let $wait_condition=SELECT COUNT(*) >= $tables_count FROM INFORMATION_SCHEMA.INNODB_TABLESPACES_ENCRYPTION WHERE MIN_KEY_VERSION = 0 AND ROTATING_OR_FLUSHING = 0;
--source include/wait_condition.inc
SELECT NAME FROM INFORMATION_SCHEMA.INNODB_TABLESPACES_ENCRYPTION WHERE MIN_KEY_VERSION = 0;
--echo # Display only encrypted create tables (t3)
SELECT NAME FROM INFORMATION_SCHEMA.INNODB_TABLESPACES_ENCRYPTION WHERE MIN_KEY_VERSION <> 0;
--echo # Enable encryption when innodb_encryption_rotate_key_age is 0
set global innodb_encrypt_tables = ON;
--echo # Wait until encryption threads to encrypt all unencrypted tablespaces
--let $tables_count= `select count(*) from information_schema.tables where engine = 'InnoDB'`
--let $wait_timeout= 600
--let $wait_condition=SELECT COUNT(*) >= $tables_count FROM INFORMATION_SCHEMA.INNODB_TABLESPACES_ENCRYPTION WHERE MIN_KEY_VERSION <> 0;
--source include/wait_condition.inc
SELECT NAME FROM INFORMATION_SCHEMA.INNODB_TABLESPACES_ENCRYPTION WHERE MIN_KEY_VERSION = 0;
--echo # Display only unencrypted create tables (t4)
SELECT NAME FROM INFORMATION_SCHEMA.INNODB_TABLESPACES_ENCRYPTION WHERE MIN_KEY_VERSION <> 0;
--let $restart_parameters=
-- source include/restart_mysqld.inc
SELECT NAME FROM INFORMATION_SCHEMA.INNODB_TABLESPACES_ENCRYPTION WHERE MIN_KEY_VERSION = 0;
SELECT NAME FROM INFORMATION_SCHEMA.INNODB_TABLESPACES_ENCRYPTION WHERE MIN_KEY_VERSION <> 0;
DROP TABLE t4, t3, t2, t1;
/*****************************************************************************
Copyright (C) 2013, 2015, Google Inc. All Rights Reserved.
Copyright (c) 2014, 2018, MariaDB Corporation. All Rights Reserved.
Copyright (c) 2014, 2019, MariaDB Corporation.
This program is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
......@@ -418,45 +418,6 @@ fil_space_crypt_t::write_page0(
}
}
/******************************************************************
Set crypt data for a tablespace
@param[in,out] space Tablespace
@param[in,out] crypt_data Crypt data to be set
@return crypt_data in tablespace */
static
fil_space_crypt_t*
fil_space_set_crypt_data(
fil_space_t* space,
fil_space_crypt_t* crypt_data)
{
fil_space_crypt_t* free_crypt_data = NULL;
fil_space_crypt_t* ret_crypt_data = NULL;
/* Provided space is protected using fil_space_acquire()
from concurrent operations. */
if (space->crypt_data != NULL) {
/* There is already crypt data present,
merge new crypt_data */
fil_space_merge_crypt_data(space->crypt_data,
crypt_data);
ret_crypt_data = space->crypt_data;
free_crypt_data = crypt_data;
} else {
space->crypt_data = crypt_data;
ret_crypt_data = space->crypt_data;
}
if (free_crypt_data != NULL) {
/* there was already crypt data present and the new crypt
* data provided as argument to this function has been merged
* into that => free new crypt data
*/
fil_space_destroy_crypt_data(&free_crypt_data);
}
return ret_crypt_data;
}
/******************************************************************
Parse a MLOG_FILE_WRITE_CRYPT_DATA log entry
@param[in] ptr Log entry start
......@@ -515,26 +476,36 @@ fil_parse_write_crypt_data(
return NULL;
}
fil_space_crypt_t* crypt_data = fil_space_create_crypt_data(encryption, key_id);
/* Need to overwrite these as above will initialize fields. */
mutex_enter(&fil_system->mutex);
fil_space_t* space = fil_space_get_by_id(space_id);
if (!space) {
mutex_exit(&fil_system->mutex);
return ptr + len;
}
fil_space_crypt_t* crypt_data = fil_space_create_crypt_data(
encryption, key_id);
crypt_data->page0_offset = offset;
crypt_data->min_key_version = min_key_version;
crypt_data->encryption = encryption;
crypt_data->type = type;
memcpy(crypt_data->iv, ptr, len);
ptr += len;
/* update fil_space memory cache with crypt_data */
if (fil_space_t* space = fil_space_acquire_silent(space_id)) {
crypt_data = fil_space_set_crypt_data(space, crypt_data);
fil_space_release(space);
/* Check is used key found from encryption plugin */
if (crypt_data->should_encrypt()
&& !crypt_data->is_key_found()) {
*err = DB_DECRYPTION_FAILED;
}
} else {
if (space->crypt_data) {
fil_space_merge_crypt_data(space->crypt_data, crypt_data);
fil_space_destroy_crypt_data(&crypt_data);
crypt_data = space->crypt_data;
} else {
space->crypt_data = crypt_data;
}
mutex_exit(&fil_system->mutex);
if (crypt_data->should_encrypt() && !crypt_data->is_key_found()) {
*err = DB_DECRYPTION_FAILED;
}
return ptr;
......@@ -919,11 +890,15 @@ fil_crypt_needs_rotation(
if (crypt_data->encryption == FIL_ENCRYPTION_DEFAULT
&& crypt_data->type == CRYPT_SCHEME_1
&& srv_encrypt_tables == 0 ) {
&& !srv_encrypt_tables) {
/* This is rotation encrypted => unencrypted */
return true;
}
if (rotate_key_age == 0) {
return false;
}
/* this is rotation encrypted => encrypted,
* only reencrypt if key is sufficiently old */
if (key_version + rotate_key_age < latest_key_version) {
......@@ -1006,7 +981,8 @@ fil_crypt_start_encrypting_space(
* crypt data in page 0 */
/* 1 - create crypt data */
crypt_data = fil_space_create_crypt_data(FIL_ENCRYPTION_DEFAULT, FIL_DEFAULT_ENCRYPTION_KEY);
crypt_data = fil_space_create_crypt_data(
FIL_ENCRYPTION_DEFAULT, FIL_DEFAULT_ENCRYPTION_KEY);
if (crypt_data == NULL) {
mutex_exit(&fil_crypt_threads_mutex);
......@@ -1019,9 +995,9 @@ fil_crypt_start_encrypting_space(
crypt_data->rotate_state.starting = true;
crypt_data->rotate_state.active_threads = 1;
mutex_enter(&crypt_data->mutex);
crypt_data = fil_space_set_crypt_data(space, crypt_data);
mutex_exit(&crypt_data->mutex);
mutex_enter(&fil_system->mutex);
space->crypt_data = crypt_data;
mutex_exit(&fil_system->mutex);
fil_crypt_start_converting = true;
mutex_exit(&fil_crypt_threads_mutex);
......@@ -2292,6 +2268,64 @@ fil_crypt_set_thread_cnt(
}
}
/** Initialize the tablespace rotation_list
if innodb_encryption_rotate_key_age=0. */
static void fil_crypt_rotation_list_fill()
{
ut_ad(mutex_own(&fil_system->mutex));
for (fil_space_t* space = UT_LIST_GET_FIRST(fil_system->space_list);
space != NULL;
space = UT_LIST_GET_NEXT(space_list, space)) {
if (space->purpose != FIL_TYPE_TABLESPACE
|| space->is_in_rotation_list()
|| space->is_stopping()
|| UT_LIST_GET_LEN(space->chain) == 0) {
continue;
}
/* Ensure that crypt_data has been initialized. */
if (!space->size) {
/* Protect the tablespace while we may
release fil_system->mutex. */
space->n_pending_ops++;
fil_space_t* s= fil_system->read_page0(
space->id);
ut_ad(!s || s == space);
space->n_pending_ops--;
if (!space->size) {
/* Page 0 was not loaded.
Skip this tablespace. */
continue;
}
}
/* Skip ENCRYPTION!=DEFAULT tablespaces. */
if (space->crypt_data
&& !space->crypt_data->is_default_encryption()) {
continue;
}
if (srv_encrypt_tables) {
/* Skip encrypted tablespaces if
innodb_encrypt_tables!=OFF */
if (space->crypt_data
&& space->crypt_data->min_key_version) {
continue;
}
} else {
/* Skip unencrypted tablespaces if
innodb_encrypt_tables=OFF */
if (!space->crypt_data
|| !space->crypt_data->min_key_version) {
continue;
}
}
UT_LIST_ADD_LAST(fil_system->rotation_list, space);
}
}
/*********************************************************************
Adjust max key age
@param[in] val New max key age */
......@@ -2300,7 +2334,12 @@ void
fil_crypt_set_rotate_key_age(
uint val)
{
mutex_enter(&fil_system->mutex);
srv_fil_crypt_rotate_key_age = val;
if (val == 0) {
fil_crypt_rotation_list_fill();
}
mutex_exit(&fil_system->mutex);
os_event_set(fil_crypt_threads_event);
}
......@@ -2324,7 +2363,16 @@ void
fil_crypt_set_encrypt_tables(
uint val)
{
mutex_enter(&fil_system->mutex);
srv_encrypt_tables = val;
if (srv_fil_crypt_rotate_key_age == 0) {
fil_crypt_rotation_list_fill();
}
mutex_exit(&fil_system->mutex);
os_event_set(fil_crypt_threads_event);
}
......
......@@ -1525,6 +1525,47 @@ fil_assign_new_space_id(
return(success);
}
/** Trigger a call to fil_node_t::read_page0()
@param[in] id tablespace identifier
@return tablespace
@retval NULL if the tablespace does not exist or cannot be read */
fil_space_t* fil_system_t::read_page0(ulint id)
{
mutex_exit(&mutex);
ut_ad(id != 0);
/* It is possible that the tablespace is dropped while we are
not holding the mutex. */
fil_mutex_enter_and_prepare_for_io(id);
fil_space_t* space = fil_space_get_by_id(id);
if (space == NULL || UT_LIST_GET_LEN(space->chain) == 0) {
return(NULL);
}
/* The following code must change when InnoDB supports
multiple datafiles per tablespace. */
ut_a(1 == UT_LIST_GET_LEN(space->chain));
fil_node_t* node = UT_LIST_GET_FIRST(space->chain);
/* It must be a single-table tablespace and we have not opened
the file yet; the following calls will open it and update the
size fields */
if (!fil_node_prepare_for_io(node, fil_system, space)) {
/* The single-table tablespace can't be opened,
because the ibd file is missing. */
return(NULL);
}
fil_node_complete_io(node, IORequestRead);
return space;
}
/*******************************************************************//**
Returns a pointer to the fil_space_t that is in the memory cache
associated with a space id. The caller must lock fil_system->mutex.
......@@ -1535,12 +1576,7 @@ fil_space_get_space(
/*================*/
ulint id) /*!< in: space id */
{
fil_space_t* space;
fil_node_t* node;
ut_ad(fil_system);
space = fil_space_get_by_id(id);
fil_space_t* space = fil_space_get_by_id(id);
if (space == NULL || space->size != 0) {
return(space);
}
......@@ -1551,41 +1587,7 @@ fil_space_get_space(
case FIL_TYPE_TEMPORARY:
case FIL_TYPE_TABLESPACE:
case FIL_TYPE_IMPORT:
ut_a(id != 0);
mutex_exit(&fil_system->mutex);
/* It is possible that the space gets evicted at this point
before the fil_mutex_enter_and_prepare_for_io() acquires
the fil_system->mutex. Check for this after completing the
call to fil_mutex_enter_and_prepare_for_io(). */
fil_mutex_enter_and_prepare_for_io(id);
/* We are still holding the fil_system->mutex. Check if
the space is still in memory cache. */
space = fil_space_get_by_id(id);
if (space == NULL || UT_LIST_GET_LEN(space->chain) == 0) {
return(NULL);
}
/* The following code must change when InnoDB supports
multiple datafiles per tablespace. */
ut_a(1 == UT_LIST_GET_LEN(space->chain));
node = UT_LIST_GET_FIRST(space->chain);
/* It must be a single-table tablespace and we have not opened
the file yet; the following calls will open it and update the
size fields */
if (!fil_node_prepare_for_io(node, fil_system, space)) {
/* The single-table tablespace can't be opened,
because the ibd file is missing. */
return(NULL);
}
fil_node_complete_io(node, IORequestRead);
space = fil_system->read_page0(id);
}
return(space);
......
......@@ -22404,16 +22404,6 @@ innodb_encrypt_tables_validate(
return 1;
}
if (!srv_fil_crypt_rotate_key_age) {
const char *msg = (encrypt_tables ? "enable" : "disable");
push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
HA_ERR_UNSUPPORTED,
"InnoDB: cannot %s encryption, "
"innodb_encryption_rotate_key_age=0"
" i.e. key rotation disabled", msg);
return 1;
}
return 0;
}
......
......@@ -532,6 +532,12 @@ struct fil_system_t {
/* !< TRUE if fil_space_create()
has issued a warning about
potential space_id reuse */
/** Trigger a call to fil_node_t::read_page0()
@param[in] id tablespace identifier
@return tablespace
@retval NULL if the tablespace does not exist or cannot be read */
fil_space_t* read_page0(ulint id);
};
/** The tablespace memory cache. This variable is NULL before the module is
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment