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 @@
# of the cache is allowed on server failures.
#
#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:
numbers for the keys stored in the cache are considered always
invalid, except when the vault server is unavailable and use
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;
static int timeout;
static int max_retries;
static char caching_enabled;
static char check_kv_version;
static long cache_timeout;
static long cache_version_timeout;
static char use_cache_on_timeout;
......@@ -345,6 +346,11 @@ static MYSQL_SYSVAR_BOOL(caching_enabled, caching_enabled,
"the Hashicorp Vault server in the local memory)",
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,
struct st_mysql_sys_var *var,
void *var_ptr,
......@@ -388,6 +394,7 @@ static struct st_mysql_sys_var *settings[] = {
MYSQL_SYSVAR(cache_timeout),
MYSQL_SYSVAR(cache_version_timeout),
MYSQL_SYSVAR(use_cache_on_timeout),
MYSQL_SYSVAR(check_kv_version),
NULL
};
......@@ -492,8 +499,8 @@ static int curl_run (char *url, std::string *response, bool soft_timeout)
return OPERATION_TIMEOUT;
}
my_printf_error(ER_UNKNOWN_ERROR, PLUGIN_ERROR_HEADER
"CURL returned this error code: %u "
" with error message: %s", 0, curl_res,
"curl returned this error code: %u "
"with the following error message: %s", 0, curl_res,
curl_errbuf[0] ? curl_errbuf :
curl_easy_strerror(curl_res));
return OPERATION_ERROR;
......@@ -714,7 +721,7 @@ static unsigned int get_latest_version (unsigned int key_id)
}
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 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,
}
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
question mark, plus length of the "/data/" and the "?version="
strings and plus a zero byte:
......@@ -1127,6 +1134,7 @@ static int hashicorp_key_management_plugin_init(void *p)
while (vault_url[vault_url_len - 1] == '/')
{
vault_url_len--;
suffix_len--;
}
/*
Checking the maximum allowable length to protect
......@@ -1154,14 +1162,119 @@ static int hashicorp_key_management_plugin_init(void *p)
cache_max_time = ms_to_ticks(cache_timeout);
cache_max_ver_time = ms_to_ticks(cache_version_timeout);
/* 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);
if (list == NULL)
{
my_printf_error(ER_UNKNOWN_ERROR, PLUGIN_ERROR_HEADER
"curl: unable to construct slist", 0);
Failure3:
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;
}
......@@ -1208,10 +1321,10 @@ maria_declare_plugin(hashicorp_key_management)
PLUGIN_LICENSE_GPL,
hashicorp_key_management_plugin_init,
hashicorp_key_management_plugin_deinit,
0x0104 /* 1.04 */,
0x0105 /* 1.05 */,
NULL, /* status variables */
settings,
"1.04",
"1.05",
MariaDB_PLUGIN_MATURITY_STABLE
}
maria_declare_plugin_end;
......@@ -3,3 +3,4 @@
--loose-hashicorp-key-management-vault-url="$VAULT_ADDR/v1/mariadbtest/"
--loose-hashicorp-key-management-token="$VAULT_TOKEN"
--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
hashicorp_key_management_cache_timeout 60000
hashicorp_key_management_cache_version_timeout 0
hashicorp_key_management_caching_enabled ON
hashicorp_key_management_check_kv_version OFF
hashicorp_key_management_max_retries 3
hashicorp_key_management_timeout 60
hashicorp_key_management_use_cache_on_timeout OFF
......
......@@ -3,6 +3,7 @@ Variable_name Value
hashicorp_key_management_cache_timeout 60000
hashicorp_key_management_cache_version_timeout 0
hashicorp_key_management_caching_enabled ON
hashicorp_key_management_check_kv_version OFF
hashicorp_key_management_max_retries 3
hashicorp_key_management_timeout 60
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