Commit 3d1f7650 authored by Julius Goryavsky's avatar Julius Goryavsky

MDEV-28276: Hashicorp: checking that kv storage is created with version 2+

For the plugin to work properly, we need support for key versioning,
and for this, the kv storage in Hashicorp Vault must be created with
version 2 or higher. This commit adds such a check performed during
plugin initialization.

Note: checking for kv storage version during plugin initialization
can be disabled via --hashicorp-key-management-check-kv-version=off
command-line option or via the corresponding option in the server
configuration files.
parent 1c22a9d8
...@@ -107,3 +107,11 @@ ...@@ -107,3 +107,11 @@
# of the cache is allowed on server failures. # of the cache is allowed on server failures.
# #
#hashicorp-key-management-cache-version-timeout=0 #hashicorp-key-management-cache-version-timeout=0
#
# This parameter enables ("on", this is the default value) or disables
# ("off") checking the kv storage version during plugin initialization.
# The plugin requires storage to be version 2 or older in order for it
# to work properly.
#
#hashicorp-key-management-check-kv-version=on
...@@ -172,3 +172,10 @@ operation: ...@@ -172,3 +172,10 @@ operation:
numbers for the keys stored in the cache are considered always numbers for the keys stored in the cache are considered always
invalid, except when the vault server is unavailable and use invalid, except when the vault server is unavailable and use
of the cache is allowed on server failures. of the cache is allowed on server failures.
--[loose-]hashicorp-key-management-check-kv-version="on"|"off"
This parameter enables ("on", this is the default value) or disables
("off") checking the kv storage version during plugin initialization.
The plugin requires storage to be version 2 or older in order for it
to work properly.
...@@ -307,6 +307,7 @@ static char* vault_ca; ...@@ -307,6 +307,7 @@ static char* vault_ca;
static int timeout; static int timeout;
static int max_retries; static int max_retries;
static char caching_enabled; static char caching_enabled;
static char check_kv_version;
static long cache_timeout; static long cache_timeout;
static long cache_version_timeout; static long cache_version_timeout;
static char use_cache_on_timeout; static char use_cache_on_timeout;
...@@ -345,6 +346,11 @@ static MYSQL_SYSVAR_BOOL(caching_enabled, caching_enabled, ...@@ -345,6 +346,11 @@ static MYSQL_SYSVAR_BOOL(caching_enabled, caching_enabled,
"the Hashicorp Vault server in the local memory)", "the Hashicorp Vault server in the local memory)",
NULL, NULL, 1); NULL, NULL, 1);
static MYSQL_SYSVAR_BOOL(check_kv_version, check_kv_version,
PLUGIN_VAR_RQCMDARG,
"Enable kv storage version check during plugin initialization",
NULL, NULL, 1);
static void cache_timeout_update (MYSQL_THD thd, static void cache_timeout_update (MYSQL_THD thd,
struct st_mysql_sys_var *var, struct st_mysql_sys_var *var,
void *var_ptr, void *var_ptr,
...@@ -388,6 +394,7 @@ static struct st_mysql_sys_var *settings[] = { ...@@ -388,6 +394,7 @@ static struct st_mysql_sys_var *settings[] = {
MYSQL_SYSVAR(cache_timeout), MYSQL_SYSVAR(cache_timeout),
MYSQL_SYSVAR(cache_version_timeout), MYSQL_SYSVAR(cache_version_timeout),
MYSQL_SYSVAR(use_cache_on_timeout), MYSQL_SYSVAR(use_cache_on_timeout),
MYSQL_SYSVAR(check_kv_version),
NULL NULL
}; };
...@@ -492,8 +499,8 @@ static int curl_run (char *url, std::string *response, bool soft_timeout) ...@@ -492,8 +499,8 @@ static int curl_run (char *url, std::string *response, bool soft_timeout)
return OPERATION_TIMEOUT; return OPERATION_TIMEOUT;
} }
my_printf_error(ER_UNKNOWN_ERROR, PLUGIN_ERROR_HEADER my_printf_error(ER_UNKNOWN_ERROR, PLUGIN_ERROR_HEADER
"CURL returned this error code: %u " "curl returned this error code: %u "
" with error message: %s", 0, curl_res, "with the following error message: %s", 0, curl_res,
curl_errbuf[0] ? curl_errbuf : curl_errbuf[0] ? curl_errbuf :
curl_easy_strerror(curl_res)); curl_easy_strerror(curl_res));
return OPERATION_ERROR; return OPERATION_ERROR;
...@@ -714,7 +721,7 @@ static unsigned int get_latest_version (unsigned int key_id) ...@@ -714,7 +721,7 @@ static unsigned int get_latest_version (unsigned int key_id)
} }
std::string response_str; std::string response_str;
/* /*
Maximum buffer length = url length plus 20 characters of Maximum buffer length = URL length plus 20 characters of
a 64-bit unsigned integer, plus a slash character, plus a 64-bit unsigned integer, plus a slash character, plus
a length of the "/data/" string and plus a zero byte: a length of the "/data/" string and plus a zero byte:
*/ */
...@@ -791,7 +798,7 @@ static unsigned int get_key_from_vault (unsigned int key_id, ...@@ -791,7 +798,7 @@ static unsigned int get_key_from_vault (unsigned int key_id,
} }
std::string response_str; std::string response_str;
/* /*
Maximum buffer length = url length plus 40 characters of the Maximum buffer length = URL length plus 40 characters of the
two 64-bit unsigned integers, plus a slash character, plus a two 64-bit unsigned integers, plus a slash character, plus a
question mark, plus length of the "/data/" and the "?version=" question mark, plus length of the "/data/" and the "?version="
strings and plus a zero byte: strings and plus a zero byte:
...@@ -1127,6 +1134,7 @@ static int hashicorp_key_management_plugin_init(void *p) ...@@ -1127,6 +1134,7 @@ static int hashicorp_key_management_plugin_init(void *p)
while (vault_url[vault_url_len - 1] == '/') while (vault_url[vault_url_len - 1] == '/')
{ {
vault_url_len--; vault_url_len--;
suffix_len--;
} }
/* /*
Checking the maximum allowable length to protect Checking the maximum allowable length to protect
...@@ -1154,14 +1162,119 @@ static int hashicorp_key_management_plugin_init(void *p) ...@@ -1154,14 +1162,119 @@ static int hashicorp_key_management_plugin_init(void *p)
cache_max_time = ms_to_ticks(cache_timeout); cache_max_time = ms_to_ticks(cache_timeout);
cache_max_ver_time = ms_to_ticks(cache_version_timeout); cache_max_ver_time = ms_to_ticks(cache_version_timeout);
/* Initialize curl: */ /* Initialize curl: */
curl_global_init(CURL_GLOBAL_ALL); CURLcode curl_res = curl_global_init(CURL_GLOBAL_ALL);
if (curl_res != CURLE_OK)
{
my_printf_error(ER_UNKNOWN_ERROR, PLUGIN_ERROR_HEADER
"unable to initialize curl library, "
"curl returned this error code: %u "
"with the following error message: %s",
0, curl_res, curl_easy_strerror(curl_res));
curl_error:
free(vault_url_data);
vault_url_data = NULL;
goto Failure;
}
list = curl_slist_append(list, token_header); list = curl_slist_append(list, token_header);
if (list == NULL) if (list == NULL)
{ {
my_printf_error(ER_UNKNOWN_ERROR, PLUGIN_ERROR_HEADER my_printf_error(ER_UNKNOWN_ERROR, PLUGIN_ERROR_HEADER
"curl: unable to construct slist", 0); "curl: unable to construct slist", 0);
Failure3:
curl_global_cleanup(); curl_global_cleanup();
goto Failure; goto curl_error;
}
/*
If we do not need to check the key-value storage version,
then we immediately return from this function:
*/
if (check_kv_version == 0) {
return 0;
}
/*
Let's construct a URL to check the version of the key-value storage:
*/
char *mount_url = (char *) malloc(vault_url_len + 11 + 6);
if (mount_url == NULL)
{
my_printf_error(ER_UNKNOWN_ERROR, PLUGIN_ERROR_HEADER
"Memory allocation error", 0);
Failure4:
curl_slist_free_all(list);
list = NULL;
goto Failure3;
}
/*
The prefix length must be recalculated, as it may have
changed in the process of discarding trailing slashes:
*/
prefix_len = vault_url_len - suffix_len;
memcpy(mount_url, vault_url_data, prefix_len);
memcpy(mount_url + prefix_len, "sys/mounts/", 11);
memcpy(mount_url + prefix_len + 11, vault_url_data + prefix_len, suffix_len);
memcpy(mount_url + prefix_len + 11 + suffix_len, "/tune", 6);
#if HASICORP_DEBUG_LOGGING
my_printf_error(ER_UNKNOWN_ERROR, PLUGIN_ERROR_HEADER
"storage mount url: [%s]",
ME_ERROR_LOG_ONLY | ME_NOTE, mount_url);
#endif
std::string response_str;
int rc = curl_run(mount_url, &response_str, false);
if (rc != OPERATION_OK)
{
storage_error:
my_printf_error(ER_UNKNOWN_ERROR, PLUGIN_ERROR_HEADER
"Unable to get storage options for \"%s\"",
0, mount_url);
free(mount_url);
goto Failure4;
}
const char *response = response_str.c_str();
size_t response_len = response_str.size();
/*
If the key is not found, this is not considered a fatal error,
but we need to add an informational message to the log:
*/
if (response_len == 0)
{
goto storage_error;
}
free(mount_url);
const char *js;
int js_len;
if (json_get_object_key(response, response + response_len, "options",
&js, &js_len) != JSV_OBJECT)
{
my_printf_error(ER_UNKNOWN_ERROR, PLUGIN_ERROR_HEADER
"Unable to get storage options (http response is: %s)",
0, response);
goto Failure4;
}
const char *ver;
int ver_len;
enum json_types jst =
json_get_object_key(js, js + js_len, "version", &ver, &ver_len);
if (jst != JSV_STRING && jst != JSV_NUMBER)
{
my_printf_error(ER_UNKNOWN_ERROR, PLUGIN_ERROR_HEADER
"Unable to get storage version (http response is: %s)",
0, response);
goto Failure4;
}
unsigned long version = strtoul(ver, NULL, 10);
if (version > UINT_MAX || (version == ULONG_MAX && errno))
{
my_printf_error(ER_UNKNOWN_ERROR, PLUGIN_ERROR_HEADER
"Integer conversion error (for version number) "
"(http response is: %s)", 0, response);
goto Failure4;
}
if (version < 2)
{
my_printf_error(ER_UNKNOWN_ERROR, PLUGIN_ERROR_HEADER
"Key-value storage must be version "
"number 2 or later", 0);
goto Failure4;
} }
return 0; return 0;
} }
...@@ -1208,10 +1321,10 @@ maria_declare_plugin(hashicorp_key_management) ...@@ -1208,10 +1321,10 @@ maria_declare_plugin(hashicorp_key_management)
PLUGIN_LICENSE_GPL, PLUGIN_LICENSE_GPL,
hashicorp_key_management_plugin_init, hashicorp_key_management_plugin_init,
hashicorp_key_management_plugin_deinit, hashicorp_key_management_plugin_deinit,
0x0104 /* 1.04 */, 0x0105 /* 1.05 */,
NULL, /* status variables */ NULL, /* status variables */
settings, settings,
"1.04", "1.05",
MariaDB_PLUGIN_MATURITY_STABLE MariaDB_PLUGIN_MATURITY_STABLE
} }
maria_declare_plugin_end; maria_declare_plugin_end;
...@@ -3,3 +3,4 @@ ...@@ -3,3 +3,4 @@
--loose-hashicorp-key-management-vault-url="$VAULT_ADDR/v1/mariadbtest/" --loose-hashicorp-key-management-vault-url="$VAULT_ADDR/v1/mariadbtest/"
--loose-hashicorp-key-management-token="$VAULT_TOKEN" --loose-hashicorp-key-management-token="$VAULT_TOKEN"
--loose-hashicorp-key-management-timeout=60 --loose-hashicorp-key-management-timeout=60
--loose-hashicorp-key-management-check-kv-version=off
[ERROR] mariadbd: hashicorp: Key-value storage must be version number 2 or later
# restart: with restart_parameters
CREATE TABLE t1 (a VARCHAR(8)) ENGINE=InnoDB ENCRYPTED=YES ENCRYPTION_KEY_ID=1;
INSERT INTO t1 VALUES ('foo'),('bar');
# restart: with restart_parameters
CREATE TABLE t2 (a VARCHAR(8)) ENGINE=InnoDB ENCRYPTED=YES ENCRYPTION_KEY_ID=2;
INSERT INTO t2 VALUES ('foo'),('bar');
DROP TABLE t1, t2;
# restart
...@@ -3,6 +3,7 @@ Variable_name Value ...@@ -3,6 +3,7 @@ Variable_name Value
hashicorp_key_management_cache_timeout 60000 hashicorp_key_management_cache_timeout 60000
hashicorp_key_management_cache_version_timeout 0 hashicorp_key_management_cache_version_timeout 0
hashicorp_key_management_caching_enabled ON hashicorp_key_management_caching_enabled ON
hashicorp_key_management_check_kv_version OFF
hashicorp_key_management_max_retries 3 hashicorp_key_management_max_retries 3
hashicorp_key_management_timeout 60 hashicorp_key_management_timeout 60
hashicorp_key_management_use_cache_on_timeout OFF hashicorp_key_management_use_cache_on_timeout OFF
......
...@@ -3,6 +3,7 @@ Variable_name Value ...@@ -3,6 +3,7 @@ Variable_name Value
hashicorp_key_management_cache_timeout 60000 hashicorp_key_management_cache_timeout 60000
hashicorp_key_management_cache_version_timeout 0 hashicorp_key_management_cache_version_timeout 0
hashicorp_key_management_caching_enabled ON hashicorp_key_management_caching_enabled ON
hashicorp_key_management_check_kv_version OFF
hashicorp_key_management_max_retries 3 hashicorp_key_management_max_retries 3
hashicorp_key_management_timeout 60 hashicorp_key_management_timeout 60
hashicorp_key_management_use_cache_on_timeout OFF hashicorp_key_management_use_cache_on_timeout OFF
......
# MDEV-28276: Checking for kv version=2 as mandatory
# The test presumes that the local vault is running at $VAULT_ADDR,
# and the token is configured in $VAULT_TOKEN.
--source include/have_innodb.inc
--source hashicorp_plugin.inc
--exec vault secrets disable bug1 > /dev/null
--exec vault secrets disable good > /dev/null
--exec vault secrets enable -path /bug1 -version=1 kv > /dev/null
--exec vault secrets enable -path /good -version=2 kv > /dev/null
--exec vault kv put /bug1/1 data=01234567890123456789012345678901 > /dev/null
--exec vault kv put /good/1 data=01234567890123456789012345678901 > /dev/null
--exec vault kv put /good/2 data=012345678901234567890123456789ab > /dev/null
--source include/shutdown_mysqld.inc
--let $LOG_FILE=$MYSQLTEST_VARDIR/log/vault.err
--error 0,1
--remove_file $LOG_FILE
--let $vault_defaults=--plugin-load-add=hashicorp_key_management --hashicorp_key_management=force --hashicorp-key-management-check-kv-version=on --hashicorp-key-management-token="$VAULT_TOKEN"
--let $defaults=--defaults-group-suffix=.1 --defaults-file=$MYSQLTEST_VARDIR/my.cnf $vault_defaults --log-error=$LOG_FILE
--error 1
--exec $MYSQLD $defaults --hashicorp-key-management-vault-url="$VAULT_ADDR/v1/bug1"
--exec grep -oE "\[ERROR\] .*: hashicorp: .*" -- $LOG_FILE
--remove_file $LOG_FILE
--let $restart_parameters=$vault_defaults --hashicorp-key-management-vault-url="$VAULT_ADDR/v1/good"
--let $restart_noprint=1
--source include/start_mysqld.inc
CREATE TABLE t1 (a VARCHAR(8)) ENGINE=InnoDB ENCRYPTED=YES ENCRYPTION_KEY_ID=1;
INSERT INTO t1 VALUES ('foo'),('bar');
--let $restart_parameters=$vault_defaults --hashicorp-key-management-vault-url="$VAULT_ADDR/v1/good//"
--source include/restart_mysqld.inc
CREATE TABLE t2 (a VARCHAR(8)) ENGINE=InnoDB ENCRYPTED=YES ENCRYPTION_KEY_ID=2;
INSERT INTO t2 VALUES ('foo'),('bar');
# Cleanup
DROP TABLE t1, t2;
--let $restart_parameters=
--source include/restart_mysqld.inc
--exec vault secrets disable bug1 > /dev/null
--exec vault secrets disable good > /dev/null
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