diff options
Diffstat (limited to 'plugin')
34 files changed, 2484 insertions, 7 deletions
diff --git a/plugin/func_test/plugin.cc b/plugin/func_test/plugin.cc index f59ee82388c..5cbf05f4f24 100644 --- a/plugin/func_test/plugin.cc +++ b/plugin/func_test/plugin.cc @@ -29,7 +29,7 @@ public: null_value= str->copy(STRING_WITH_LEN("sysconst_test"), system_charset_info); return null_value ? NULL : str; } - bool fix_length_and_dec() override + bool fix_length_and_dec(THD *thd) override { max_length= MAX_FIELD_NAME * system_charset_info->mbmaxlen; set_maybe_null(); diff --git a/plugin/hashicorp_key_management/CMakeLists.txt b/plugin/hashicorp_key_management/CMakeLists.txt new file mode 100644 index 00000000000..bd1eee844ab --- /dev/null +++ b/plugin/hashicorp_key_management/CMakeLists.txt @@ -0,0 +1,23 @@ +INCLUDE(FindCURL) +IF(NOT CURL_FOUND) + # Can't build plugin + RETURN() +ENDIF() + +INCLUDE_DIRECTORIES(${CURL_INCLUDE_DIR}) + +set(CPACK_RPM_hashicorp-key-management_PACKAGE_SUMMARY "Hashicorp Key Management plugin for MariaDB" PARENT_SCOPE) +set(CPACK_RPM_hashicorp-key-management_PACKAGE_DESCRIPTION "This encryption plugin uses Hashicorp Vault for storing encryption + keys for MariaDB Data-at-Rest encryption." PARENT_SCOPE) + +MYSQL_ADD_PLUGIN(HASHICORP_KEY_MANAGEMENT + hashicorp_key_management_plugin.cc + LINK_LIBRARIES ${CURL_LIBRARIES} + CONFIG hashicorp_key_management.cnf + COMPONENT hashicorp-key-management + MODULE_ONLY) + +ADD_FEATURE_INFO(HASHICORP_KEY_MANAGEMENT "ON" "Hashicorp Key Management Plugin") + +INSTALL_DOCUMENTATION(hashicorp_key_management.txt + COMPONENT plugin-hashicorp-key-management) diff --git a/plugin/hashicorp_key_management/hashicorp_key_management.cnf b/plugin/hashicorp_key_management/hashicorp_key_management.cnf new file mode 100644 index 00000000000..275626a9e7b --- /dev/null +++ b/plugin/hashicorp_key_management/hashicorp_key_management.cnf @@ -0,0 +1,117 @@ +# Copyright (C) 2019-2022 MariaDB Corporation +# +# This is a default configuration for the Hashicorp Vault plugin. +# You can read more about the parameters of this plugin in the +# hashicorp_key_management.txt file. +# +# NOTE THAT YOU MUST MANUALLY UNCOMMENT THE "plugin-load-add" +# LINE AND ALL THE NECESSARY PARAMETERS BELOW, SETTING THEM +# TO APPROPRIATE VALUES! +# +[mariadb] + +# +# To use Hashicorp Vault KMS, the plugin must be preloaded and +# activated on the server: +# +#plugin-load-add=hashicorp_key_management.so + +# Most of its parameters should not be changed during plugin +# operation and therefore must be preconfigured as part of +# the server configuration: + +# +# HTTP[s] URL that is used to connect to the Hashicorp Vault server. +# It must include the name of the scheme ("https://" for a secure +# connection) and, according to the API rules for storages of the +# key-value type in Hashicorp Vault, after the server address, the +# path must begin with the "/v1/" string (as prefix), for example: +# "https://127.0.0.1:8200/v1/my_secrets" +# +#hashicorp-key-management-vault-url="<url>" + +# +# Authentication token that passed to the Hashicorp Vault +# in the request header: +# +#hashicorp-key-management-token="<token>" + +# +# Optional path to the Certificate Authority (CA) bundle +# (is a file that contains root and intermediate certificates): +# +#hashicorp-key-management-vault-ca="<path>" + +# +# Set the duration (in seconds) for the Hashicorp Vault server +# connection timeout. The allowed range is from 1 to 86400 seconds. +# The user can also specify a zero value, which means the default +# timeout value set by the libcurl library (currently 300 seconds): +# +#hashicorp-key-management-timeout=15 + +# +# Number of server request retries in case of timeout: +# +#hashicorp-key-management-retries=3 + +# +# Enable key caching (storing key values received from +# the Hashicorp Vault server in the local memory): +# +#hashicorp-key-management-caching-enabled="on" + +# +# This parameter instructs the plugin to use the key values +# or version numbers taken from the cache in the event of a +# timeout when accessing the vault server. By default this +# option is disabled. +# +# Please note that key values or version numbers will be read +# from the cache when the timeout expires only after the number +# of attempts to read them from the storage server that specified +# by the hashicorp-key-management-retries parameter has been +# exhausted: +# +#hashicorp-key-management-use-cache-on-timeout="off" + +# +# The time (in milliseconds) after which the value of the key +# stored in the cache becomes invalid and an attempt to read this +# data causes a new request send to the vault server. By default, +# cache entries become invalid after 60,000 milliseconds (after +# one minute). +# +# If the value of this parameter is zero, then the keys will always +# be considered invalid, but they still can be used if the vault +# server is unavailable and the corresponding cache operating mode +# (--[loose-]hashicorp-key-management-use-cache-on-timeout="on") +# is enabled. +# +#hashicorp-key-management-cache-timeout=0 + +# +# The time (in milliseconds) after which the information about +# latest version number of the key (which stored in the cache) +# becomes invalid and an attempt to read this information causes +# a new request send to the vault server. +# +# If the value of this parameter is zero, then information abount +# latest key version numbers always considered invalid, unless +# there is no communication with the vault server and use of the +# cache is allowed when the server is unavailable. +# +# By default, this parameter is zero, that is, the latest version +# 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. +# +#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 diff --git a/plugin/hashicorp_key_management/hashicorp_key_management.txt b/plugin/hashicorp_key_management/hashicorp_key_management.txt new file mode 100644 index 00000000000..1750154858e --- /dev/null +++ b/plugin/hashicorp_key_management/hashicorp_key_management.txt @@ -0,0 +1,181 @@ +This file describes a hasicorp_key_management plugin that is used to +implement encryption using keys stored in the Hashicorp Vault KMS. + +The current version of this plugin implements the following features: + +- Authentication is done using the Hashicorp Vault's token + authentication method; +- If additional client authentication is required, then the + path to the CA authentication bundle file may be passed + as a plugin parameter; +- The creation of the keys and their management is carried + out using the Hashicorp Vault KMS and their tools; +- The plugin uses libcurl (https) as an interface to + the HashiCorp Vault server; +- JSON parsing is performed through the JSON service + (through the include/mysql/service_json.h); +- HashiCorp Vault 1.2.4 was used for development and testing. + +Since we require support for key versioning, then the key-value +storage must be configured in Hashicorp Vault as a key-value storage +that uses the interface of the second version. For example, you can +create it as follows: + +~$ vault secrets enable -path /test -version=2 kv + +Key names must correspond to their numerical identifiers. +Key identifiers itself, their possible values and rules of use +are described in more detail in the MariaDB main documentation. + +From the point of view of the key-value storage (in terms +of Hashicorp Vault), the key is a secret containing one key-value +pair with the name "data" and a value representing a binary string +containing the key value, for example: + +~$ vault kv get /test/1 + +====== Metadata ====== +Key Value +--- ----- +created_time 2019-12-14T14:19:19.42432951Z +deletion_time n/a +destroyed false +version 1 + +==== Data ==== +Key Value +--- ----- +data 0123456789ABCDEF0123456789ABCDEF + +Keys values are strings containing binary data. MariaDB currently +uses the AES algorithm with 256-bit keys as the default encryption +method. In this case, the keys that will be stored in the Hashicorp +Vault should be 32-byte strings. Most likely you will use some utilities +for creating and administering keys designed to work with Hashicorp +Vault. But in the simplest case, keys can be created from the command +line through the vault utility, for example, as follows: + +~$ vault kv put /test/1 data="0123456789ABCDEF0123456789ABCDEF" + +If you use default encryption (AES), you should ensure that the +key length is 32 bytes, otherwise it may fail to use InnoDB as +a data storage. + +The plugin currently does not unseal Hashicorp Vault on its own, +you must do this in advance and on your own. + +To use Hashicorp Vault KMS, the plugin must be preloaded and +activated on the server. Most of its parameters should not be +changed during plugin operation and therefore must be preconfigured +as part of the server configuration through configuration file or +command line options: + +--plugin-load-add=hashicorp_key_management.so +--loose-hashicorp-key-management +--loose-hashicorp-key-management-vault-url="$VAULT_ADDR/v1/test" +--loose-hashicorp-key-management-token="$VAULT_TOKEN" + +Currently, the plugin supports the following parameters, which +must be set in advance and cannot be changed during server +operation: + +--[loose-]hashicorp-key-management-vault-url="<url>" + + HTTP[s] URL that is used to connect to the Hashicorp Vault + server. It must include the name of the scheme (https:// + for a secure connection) and, according to the API rules + for storages of the key-value type in Hashicorp Vault, + after the server address, the path must begin with the + "/v1/" string (as prefix), for example: + + https://127.0.0.1:8200/v1/my_secrets + + By default, the path is not set, therefore you must + replace with the correct path to your secrets. + +--[loose-]hashicorp-key-management-token="<token>" + + Authentication token that passed to the Hashicorp Vault + in the request header. + + By default, this parameter contains an empty string, + so you must specify the correct value for it, otherwise + the Hashicorp Vault server will refuse authorization. + +--[loose-]hashicorp-key-management-vault-ca="<path>" + + Path to the Certificate Authority (CA) bundle (is a file + that contains root and intermediate certificates). + + By default, this parameter contains an empty string, + which means no CA bundle. + +--[loose-]hashicorp-key-management-timeout=<timeout> + + Set the duration (in seconds) for the Hashicorp Vault server + connection timeout. The default value is 15 seconds. The allowed + range is from 1 to 86400 seconds. The user can also specify a zero + value, which means the default timeout value set by the libcurl + library (currently 300 seconds). + +--[loose-]hashicorp-key-management-retries=<retries> + + Number of server request retries in case of timeout. + Default is three retries. + +--[loose-]hashicorp-key-management-caching-enabled="on"|"off" + + Enable key caching (storing key values received from + the Hashicorp Vault server in the local memory). By default + caching is enabled. + +--[loose-]hashicorp-key-management-use-cache-on-timeout="on"|"off" + + This parameter instructs the plugin to use the key values + or version numbers taken from the cache in the event of a + timeout when accessing the vault server. By default this + option is disabled. + + Please note that key values or version numbers will be read + from the cache when the timeout expires only after the number + of attempts to read them from the storage server that specified + by the --[loose-]hashicorp-key-management-retries parameter + has been exhausted. + +--[loose-]hashicorp-key-management-cache-timeout=<timeout> + + The time (in milliseconds) after which the value of the key + stored in the cache becomes invalid and an attempt to read this + data causes a new request send to the vault server. By default, + cache entries become invalid after 60,000 milliseconds (after + one minute). + + If the value of this parameter is zero, then the keys will always + be considered invalid, but they still can be used if the vault + server is unavailable and the corresponding cache operating mode + (--[loose-]hashicorp-key-management-use-cache-on-timeout="on") + is enabled. + +--[loose-]hashicorp-key-management-cache-version-timeout=<timeout> + + The time (in milliseconds) after which the information about + latest version number of the key (which stored in the cache) + becomes invalid and an attempt to read this information causes + a new request send to the vault server. + + If the value of this parameter is zero, then information abount + latest key version numbers always considered invalid, unless + there is no communication with the vault server and use of the + cache is allowed when the server is unavailable. + + By default, this parameter is zero, that is, the latest version + 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. diff --git a/plugin/hashicorp_key_management/hashicorp_key_management_plugin.cc b/plugin/hashicorp_key_management/hashicorp_key_management_plugin.cc new file mode 100644 index 00000000000..7c72af688e4 --- /dev/null +++ b/plugin/hashicorp_key_management/hashicorp_key_management_plugin.cc @@ -0,0 +1,1385 @@ +/* Copyright (C) 2019-2022 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 Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1335 USA */ + +#include <my_global.h> +#include <mysql/plugin_encryption.h> +#include <mysqld_error.h> +#include <string.h> +#include <errno.h> +#include <string> +#include <sstream> +#include <curl/curl.h> +#ifdef _WIN32 +#include <malloc.h> +#define alloca _alloca +#endif +#include <algorithm> +#include <unordered_map> +#include <mutex> + +#if defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND) +#define HASHICORP_HAVE_EXCEPTIONS 1 +#else +#define HASHICORP_HAVE_EXCEPTIONS 0 +#endif + +#define HASHICORP_DEBUG_LOGGING 0 + +#define PLUGIN_ERROR_HEADER "hashicorp: " + +/* Key version information structure: */ +typedef struct VER_INFO +{ + unsigned int key_version; + clock_t timestamp; + VER_INFO() : key_version(0), timestamp(0) {}; + VER_INFO(unsigned int key_version_, clock_t timestamp_) : + key_version(key_version_), timestamp(timestamp_) {}; +} VER_INFO; + +/* Key information structure: */ +typedef struct KEY_INFO +{ + unsigned int key_id; + unsigned int key_version; + clock_t timestamp; + unsigned int length; + unsigned char data [MY_AES_MAX_KEY_LENGTH]; + KEY_INFO() : key_id(0), key_version(0), timestamp(0), length(0) {}; + KEY_INFO(unsigned int key_id_, + unsigned int key_version_, + clock_t timestamp_, + unsigned int length_) : + key_id(key_id_), key_version(key_version_), + timestamp(timestamp_), length(length_) {}; +} KEY_INFO; + +/* Cache for the latest version, per key id: */ +typedef std::unordered_map<unsigned int, VER_INFO> VER_MAP; + +/* Cache for key information: */ +typedef std::unordered_map<unsigned long long, KEY_INFO> KEY_MAP; + +#define KEY_ID_AND_VERSION(key_id, version) \ + ((unsigned long long)key_id << 32 | version) + +class HCData +{ +private: + struct curl_slist *slist; + char *vault_url_data; + size_t vault_url_len; + char *local_token; + char *token_header; + bool curl_inited; +public: + HCData() + :slist(NULL), + vault_url_data(NULL), + vault_url_len(0), + local_token(NULL), + token_header(NULL), + curl_inited(false) + {} + unsigned int get_latest_version (unsigned int key_id); + unsigned int get_key_from_vault (unsigned int key_id, + unsigned int key_version, + unsigned char *dstbuf, + unsigned int *buflen); + int init (); + void deinit () + { + if (slist) + { + curl_slist_free_all(slist); + slist = NULL; + } + if (curl_inited) + { + curl_global_cleanup(); + curl_inited = false; + } + vault_url_len = 0; + if (vault_url_data) + { + free(vault_url_data); + vault_url_data = NULL; + } + if (token_header) + { + free(token_header); + token_header = NULL; + } + if (local_token) + { + free(local_token); + local_token = NULL; + } + } + void cache_clean () + { + latest_version_cache.clear(); + key_info_cache.clear(); + } +private: + std::mutex mtx; + VER_MAP latest_version_cache; + KEY_MAP key_info_cache; +private: + void cache_add (const KEY_INFO& info, bool update_version); + unsigned int cache_get (unsigned int key_id, unsigned int key_version, + unsigned char* data, unsigned int* buflen, + bool with_timeouts); + unsigned int cache_check_version (unsigned int key_id); + unsigned int cache_get_version (unsigned int key_id); + int curl_run (const char *url, std::string *response, + bool soft_timeout) const; + int check_version (const char *mount_url) const; + void *alloc (size_t nbytes) const + { + void *res = (char *) malloc(nbytes); + if (!res) + { + my_printf_error(ER_UNKNOWN_ERROR, PLUGIN_ERROR_HEADER + "Memory allocation error", 0); + } + return res; + } +}; + +static HCData data; + +static clock_t cache_max_time; +static clock_t cache_max_ver_time; + +/* + Convert milliseconds to timer ticks with rounding + to nearest integer: +*/ +static clock_t ms_to_ticks (long ms) +{ + long long ticks_1000 = ms * (long long) CLOCKS_PER_SEC; + clock_t ticks = (clock_t) (ticks_1000 / 1000); + return ticks + ((clock_t) (ticks_1000 % 1000) >= 500); +} + +void HCData::cache_add (const KEY_INFO& info, bool update_version) +{ + unsigned int key_id = info.key_id; + unsigned int key_version = info.key_version; + mtx.lock(); + VER_INFO &ver_info = latest_version_cache[key_id]; + if (update_version || ver_info.key_version < key_version) + { + ver_info.key_version = key_version; + ver_info.timestamp = info.timestamp; + } + key_info_cache[KEY_ID_AND_VERSION(key_id, key_version)] = info; +#if HASHICORP_DEBUG_LOGGING + my_printf_error(ER_UNKNOWN_ERROR, PLUGIN_ERROR_HEADER + "cache_add: key_id = %u, key_version = %u, " + "timestamp = %u, update_version = %u, new version = %u", + ME_ERROR_LOG_ONLY | ME_NOTE, key_id, key_version, + ver_info.timestamp, (int) update_version, + ver_info.key_version); +#endif + mtx.unlock(); +} + +unsigned int + HCData::cache_get (unsigned int key_id, unsigned int key_version, + unsigned char* data, unsigned int* buflen, + bool with_timeouts) +{ + unsigned int version = key_version; + clock_t current_time = clock(); + mtx.lock(); + if (key_version == ENCRYPTION_KEY_VERSION_INVALID) + { + clock_t timestamp; +#if HASHICORP_HAVE_EXCEPTIONS + try + { + VER_INFO &ver_info = latest_version_cache.at(key_id); + version = ver_info.key_version; + timestamp = ver_info.timestamp; + } + catch (const std::out_of_range &e) +#else + VER_MAP::const_iterator ver_iter = latest_version_cache.find(key_id); + if (ver_iter != latest_version_cache.end()) + { + version = ver_iter->second.key_version; + timestamp = ver_iter->second.timestamp; + } + else +#endif + { + mtx.unlock(); + return ENCRYPTION_KEY_VERSION_INVALID; + } +#if HASHICORP_DEBUG_LOGGING + my_printf_error(ER_UNKNOWN_ERROR, PLUGIN_ERROR_HEADER + "cache_get: key_id = %u, key_version = %u, " + "last version = %u, version timestamp = %u, " + "current time = %u, diff = %u", + ME_ERROR_LOG_ONLY | ME_NOTE, key_id, key_version, + version, timestamp, current_time, + current_time - timestamp); +#endif + if (with_timeouts && current_time - timestamp > cache_max_ver_time) + { + mtx.unlock(); + return ENCRYPTION_KEY_VERSION_INVALID; + } + } + KEY_INFO info; +#if HASHICORP_HAVE_EXCEPTIONS + try + { + info = key_info_cache.at(KEY_ID_AND_VERSION(key_id, version)); + } + catch (const std::out_of_range &e) +#else + KEY_MAP::const_iterator key_iter = + key_info_cache.find(KEY_ID_AND_VERSION(key_id, version)); + if (key_iter != key_info_cache.end()) + { + info = key_iter->second; + } + else +#endif + { + mtx.unlock(); + return ENCRYPTION_KEY_VERSION_INVALID; + } + mtx.unlock(); +#if HASHICORP_DEBUG_LOGGING + my_printf_error(ER_UNKNOWN_ERROR, PLUGIN_ERROR_HEADER + "cache_get: key_id = %u, key_version = %u, " + "effective version = %u, key data timestamp = %u, " + "current time = %u, diff = %u", + ME_ERROR_LOG_ONLY | ME_NOTE, key_id, key_version, + version, info.timestamp, current_time, + current_time - info.timestamp); +#endif + unsigned int length= info.length; + if (with_timeouts && current_time - info.timestamp > cache_max_time) + { + return ENCRYPTION_KEY_VERSION_INVALID; + } + unsigned int max_length = *buflen; + *buflen = length; + if (max_length >= length) + { + memcpy(data, info.data, length); + } + else + { +#ifndef NDEBUG + if (max_length) + { + my_printf_error(ER_UNKNOWN_ERROR, PLUGIN_ERROR_HEADER + "Encryption key buffer is too small", + ME_ERROR_LOG_ONLY | ME_NOTE); + } +#endif + return ENCRYPTION_KEY_BUFFER_TOO_SMALL; + } + return 0; +} + +unsigned int HCData::cache_get_version (unsigned int key_id) +{ + unsigned int version; + mtx.lock(); +#if HASHICORP_HAVE_EXCEPTIONS + try + { + version = latest_version_cache.at(key_id).key_version; + } + catch (const std::out_of_range &e) +#else + VER_MAP::const_iterator ver_iter = latest_version_cache.find(key_id); + if (ver_iter != latest_version_cache.end()) + { + version = ver_iter->second.key_version; + } + else +#endif + { + version = ENCRYPTION_KEY_VERSION_INVALID; + } + mtx.unlock(); + return version; +} + +unsigned int HCData::cache_check_version (unsigned int key_id) +{ + unsigned int version; + clock_t timestamp; + mtx.lock(); +#if HASHICORP_HAVE_EXCEPTIONS + try + { + VER_INFO &ver_info = latest_version_cache.at(key_id); + version = ver_info.key_version; + timestamp = ver_info.timestamp; + } + catch (const std::out_of_range &e) +#else + VER_MAP::const_iterator ver_iter = latest_version_cache.find(key_id); + if (ver_iter != latest_version_cache.end()) + { + version = ver_iter->second.key_version; + timestamp = ver_iter->second.timestamp; + } + else +#endif + { + mtx.unlock(); +#if HASHICORP_DEBUG_LOGGING + my_printf_error(ER_UNKNOWN_ERROR, PLUGIN_ERROR_HEADER + "cache_check_version: key_id = %u (not in the cache)", + ME_ERROR_LOG_ONLY | ME_NOTE, + key_id); +#endif + return ENCRYPTION_KEY_VERSION_INVALID; + } + mtx.unlock(); + clock_t current_time = clock(); +#if HASHICORP_DEBUG_LOGGING + my_printf_error(ER_UNKNOWN_ERROR, PLUGIN_ERROR_HEADER + "cache_check_version: key_id = %u, " + "last version = %u, version timestamp = %u, " + "current time = %u, diff = %u", + ME_ERROR_LOG_ONLY | ME_NOTE, key_id, version, + version, timestamp, current_time, + current_time - timestamp); +#endif + if (current_time - timestamp <= cache_max_ver_time) + { + return version; + } + else + { + return ENCRYPTION_KEY_VERSION_INVALID; + } +} + +static char* vault_url; +static char* token; +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; + +static MYSQL_SYSVAR_STR(vault_ca, vault_ca, + PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY, + "Path to the Certificate Authority (CA) bundle (is a file " + "that contains root and intermediate certificates)", + NULL, NULL, ""); + +static MYSQL_SYSVAR_STR(vault_url, vault_url, + PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY, + "HTTP[s] URL that is used to connect to the Hashicorp Vault server", + NULL, NULL, ""); + +static MYSQL_SYSVAR_STR(token, token, + PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY | PLUGIN_VAR_NOSYSVAR, + "Authentication token that passed to the Hashicorp Vault " + "in the request header", + NULL, NULL, ""); + +static MYSQL_SYSVAR_INT(timeout, timeout, + PLUGIN_VAR_RQCMDARG, + "Duration (in seconds) for the Hashicorp Vault server " + "connection timeout", + NULL, NULL, 15, 0, 86400, 1); + +static MYSQL_SYSVAR_INT(max_retries, max_retries, + PLUGIN_VAR_RQCMDARG, + "Number of server request retries in case of timeout", + NULL, NULL, 3, 0, INT_MAX, 1); + +static MYSQL_SYSVAR_BOOL(caching_enabled, caching_enabled, + PLUGIN_VAR_RQCMDARG, + "Enable key caching (storing key values received from " + "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, + const void *save) +{ + long timeout = * (long *) save; + * (long *) var_ptr = timeout; + cache_max_time = ms_to_ticks(timeout); +} + +static MYSQL_SYSVAR_LONG(cache_timeout, cache_timeout, + PLUGIN_VAR_RQCMDARG, + "Cache timeout for key data (in milliseconds)", + NULL, cache_timeout_update, 60000, 0, LONG_MAX, 1); + +static void + cache_version_timeout_update (MYSQL_THD thd, + struct st_mysql_sys_var *var, + void *var_ptr, + const void *save) +{ + long timeout = * (long *) save; + * (long *) var_ptr = timeout; + cache_max_ver_time = ms_to_ticks(timeout); +} + +static MYSQL_SYSVAR_LONG(cache_version_timeout, cache_version_timeout, + PLUGIN_VAR_RQCMDARG, + "Cache timeout for key version (in milliseconds)", + NULL, cache_version_timeout_update, 0, 0, LONG_MAX, 1); + +static MYSQL_SYSVAR_BOOL(use_cache_on_timeout, use_cache_on_timeout, + PLUGIN_VAR_RQCMDARG, + "In case of timeout (when accessing the vault server) " + "use the value taken from the cache", + NULL, NULL, 0); + +static struct st_mysql_sys_var *settings[] = { + MYSQL_SYSVAR(vault_url), + MYSQL_SYSVAR(token), + MYSQL_SYSVAR(vault_ca), + MYSQL_SYSVAR(timeout), + MYSQL_SYSVAR(max_retries), + MYSQL_SYSVAR(caching_enabled), + MYSQL_SYSVAR(cache_timeout), + MYSQL_SYSVAR(cache_version_timeout), + MYSQL_SYSVAR(use_cache_on_timeout), + MYSQL_SYSVAR(check_kv_version), + NULL +}; + +/* + Reasonable length limit to protect against accidentally reading + the wrong key or from trying to overload the server with unnecessary + work to receive too long responses to requests: +*/ +#define MAX_RESPONSE_SIZE 131072 + +static size_t write_response_memory (void *contents, size_t size, size_t nmemb, + void *userp) +{ + size_t realsize = size * nmemb; + std::ostringstream *read_data = static_cast<std::ostringstream *>(userp); + size_t current_length = read_data->tellp(); + if (current_length + realsize > MAX_RESPONSE_SIZE) + return 0; // response size limit exceeded + read_data->write(static_cast<char *>(contents), realsize); + if (!read_data->good()) + return 0; + return realsize; +} + +enum { + OPERATION_OK, + OPERATION_TIMEOUT, + OPERATION_ERROR +}; + +static CURLcode + perform_with_retries (CURL *curl, std::ostringstream *read_data_stream) +{ + int retries= max_retries; + CURLcode curl_res; + do { + curl_res= curl_easy_perform(curl); + if (curl_res != CURLE_OPERATION_TIMEDOUT) + { + break; + } + read_data_stream->clear(); + read_data_stream->str(""); + } while (retries--); + return curl_res; +} + +int HCData::curl_run (const char *url, std::string *response, + bool soft_timeout) const +{ + char curl_errbuf[CURL_ERROR_SIZE]; + std::ostringstream read_data_stream; + long http_code = 0; + CURLcode curl_res = CURLE_OK; + CURL *curl = curl_easy_init(); + if (curl == NULL) + { + my_printf_error(ER_UNKNOWN_ERROR, PLUGIN_ERROR_HEADER + "Cannot initialize curl session", + ME_ERROR_LOG_ONLY); + return OPERATION_ERROR; + } + curl_errbuf[0] = '\0'; + if ((curl_res= curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, curl_errbuf)) != + CURLE_OK || + (curl_res= curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, + write_response_memory)) != CURLE_OK || + (curl_res= curl_easy_setopt(curl, CURLOPT_WRITEDATA, + &read_data_stream)) != + CURLE_OK || + (curl_res= curl_easy_setopt(curl, CURLOPT_HTTPHEADER, slist)) != + CURLE_OK || + /* + The options CURLOPT_SSL_VERIFYPEER and CURLOPT_SSL_VERIFYHOST are + set explicitly to withstand possible future changes in curl defaults: + */ + (curl_res= curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1)) != + CURLE_OK || + (curl_res= curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 2L)) != + CURLE_OK || + (strlen(vault_ca) != 0 && + (curl_res= curl_easy_setopt(curl, CURLOPT_CAINFO, vault_ca)) != + CURLE_OK) || + (curl_res= curl_easy_setopt(curl, CURLOPT_USE_SSL, CURLUSESSL_ALL)) != + CURLE_OK || + (curl_res= curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L)) != + CURLE_OK || + (timeout && + ((curl_res= curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, timeout)) != + CURLE_OK || + (curl_res= curl_easy_setopt(curl, CURLOPT_TIMEOUT, timeout)) != + CURLE_OK)) || + (curl_res = curl_easy_setopt(curl, CURLOPT_URL, url)) != CURLE_OK || + (curl_res = perform_with_retries(curl, &read_data_stream)) != CURLE_OK || + (curl_res = curl_easy_getinfo (curl, CURLINFO_RESPONSE_CODE, + &http_code)) != CURLE_OK) + { + curl_easy_cleanup(curl); + if (soft_timeout && curl_res == CURLE_OPERATION_TIMEDOUT) + { + return OPERATION_TIMEOUT; + } + my_printf_error(ER_UNKNOWN_ERROR, PLUGIN_ERROR_HEADER + "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; + } + curl_easy_cleanup(curl); + *response = read_data_stream.str(); + bool is_error = http_code < 200 || http_code >= 300; + if (is_error) + { + const char *res = response->c_str(); + /* + Error 404 requires special handling - in case the server + returned an empty array of error strings (the value of the + "error" object in JSON is equal to an empty array), we should + ignore this error at this level, since this means the missing + key (this problem is handled at a higher level), but if the + error object contains anything other than empty array, then + we need to print the error message to the log: + */ + if (http_code == 404) + { + const char *err; + int err_len; + if (json_get_object_key(res, res + response->size(), + "errors", &err, &err_len) == JSV_ARRAY) + { + const char *ev; + int ev_len; + if (json_get_array_item(err, err + err_len, 0, &ev, &ev_len) == + JSV_NOTHING) + { + *response = std::string(""); + is_error = false; + } + } + } + if (is_error) + { + my_printf_error(ER_UNKNOWN_ERROR, PLUGIN_ERROR_HEADER + "Hashicorp server error: %d, response: %s", + ME_ERROR_LOG_ONLY | ME_WARNING, http_code, res); + } + } + return is_error ? OPERATION_ERROR : OPERATION_OK; +} + +static inline int c2xdigit (int c) +{ + if (c > 9) + { + c -= 'A' - '0' - 10; + if (c > 15) + { + c -= 'a' - 'A'; + } + } + return c; +} + +static int hex2buf (unsigned int max_length, unsigned char *dstbuf, + int key_len, const char *key) +{ + int length = 0; + while (key_len >= 2) + { + int c1 = key[0]; + int c2 = key[1]; + if (! isxdigit(c1) || ! isxdigit(c2)) + { + break; + } + if (max_length) + { + c1 = c2xdigit(c1 - '0'); + c2 = c2xdigit(c2 - '0'); + dstbuf[length++] = (c1 << 4) + c2; + } + key += 2; + key_len -= 2; + } + if (key_len) + { + if (key_len != 1) + { + my_printf_error(ER_UNKNOWN_ERROR, PLUGIN_ERROR_HEADER + "Syntax error - the key data should contain only " + "hexadecimal digits", + 0); + } + else + { + my_printf_error(ER_UNKNOWN_ERROR, PLUGIN_ERROR_HEADER + "Syntax error - extra character in the key data", + 0); + } + return -1; + } + return 0; +} + +static int get_data (const std::string &response_str, + const char **js, int *js_len, + unsigned int key_id, + unsigned int key_version) +{ + 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) + { + if (key_version == ENCRYPTION_KEY_VERSION_INVALID) + { + my_printf_error(ER_UNKNOWN_ERROR, PLUGIN_ERROR_HEADER + "Key not found (key id: %u)", + ME_ERROR_LOG_ONLY | ME_NOTE, key_id); + } + else + { + my_printf_error(ER_UNKNOWN_ERROR, PLUGIN_ERROR_HEADER + "Key not found (key id: %u, key version: %u)", + ME_ERROR_LOG_ONLY | ME_NOTE, key_id, key_version); + } + return 1; + } + if (json_get_object_key(response, response + response_len, "data", + js, js_len) != JSV_OBJECT) + { + my_printf_error(ER_UNKNOWN_ERROR, PLUGIN_ERROR_HEADER + "Unable to get data object (http response is: %s)", + 0, response); + return 2; + } + return 0; +} + +static unsigned int get_version (const char *js, int js_len, + const std::string &response_str, + int *rc) +{ + const char *ver; + int ver_len; + *rc = 1; + if (json_get_object_key(js, js + js_len, "metadata", + &ver, &ver_len) != JSV_OBJECT) + { + my_printf_error(ER_UNKNOWN_ERROR, PLUGIN_ERROR_HEADER + "Unable to get metadata object (http response is: %s)", + 0, response_str.c_str()); + return ENCRYPTION_KEY_VERSION_INVALID; + } + if (json_get_object_key(ver, ver + ver_len, "version", + &ver, &ver_len) != JSV_NUMBER) + { + my_printf_error(ER_UNKNOWN_ERROR, PLUGIN_ERROR_HEADER + "Unable to get version number (http response is: %s)", + 0, response_str.c_str()); + return ENCRYPTION_KEY_VERSION_INVALID; + } + errno = 0; + 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_str.c_str()); + return ENCRYPTION_KEY_VERSION_INVALID; + } + *rc = 0; + return (unsigned int) version; +} + +static int get_key_data (const char *js, int js_len, + const char **key, int *key_len, + const std::string &response_str) +{ + if (json_get_object_key(js, js + js_len, "data", + &js, &js_len) != JSV_OBJECT) + { + my_printf_error(ER_UNKNOWN_ERROR, PLUGIN_ERROR_HEADER + "Unable to get second-level data object " + "(http response is: %s)", + 0, response_str.c_str()); + return 1; + } + if (json_get_object_key(js, js + js_len, "data", + key, key_len) != JSV_STRING) + { + my_printf_error(ER_UNKNOWN_ERROR, PLUGIN_ERROR_HEADER + "Unable to get data string (http response is: %s)", + 0, response_str.c_str()); + return 1; + } + return 0; +} + +unsigned int HCData::get_latest_version (unsigned int key_id) +{ + unsigned int version; +#if HASHICORP_DEBUG_LOGGING + my_printf_error(ER_UNKNOWN_ERROR, PLUGIN_ERROR_HEADER + "get_latest_version: key_id = %u", + ME_ERROR_LOG_ONLY | ME_NOTE, key_id); +#endif + if (caching_enabled) + { + version = cache_check_version(key_id); + if (version != ENCRYPTION_KEY_VERSION_INVALID) + { + return version; + } + } + std::string response_str; + /* + 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: + */ + size_t buf_len = vault_url_len + (20 + 6 + 1); + char *url = (char *) alloca(buf_len); + snprintf(url, buf_len, "%s%u", vault_url_data, key_id); + bool use_cache= caching_enabled && use_cache_on_timeout; + int rc; + if ((rc= curl_run(url, &response_str, use_cache)) != OPERATION_OK) + { + if (rc == OPERATION_TIMEOUT) + { + version = cache_get_version(key_id); + if (version != ENCRYPTION_KEY_VERSION_INVALID) + { + return version; + } + } + my_printf_error(ER_UNKNOWN_ERROR, PLUGIN_ERROR_HEADER + "Unable to get key data", 0); + return ENCRYPTION_KEY_VERSION_INVALID; + } + const char *js; + int js_len; + if (get_data(response_str, &js, &js_len, key_id, + ENCRYPTION_KEY_VERSION_INVALID)) + { + return ENCRYPTION_KEY_VERSION_INVALID; + } + version = get_version(js, js_len, response_str, &rc); + if (!caching_enabled || rc) + { + return version; + } + const char* key; + int key_len; + if (get_key_data(js, js_len, &key, &key_len, response_str)) + { + return ENCRYPTION_KEY_VERSION_INVALID; + } + unsigned int length = (unsigned int) key_len >> 1; + KEY_INFO info(key_id, version, clock(), length); + if (length > sizeof(info.data)) + { + my_printf_error(ER_UNKNOWN_ERROR, PLUGIN_ERROR_HEADER + "Encryption key data is too long", + ME_ERROR_LOG_ONLY | ME_NOTE); + return ENCRYPTION_KEY_VERSION_INVALID; + } + int ret = hex2buf(sizeof(info.data), info.data, key_len, key); + if (ret) + { + return ENCRYPTION_KEY_VERSION_INVALID; + } + cache_add(info, true); + return version; +} + +unsigned int HCData::get_key_from_vault (unsigned int key_id, + unsigned int key_version, + unsigned char *dstbuf, + unsigned int *buflen) +{ +#if HASHICORP_DEBUG_LOGGING + my_printf_error(ER_UNKNOWN_ERROR, PLUGIN_ERROR_HEADER + "get_latest_version: key_id = %u, key_version = %u", + ME_ERROR_LOG_ONLY | ME_NOTE, key_id, key_version); +#endif + if (caching_enabled && + cache_get(key_id, key_version, dstbuf, buflen, true) != + ENCRYPTION_KEY_VERSION_INVALID) + { + return 0; + } + std::string response_str; + /* + 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: + */ + size_t buf_len = vault_url_len + (40 + 6 + 9 + 1); + char *url = (char *) alloca(buf_len); + if (key_version != ENCRYPTION_KEY_VERSION_INVALID) + snprintf(url, buf_len, "%s%u?version=%u", + vault_url_data, key_id, key_version); + else + snprintf(url, buf_len, "%s%u", vault_url_data, key_id); + bool use_cache= caching_enabled && use_cache_on_timeout; + int rc; + if ((rc= curl_run(url, &response_str, use_cache)) != OPERATION_OK) + { + if (rc == OPERATION_TIMEOUT) + { + if (cache_get(key_id, key_version, dstbuf, buflen, false) != + ENCRYPTION_KEY_VERSION_INVALID) + { + return 0; + } + } + my_printf_error(ER_UNKNOWN_ERROR, PLUGIN_ERROR_HEADER + "Unable to get key data", 0); + return ENCRYPTION_KEY_VERSION_INVALID; + } + const char *js; + int js_len; + if (get_data(response_str, &js, &js_len, key_id, key_version)) + { + return ENCRYPTION_KEY_VERSION_INVALID; + } +#ifndef NDEBUG + unsigned long version; +#else + unsigned long version= key_version; + if (caching_enabled && + key_version == ENCRYPTION_KEY_VERSION_INVALID) +#endif + { + int rc; + version = get_version(js, js_len, response_str, &rc); + if (rc) + { + return version; + } + } +#ifndef NDEBUG + /* + An internal check that is needed only for debugging the plugin + operation - in order to ensure that we get from the Hashicorp Vault + server exactly the version of the key that is needed: + */ + if (key_version != ENCRYPTION_KEY_VERSION_INVALID && + key_version != version) + { + my_printf_error(ER_UNKNOWN_ERROR, PLUGIN_ERROR_HEADER + "Key version mismatch", 0); + return ENCRYPTION_KEY_VERSION_INVALID; + } +#endif + const char* key; + int key_len; + if (get_key_data(js, js_len, &key, &key_len, response_str)) + { + return ENCRYPTION_KEY_VERSION_INVALID; + } + unsigned int max_length = dstbuf ? *buflen : 0; + unsigned int length = (unsigned int) key_len >> 1; + *buflen = length; + if (length > max_length) + { +#ifndef NDEBUG + if (max_length) + { + my_printf_error(ER_UNKNOWN_ERROR, PLUGIN_ERROR_HEADER + "Encryption key buffer is too small", + ME_ERROR_LOG_ONLY | ME_NOTE); + } +#endif + return ENCRYPTION_KEY_BUFFER_TOO_SMALL; + } + int ret = hex2buf(max_length, dstbuf, key_len, key); + if (ret) + { + return ENCRYPTION_KEY_VERSION_INVALID; + } + if (caching_enabled) + { + KEY_INFO info(key_id, (unsigned int) version, clock(), length); + memcpy(info.data, dstbuf, length); + cache_add(info, key_version == ENCRYPTION_KEY_VERSION_INVALID); + } + return 0; +} + +static unsigned int get_latest_version (unsigned int key_id) +{ + return data.get_latest_version(key_id); +} + +static unsigned int get_key_from_vault (unsigned int key_id, + unsigned int key_version, + unsigned char *dstbuf, + unsigned int *buflen) +{ + return data.get_key_from_vault(key_id, key_version, dstbuf, buflen); +} + +struct st_mariadb_encryption hashicorp_key_management_plugin= { + MariaDB_ENCRYPTION_INTERFACE_VERSION, + get_latest_version, + get_key_from_vault, + 0, 0, 0, 0, 0 +}; + +#ifdef _MSC_VER + +static int setenv (const char *name, const char *value, int overwrite) +{ + if (!overwrite) + { + size_t len= 0; + int rc= getenv_s(&len, NULL, 0, name); + if (rc) + { + return rc; + } + if (len) + { + errno = EINVAL; + return EINVAL; + } + } + return _putenv_s(name, value); +} + +#endif + +#define MAX_URL_SIZE 32768 + +int HCData::init () +{ + const static char *x_vault_token = "X-Vault-Token:"; + const static size_t x_vault_token_len = strlen(x_vault_token); + char *token_env= getenv("VAULT_TOKEN"); + size_t token_len = strlen(token); + if (token_len == 0) + { + if (token_env) + { + token_len = strlen(token_env); + if (token_len != 0) + { + /* + The value of the token parameter obtained using the getenv() + system call, which does not guarantee that the memory pointed + to by the returned pointer can be read in the long term (for + example, after changing the values of the environment variables + of the current process). Therefore, we need to copy the token + value to the working buffer: + */ + if (!(token = (char *) alloc(token_len + 1))) + { + return 1; + } + memcpy(token, token_env, token_len + 1); + local_token = token; + } + } + if (token_len == 0) { + my_printf_error(ER_UNKNOWN_ERROR, PLUGIN_ERROR_HEADER + "The --hashicorp-key-management-token option value " + "or the value of the corresponding parameter in the " + "configuration file must be specified, otherwise the " + "VAULT_TOKEN environment variable must be set", + 0); + return 1; + } + } + else + { + /* + If the VAULT_TOKEN environment variable is not set or + is not equal to the value of the token parameter, then + we must set (overwrite) it for correct operation of + the mariabackup: + */ + bool not_equal= token_env != NULL && strcmp(token_env, token) != 0; + if (token_env == NULL || not_equal) + { + setenv("VAULT_TOKEN", token, 1); + if (not_equal) + { + my_printf_error(ER_UNKNOWN_ERROR, PLUGIN_ERROR_HEADER + "The --hashicorp-key-management-token option value " + "or the value of the corresponding parameter is not " + "equal to the value of the VAULT_TOKEN environment " + "variable", + ME_ERROR_LOG_ONLY | ME_WARNING); + } + } + } +#if HASHICORP_DEBUG_LOGGING + my_printf_error(ER_UNKNOWN_ERROR, PLUGIN_ERROR_HEADER + "plugin_init: token = %s, token_len = %d", + ME_ERROR_LOG_ONLY | ME_NOTE, token, (int) token_len); +#endif + size_t buf_len = x_vault_token_len + token_len + 1; + if (!(token_header = (char *) alloc(buf_len))) + { + return 1; + } + snprintf(token_header, buf_len, "%s%s", x_vault_token, token); + /* We need to check that the path inside the URL starts with "/v1/": */ + const char *suffix = strchr(vault_url, '/'); + if (suffix == NULL) + { +Bad_url: + my_printf_error(ER_UNKNOWN_ERROR, PLUGIN_ERROR_HEADER + "According to the Hashicorp Vault API rules, " + "the path inside the URL must start with " + "the \"/v1/\" prefix, while the supplied " + "URL value is: \"%s\"", 0, vault_url); + return 1; + } + size_t prefix_len = (size_t) (suffix - vault_url); + if (prefix_len == 0) + { +No_Host: + my_printf_error(ER_UNKNOWN_ERROR, PLUGIN_ERROR_HEADER + "Supplied URL does not contain a hostname: \"%s\"", + 0, vault_url); + return 1; + } + /* Check if the suffix consists only of the slash: */ + size_t suffix_len = strlen(suffix + 1) + 1; + if (suffix_len == 1) + { + goto Bad_url; + } + vault_url_len = prefix_len + suffix_len; + /* + The scheme always ends with "://", while the "suffix" + points to the first of the slashes: + */ + if (*(suffix - 1) == ':' && suffix[1] == '/') + { + /* Let's check that only the schema is present: */ + if (suffix_len == 2) + { + goto No_Host; + } + /* Save the current position: */ + const char *start = suffix + 2; + /* We need to find next slash: */ + suffix = strchr(start, '/'); + if (suffix == NULL) + { + goto Bad_url; + } + /* Update the prefix and suffix lengths: */ + prefix_len = (size_t) (suffix - vault_url); + suffix_len = vault_url_len - prefix_len; + /* + The slash right after the scheme is the absence of a hostname, + this is invalid for all schemes, except for the "file://" + (this allowed for debugging purposes only): + */ + if (suffix == start && + (prefix_len != 7 || memcmp(vault_url, "file", 4) != 0)) + { + goto No_Host; + } + /* Check if the suffix consists only of the slash: */ + if (suffix_len == 1) + { + goto Bad_url; + } + } + /* Let's skip all leading slashes: */ + while (suffix[1] == '/') + { + suffix++; + suffix_len--; + if (suffix_len == 1) + { + goto Bad_url; + } + } + /* + Checking for "/v1" sequence (the leading slash has + already been checked): + */ + if (suffix_len < 3 || suffix[1] != 'v' || suffix[2] != '1') + { + goto Bad_url; + } + /* Let's skip the "/v1" sequence: */ + suffix_len -= 3; + if (suffix_len == 0) + { +No_Secret: + my_printf_error(ER_UNKNOWN_ERROR, PLUGIN_ERROR_HEADER + "Supplied URL does not contain a secret name: \"%s\"", + 0, vault_url); + return 1; + } + suffix += 3; + /* Checking for a slash at the end of the "/v1/" sequence: */ + if (suffix[0] != '/') + { + goto Bad_url; + } + /* Skip slashes after the "/v1" sequence: */ + do + { + suffix++; + suffix_len--; + if (suffix_len == 0) + { + goto No_Secret; + } + } while (suffix[0] == '/'); + /* Remove trailing slashes at the end of the url: */ + while (vault_url[vault_url_len - 1] == '/') + { + vault_url_len--; + suffix_len--; + } + /* + Checking the maximum allowable length to protect + against allocating too much memory on the stack: + */ + if (vault_url_len > MAX_URL_SIZE) + { + my_printf_error(ER_UNKNOWN_ERROR, PLUGIN_ERROR_HEADER + "Maximum allowed vault URL length exceeded", 0); + return 1; + } + /* + In advance, we create a buffer containing the URL for vault + + the "/data/" suffix (7 characters): + */ + if (!(vault_url_data = (char *) alloc(vault_url_len + 7))) + { + return 1; + } + memcpy(vault_url_data, vault_url, vault_url_len); + memcpy(vault_url_data + vault_url_len, "/data/", 7); + cache_max_time = ms_to_ticks(cache_timeout); + cache_max_ver_time = ms_to_ticks(cache_version_timeout); + /* Initialize curl: */ + 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)); + return 1; + } + curl_inited = true; + slist = curl_slist_append(slist, token_header); + if (slist == NULL) + { + my_printf_error(ER_UNKNOWN_ERROR, PLUGIN_ERROR_HEADER + "curl: unable to construct slist", 0); + return 1; + } + /* + 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 *) alloc(vault_url_len + 11 + 6); + if (mount_url == NULL) + { + my_printf_error(ER_UNKNOWN_ERROR, PLUGIN_ERROR_HEADER + "Memory allocation error", 0); + return 1; + } + /* + 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 HASHICORP_DEBUG_LOGGING + my_printf_error(ER_UNKNOWN_ERROR, PLUGIN_ERROR_HEADER + "storage mount url: [%s]", + ME_ERROR_LOG_ONLY | ME_NOTE, mount_url); +#endif + int rc = check_version(mount_url); + free(mount_url); + return rc; +} + +int HCData::check_version (const char *mount_url) const +{ + 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); + return 1; + } + 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; + } + 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); + return 1; + } + 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); + return 1; + } + 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); + return 1; + } + if (version < 2) + { + my_printf_error(ER_UNKNOWN_ERROR, PLUGIN_ERROR_HEADER + "Key-value storage must be version " + "number 2 or later", 0); + return 1; + } + return 0; +} + +static int hashicorp_key_management_plugin_init(void *p) +{ + int rc = data.init(); + if (rc) + { + data.deinit(); + } + return rc; +} + +static int hashicorp_key_management_plugin_deinit(void *p) +{ + data.cache_clean(); + data.deinit(); + return 0; +} + +/* + Plugin library descriptor +*/ +maria_declare_plugin(hashicorp_key_management) +{ + MariaDB_ENCRYPTION_PLUGIN, + &hashicorp_key_management_plugin, + "hashicorp_key_management", + "MariaDB Corporation", + "HashiCorp Vault key management plugin", + PLUGIN_LICENSE_GPL, + hashicorp_key_management_plugin_init, + hashicorp_key_management_plugin_deinit, + 0x0200 /* 2.0 */, + NULL, /* status variables */ + settings, + "2.0", + MariaDB_PLUGIN_MATURITY_STABLE +} +maria_declare_plugin_end; diff --git a/plugin/hashicorp_key_management/mysql-test/vault/include/have_hashicorp_key_management_plugin.inc b/plugin/hashicorp_key_management/mysql-test/vault/include/have_hashicorp_key_management_plugin.inc new file mode 100644 index 00000000000..dcfb8d1bb85 --- /dev/null +++ b/plugin/hashicorp_key_management/mysql-test/vault/include/have_hashicorp_key_management_plugin.inc @@ -0,0 +1,4 @@ +if (`SELECT COUNT(*)=0 FROM INFORMATION_SCHEMA.PLUGINS WHERE PLUGIN_NAME = 'hashicorp_key_management' AND PLUGIN_STATUS='ACTIVE'`) +{ + --skip Test requires active hashicorp_key_management plugin +} diff --git a/plugin/hashicorp_key_management/mysql-test/vault/include/have_hashicorp_key_management_plugin.opt b/plugin/hashicorp_key_management/mysql-test/vault/include/have_hashicorp_key_management_plugin.opt new file mode 100644 index 00000000000..1e677dad8b3 --- /dev/null +++ b/plugin/hashicorp_key_management/mysql-test/vault/include/have_hashicorp_key_management_plugin.opt @@ -0,0 +1,6 @@ +--plugin-load-add=$HASHICORP_KEY_MANAGEMENT_SO +--loose-hashicorp-key-management +--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 diff --git a/plugin/hashicorp_key_management/mysql-test/vault/include/have_mariabackup.inc b/plugin/hashicorp_key_management/mysql-test/vault/include/have_mariabackup.inc new file mode 100644 index 00000000000..7506317193f --- /dev/null +++ b/plugin/hashicorp_key_management/mysql-test/vault/include/have_mariabackup.inc @@ -0,0 +1,3 @@ +if (!$XTRABACKUP) { + --skip Needs mariabackup; +} diff --git a/plugin/hashicorp_key_management/mysql-test/vault/r/hashicorp_cache_after_recreate.result b/plugin/hashicorp_key_management/mysql-test/vault/r/hashicorp_cache_after_recreate.result new file mode 100644 index 00000000000..a48c7873a09 --- /dev/null +++ b/plugin/hashicorp_key_management/mysql-test/vault/r/hashicorp_cache_after_recreate.result @@ -0,0 +1,9 @@ +# restart: with restart_parameters +CREATE TABLE t1 (a VARCHAR(8)) ENGINE=InnoDB ENCRYPTED=YES ENCRYPTION_KEY_ID=4; +INSERT INTO t1 VALUES ('foo'),('bar'); +select @@hashicorp_key_management_caching_enabled, @@hashicorp_key_management_cache_timeout; +@@hashicorp_key_management_caching_enabled @@hashicorp_key_management_cache_timeout +1 180000 +CREATE TABLE t2 (a VARCHAR(8)) ENGINE=InnoDB ENCRYPTED=YES ENCRYPTION_KEY_ID=4; +DROP TABLE IF EXISTS t1, t2; +# restart diff --git a/plugin/hashicorp_key_management/mysql-test/vault/r/hashicorp_cache_timeout_update.result b/plugin/hashicorp_key_management/mysql-test/vault/r/hashicorp_cache_timeout_update.result new file mode 100644 index 00000000000..a26b813a3e7 --- /dev/null +++ b/plugin/hashicorp_key_management/mysql-test/vault/r/hashicorp_cache_timeout_update.result @@ -0,0 +1,19 @@ +SELECT +@@HASHICORP_KEY_MANAGEMENT_CACHE_VERSION_TIMEOUT, +@@HASHICORP_KEY_MANAGEMENT_CACHE_TIMEOUT; +@@HASHICORP_KEY_MANAGEMENT_CACHE_VERSION_TIMEOUT @@HASHICORP_KEY_MANAGEMENT_CACHE_TIMEOUT +0 180000 +SET GLOBAL +HASHICORP_KEY_MANAGEMENT_CACHE_VERSION_TIMEOUT= 1, +HASHICORP_KEY_MANAGEMENT_CACHE_TIMEOUT= 1; +SELECT +@@HASHICORP_KEY_MANAGEMENT_CACHE_VERSION_TIMEOUT, +@@HASHICORP_KEY_MANAGEMENT_CACHE_TIMEOUT; +@@HASHICORP_KEY_MANAGEMENT_CACHE_VERSION_TIMEOUT @@HASHICORP_KEY_MANAGEMENT_CACHE_TIMEOUT +1 1 +SET GLOBAL HASHICORP_KEY_MANAGEMENT_CACHE_VERSION_TIMEOUT=0, HASHICORP_KEY_MANAGEMENT_CACHE_TIMEOUT=180000; +SELECT +@@HASHICORP_KEY_MANAGEMENT_CACHE_VERSION_TIMEOUT, +@@HASHICORP_KEY_MANAGEMENT_CACHE_TIMEOUT; +@@HASHICORP_KEY_MANAGEMENT_CACHE_VERSION_TIMEOUT @@HASHICORP_KEY_MANAGEMENT_CACHE_TIMEOUT +0 180000 diff --git a/plugin/hashicorp_key_management/mysql-test/vault/r/hashicorp_check_kv_version.result b/plugin/hashicorp_key_management/mysql-test/vault/r/hashicorp_check_kv_version.result new file mode 100644 index 00000000000..b7a1c07782e --- /dev/null +++ b/plugin/hashicorp_key_management/mysql-test/vault/r/hashicorp_check_kv_version.result @@ -0,0 +1,9 @@ +[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 diff --git a/plugin/hashicorp_key_management/mysql-test/vault/r/hashicorp_encode.result b/plugin/hashicorp_key_management/mysql-test/vault/r/hashicorp_encode.result new file mode 100644 index 00000000000..04ac22b9626 --- /dev/null +++ b/plugin/hashicorp_key_management/mysql-test/vault/r/hashicorp_encode.result @@ -0,0 +1,49 @@ +SHOW GLOBAL variables LIKE "hashicorp%"; +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 +hashicorp_key_management_vault_ca +hashicorp_key_management_vault_url VAULT_ADDR/v1/mariadbtest/ +create table t1(c1 bigint not null, b char(200)) engine=innodb encrypted=yes encryption_key_id=1; +show create table t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `c1` bigint(20) NOT NULL, + `b` char(200) DEFAULT NULL +) ENGINE=InnoDB DEFAULT CHARSET=latin1 COLLATE=latin1_swedish_ci `encrypted`=yes `encryption_key_id`=1 +insert t1 values (12345, repeat('1234567890', 20)); +alter table t1 encryption_key_id=2; +show create table t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `c1` bigint(20) NOT NULL, + `b` char(200) DEFAULT NULL +) ENGINE=InnoDB DEFAULT CHARSET=latin1 COLLATE=latin1_swedish_ci `encrypted`=yes `encryption_key_id`=2 +alter table t1 encryption_key_id=33; +ERROR HY000: Table storage engine 'InnoDB' does not support the create option 'ENCRYPTION_KEY_ID' +show create table t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `c1` bigint(20) NOT NULL, + `b` char(200) DEFAULT NULL +) ENGINE=InnoDB DEFAULT CHARSET=latin1 COLLATE=latin1_swedish_ci `encrypted`=yes `encryption_key_id`=2 +alter table t1 encryption_key_id=3; +show create table t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `c1` bigint(20) NOT NULL, + `b` char(200) DEFAULT NULL +) ENGINE=InnoDB DEFAULT CHARSET=latin1 COLLATE=latin1_swedish_ci `encrypted`=yes `encryption_key_id`=3 +alter table t1 encryption_key_id=4; +show create table t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `c1` bigint(20) NOT NULL, + `b` char(200) DEFAULT NULL +) ENGINE=InnoDB DEFAULT CHARSET=latin1 COLLATE=latin1_swedish_ci `encrypted`=yes `encryption_key_id`=4 +drop table t1; diff --git a/plugin/hashicorp_key_management/mysql-test/vault/r/hashicorp_key_migration.result b/plugin/hashicorp_key_management/mysql-test/vault/r/hashicorp_key_migration.result new file mode 100644 index 00000000000..e2cc0452958 --- /dev/null +++ b/plugin/hashicorp_key_management/mysql-test/vault/r/hashicorp_key_migration.result @@ -0,0 +1,39 @@ +# restart: with restart_parameters +CREATE TABLE t1 (a VARCHAR(8)) ENGINE=InnoDB ENCRYPTED=YES ENCRYPTION_KEY_ID=1; +INSERT INTO t1 VALUES ('foo'),('bar'); +SELECT * FROM t1; +a +foo +bar +# restart: with restart_parameters +CREATE TABLE t2 (a VARCHAR(8)) ENGINE=InnoDB ENCRYPTED=YES ENCRYPTION_KEY_ID=1; +INSERT INTO t2 VALUES ('baz'),('qux'); +SELECT * FROM t2; +a +baz +qux +# +# This should not fail, but it does if the bug is not fixed +# +SELECT * FROM t1; +a +foo +bar +SHOW WARNINGS; +Level Code Message +# restart: with restart_parameters +SELECT * FROM t1; +a +foo +bar +# +# This should not fail, but it does if the bug is not fixed +# +SELECT * FROM t2; +a +baz +qux +SHOW WARNINGS; +Level Code Message +DROP TABLE t1, t2; +# restart diff --git a/plugin/hashicorp_key_management/mysql-test/vault/r/hashicorp_key_rotation_age.result b/plugin/hashicorp_key_management/mysql-test/vault/r/hashicorp_key_rotation_age.result new file mode 100644 index 00000000000..6e06e0f1d30 --- /dev/null +++ b/plugin/hashicorp_key_management/mysql-test/vault/r/hashicorp_key_rotation_age.result @@ -0,0 +1,97 @@ +SHOW GLOBAL variables LIKE "hashicorp%"; +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 +hashicorp_key_management_vault_ca +hashicorp_key_management_vault_url VAULT_ADDR/v1/mariadbtest/ +# Restart the server with encryption +# restart: with restart_parameters +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; +CREATE TABLE t33(f1 INT, f2 VARCHAR(256)) engine=innodb encrypted=yes encryption_key_id=2; +INSERT INTO t33 VALUES (12345, '1234567890'); +# Restart the server with encryption and rotate key age +# restart: with restart_parameters +# Wait until encryption threads have encrypted all tablespaces +SELECT NAME FROM INFORMATION_SCHEMA.INNODB_TABLESPACES_ENCRYPTION WHERE MIN_KEY_VERSION = 0 AND NAME LIKE "test/%"; +NAME +SELECT NAME, CURRENT_KEY_VERSION FROM INFORMATION_SCHEMA.INNODB_TABLESPACES_ENCRYPTION WHERE MIN_KEY_VERSION <> 0 AND NAME LIKE "test/%"; +NAME CURRENT_KEY_VERSION +test/t1 1 +test/t2 1 +test/t3 1 +test/t33 1 +# Restart the server with innodb_encryption_rotate_key_age= 0 +# restart: with restart_parameters +create table t4 (f1 int not null)engine=innodb encrypted=NO; +alter table t33 encryption_key_id=111; +ERROR HY000: Table storage engine 'InnoDB' does not support the create option 'ENCRYPTION_KEY_ID' +# Update key value to version 2 +alter table t33 encryption_key_id=222; +ERROR HY000: Table storage engine 'InnoDB' does not support the create option 'ENCRYPTION_KEY_ID' +# Wait until encryption threads have encrypted all tablespaces +SELECT NAME FROM INFORMATION_SCHEMA.INNODB_TABLESPACES_ENCRYPTION WHERE MIN_KEY_VERSION = 0 AND NAME LIKE "test/%"; +NAME +test/t4 +SELECT NAME, CURRENT_KEY_VERSION FROM INFORMATION_SCHEMA.INNODB_TABLESPACES_ENCRYPTION WHERE MIN_KEY_VERSION <> 0 AND NAME LIKE "test/%"; +NAME CURRENT_KEY_VERSION +test/t1 2 +test/t2 2 +test/t3 2 +test/t33 1 +# Disable encryption when innodb_encryption_rotate_key_age is 0 +set global innodb_encrypt_tables = OFF; +# Wait until encryption threads to decrypt all encrypted tablespaces +SELECT NAME FROM INFORMATION_SCHEMA.INNODB_TABLESPACES_ENCRYPTION WHERE MIN_KEY_VERSION = 0 AND NAME LIKE "test/%"; +NAME +test/t1 +test/t2 +test/t4 +# Display only encrypted create tables (t3) +SELECT NAME, CURRENT_KEY_VERSION FROM INFORMATION_SCHEMA.INNODB_TABLESPACES_ENCRYPTION WHERE MIN_KEY_VERSION <> 0 AND NAME LIKE "test/%"; +NAME CURRENT_KEY_VERSION +test/t3 2 +test/t33 1 +alter table t33 encryption_key_id=333; +ERROR HY000: Table storage engine 'InnoDB' does not support the create option 'ENCRYPTION_KEY_ID' +# Update key value to version 3 +alter table t33 encryption_key_id=444; +ERROR HY000: Table storage engine 'InnoDB' does not support the create option 'ENCRYPTION_KEY_ID' +# 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 AND NAME LIKE "test/%"; +NAME +test/t4 +# Display only unencrypted create tables (t4) +SELECT NAME, CURRENT_KEY_VERSION FROM INFORMATION_SCHEMA.INNODB_TABLESPACES_ENCRYPTION WHERE MIN_KEY_VERSION <> 0 AND NAME LIKE "test/%"; +NAME CURRENT_KEY_VERSION +test/t1 3 +test/t2 3 +test/t3 3 +test/t33 1 +# restart: with restart_parameters +alter table t33 encryption_key_id=555; +ERROR HY000: Table storage engine 'InnoDB' does not support the create option 'ENCRYPTION_KEY_ID' +SELECT NAME FROM INFORMATION_SCHEMA.INNODB_TABLESPACES_ENCRYPTION WHERE MIN_KEY_VERSION = 0 AND NAME LIKE "test/%"; +NAME +test/t4 +SELECT NAME, CURRENT_KEY_VERSION FROM INFORMATION_SCHEMA.INNODB_TABLESPACES_ENCRYPTION WHERE MIN_KEY_VERSION <> 0 AND NAME LIKE "test/%"; +NAME CURRENT_KEY_VERSION +test/t1 3 +test/t2 3 +test/t3 3 +test/t33 1 +DROP TABLE t4, t3, t2, t1; +DROP TABLE t33; +# restart diff --git a/plugin/hashicorp_key_management/mysql-test/vault/r/hashicorp_mariabackup.result b/plugin/hashicorp_key_management/mysql-test/vault/r/hashicorp_mariabackup.result new file mode 100644 index 00000000000..6a4a03ff9b4 --- /dev/null +++ b/plugin/hashicorp_key_management/mysql-test/vault/r/hashicorp_mariabackup.result @@ -0,0 +1,13 @@ +CREATE TABLE t(i INT) ENGINE INNODB encrypted=yes encryption_key_id=1; +INSERT INTO t VALUES(1); +# mariabackup backup +INSERT INTO t VALUES(2); +# mariabackup prepare +# shutdown server +# remove datadir +# mariabackup move back +# restart +SELECT * FROM t; +i +1 +DROP TABLE t; diff --git a/plugin/hashicorp_key_management/mysql-test/vault/r/hashicorp_url_prefix.result b/plugin/hashicorp_key_management/mysql-test/vault/r/hashicorp_url_prefix.result new file mode 100644 index 00000000000..3dea0485166 --- /dev/null +++ b/plugin/hashicorp_key_management/mysql-test/vault/r/hashicorp_url_prefix.result @@ -0,0 +1,31 @@ +[ERROR] mariadbd: hashicorp: Supplied URL does not contain a hostname: "/" +[ERROR] mariadbd: hashicorp: According to the Hashicorp Vault API rules, the path inside the URL must start with the "/v1/" prefix, while the supplied URL value is: "abcd" +[ERROR] mariadbd: hashicorp: According to the Hashicorp Vault API rules, the path inside the URL must start with the "/v1/" prefix, while the supplied URL value is: "abcd/" +[ERROR] mariadbd: hashicorp: According to the Hashicorp Vault API rules, the path inside the URL must start with the "/v1/" prefix, while the supplied URL value is: "abcd//" +[ERROR] mariadbd: hashicorp: Supplied URL does not contain a hostname: "/abcd" +[ERROR] mariadbd: hashicorp: According to the Hashicorp Vault API rules, the path inside the URL must start with the "/v1/" prefix, while the supplied URL value is: "abcd/v" +[ERROR] mariadbd: hashicorp: Supplied URL does not contain a secret name: "a/v1" +[ERROR] mariadbd: hashicorp: According to the Hashicorp Vault API rules, the path inside the URL must start with the "/v1/" prefix, while the supplied URL value is: "a/v1b" +[ERROR] mariadbd: hashicorp: Supplied URL does not contain a secret name: "a/v1/" +[ERROR] mariadbd: hashicorp: Supplied URL does not contain a secret name: "a/v1///" +[ERROR] mariadbd: hashicorp: Supplied URL does not contain a secret name: "a///v1/" +[ERROR] mariadbd: hashicorp: Supplied URL does not contain a hostname: "///v1/a" +[ERROR] mariadbd: hashicorp: Supplied URL does not contain a hostname: "http://" +[ERROR] mariadbd: hashicorp: Supplied URL does not contain a hostname: "http:///" +[ERROR] mariadbd: hashicorp: According to the Hashicorp Vault API rules, the path inside the URL must start with the "/v1/" prefix, while the supplied URL value is: "http://a" +[ERROR] mariadbd: hashicorp: According to the Hashicorp Vault API rules, the path inside the URL must start with the "/v1/" prefix, while the supplied URL value is: "http://abcd" +[ERROR] mariadbd: hashicorp: According to the Hashicorp Vault API rules, the path inside the URL must start with the "/v1/" prefix, while the supplied URL value is: "http://abcd/" +[ERROR] mariadbd: hashicorp: According to the Hashicorp Vault API rules, the path inside the URL must start with the "/v1/" prefix, while the supplied URL value is: "http://abcd//" +[ERROR] mariadbd: hashicorp: Supplied URL does not contain a hostname: "http:///abcd" +[ERROR] mariadbd: hashicorp: According to the Hashicorp Vault API rules, the path inside the URL must start with the "/v1/" prefix, while the supplied URL value is: "http://abcd/v" +[ERROR] mariadbd: hashicorp: Supplied URL does not contain a secret name: "http://a/v1" +[ERROR] mariadbd: hashicorp: According to the Hashicorp Vault API rules, the path inside the URL must start with the "/v1/" prefix, while the supplied URL value is: "http://a/v1b" +[ERROR] mariadbd: hashicorp: Supplied URL does not contain a secret name: "http://a/v1/" +[ERROR] mariadbd: hashicorp: Supplied URL does not contain a secret name: "http://a/v1///" +[ERROR] mariadbd: hashicorp: Supplied URL does not contain a secret name: "http://a///v1/" +[ERROR] mariadbd: hashicorp: Supplied URL does not contain a hostname: "http://///v1/a" +# restart: with restart_parameters +CREATE TABLE t1 (a VARCHAR(8)) ENGINE=InnoDB ENCRYPTED=YES ENCRYPTION_KEY_ID=1; +INSERT INTO t1 VALUES ('foo'),('bar'); +DROP TABLE t1; +# restart diff --git a/plugin/hashicorp_key_management/mysql-test/vault/suite.pm b/plugin/hashicorp_key_management/mysql-test/vault/suite.pm new file mode 100644 index 00000000000..1d78985f363 --- /dev/null +++ b/plugin/hashicorp_key_management/mysql-test/vault/suite.pm @@ -0,0 +1,16 @@ +package My::Suite::Vault; + +@ISA = qw(My::Suite); + +use strict; + +return "Hashicorp Vault key management utility not found" + unless `sh -c "command -v vault"`; + +return "You need to set the value of the VAULT_ADDR variable" + unless $ENV{VAULT_ADDR}; + +return "You need to set the value of the VAULT_TOKEN variable" + unless $ENV{VAULT_TOKEN}; + +bless {}; diff --git a/plugin/hashicorp_key_management/mysql-test/vault/t/hashicorp_cache_after_recreate.opt b/plugin/hashicorp_key_management/mysql-test/vault/t/hashicorp_cache_after_recreate.opt new file mode 100644 index 00000000000..0a4ff1d5986 --- /dev/null +++ b/plugin/hashicorp_key_management/mysql-test/vault/t/hashicorp_cache_after_recreate.opt @@ -0,0 +1,2 @@ +--loose-hashicorp-key-management-cache-timeout=180000 +--loose-hashicorp-key-management-cache-version-timeout=180000 diff --git a/plugin/hashicorp_key_management/mysql-test/vault/t/hashicorp_cache_after_recreate.test b/plugin/hashicorp_key_management/mysql-test/vault/t/hashicorp_cache_after_recreate.test new file mode 100644 index 00000000000..9dee7376497 --- /dev/null +++ b/plugin/hashicorp_key_management/mysql-test/vault/t/hashicorp_cache_after_recreate.test @@ -0,0 +1,35 @@ +# MDEV-28330: Key caching doesn't appear to be working + +# 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 bug > /dev/null +--exec vault secrets enable -path /bug -version=2 kv > /dev/null +--exec vault kv put /bug/1 data=01234567890123456789012345678901 > /dev/null +--exec vault kv put /bug/4 data=01234567890123456789012345678904 > /dev/null + +--let $restart_parameters=--plugin-load-add=hashicorp_key_management --hashicorp-key-management-vault-url="$VAULT_ADDR/v1/bug/" --hashicorp-key-management-token="$VAULT_TOKEN" +--let $restart_noprint=1 +--source include/restart_mysqld.inc + +CREATE TABLE t1 (a VARCHAR(8)) ENGINE=InnoDB ENCRYPTED=YES ENCRYPTION_KEY_ID=4; +INSERT INTO t1 VALUES ('foo'),('bar'); + +select @@hashicorp_key_management_caching_enabled, @@hashicorp_key_management_cache_timeout; + +--exec vault secrets disable bug > /dev/null +--exec vault secrets enable -path /bug -version=2 kv > /dev/null +--exec vault kv put /bug/1 data=01234567890123456789012345678901 > /dev/null + +CREATE TABLE t2 (a VARCHAR(8)) ENGINE=InnoDB ENCRYPTED=YES ENCRYPTION_KEY_ID=4; + +# Cleanup +DROP TABLE IF EXISTS t1, t2; + +--let $restart_parameters= +--source include/restart_mysqld.inc + +--exec vault secrets disable bug > /dev/null diff --git a/plugin/hashicorp_key_management/mysql-test/vault/t/hashicorp_cache_timeout_update.opt b/plugin/hashicorp_key_management/mysql-test/vault/t/hashicorp_cache_timeout_update.opt new file mode 100644 index 00000000000..cdc590b33ed --- /dev/null +++ b/plugin/hashicorp_key_management/mysql-test/vault/t/hashicorp_cache_timeout_update.opt @@ -0,0 +1,2 @@ +--loose-hashicorp-key-management-cache-timeout=180000 +--loose-hashicorp-key-management-cache-version-timeout=0 diff --git a/plugin/hashicorp_key_management/mysql-test/vault/t/hashicorp_cache_timeout_update.test b/plugin/hashicorp_key_management/mysql-test/vault/t/hashicorp_cache_timeout_update.test new file mode 100644 index 00000000000..61c23d44548 --- /dev/null +++ b/plugin/hashicorp_key_management/mysql-test/vault/t/hashicorp_cache_timeout_update.test @@ -0,0 +1,23 @@ +# MDEV-28291: Cache variables claim to be dynamic but changes are ignored + +--source hashicorp_plugin.inc +--source hashicorp_init.inc + +--let $ct=`SELECT @@HASHICORP_KEY_MANAGEMENT_CACHE_TIMEOUT` +--let $vt=`SELECT @@HASHICORP_KEY_MANAGEMENT_CACHE_VERSION_TIMEOUT` + +SELECT + @@HASHICORP_KEY_MANAGEMENT_CACHE_VERSION_TIMEOUT, + @@HASHICORP_KEY_MANAGEMENT_CACHE_TIMEOUT; +SET GLOBAL + HASHICORP_KEY_MANAGEMENT_CACHE_VERSION_TIMEOUT= 1, + HASHICORP_KEY_MANAGEMENT_CACHE_TIMEOUT= 1; +SELECT + @@HASHICORP_KEY_MANAGEMENT_CACHE_VERSION_TIMEOUT, + @@HASHICORP_KEY_MANAGEMENT_CACHE_TIMEOUT; +--eval SET GLOBAL HASHICORP_KEY_MANAGEMENT_CACHE_VERSION_TIMEOUT=$vt, HASHICORP_KEY_MANAGEMENT_CACHE_TIMEOUT=$ct +SELECT + @@HASHICORP_KEY_MANAGEMENT_CACHE_VERSION_TIMEOUT, + @@HASHICORP_KEY_MANAGEMENT_CACHE_TIMEOUT; + +--source hashicorp_deinit.inc diff --git a/plugin/hashicorp_key_management/mysql-test/vault/t/hashicorp_check_kv_version.test b/plugin/hashicorp_key_management/mysql-test/vault/t/hashicorp_check_kv_version.test new file mode 100644 index 00000000000..c108781bbd2 --- /dev/null +++ b/plugin/hashicorp_key_management/mysql-test/vault/t/hashicorp_check_kv_version.test @@ -0,0 +1,54 @@ +# 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 diff --git a/plugin/hashicorp_key_management/mysql-test/vault/t/hashicorp_deinit.inc b/plugin/hashicorp_key_management/mysql-test/vault/t/hashicorp_deinit.inc new file mode 100644 index 00000000000..b6551e0bc92 --- /dev/null +++ b/plugin/hashicorp_key_management/mysql-test/vault/t/hashicorp_deinit.inc @@ -0,0 +1 @@ +--exec vault secrets disable mariadbtest > /dev/null diff --git a/plugin/hashicorp_key_management/mysql-test/vault/t/hashicorp_encode.test b/plugin/hashicorp_key_management/mysql-test/vault/t/hashicorp_encode.test new file mode 100644 index 00000000000..338b3413e77 --- /dev/null +++ b/plugin/hashicorp_key_management/mysql-test/vault/t/hashicorp_encode.test @@ -0,0 +1,25 @@ +--source include/have_innodb.inc +--source hashicorp_plugin.inc + +--source hashicorp_init.inc + +replace_result $VAULT_ADDR VAULT_ADDR; +SHOW GLOBAL variables LIKE "hashicorp%"; + +create table t1(c1 bigint not null, b char(200)) engine=innodb encrypted=yes encryption_key_id=1; +show create table t1; +insert t1 values (12345, repeat('1234567890', 20)); + +alter table t1 encryption_key_id=2; +show create table t1; +--error ER_ILLEGAL_HA_CREATE_OPTION +alter table t1 encryption_key_id=33; +show create table t1; +alter table t1 encryption_key_id=3; +show create table t1; +alter table t1 encryption_key_id=4; +show create table t1; + +drop table t1; + +--source hashicorp_deinit.inc diff --git a/plugin/hashicorp_key_management/mysql-test/vault/t/hashicorp_init.inc b/plugin/hashicorp_key_management/mysql-test/vault/t/hashicorp_init.inc new file mode 100644 index 00000000000..172c1d87935 --- /dev/null +++ b/plugin/hashicorp_key_management/mysql-test/vault/t/hashicorp_init.inc @@ -0,0 +1,7 @@ +--exec vault secrets disable mariadbtest > /dev/null +--exec vault secrets enable -path /mariadbtest -version=2 kv > /dev/null +--exec vault kv put /mariadbtest/1 data="123456789ABCDEF0123456789ABCDEF0" > /dev/null +--exec vault kv put /mariadbtest/2 data="23456789ABCDEF0123456789ABCDef01" > /dev/null +--exec vault kv put /mariadbtest/3 data="00000000000000000000000000000000" > /dev/null +--exec vault kv put /mariadbtest/3 data="00000000000000000000000000000001" > /dev/null +--exec vault kv put /mariadbtest/4 data="456789ABCDEF0123456789ABCDEF0123" > /dev/null diff --git a/plugin/hashicorp_key_management/mysql-test/vault/t/hashicorp_key_migration.test b/plugin/hashicorp_key_management/mysql-test/vault/t/hashicorp_key_migration.test new file mode 100644 index 00000000000..2e67c2cc639 --- /dev/null +++ b/plugin/hashicorp_key_management/mysql-test/vault/t/hashicorp_key_migration.test @@ -0,0 +1,57 @@ +# MDEV-28279: Cannot migrate hexadecimal keys from file key management + +# 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 + +--let $my_key=012345678901234567890123456789aB +--exec echo "1;$my_key" > $MYSQL_TMP_DIR/mykeys.txt +--let $restart_parameters=--plugin-load-add=file_key_management --loose-file-key-management-filename=$MYSQL_TMP_DIR/mykeys.txt --hashicorp-key-management=off +--let $restart_noprint=1 +--source include/restart_mysqld.inc + +if (`SELECT COUNT(*)=0 FROM INFORMATION_SCHEMA.PLUGINS WHERE PLUGIN_NAME = 'file_key_management' AND PLUGIN_STATUS='ACTIVE'`) +{ + --skip Test requires file_key_management plugin +} + +CREATE TABLE t1 (a VARCHAR(8)) ENGINE=InnoDB ENCRYPTED=YES ENCRYPTION_KEY_ID=1; +INSERT INTO t1 VALUES ('foo'),('bar'); +SELECT * FROM t1; + +--exec vault secrets disable bug > /dev/null +--exec vault secrets enable -path /bug -version=2 kv > /dev/null +--exec vault kv put /bug/1 data=$my_key > /dev/null +--let $restart_parameters=--plugin-load-add=hashicorp_key_management --hashicorp-key-management-vault-url="$VAULT_ADDR/v1/bug/" --hashicorp-key-management-token="$VAULT_TOKEN" +--source include/restart_mysqld.inc + +CREATE TABLE t2 (a VARCHAR(8)) ENGINE=InnoDB ENCRYPTED=YES ENCRYPTION_KEY_ID=1; +INSERT INTO t2 VALUES ('baz'),('qux'); +SELECT * FROM t2; +--echo # +--echo # This should not fail, but it does if the bug is not fixed +--echo # +--error 0,1932,1877 +SELECT * FROM t1; +SHOW WARNINGS; + +--let $restart_parameters=--plugin-load-add=file_key_management --file-key-management-filename=$MYSQL_TMP_DIR/mykeys.txt --hashicorp-key-management=off +--source include/restart_mysqld.inc + +SELECT * FROM t1; +--echo # +--echo # This should not fail, but it does if the bug is not fixed +--echo # +--error 0,1932,1877 +SELECT * FROM t2; +SHOW WARNINGS; + +# Cleanup +DROP TABLE t1, t2; + +--exec vault secrets disable bug > /dev/null + +--let $restart_parameters= +--source include/restart_mysqld.inc diff --git a/plugin/hashicorp_key_management/mysql-test/vault/t/hashicorp_key_rotation_age.opt b/plugin/hashicorp_key_management/mysql-test/vault/t/hashicorp_key_rotation_age.opt new file mode 100644 index 00000000000..42a86275483 --- /dev/null +++ b/plugin/hashicorp_key_management/mysql-test/vault/t/hashicorp_key_rotation_age.opt @@ -0,0 +1 @@ +--loose-hashicorp-key-management-cache-version-timeout=0 diff --git a/plugin/hashicorp_key_management/mysql-test/vault/t/hashicorp_key_rotation_age.test b/plugin/hashicorp_key_management/mysql-test/vault/t/hashicorp_key_rotation_age.test new file mode 100644 index 00000000000..ce99406ab06 --- /dev/null +++ b/plugin/hashicorp_key_management/mysql-test/vault/t/hashicorp_key_rotation_age.test @@ -0,0 +1,135 @@ +--source include/have_innodb.inc +--source include/not_embedded.inc +--source hashicorp_plugin.inc + +--source hashicorp_init.inc + +replace_result $VAULT_ADDR VAULT_ADDR; +SHOW GLOBAL variables LIKE "hashicorp%"; + +--echo # Restart the server with encryption +let $default_parameters="--innodb-tablespaces-encryption --innodb_encrypt_tables=ON"; +let $restart_noprint=1; +let $restart_parameters=$default_parameters; +--source include/restart_mysqld.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; + +CREATE TABLE t33(f1 INT, f2 VARCHAR(256)) engine=innodb encrypted=yes encryption_key_id=2; +INSERT INTO t33 VALUES (12345, '1234567890'); + +--echo # Restart the server with encryption and rotate key age + +let $restart_parameters=$default_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 AND NAME LIKE "test/%"; +--sorted_result +SELECT NAME, CURRENT_KEY_VERSION FROM INFORMATION_SCHEMA.INNODB_TABLESPACES_ENCRYPTION WHERE MIN_KEY_VERSION <> 0 AND NAME LIKE "test/%"; + +--echo # Restart the server with innodb_encryption_rotate_key_age= 0 + +let $restart_parameters=$default_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; + +# artificial error useful for debugging a plugin +--error ER_ILLEGAL_HA_CREATE_OPTION +alter table t33 encryption_key_id=111; + +--echo # Update key value to version 2 +--exec vault kv put /mariadbtest/1 data="11112222333344445555666677778888" > /dev/null +--sleep 2 + +# artificial error useful for debugging a plugin +--error ER_ILLEGAL_HA_CREATE_OPTION +alter table t33 encryption_key_id=222; + +--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 AND NAME LIKE "test/%"; +--sorted_result +SELECT NAME, CURRENT_KEY_VERSION FROM INFORMATION_SCHEMA.INNODB_TABLESPACES_ENCRYPTION WHERE MIN_KEY_VERSION <> 0 AND NAME LIKE "test/%"; + +--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 encrypted 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 AND ROTATING_OR_FLUSHING = 0; +--source include/wait_condition.inc + +--sorted_result +SELECT NAME FROM INFORMATION_SCHEMA.INNODB_TABLESPACES_ENCRYPTION WHERE MIN_KEY_VERSION = 0 AND NAME LIKE "test/%"; +--echo # Display only encrypted create tables (t3) +--sorted_result +SELECT NAME, CURRENT_KEY_VERSION FROM INFORMATION_SCHEMA.INNODB_TABLESPACES_ENCRYPTION WHERE MIN_KEY_VERSION <> 0 AND NAME LIKE "test/%"; + +# artificial error useful for debugging a plugin +--error ER_ILLEGAL_HA_CREATE_OPTION +alter table t33 encryption_key_id=333; + +--echo # Update key value to version 3 +--exec vault kv put /mariadbtest/1 data="5555222233334444555566667777AAAA" > /dev/null +--sleep 2 + +# artificial error useful for debugging a plugin +--error ER_ILLEGAL_HA_CREATE_OPTION +alter table t33 encryption_key_id=444; + +--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 AND NAME LIKE "test/%"; +--echo # Display only unencrypted create tables (t4) +--sorted_result +SELECT NAME, CURRENT_KEY_VERSION FROM INFORMATION_SCHEMA.INNODB_TABLESPACES_ENCRYPTION WHERE MIN_KEY_VERSION <> 0 AND NAME LIKE "test/%"; + +--let $restart_parameters=$default_parameters +--source include/restart_mysqld.inc + +# artificial error useful for debugging a plugin +--error ER_ILLEGAL_HA_CREATE_OPTION +alter table t33 encryption_key_id=555; + +SELECT NAME FROM INFORMATION_SCHEMA.INNODB_TABLESPACES_ENCRYPTION WHERE MIN_KEY_VERSION = 0 AND NAME LIKE "test/%"; +--sorted_result +SELECT NAME, CURRENT_KEY_VERSION FROM INFORMATION_SCHEMA.INNODB_TABLESPACES_ENCRYPTION WHERE MIN_KEY_VERSION <> 0 AND NAME LIKE "test/%"; + +DROP TABLE t4, t3, t2, t1; +DROP TABLE t33; + +--let $restart_parameters= +--source include/restart_mysqld.inc + +--source hashicorp_deinit.inc diff --git a/plugin/hashicorp_key_management/mysql-test/vault/t/hashicorp_mariabackup.test b/plugin/hashicorp_key_management/mysql-test/vault/t/hashicorp_mariabackup.test new file mode 100644 index 00000000000..6ade4e115a1 --- /dev/null +++ b/plugin/hashicorp_key_management/mysql-test/vault/t/hashicorp_mariabackup.test @@ -0,0 +1,39 @@ +--source include/big_test.inc +--source include/have_innodb.inc +--source include/have_mariabackup.inc +--source hashicorp_plugin.inc + +--source hashicorp_init.inc + +CREATE TABLE t(i INT) ENGINE INNODB encrypted=yes encryption_key_id=1; +INSERT INTO t VALUES(1); + +echo # mariabackup backup; +let $targetdir=$MYSQLTEST_VARDIR/tmp/backup; + +--disable_result_log +exec $XTRABACKUP --defaults-file=$MYSQLTEST_VARDIR/my.cnf --backup --target-dir=$targetdir; +--enable_result_log + +INSERT INTO t VALUES(2); + +echo # mariabackup prepare; +--disable_result_log +exec $XTRABACKUP --prepare --target-dir=$targetdir; + +let $_datadir= `SELECT @@datadir`; +echo # shutdown server; +--source include/shutdown_mysqld.inc +echo # remove datadir; +rmdir $_datadir; +echo # mariabackup move back; +exec $XTRABACKUP --defaults-file=$MYSQLTEST_VARDIR/my.cnf --copy-back --datadir=$_datadir --target-dir=$targetdir --parallel=2 --throttle=1; +--source include/start_mysqld.inc +--enable_result_log + +SELECT * FROM t; +DROP TABLE t; + +rmdir $targetdir; + +--source hashicorp_deinit.inc diff --git a/plugin/hashicorp_key_management/mysql-test/vault/t/hashicorp_plugin.inc b/plugin/hashicorp_key_management/mysql-test/vault/t/hashicorp_plugin.inc new file mode 100644 index 00000000000..4d505fb30e3 --- /dev/null +++ b/plugin/hashicorp_key_management/mysql-test/vault/t/hashicorp_plugin.inc @@ -0,0 +1,2 @@ +--source include/have_hashicorp_key_management_plugin.inc +--source include/not_windows.inc diff --git a/plugin/hashicorp_key_management/mysql-test/vault/t/hashicorp_url_prefix.test b/plugin/hashicorp_key_management/mysql-test/vault/t/hashicorp_url_prefix.test new file mode 100644 index 00000000000..4d26affb467 --- /dev/null +++ b/plugin/hashicorp_key_management/mysql-test/vault/t/hashicorp_url_prefix.test @@ -0,0 +1,93 @@ +# MDEV-28277: Checking for mandatory "/v1/" prefix in the URL + +# 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 bug > /dev/null +--exec vault secrets enable -path /bug -version=2 kv > /dev/null +--exec vault kv put /bug/1 data=01234567890123456789012345678901 > /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=off --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="/" +--error 1 +--exec $MYSQLD $defaults --hashicorp-key-management-vault-url="abcd" +--error 1 +--exec $MYSQLD $defaults --hashicorp-key-management-vault-url="abcd/" +--error 1 +--exec $MYSQLD $defaults --hashicorp-key-management-vault-url="abcd//" +--error 1 +--exec $MYSQLD $defaults --hashicorp-key-management-vault-url="/abcd" +--error 1 +--exec $MYSQLD $defaults --hashicorp-key-management-vault-url="abcd/v" +--error 1 +--exec $MYSQLD $defaults --hashicorp-key-management-vault-url="a/v1" +--error 1 +--exec $MYSQLD $defaults --hashicorp-key-management-vault-url="a/v1b" +--error 1 +--exec $MYSQLD $defaults --hashicorp-key-management-vault-url="a/v1/" +--error 1 +--exec $MYSQLD $defaults --hashicorp-key-management-vault-url="a/v1///" +--error 1 +--exec $MYSQLD $defaults --hashicorp-key-management-vault-url="a///v1/" +--error 1 +--exec $MYSQLD $defaults --hashicorp-key-management-vault-url="///v1/a" +--error 1 +--exec $MYSQLD $defaults --hashicorp-key-management-vault-url="http://" +--error 1 +--exec $MYSQLD $defaults --hashicorp-key-management-vault-url="http:///" +--error 1 +--exec $MYSQLD $defaults --hashicorp-key-management-vault-url="http://a" +--error 1 +--exec $MYSQLD $defaults --hashicorp-key-management-vault-url="http://abcd" +--error 1 +--exec $MYSQLD $defaults --hashicorp-key-management-vault-url="http://abcd/" +--error 1 +--exec $MYSQLD $defaults --hashicorp-key-management-vault-url="http://abcd//" +--error 1 +--exec $MYSQLD $defaults --hashicorp-key-management-vault-url="http:///abcd" +--error 1 +--exec $MYSQLD $defaults --hashicorp-key-management-vault-url="http://abcd/v" +--error 1 +--exec $MYSQLD $defaults --hashicorp-key-management-vault-url="http://a/v1" +--error 1 +--exec $MYSQLD $defaults --hashicorp-key-management-vault-url="http://a/v1b" +--error 1 +--exec $MYSQLD $defaults --hashicorp-key-management-vault-url="http://a/v1/" +--error 1 +--exec $MYSQLD $defaults --hashicorp-key-management-vault-url="http://a/v1///" +--error 1 +--exec $MYSQLD $defaults --hashicorp-key-management-vault-url="http://a///v1/" +--error 1 +--exec $MYSQLD $defaults --hashicorp-key-management-vault-url="http://///v1/a" + +--exec grep -oE "\[ERROR\] .*: hashicorp: .*" -- $LOG_FILE + +--remove_file $LOG_FILE + +--let $restart_parameters=$vault_defaults --hashicorp-key-management-vault-url="$VAULT_ADDR/v1/bug///" +--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'); + +# Cleanup + +DROP TABLE t1; + +--let $restart_parameters= +--source include/restart_mysqld.inc + +--exec vault secrets disable bug > /dev/null diff --git a/plugin/type_inet/item_inetfunc.h b/plugin/type_inet/item_inetfunc.h index 4acb42d2175..da87c7b3309 100644 --- a/plugin/type_inet/item_inetfunc.h +++ b/plugin/type_inet/item_inetfunc.h @@ -37,7 +37,7 @@ public: static LEX_CSTRING name= {STRING_WITH_LEN("inet_aton") }; return name; } - bool fix_length_and_dec() override + bool fix_length_and_dec(THD *thd) override { decimals= 0; max_length= 21; @@ -65,7 +65,7 @@ public: static LEX_CSTRING name= {STRING_WITH_LEN("inet_ntoa") }; return name; } - bool fix_length_and_dec() override + bool fix_length_and_dec(THD *thd) override { decimals= 0; fix_length_and_charset(3 * 8 + 7, default_charset()); @@ -111,7 +111,7 @@ public: static LEX_CSTRING name= {STRING_WITH_LEN("inet6_aton") }; return name; } - bool fix_length_and_dec() override + bool fix_length_and_dec(THD *thd) override { decimals= 0; fix_length_and_charset(16, &my_charset_bin); @@ -143,7 +143,7 @@ public: return name; } - bool fix_length_and_dec() override + bool fix_length_and_dec(THD *thd) override { decimals= 0; diff --git a/plugin/type_uuid/item_uuidfunc.h b/plugin/type_uuid/item_uuidfunc.h index 296b6592f10..aa6ff999b99 100644 --- a/plugin/type_uuid/item_uuidfunc.h +++ b/plugin/type_uuid/item_uuidfunc.h @@ -27,7 +27,7 @@ protected: { return MY_UUID_BARE_STRING_LENGTH + with_dashes*MY_UUID_SEPARATORS; } public: Item_func_sys_guid(THD *thd): Item_str_func(thd), with_dashes(false) {} - bool fix_length_and_dec() override + bool fix_length_and_dec(THD *thd) override { collation.set(DTCollation_numeric()); fix_char_length(uuid_len()); diff --git a/plugin/user_variables/user_variables.cc b/plugin/user_variables/user_variables.cc index fe87e17f4ee..df2ab4af1f8 100644 --- a/plugin/user_variables/user_variables.cc +++ b/plugin/user_variables/user_variables.cc @@ -11,7 +11,7 @@ You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1335 USA */ + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #define MYSQL_SERVER #include <my_global.h> |