summaryrefslogtreecommitdiff
path: root/plugin
diff options
context:
space:
mode:
Diffstat (limited to 'plugin')
-rw-r--r--plugin/func_test/plugin.cc2
-rw-r--r--plugin/hashicorp_key_management/CMakeLists.txt23
-rw-r--r--plugin/hashicorp_key_management/hashicorp_key_management.cnf117
-rw-r--r--plugin/hashicorp_key_management/hashicorp_key_management.txt181
-rw-r--r--plugin/hashicorp_key_management/hashicorp_key_management_plugin.cc1385
-rw-r--r--plugin/hashicorp_key_management/mysql-test/vault/include/have_hashicorp_key_management_plugin.inc4
-rw-r--r--plugin/hashicorp_key_management/mysql-test/vault/include/have_hashicorp_key_management_plugin.opt6
-rw-r--r--plugin/hashicorp_key_management/mysql-test/vault/include/have_mariabackup.inc3
-rw-r--r--plugin/hashicorp_key_management/mysql-test/vault/r/hashicorp_cache_after_recreate.result9
-rw-r--r--plugin/hashicorp_key_management/mysql-test/vault/r/hashicorp_cache_timeout_update.result19
-rw-r--r--plugin/hashicorp_key_management/mysql-test/vault/r/hashicorp_check_kv_version.result9
-rw-r--r--plugin/hashicorp_key_management/mysql-test/vault/r/hashicorp_encode.result49
-rw-r--r--plugin/hashicorp_key_management/mysql-test/vault/r/hashicorp_key_migration.result39
-rw-r--r--plugin/hashicorp_key_management/mysql-test/vault/r/hashicorp_key_rotation_age.result97
-rw-r--r--plugin/hashicorp_key_management/mysql-test/vault/r/hashicorp_mariabackup.result13
-rw-r--r--plugin/hashicorp_key_management/mysql-test/vault/r/hashicorp_url_prefix.result31
-rw-r--r--plugin/hashicorp_key_management/mysql-test/vault/suite.pm16
-rw-r--r--plugin/hashicorp_key_management/mysql-test/vault/t/hashicorp_cache_after_recreate.opt2
-rw-r--r--plugin/hashicorp_key_management/mysql-test/vault/t/hashicorp_cache_after_recreate.test35
-rw-r--r--plugin/hashicorp_key_management/mysql-test/vault/t/hashicorp_cache_timeout_update.opt2
-rw-r--r--plugin/hashicorp_key_management/mysql-test/vault/t/hashicorp_cache_timeout_update.test23
-rw-r--r--plugin/hashicorp_key_management/mysql-test/vault/t/hashicorp_check_kv_version.test54
-rw-r--r--plugin/hashicorp_key_management/mysql-test/vault/t/hashicorp_deinit.inc1
-rw-r--r--plugin/hashicorp_key_management/mysql-test/vault/t/hashicorp_encode.test25
-rw-r--r--plugin/hashicorp_key_management/mysql-test/vault/t/hashicorp_init.inc7
-rw-r--r--plugin/hashicorp_key_management/mysql-test/vault/t/hashicorp_key_migration.test57
-rw-r--r--plugin/hashicorp_key_management/mysql-test/vault/t/hashicorp_key_rotation_age.opt1
-rw-r--r--plugin/hashicorp_key_management/mysql-test/vault/t/hashicorp_key_rotation_age.test135
-rw-r--r--plugin/hashicorp_key_management/mysql-test/vault/t/hashicorp_mariabackup.test39
-rw-r--r--plugin/hashicorp_key_management/mysql-test/vault/t/hashicorp_plugin.inc2
-rw-r--r--plugin/hashicorp_key_management/mysql-test/vault/t/hashicorp_url_prefix.test93
-rw-r--r--plugin/type_inet/item_inetfunc.h8
-rw-r--r--plugin/type_uuid/item_uuidfunc.h2
-rw-r--r--plugin/user_variables/user_variables.cc2
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>