mirror of
https://github.com/MariaDB/server.git
synced 2025-08-09 22:24:09 +03:00
MDEV-9659 : AWS KMS encryption plugin
This commit is contained in:
151
plugin/aws_key_management/CMakeLists.txt
Normal file
151
plugin/aws_key_management/CMakeLists.txt
Normal file
@@ -0,0 +1,151 @@
|
||||
# We build parts of AWS C++ SDK as CMake external project
|
||||
# The restrictions of the SDK (https://github.com/awslabs/aws-sdk-cpp/blob/master/README.md)
|
||||
# are
|
||||
|
||||
# - OS : Windows,Linux or OSX
|
||||
# - C++11 compiler : VS2013+, gcc 4.7+, clang 3.3+
|
||||
# - libcurl development package needs to be present on Unixes
|
||||
#
|
||||
# If we build SDK outselves, we'll need require GIT to be present on the build machine
|
||||
|
||||
|
||||
# Give message why the building this plugin is skipped (only if -DVERBOSE is defined)
|
||||
# or if plugin is explicitely requested to build. Then bail out.
|
||||
MACRO(SKIP_AWS_PLUGIN msg)
|
||||
IF(VERBOSE OR "${PLUGIN_AWS_KEY_MANAGEMENT}" MATCHES "^(STATIC|DYNAMIC)$")
|
||||
MESSAGE(STATUS "Skip aws_key_management - ${msg}")
|
||||
ENDIF()
|
||||
RETURN()
|
||||
ENDMACRO()
|
||||
|
||||
|
||||
# This plugin needs recent C++ compilers (AWS C++ SDK header files are using C++11 features)
|
||||
SET(CXX11_FLAGS)
|
||||
SET(OLD_COMPILER_MSG "AWS SDK requires c++11 -capable compiler (minimal supported versions are g++ 4.7, clang 3.3, VS2103)")
|
||||
|
||||
IF(CMAKE_CXX_COMPILER_ID MATCHES "GNU")
|
||||
EXECUTE_PROCESS(COMMAND ${CMAKE_CXX_COMPILER} -dumpversion OUTPUT_VARIABLE GCC_VERSION)
|
||||
IF (GCC_VERSION VERSION_LESS 4.8)
|
||||
SKIP_AWS_PLUGIN("${OLD_COMPILER_MSG}")
|
||||
ENDIF()
|
||||
SET(CXX11_FLAGS "-std=c++11")
|
||||
ELSEIF (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
||||
IF ((CMAKE_CXX_COMPILER_VERSION AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 3.3) OR
|
||||
(CLANG_VERSION_STRING AND CLANG_VERSION_STRING VERSION_LESS 3.3))
|
||||
SKIP_AWS_PLUGIN("${OLD_COMPILER_MSG}")
|
||||
ENDIF()
|
||||
SET(CXX11_FLAGS "-stdlib=libc++")
|
||||
ELSEIF(MSVC)
|
||||
IF (MSVC_VERSION LESS 1800)
|
||||
SKIP_AWS_PLUGIN("${OLD_COMPILER_MSG}")
|
||||
ENDIF()
|
||||
ELSE()
|
||||
SKIP_AWS_PLUGIN("Compiler not supported by AWS C++ SDK")
|
||||
ENDIF()
|
||||
|
||||
IF (NOT(WIN32 OR APPLE OR (CMAKE_SYSTEM_NAME MATCHES "Linux")))
|
||||
SKIP_AWS_PLUGIN("OS unsupported by AWS SDK")
|
||||
ENDIF()
|
||||
|
||||
|
||||
# Figure out where AWS installs SDK libraries
|
||||
# The below is defined in AWS SDK's CMakeLists.txt
|
||||
# (and their handling is weird, every OS has special install directory)
|
||||
IF(WIN32)
|
||||
SET(SDK_INSTALL_BINARY_PREFIX "windows")
|
||||
ELSEIF(APPLE)
|
||||
SET(SDK_INSTALL_BINARY_PREFIX "mac")
|
||||
ELSEIF(UNIX)
|
||||
SET(SDK_INSTALL_BINARY_PREFIX "linux")
|
||||
ENDIF()
|
||||
IF(NOT APPLE)
|
||||
IF(CMAKE_SIZEOF_VOID_P EQUAL 8)
|
||||
SET(SDK_INSTALL_BINARY_PREFIX "${SDK_INSTALL_BINARY_PREFIX}/intel64")
|
||||
ELSE()
|
||||
SET(SDK_INSTALL_BINARY_PREFIX "${SDK_INSTALL_BINARY_PREFIX}/ia32")
|
||||
ENDIF()
|
||||
ENDIF()
|
||||
IF(CMAKE_CONFIGURATION_TYPES)
|
||||
SET(SDK_INSTALL_BINARY_PREFIX "${SDK_INSTALL_BINARY_PREFIX}/${CMAKE_CFG_INTDIR}")
|
||||
ENDIF()
|
||||
|
||||
FIND_LIBRARY(AWS_CPP_SDK_CORE NAMES aws-cpp-sdk-core PATH_SUFFIXES "${SDK_INSTALL_BINARY_PREFIX}")
|
||||
FIND_LIBRARY(AWS_CPP_SDK_KMS NAMES aws-cpp-sdk-core PATH_SUFFIXES "${SDK_INSTALL_BINARY_PREFIX}")
|
||||
SET(CMAKE_REQUIRED_FLAGS ${CXX11_FLAGS})
|
||||
CHECK_INCLUDE_FILE_CXX(aws/kms/KMSClient.h HAVE_AWS_HEADERS)
|
||||
|
||||
IF(AWS_CPP_SDK_CORE AND AWS_CPP_SDK_KMS AND HAVE_AWS_HEADERS)
|
||||
# AWS C++ SDK installed
|
||||
SET(AWS_SDK_LIBS ${AWS_CPP_SDK_CORE} ${AWS_CPP_SDK_KMS})
|
||||
ELSE()
|
||||
# Build from source, using ExternalProject_Add
|
||||
IF(CMAKE_VERSION VERSION_LESS "2.8.8")
|
||||
SKIP_AWS_PLUGIN("CMake is too old")
|
||||
ENDIF()
|
||||
FIND_PACKAGE(Git)
|
||||
IF(NOT GIT_FOUND)
|
||||
SKIP_AWS_PLUGIN("no GIT")
|
||||
ENDIF()
|
||||
INCLUDE(ExternalProject)
|
||||
IF(UNIX)
|
||||
FIND_PACKAGE(CURL)
|
||||
IF(NOT CURL_FOUND)
|
||||
SKIP_AWS_PLUGIN("AWS C++ SDK requires libcurl development package")
|
||||
ENDIF()
|
||||
SET(PIC_FLAG -fPIC)
|
||||
ENDIF()
|
||||
IF(MSVC)
|
||||
SET(EXTRA_SDK_CMAKE_FLAGS -DCMAKE_CXX_FLAGS_DEBUGOPT="" -DCMAKE_EXE_LINKER_FLAGS_DEBUGOPT="" -DCMAKE_CXX_FLAGS=/wd4592)
|
||||
ENDIF()
|
||||
IF(CMAKE_CXX_COMPILER)
|
||||
SET(EXTRA_SDK_CMAKE_FLAGS ${EXTRA_SDK_CMAKE_FLAGS} -DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER})
|
||||
ENDIF()
|
||||
|
||||
# Relax AWS C++ SDK unreasonably high requirements for CMake version. Use replace utility (from MariaDB build)
|
||||
# to patch their CMakeLists.txt
|
||||
SET(AWS_SDK_PATCH_COMMAND )
|
||||
ExternalProject_Add(
|
||||
aws_sdk_cpp
|
||||
GIT_REPOSITORY "https://github.com/awslabs/aws-sdk-cpp.git"
|
||||
GIT_TAG "0.9.6" # single tag
|
||||
UPDATE_COMMAND ""
|
||||
PATCH_COMMAND replace 3.1.2 2.8 -- ${CMAKE_BINARY_DIR}/aws-sdk-cpp/CMakeLists.txt
|
||||
SOURCE_DIR "${CMAKE_BINARY_DIR}/aws-sdk-cpp"
|
||||
CMAKE_ARGS
|
||||
-DBUILD_ONLY=aws-cpp-sdk-kms -DSTATIC_LINKING=1
|
||||
"-DCMAKE_CXX_FLAGS_DEBUG=${CMAKE_CXX_FLAGS_DEBUG} ${PIC_FLAG}"
|
||||
"-DCMAKE_CXX_FLAGS_RELWITHDEBINFO=${CMAKE_CXX_FLAGS_RELWITHDEBINFO} ${PIC_FLAG}"
|
||||
"-DCMAKE_CXX_FLAGS_RELEASE=${CMAKE_CXX_FLAGS_RELEASE} ${PIC_FLAG}"
|
||||
"-DCMAKE_CXX_FLAGS_MINSIZEREL=${CMAKE_CXX_FLAGS_MINSIZEREL} ${PIC_FLAG}"
|
||||
${EXTRA_SDK_CMAKE_FLAGS}
|
||||
-DCMAKE_INSTALL_PREFIX=${CMAKE_BINARY_DIR}/aws_sdk_cpp
|
||||
TEST_COMMAND ""
|
||||
)
|
||||
|
||||
# We do not need to build the whole SDK , just 2 of its libs
|
||||
set(AWS_SDK_LIBS aws-cpp-sdk-core aws-cpp-sdk-kms)
|
||||
FOREACH(lib ${AWS_SDK_LIBS})
|
||||
ADD_LIBRARY(${lib} STATIC IMPORTED GLOBAL)
|
||||
ADD_DEPENDENCIES(${lib} aws_sdk_cpp)
|
||||
SET(loc "${CMAKE_BINARY_DIR}/aws_sdk_cpp/lib/${SDK_INSTALL_BINARY_PREFIX}/${CMAKE_STATIC_LIBRARY_PREFIX}${lib}${CMAKE_STATIC_LIBRARY_SUFFIX}")
|
||||
SET_TARGET_PROPERTIES(${lib} PROPERTIES IMPORTED_LOCATION ${loc})
|
||||
IF(WIN32)
|
||||
SET_TARGET_PROPERTIES(${lib} PROPERTIES IMPORTED_LINK_INTERFACE_LIBRARIES "bcrypt;winhttp;wininet;userenv")
|
||||
ELSE()
|
||||
SET_TARGET_PROPERTIES(${lib} PROPERTIES IMPORTED_LINK_INTERFACE_LIBRARIES "${SSL_LIBRARIES};${CURL_LIBRARIES}")
|
||||
ENDIF()
|
||||
ENDFOREACH()
|
||||
|
||||
IF(CMAKE_SYSTEM_NAME MATCHES "Linux")
|
||||
# Need whole-archive , otherwise static libraries are not linked
|
||||
SET(AWS_SDK_LIBS -Wl,--whole-archive ${AWS_SDK_LIBS} -Wl,--no-whole-archive)
|
||||
ENDIF()
|
||||
SET_TARGET_PROPERTIES(aws_sdk_cpp PROPERTIES EXCLUDE_FROM_ALL TRUE)
|
||||
INCLUDE_DIRECTORIES(${CMAKE_BINARY_DIR}/aws_sdk_cpp/include)
|
||||
ENDIF()
|
||||
|
||||
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${CXX11_FLAGS}")
|
||||
MYSQL_ADD_PLUGIN(aws_key_management aws_key_management_plugin.cc
|
||||
COMPONENT aws-key-management
|
||||
LINK_LIBRARIES ${AWS_SDK_LIBS}
|
||||
DISABLED)
|
587
plugin/aws_key_management/aws_key_management_plugin.cc
Normal file
587
plugin/aws_key_management/aws_key_management_plugin.cc
Normal file
@@ -0,0 +1,587 @@
|
||||
/*
|
||||
Copyright (c) 2016 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-1301 USA */
|
||||
|
||||
|
||||
#include <my_global.h>
|
||||
#include <my_pthread.h>
|
||||
#include <my_sys.h>
|
||||
#include <my_dir.h>
|
||||
#include <mysql/plugin_encryption.h>
|
||||
#include <my_crypt.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <mysqld_error.h>
|
||||
#include <base64.h>
|
||||
#include <map>
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <iterator>
|
||||
#include <sstream>
|
||||
#include <fstream>
|
||||
|
||||
#include <aws/core/client/AWSError.h>
|
||||
#include <aws/core/utils/logging/AWSLogging.h>
|
||||
#include <aws/core/utils/logging/ConsoleLogSystem.h>
|
||||
#include <aws/kms/KMSClient.h>
|
||||
#include <aws/kms/model/DecryptRequest.h>
|
||||
#include <aws/kms/model/DecryptResult.h>
|
||||
#include <aws/kms/model/GenerateDataKeyWithoutPlaintextRequest.h>
|
||||
#include <aws/kms/model/GenerateDataKeyWithoutPlaintextResult.h>
|
||||
#include <aws/core/utils/Outcome.h>
|
||||
|
||||
using namespace std;
|
||||
using namespace Aws::KMS;
|
||||
using namespace Aws::KMS::Model;
|
||||
using namespace Aws::Utils::Logging;
|
||||
extern void sql_print_error(const char *format, ...);
|
||||
extern void sql_print_warning(const char *format, ...);
|
||||
extern void sql_print_information(const char *format, ...);
|
||||
|
||||
|
||||
/* Plaintext key info struct */
|
||||
struct KEY_INFO
|
||||
{
|
||||
unsigned int key_id;
|
||||
unsigned int key_version;
|
||||
unsigned int length;
|
||||
unsigned char data[MY_AES_MAX_KEY_LENGTH];
|
||||
bool load_failed; /* if true, do not attempt to reload?*/
|
||||
public:
|
||||
KEY_INFO() : key_id(0), key_version(0), length(0), load_failed(false){};
|
||||
};
|
||||
#define KEY_ID_AND_VERSION(key_id,version) ((longlong)key_id << 32 | version)
|
||||
|
||||
/* Cache for the latest version, per key id */
|
||||
static std::map<uint, uint> latest_version_cache;
|
||||
|
||||
/* Cache for plaintext keys */
|
||||
static std::map<ulonglong, KEY_INFO> key_info_cache;
|
||||
|
||||
static const char *key_spec_names[]={ "AES_128", "AES_256", 0 };
|
||||
|
||||
/* Plugin variables */
|
||||
static char* master_key_id;
|
||||
static unsigned long key_spec;
|
||||
static unsigned long log_level;
|
||||
static int rotate_key;
|
||||
|
||||
/* AWS functionality*/
|
||||
static int aws_decrypt_key(const char *path, KEY_INFO *info);
|
||||
static int aws_generate_datakey(uint key_id, uint version);
|
||||
|
||||
static int extract_id_and_version(const char *name, uint *id, uint *ver);
|
||||
static unsigned int get_latest_key_version(unsigned int key_id);
|
||||
static unsigned int get_latest_key_version_nolock(unsigned int key_id);
|
||||
static int load_key(KEY_INFO *info);
|
||||
|
||||
/* Mutex to serialize access to caches */
|
||||
static mysql_mutex_t mtx;
|
||||
|
||||
#ifdef HAVE_PSI_INTERFACE
|
||||
static uint mtx_key;
|
||||
static PSI_mutex_info mtx_info = {&mtx_key, "mtx", 0};
|
||||
#endif
|
||||
|
||||
static Aws::KMS::KMSClient *client;
|
||||
|
||||
/* Redirect AWS trace to error log */
|
||||
class MySQLLogSystem : public Aws::Utils::Logging::FormattedLogSystem
|
||||
{
|
||||
public:
|
||||
|
||||
using Base = FormattedLogSystem;
|
||||
MySQLLogSystem(LogLevel logLevel) :
|
||||
Base(logLevel)
|
||||
{
|
||||
}
|
||||
virtual LogLevel GetLogLevel(void) const override
|
||||
{
|
||||
return (LogLevel)log_level;
|
||||
}
|
||||
virtual ~MySQLLogSystem()
|
||||
{
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void ProcessFormattedStatement(Aws::String&& statement) override
|
||||
{
|
||||
#ifdef _WIN32
|
||||
/*
|
||||
On Windows, we can't use C runtime functions to write to stdout,
|
||||
because we compile with static C runtime, so plugin has a stdout
|
||||
different from server. Thus we're using WriteFile().
|
||||
*/
|
||||
DWORD nSize= (DWORD)statement.size();
|
||||
DWORD nWritten;
|
||||
const char *s= statement.c_str();
|
||||
HANDLE h= GetStdHandle(STD_OUTPUT_HANDLE);
|
||||
|
||||
WriteFile(h, s, nSize, &nWritten, NULL);
|
||||
#else
|
||||
printf("%s", statement.c_str());
|
||||
#endif
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
Plugin initialization.
|
||||
|
||||
Create KMS client and scan datadir to find out which keys and versions
|
||||
are present.
|
||||
*/
|
||||
static int plugin_init(void *p)
|
||||
{
|
||||
DBUG_ENTER("plugin_init");
|
||||
client = new KMSClient();
|
||||
if (!client)
|
||||
{
|
||||
sql_print_error("Can not initialize KMS client");
|
||||
DBUG_RETURN(-1);
|
||||
}
|
||||
InitializeAWSLogging(Aws::MakeShared<MySQLLogSystem>("aws_key_management_plugin", (Aws::Utils::Logging::LogLevel) log_level));
|
||||
#ifdef HAVE_PSI_INTERFACE
|
||||
mysql_mutex_register("aws_key_management", &mtx_info, 1);
|
||||
#endif
|
||||
mysql_mutex_init(mtx_key, &mtx, NULL);
|
||||
|
||||
MY_DIR *dirp = my_dir(".", MYF(0));
|
||||
if (!dirp)
|
||||
{
|
||||
sql_print_error("Can't scan current directory");
|
||||
DBUG_RETURN(-1);
|
||||
}
|
||||
for (unsigned int i=0; i < dirp->number_of_files; i++)
|
||||
{
|
||||
|
||||
KEY_INFO info;
|
||||
if (extract_id_and_version(dirp->dir_entry[i].name, &info.key_id, &info.key_version) == 0)
|
||||
{
|
||||
key_info_cache[KEY_ID_AND_VERSION(info.key_id, info.key_version)]= info;
|
||||
latest_version_cache[info.key_id]= max(info.key_version, latest_version_cache[info.key_id]);
|
||||
}
|
||||
}
|
||||
my_dirend(dirp);
|
||||
DBUG_RETURN(0);
|
||||
}
|
||||
|
||||
|
||||
static int plugin_deinit(void *p)
|
||||
{
|
||||
DBUG_ENTER("plugin_deinit");
|
||||
latest_version_cache.clear();
|
||||
key_info_cache.clear();
|
||||
mysql_mutex_destroy(&mtx);
|
||||
delete client;
|
||||
ShutdownAWSLogging();
|
||||
DBUG_RETURN(0);
|
||||
}
|
||||
|
||||
/* Generate filename to store the ciphered key */
|
||||
static void format_keyfile_name(char *buf, size_t size, uint key_id, uint version)
|
||||
{
|
||||
snprintf(buf, size, "aws-kms-key.%u.%u", key_id, version);
|
||||
}
|
||||
|
||||
/* Extract key id and version from file name */
|
||||
static int extract_id_and_version(const char *name, uint *id, uint *ver)
|
||||
{
|
||||
int len;
|
||||
int n= sscanf(name, "aws-kms-key.%u.%u%n", id, ver, &len);
|
||||
if (n == 2 && *id > 0 && *ver > 0 && len == (int)strlen(name))
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
Decrypt key stored in aws-kms-key.<id>.<version>
|
||||
Cache the decrypted key.
|
||||
*/
|
||||
static int load_key(KEY_INFO *info)
|
||||
{
|
||||
int ret;
|
||||
char path[256];
|
||||
DBUG_ENTER("load_key");
|
||||
DBUG_PRINT("enter", ("id=%u,ver=%u", info->key_id, info->key_version));
|
||||
format_keyfile_name(path, sizeof(path), info->key_id, info->key_version);
|
||||
ret= aws_decrypt_key(path, info);
|
||||
if (ret)
|
||||
info->load_failed= true;
|
||||
|
||||
latest_version_cache[info->key_id]= max(latest_version_cache[info->key_id], info->key_version);
|
||||
key_info_cache[KEY_ID_AND_VERSION(info->key_id, info->key_version)]= *info;
|
||||
|
||||
if (!ret)
|
||||
{
|
||||
sql_print_information("AWS KMS plugin: loaded key %u, version %u, key length %u bit",
|
||||
info->key_id, info->key_version,(uint)info->length*8);
|
||||
}
|
||||
else
|
||||
{
|
||||
sql_print_warning("AWS KMS plugin: key %u, version %u could not be decrypted",
|
||||
info->key_id, info->key_version);
|
||||
}
|
||||
DBUG_RETURN(ret);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
Get latest version for the key.
|
||||
|
||||
If key is not decrypted yet, this function also decrypt the key
|
||||
and error will be returned if decryption fails.
|
||||
|
||||
The reason for that is that Innodb crashes
|
||||
in case errors are returned by get_key(),
|
||||
|
||||
A new key will be created if it does not exist, provided there is
|
||||
valid master_key_id.
|
||||
*/
|
||||
static unsigned int get_latest_key_version(unsigned int key_id)
|
||||
{
|
||||
unsigned int ret;
|
||||
DBUG_ENTER("get_latest_key_version");
|
||||
mysql_mutex_lock(&mtx);
|
||||
ret= get_latest_key_version_nolock(key_id);
|
||||
mysql_mutex_unlock(&mtx);
|
||||
DBUG_PRINT("info", ("key=%u,ret=%u", key_id, ret));
|
||||
DBUG_RETURN(ret);
|
||||
}
|
||||
|
||||
static unsigned int get_latest_key_version_nolock(unsigned int key_id)
|
||||
{
|
||||
KEY_INFO info;
|
||||
uint ver;
|
||||
DBUG_ENTER("get_latest_key_version_nolock");
|
||||
ver= latest_version_cache[key_id];
|
||||
if (ver > 0)
|
||||
{
|
||||
info= key_info_cache[KEY_ID_AND_VERSION(key_id, ver)];
|
||||
}
|
||||
if (info.load_failed)
|
||||
{
|
||||
/* Decryption failed previously, don't retry */
|
||||
DBUG_RETURN(ENCRYPTION_KEY_VERSION_INVALID);
|
||||
}
|
||||
else if (ver > 0)
|
||||
{
|
||||
/* Key exists already, return it*/
|
||||
if (info.length > 0)
|
||||
DBUG_RETURN(ver);
|
||||
}
|
||||
else // (ver == 0)
|
||||
{
|
||||
/* Generate a new key, version 1 */
|
||||
if (!master_key_id[0])
|
||||
{
|
||||
my_printf_error(ER_UNKNOWN_ERROR,
|
||||
"Can't generate encryption key %u, because 'aws_key_management_master_key_id' parameter is not set",
|
||||
MYF(0), key_id);
|
||||
DBUG_RETURN(ENCRYPTION_KEY_VERSION_INVALID);
|
||||
}
|
||||
if (aws_generate_datakey(key_id, 1) != 0)
|
||||
DBUG_RETURN(ENCRYPTION_KEY_VERSION_INVALID);
|
||||
info.key_id= key_id;
|
||||
info.key_version= 1;
|
||||
info.length= 0;
|
||||
}
|
||||
|
||||
if (load_key(&info))
|
||||
DBUG_RETURN(ENCRYPTION_KEY_VERSION_INVALID);
|
||||
DBUG_RETURN(info.key_version);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
Decrypt a file with KMS
|
||||
*/
|
||||
static int aws_decrypt_key(const char *path, KEY_INFO *info)
|
||||
{
|
||||
DBUG_ENTER("aws_decrypt_key");
|
||||
|
||||
/* Read file content into memory */
|
||||
ifstream ifs(path, ios::binary | ios::ate);
|
||||
if (!ifs.good())
|
||||
{
|
||||
sql_print_error("can't open file %s", path);
|
||||
DBUG_RETURN(-1);
|
||||
}
|
||||
size_t pos = (size_t)ifs.tellg();
|
||||
if (!pos || pos == SIZE_T_MAX)
|
||||
{
|
||||
sql_print_error("invalid key file %s", path);
|
||||
DBUG_RETURN(-1);
|
||||
}
|
||||
std::vector<char> contents(pos);
|
||||
ifs.seekg(0, ios::beg);
|
||||
ifs.read(&contents[0], pos);
|
||||
|
||||
/* Decrypt data the with AWS */
|
||||
DecryptRequest request;
|
||||
Aws::Utils::ByteBuffer byteBuffer((unsigned char *)contents.data(), pos);
|
||||
request.SetCiphertextBlob(byteBuffer);
|
||||
DecryptOutcome outcome = client->Decrypt(request);
|
||||
if (!outcome.IsSuccess())
|
||||
{
|
||||
sql_print_error("AWS KMS plugin: Decrypt failed for %s : %s", path,
|
||||
outcome.GetError().GetMessage().c_str());
|
||||
DBUG_RETURN(-1);
|
||||
}
|
||||
Aws::Utils::ByteBuffer plaintext = outcome.GetResult().GetPlaintext();
|
||||
size_t len = plaintext.GetLength();
|
||||
|
||||
if (len > (int)sizeof(info->data))
|
||||
{
|
||||
sql_print_error("AWS KMS plugin: encoding key too large for %s", path);
|
||||
DBUG_RETURN(ENCRYPTION_KEY_BUFFER_TOO_SMALL);
|
||||
}
|
||||
memcpy(info->data, plaintext.GetUnderlyingData(), len);
|
||||
info->length= len;
|
||||
DBUG_RETURN(0);
|
||||
}
|
||||
|
||||
|
||||
/* Generate a new datakey and store it a file */
|
||||
static int aws_generate_datakey(uint keyid, uint version)
|
||||
{
|
||||
|
||||
DBUG_ENTER("aws_generate_datakey");
|
||||
GenerateDataKeyWithoutPlaintextRequest request;
|
||||
request.SetKeyId(master_key_id);
|
||||
request.SetKeySpec(DataKeySpecMapper::GetDataKeySpecForName(key_spec_names[key_spec]));
|
||||
|
||||
GenerateDataKeyWithoutPlaintextOutcome outcome;
|
||||
outcome= client->GenerateDataKeyWithoutPlaintext(request);
|
||||
if (!outcome.IsSuccess())
|
||||
{
|
||||
sql_print_error("AWS KMS plugin : GenerateDataKeyWithoutPlaintext failed : %s - %s",
|
||||
outcome.GetError().GetExceptionName().c_str(),
|
||||
outcome.GetError().GetMessage().c_str());
|
||||
DBUG_RETURN(-1);
|
||||
}
|
||||
|
||||
string out;
|
||||
char filename[20];
|
||||
Aws::Utils::ByteBuffer byteBuffer = outcome.GetResult().GetCiphertextBlob();
|
||||
|
||||
format_keyfile_name(filename, sizeof(filename), keyid, version);
|
||||
int fd= my_open(filename, O_RDWR | O_CREAT, 0);
|
||||
if (fd < 0)
|
||||
{
|
||||
sql_print_error("AWS KMS plugin: Can't create file %s", filename);
|
||||
DBUG_RETURN(-1);
|
||||
}
|
||||
size_t len= byteBuffer.GetLength();
|
||||
if (my_write(fd, byteBuffer.GetUnderlyingData(), len, 0) != len)
|
||||
{
|
||||
sql_print_error("AWS KMS plugin: can't write to %s", filename);
|
||||
my_close(fd, 0);
|
||||
my_delete(filename, 0);
|
||||
DBUG_RETURN(-1);
|
||||
}
|
||||
my_close(fd, 0);
|
||||
sql_print_information("AWS KMS plugin: generated encrypted datakey for key id=%u, version=%u",
|
||||
keyid, version);
|
||||
DBUG_RETURN(0);
|
||||
}
|
||||
|
||||
/* Key rotation for a single key */
|
||||
static int rotate_single_key(uint key_id)
|
||||
{
|
||||
uint ver;
|
||||
ver= latest_version_cache[key_id];
|
||||
|
||||
if (!ver)
|
||||
{
|
||||
my_printf_error(ER_UNKNOWN_ERROR, "key %u does not exist", MYF(ME_JUST_WARNING), key_id);
|
||||
return -1;
|
||||
}
|
||||
else if (aws_generate_datakey(key_id, ver + 1))
|
||||
{
|
||||
my_printf_error(ER_UNKNOWN_ERROR, "Could not generate datakey for key id= %u, ver= %u",
|
||||
MYF(ME_JUST_WARNING), key_id, ver);
|
||||
return -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
KEY_INFO info;
|
||||
info.key_id= key_id;
|
||||
info.key_version = ver + 1;
|
||||
if (load_key(&info))
|
||||
{
|
||||
my_printf_error(ER_UNKNOWN_ERROR, "Could not load datakey for key id= %u, ver= %u",
|
||||
MYF(ME_JUST_WARNING), key_id, ver);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Key rotation for all key ids */
|
||||
static int rotate_all_keys()
|
||||
{
|
||||
int ret= 0;
|
||||
for (map<uint, uint>::iterator it= latest_version_cache.begin(); it != latest_version_cache.end(); it++)
|
||||
{
|
||||
ret= rotate_single_key(it->first);
|
||||
if (ret)
|
||||
break;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void update_rotate(MYSQL_THD, struct st_mysql_sys_var *, void *, const void *val)
|
||||
{
|
||||
if (!master_key_id[0])
|
||||
{
|
||||
my_printf_error(ER_UNKNOWN_ERROR,
|
||||
"aws_key_management_master_key_id must be set to generate new data keys", MYF(ME_JUST_WARNING));
|
||||
return;
|
||||
}
|
||||
mysql_mutex_lock(&mtx);
|
||||
rotate_key= *(int *)val;
|
||||
switch (rotate_key)
|
||||
{
|
||||
case 0:
|
||||
break;
|
||||
case -1:
|
||||
rotate_all_keys();
|
||||
break;
|
||||
default:
|
||||
rotate_single_key(rotate_key);
|
||||
break;
|
||||
}
|
||||
rotate_key= 0;
|
||||
mysql_mutex_unlock(&mtx);
|
||||
}
|
||||
|
||||
static unsigned int get_key(
|
||||
unsigned int key_id,
|
||||
unsigned int version,
|
||||
unsigned char* dstbuf,
|
||||
unsigned int* buflen)
|
||||
{
|
||||
KEY_INFO info;
|
||||
|
||||
DBUG_ENTER("get_key");
|
||||
mysql_mutex_lock(&mtx);
|
||||
info= key_info_cache[KEY_ID_AND_VERSION(key_id, version)];
|
||||
if (info.length == 0 && !info.load_failed)
|
||||
{
|
||||
info.key_id= key_id;
|
||||
info.key_version= version;
|
||||
load_key(&info);
|
||||
}
|
||||
mysql_mutex_unlock(&mtx);
|
||||
if (info.load_failed)
|
||||
DBUG_RETURN(ENCRYPTION_KEY_VERSION_INVALID);
|
||||
if (*buflen < info.length)
|
||||
{
|
||||
*buflen= info.length;
|
||||
DBUG_RETURN(ENCRYPTION_KEY_BUFFER_TOO_SMALL);
|
||||
}
|
||||
*buflen= info.length;
|
||||
memcpy(dstbuf, info.data, info.length);
|
||||
DBUG_RETURN(0);
|
||||
}
|
||||
|
||||
|
||||
/* Plugin defs */
|
||||
struct st_mariadb_encryption aws_key_management_plugin= {
|
||||
MariaDB_ENCRYPTION_INTERFACE_VERSION,
|
||||
get_latest_key_version,
|
||||
get_key,
|
||||
// use default encrypt/decrypt functions
|
||||
0, 0, 0, 0, 0
|
||||
};
|
||||
|
||||
|
||||
static TYPELIB key_spec_typelib =
|
||||
{
|
||||
array_elements(key_spec_names) - 1, "",
|
||||
key_spec_names, NULL
|
||||
};
|
||||
|
||||
const char *log_level_names[] =
|
||||
{
|
||||
"Off",
|
||||
"Fatal",
|
||||
"Error",
|
||||
"Warn",
|
||||
"Info",
|
||||
"Debug",
|
||||
"Trace",
|
||||
0
|
||||
};
|
||||
|
||||
static TYPELIB log_level_typelib =
|
||||
{
|
||||
array_elements(log_level_names) - 1, "",
|
||||
log_level_names, NULL
|
||||
};
|
||||
|
||||
static MYSQL_SYSVAR_STR(master_key_id, master_key_id,
|
||||
PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_MEMALLOC,
|
||||
"Key id for master encryption key. Used to create new datakeys. If not set, no new keys will be created",
|
||||
NULL, NULL, "");
|
||||
|
||||
static MYSQL_SYSVAR_ENUM(key_spec, key_spec,
|
||||
PLUGIN_VAR_RQCMDARG,
|
||||
"Encryption algorithm used to create new keys.",
|
||||
NULL, NULL, 0, &key_spec_typelib);
|
||||
|
||||
|
||||
static MYSQL_SYSVAR_ENUM(log_level, log_level,
|
||||
PLUGIN_VAR_RQCMDARG,
|
||||
"Logging for AWS API",
|
||||
NULL, NULL, 0, &log_level_typelib);
|
||||
|
||||
|
||||
static MYSQL_SYSVAR_INT(rotate_key, rotate_key,
|
||||
PLUGIN_VAR_RQCMDARG,
|
||||
"Set this variable to key id to perform rotation of the key. Specify -1 to rotate all keys",
|
||||
NULL, update_rotate, 0, -1, INT_MAX, 1);
|
||||
|
||||
static struct st_mysql_sys_var* settings[]= {
|
||||
MYSQL_SYSVAR(master_key_id),
|
||||
MYSQL_SYSVAR(key_spec),
|
||||
MYSQL_SYSVAR(rotate_key),
|
||||
MYSQL_SYSVAR(log_level),
|
||||
NULL
|
||||
};
|
||||
|
||||
/*
|
||||
Plugin library descriptor
|
||||
*/
|
||||
maria_declare_plugin(aws_key_management)
|
||||
{
|
||||
MariaDB_ENCRYPTION_PLUGIN,
|
||||
&aws_key_management_plugin,
|
||||
"aws_key_management",
|
||||
"MariaDB Corporation",
|
||||
"AWS key management plugin",
|
||||
PLUGIN_LICENSE_GPL,
|
||||
plugin_init,
|
||||
plugin_deinit,
|
||||
0x0100,
|
||||
NULL,
|
||||
settings,
|
||||
"1.0",
|
||||
MariaDB_PLUGIN_MATURITY_EXPERIMENTAL
|
||||
}
|
||||
maria_declare_plugin_end;
|
@@ -9,7 +9,7 @@ IF(ESSENTIALS)
|
||||
ENDIF()
|
||||
ELSE()
|
||||
SET(CPACK_COMPONENTS_USED
|
||||
"Server;Client;Development;SharedLibraries;Embedded;Documentation;IniFiles;Readme;Debuginfo;Common;connect-engine;ClientPlugins;gssapi-server;gssapi-client")
|
||||
"Server;Client;Development;SharedLibraries;Embedded;Documentation;IniFiles;Readme;Debuginfo;Common;connect-engine;ClientPlugins;gssapi-server;gssapi-client;aws-key-management")
|
||||
ENDIF()
|
||||
|
||||
SET( WIX_FEATURE_MySQLServer_EXTRA_FEATURES "DBInstance;SharedClientServerComponents")
|
||||
@@ -57,7 +57,7 @@ SET(CPACK_COMPONENT_GROUP_MYSQLSERVER_DESCRIPTION "Install server")
|
||||
|
||||
|
||||
#Miscellaneous (hidden) components, part of server / or client programs
|
||||
FOREACH(comp connect-engine ClientPlugins gssapi-server gssapi-client)
|
||||
FOREACH(comp connect-engine ClientPlugins gssapi-server gssapi-client aws-key-management)
|
||||
STRING(TOUPPER "${comp}" comp)
|
||||
SET(CPACK_COMPONENT_${comp}_GROUP "MySQLServer")
|
||||
SET(CPACK_COMPONENT_${comp}_HIDDEN 1)
|
||||
|
Reference in New Issue
Block a user