Commit 1146b713 authored by Julius Goryavsky's avatar Julius Goryavsky

MDEV-19281: Plugin implementation for the Hashicorp Vault KMS

- 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;
- Key values stored as hexadecimal strings;
- Key values caching is supported.
- Implemented a time-invalidated cache for key values and
  for key version numbers received from the Hashicorp Valult
  server;
- 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.
parent 706a8232
......@@ -983,6 +983,15 @@ Description: CrackLib Password Validation Plugin for MariaDB
.
Install and configure this to enforce stronger passwords for MariaDB users.
Package: mariadb-plugin-hashicorp-key-management
Architecture: any
Depends: mariadb-server-10.9 (= ${binary:Version}),
${misc:Depends},
${shlibs:Depends}
Description: Hashicorp Key Management plugin for MariaDB
This encryption plugin uses Hashicorp Vault for storing encryption
keys for MariaDB Data-at-Rest encryption.
Package: mariadb-plugin-provider-bzip2
Architecture: any
Depends: mariadb-server,
......
etc/mysql/mariadb.conf.d/hashicorp_key_management.cnf
usr/lib/mysql/plugin/hashicorp_key_management.so
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)
INSTALL_DOCUMENTATION(hashicorp_key_management.txt
COMPONENT hashicorp-key-management)
# 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 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.
/* 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 <mysql/plugin_encryption.h>
#include <mysqld_error.h>
#include <string.h>
#include <stdlib.h>
#include <limits.h>
#include <time.h>
#include <errno.h>
#include <string>
#include <sstream>
#include <curl/curl.h>
#ifdef _WIN32
#include <malloc.h>
#define alloca _alloca
#elif !defined(__FreeBSD__)
#include <alloca.h>
#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 HASICORP_DEBUG_LOGGING 0
#define PLUGIN_ERROR_HEADER "hashicorp: "
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);
}
static std::mutex mtx;
/* 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;
static VER_MAP latest_version_cache;
/* Cache for key information: */
typedef std::unordered_map<unsigned long long, KEY_INFO> KEY_MAP;
static KEY_MAP key_info_cache;
#define KEY_ID_AND_VERSION(key_id, version) \
((unsigned long long)key_id << 32 | version)
static void 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 HASICORP_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();
}
static unsigned int
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 HASICORP_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 HASICORP_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;
}
static unsigned int 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;
}
static unsigned int 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 HASICORP_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 HASICORP_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 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 void cache_timeout_update (MYSQL_THD thd,
struct st_mysql_sys_var *var,
void *var_ptr,
const void *save)
{
cache_max_time = ms_to_ticks(* (long *) var_ptr);
}
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)
{
cache_max_ver_time = ms_to_ticks(* (long *) var_ptr);
}
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),
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;
}
static struct curl_slist *list;
static int curl_run (char *url, std::string *response, bool soft_timeout)
{
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, list)) !=
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 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 + strlen(res),
"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';
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 const char * get_data (const std::string response_str,
const char **js, int *js_len)
{
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)
{
my_printf_error(ER_UNKNOWN_ERROR, PLUGIN_ERROR_HEADER
"Key not found",
ME_ERROR_LOG_ONLY | ME_NOTE);
return NULL;
}
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 NULL;
}
return response;
}
static unsigned int get_version (const char *js, int js_len,
const char *response, 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);
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);
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);
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 char *response)
{
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);
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);
return 1;
}
return 0;
}
static char *vault_url_data;
static size_t vault_url_len;
static unsigned int get_latest_version (unsigned int key_id)
{
unsigned int version;
#if HASICORP_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;
const char *response = get_data(response_str, &js, &js_len);
if (response == NULL)
{
return ENCRYPTION_KEY_VERSION_INVALID;
}
version = get_version(js, js_len, response, &rc);
if (!caching_enabled || rc)
{
return version;
}
const char* key;
int key_len;
if (get_key_data(js, js_len, &key, &key_len, response))
{
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;
}
static unsigned int get_key_from_vault (unsigned int key_id,
unsigned int key_version,
unsigned char *dstbuf,
unsigned int *buflen)
{
#if HASICORP_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;
const char *response = get_data(response_str, &js, &js_len);
if (response == NULL)
{
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, &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))
{
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;
}
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
static char *local_token;
static char *token_header;
static int hashicorp_key_management_plugin_init(void *p)
{
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:
*/
token = (char *) malloc(token_len + 1);
if (token == NULL)
{
my_printf_error(ER_UNKNOWN_ERROR, PLUGIN_ERROR_HEADER
"Memory allocation error", 0);
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 HASICORP_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;
token_header = (char *) malloc(buf_len);
if (token_header == NULL)
{
my_printf_error(ER_UNKNOWN_ERROR, PLUGIN_ERROR_HEADER
"Memory allocation error", 0);
goto Failure2;
}
snprintf(token_header, buf_len, "%s%s", x_vault_token, token);
vault_url_len = strlen(vault_url);
/*
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);
Failure:
free(token_header);
token_header = NULL;
Failure2:
if (local_token)
{
free(local_token);
local_token = NULL;
}
return 1;
}
if (vault_url_len && vault_url[vault_url_len - 1] == '/')
{
vault_url_len--;
}
/*
In advance, we create a buffer containing the URL for vault
+ the "/data/" suffix (7 characters):
*/
vault_url_data = (char *) malloc(vault_url_len + 7);
if (vault_url_data == NULL)
{
my_printf_error(ER_UNKNOWN_ERROR, PLUGIN_ERROR_HEADER
"Memory allocation error", 0);
goto Failure;
}
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: */
curl_global_init(CURL_GLOBAL_ALL);
list = curl_slist_append(list, token_header);
if (list == NULL)
{
my_printf_error(ER_UNKNOWN_ERROR, PLUGIN_ERROR_HEADER
"curl: unable to construct slist", 0);
curl_global_cleanup();
goto Failure;
}
return 0;
}
static int hashicorp_key_management_plugin_deinit(void *p)
{
if (list)
{
curl_slist_free_all(list);
list = NULL;
}
latest_version_cache.clear();
key_info_cache.clear();
curl_global_cleanup();
if (vault_url_data)
{
free(vault_url_data);
vault_url_data = NULL;
vault_url_len = 0;
}
if (token_header)
{
free(token_header);
token_header = NULL;
}
if (local_token)
{
free(local_token);
local_token = NULL;
}
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,
0x0104 /* 1.04 */,
NULL, /* status variables */
settings,
"1.04",
MariaDB_PLUGIN_MATURITY_STABLE
}
maria_declare_plugin_end;
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
}
--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
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_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 `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 `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 `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 `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 `encrypted`=yes `encryption_key_id`=4
drop table t1;
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_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
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;
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 {};
--exec vault secrets disable mariadbtest > /dev/null
--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
--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
--loose-hashicorp-key-management-cache-version-timeout=0
--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
--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
--source include/have_hashicorp_key_management_plugin.inc
--source include/not_windows.inc
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment