1 /*
2 Copyright (c) 2016 MariaDB Corporation
3
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; version 2 of the License.
7
8 This program is distributed in the hope that it will be useful,
9 but WITHOUT ANY WARRANTY; without even the implied warranty of
10 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 GNU General Public License for more details.
12
13 You should have received a copy of the GNU General Public License
14 along with this program; if not, write to the Free Software
15 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1335 USA */
16
17
pam_sm_authenticate(pam_handle_t * pamh,int flags,int argc,const char * argv[])18 #include <my_global.h>
19 #include <typelib.h>
20 #include <mysql/plugin_encryption.h>
21 #include <my_crypt.h>
22 #include <string.h>
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <mysqld_error.h>
26 #include <my_sys.h>
27 #include <map>
28 #include <algorithm>
29 #include <string>
30 #include <vector>
31 #include <iterator>
32 #include <sstream>
33 #include <fstream>
34
35 #ifndef _WIN32
36 #include <dirent.h>
37 #endif
38
39 #include <aws/core/Aws.h>
40 #include <aws/core/client/AWSError.h>
41 #include <aws/core/utils/logging/AWSLogging.h>
42 #include <aws/core/utils/logging/ConsoleLogSystem.h>
43 #include <aws/kms/KMSClient.h>
44 #include <aws/kms/model/DecryptRequest.h>
45 #include <aws/kms/model/DecryptResult.h>
46 #include <aws/kms/model/GenerateDataKeyWithoutPlaintextRequest.h>
47 #include <aws/kms/model/GenerateDataKeyWithoutPlaintextResult.h>
48 #include <aws/core/utils/Outcome.h>
49
50 using namespace std;
51 using namespace Aws::KMS;
52 using namespace Aws::KMS::Model;
53 using namespace Aws::Utils::Logging;
54
55
56 /* Plaintext key info struct */
57 struct KEY_INFO
58 {
59 unsigned int key_id;
60 unsigned int key_version;
61 unsigned int length;
62 unsigned char data[MY_AES_MAX_KEY_LENGTH];
63 bool load_failed; /* if true, do not attempt to reload?*/
64 public:
65 KEY_INFO() : key_id(0), key_version(0), length(0), load_failed(false){};
66 };
67 #define KEY_ID_AND_VERSION(key_id,version) ((longlong)key_id << 32 | version)
68
69 /* Cache for the latest version, per key id */
70 static std::map<uint, uint> latest_version_cache;
71
72 /* Cache for plaintext keys */
73 static std::map<ulonglong, KEY_INFO> key_info_cache;
74
pam_sm_setcred(pam_handle_t * pamh,int flags,int argc,const char * argv[])75 static const char *key_spec_names[]={ "AES_128", "AES_256", 0 };
76
77 /* Plugin variables */
78 static char* master_key_id;
79 static char* region;
80 static unsigned long key_spec;
81 static unsigned long log_level;
82 static int rotate_key;
83 static int request_timeout;
84
85 #ifndef DBUG_OFF
86 #define WITH_AWS_MOCK 1
87 #else
88 #define WITH_AWS_MOCK 0
89 #endif
90
91 #if WITH_AWS_MOCK
92 static char mock;
93 #endif
94
95
96 /* AWS functionality*/
97 static int read_and_decrypt_key(const char *path, KEY_INFO *info);
98 static int generate_and_save_datakey(uint key_id, uint version);
99
100 static int extract_id_and_version(const char *name, uint *id, uint *ver);
101 static unsigned int get_latest_key_version(unsigned int key_id);
102 static unsigned int get_latest_key_version_nolock(unsigned int key_id);
103 static int load_key(KEY_INFO *info);
104 static std::mutex mtx;
105
106
107 static Aws::KMS::KMSClient *client;
108
109 static void print_kms_error(const char *func, const Aws::Client::AWSError<Aws::KMS::KMSErrors>& err)
110 {
111 my_printf_error(ER_UNKNOWN_ERROR,
112 "AWS KMS plugin : KMS Client API '%s' failed : %s - %s",
113 ME_ERROR_LOG_ONLY,
114 func, err.GetExceptionName().c_str(), err.GetMessage().c_str());
115 }
116
117 #if WITH_AWS_MOCK
118 /*
119 Mock routines to test plugin without actual AWS KMS interaction
120 we only need to mock 2 functions - generating encrypted key, and decrypt
121
122 This mock functions do no-op encryption, i.e encrypt and decrypt of
123 a buffer return the buffer itself.
124 */
125
126 /*
127 Generate random "encrypted" key. We do not encrypt anything in mock mode.
128 */
129 static int mock_generate_encrypted_key(Aws::Utils::ByteBuffer *result)
130 {
131 size_t len = key_spec == 0?16 : 32;
132 *result = Aws::Utils::ByteBuffer(len);
133 my_random_bytes(result->GetUnderlyingData(), (int)len);
134 return 0;
135 }
136
137
138 static int mock_decrypt(Aws::Utils::ByteBuffer input, Aws::Utils::ByteBuffer* output)
139 {
140 /* We do not encrypt or decrypt in mock mode.*/
141 *output = input;
142 return 0;
143 }
144 #endif
145
146 /* Redirect AWS trace to error log */
147 class MySQLLogSystem : public Aws::Utils::Logging::FormattedLogSystem
148 {
149 public:
150
151 using Base = FormattedLogSystem;
152 MySQLLogSystem(LogLevel logLevel) :
153 Base(logLevel)
154 {
155 }
156 virtual LogLevel GetLogLevel(void) const override
157 {
158 return (LogLevel)log_level;
159 }
160 virtual ~MySQLLogSystem()
161 {
162 }
163
164 virtual void Flush(void) override
165 {
166 }
167
168 protected:
169 virtual void ProcessFormattedStatement(Aws::String&& statement) override
170 {
171 #ifdef _WIN32
172 /*
173 On Windows, we can't use C runtime functions to write to stdout,
174 because we compile with static C runtime, so plugin has a stdout
175 different from server. Thus we're using WriteFile().
176 */
177 DWORD nSize= (DWORD)statement.size();
178 DWORD nWritten;
179 const char *s= statement.c_str();
180 HANDLE h= GetStdHandle(STD_OUTPUT_HANDLE);
181
182 WriteFile(h, s, nSize, &nWritten, NULL);
183 #else
184 printf("%s", statement.c_str());
185 #endif
186 }
187 };
188
189 /* Get list of files in current directory */
190 static vector<string> traverse_current_directory()
191 {
192 vector<string> v;
193 #ifdef _WIN32
194 WIN32_FIND_DATA find_data;
195 HANDLE h= FindFirstFile("*.*", &find_data);
196 if (h == INVALID_HANDLE_VALUE)
197 return v;
198 do
199 {
200 v.push_back(find_data.cFileName);
201 }
202 while (FindNextFile(h, &find_data));
203 FindClose(h);
204 #else
205 DIR *dir = opendir(".");
206 if (!dir)
207 return v;
208 struct dirent *e;
209 while ((e= readdir(dir)))
210 v.push_back(e->d_name);
211 closedir(dir);
212 #endif
213 return v;
214 }
215
216 Aws::SDKOptions sdkOptions;
217
218 static int aws_init()
219 {
220
221 #ifdef HAVE_WOLFSSL
222 sdkOptions.cryptoOptions.initAndCleanupOpenSSL = true;
223 #else
224 /* Server initialized OpenSSL already, thus AWS must skip it */
225 sdkOptions.cryptoOptions.initAndCleanupOpenSSL = false;
226 #endif
227
228 Aws::InitAPI(sdkOptions);
229 InitializeAWSLogging(Aws::MakeShared<MySQLLogSystem>("aws_key_management_plugin", (Aws::Utils::Logging::LogLevel) log_level));
230
231 Aws::Client::ClientConfiguration clientConfiguration;
232 if (region && region[0])
233 {
234 clientConfiguration.region = region;
235 }
236 if (request_timeout)
237 {
238 clientConfiguration.requestTimeoutMs= request_timeout;
239 clientConfiguration.connectTimeoutMs= request_timeout;
240 }
241 client = new KMSClient(clientConfiguration);
242 if (!client)
243 {
244 my_printf_error(ER_UNKNOWN_ERROR, "Can't initialize KMS client", ME_ERROR_LOG_ONLY | ME_WARNING);
245 return -1;
246 }
247 return 0;
248 }
249
250 static int init()
251 {
252 #if WITH_AWS_MOCK
253 if(mock)
254 return 0;
255 #endif
256 return aws_init();
257 }
258
259 /*
260 Plugin initialization.
261
262 Create KMS client and scan datadir to find out which keys and versions
263 are present.
264 */
265 static int plugin_init(void *p)
266 {
267 if (init())
268 return -1;
269
270 vector<string> files= traverse_current_directory();
271 for (size_t i=0; i < files.size(); i++)
272 {
273
274 KEY_INFO info;
275 if (extract_id_and_version(files[i].c_str(), &info.key_id, &info.key_version) == 0)
276 {
277 key_info_cache[KEY_ID_AND_VERSION(info.key_id, info.key_version)]= info;
278 latest_version_cache[info.key_id]= max(info.key_version, latest_version_cache[info.key_id]);
279 }
280 }
281 return 0;
282 }
283
284
285 static void aws_shutdown()
286 {
287 delete client;
288 ShutdownAWSLogging();
289 Aws::ShutdownAPI(sdkOptions);
290 }
291
292
293 static void shutdown()
294 {
295 #if WITH_AWS_MOCK
296 if(mock)
297 return;
298 #endif
299 aws_shutdown();
300 }
301
302
303 static int plugin_deinit(void *p)
304 {
305 latest_version_cache.clear();
306 key_info_cache.clear();
307 shutdown();
308 return 0;
309 }
310
311 /* Generate filename to store the ciphered key */
312 static void format_keyfile_name(char *buf, size_t size, uint key_id, uint version)
313 {
314 snprintf(buf, size, "aws-kms-key.%u.%u", key_id, version);
315 }
316
317 /* Extract key id and version from file name */
318 static int extract_id_and_version(const char *name, uint *id, uint *ver)
319 {
320 int len;
321 int n= sscanf(name, "aws-kms-key.%u.%u%n", id, ver, &len);
322 if (n == 2 && *id > 0 && *ver > 0 && len == (int)strlen(name))
323 return 0;
324 return 1;
325 }
326
327 /*
328 Decrypt key stored in aws-kms-key.<id>.<version>
329 Cache the decrypted key.
330 */
331 static int load_key(KEY_INFO *info)
332 {
333 int ret;
334 char path[256];
335
336 format_keyfile_name(path, sizeof(path), info->key_id, info->key_version);
337 ret= read_and_decrypt_key(path, info);
338 if (ret)
339 info->load_failed= true;
340
341 latest_version_cache[info->key_id]= max(latest_version_cache[info->key_id], info->key_version);
342 key_info_cache[KEY_ID_AND_VERSION(info->key_id, info->key_version)]= *info;
343
344 if (!ret)
345 {
346 my_printf_error(ER_UNKNOWN_ERROR, "AWS KMS plugin: loaded key %u, version %u, key length %u bit", ME_ERROR_LOG_ONLY | ME_NOTE,
347 info->key_id, info->key_version,(uint)info->length*8);
348 }
349 else
350 {
351 my_printf_error(ER_UNKNOWN_ERROR, "AWS KMS plugin: key %u, version %u could not be decrypted", ME_ERROR_LOG_ONLY | ME_WARNING,
352 info->key_id, info->key_version);
353 }
354 return ret;
355 }
356
357
358 /*
359 Get latest version for the key.
360
361 If key is not decrypted yet, this function also decrypt the key
362 and error will be returned if decryption fails.
363
364 The reason for that is that Innodb crashes
365 in case errors are returned by get_key(),
366
367 A new key will be created if it does not exist, provided there is
368 valid master_key_id.
369 */
370 static unsigned int get_latest_key_version(unsigned int key_id)
371 {
372 unsigned int ret;
373 mtx.lock();
374 ret= get_latest_key_version_nolock(key_id);
375 mtx.unlock();
376 return ret;
377 }
378
379 static unsigned int get_latest_key_version_nolock(unsigned int key_id)
380 {
381 KEY_INFO info;
382 uint ver;
383
384 ver= latest_version_cache[key_id];
385 if (ver > 0)
386 {
387 info= key_info_cache[KEY_ID_AND_VERSION(key_id, ver)];
388 }
389 if (info.load_failed)
390 {
391 /* Decryption failed previously, don't retry */
392 return(ENCRYPTION_KEY_VERSION_INVALID);
393 }
394 else if (ver > 0)
395 {
396 /* Key exists already, return it*/
397 if (info.length > 0)
398 return(ver);
399 }
400 else // (ver == 0)
401 {
402 /* Generate a new key, version 1 */
403 if (generate_and_save_datakey(key_id, 1) != 0)
404 return(ENCRYPTION_KEY_VERSION_INVALID);
405 info.key_id= key_id;
406 info.key_version= 1;
407 info.length= 0;
408 }
409
410 if (load_key(&info))
411 return(ENCRYPTION_KEY_VERSION_INVALID);
412 return(info.key_version);
413 }
414
415 /* Decrypt Byte buffer with AWS. */
416 static int aws_decrypt(Aws::Utils::ByteBuffer input, Aws::Utils::ByteBuffer* output)
417 {
418 DecryptRequest request;
419 request.SetCiphertextBlob(input);
420 DecryptOutcome outcome = client->Decrypt(request);
421 if (!outcome.IsSuccess())
422 {
423 print_kms_error("Decrypt", outcome.GetError());
424 return -1;
425 }
426 *output= outcome.GetResult().GetPlaintext();
427 return 0;
428 }
429
430
431 static int decrypt(Aws::Utils::ByteBuffer input, Aws::Utils::ByteBuffer* output)
432 {
433 #if WITH_AWS_MOCK
434 if(mock)
435 return mock_decrypt(input,output);
436 #endif
437 return aws_decrypt(input, output);
438 }
439
440 /*
441 Decrypt a file with KMS
442 */
443 static int read_and_decrypt_key(const char *path, KEY_INFO *info)
444 {
445
446 /* Read file content into memory */
447 ifstream ifs(path, ios::binary | ios::ate);
448 if (!ifs.good())
449 {
450 my_printf_error(ER_UNKNOWN_ERROR, "can't open file %s", ME_ERROR_LOG_ONLY, path);
451 return(-1);
452 }
453 size_t pos = (size_t)ifs.tellg();
454 if (!pos || pos == SIZE_T_MAX)
455 {
456 my_printf_error(ER_UNKNOWN_ERROR, "invalid key file %s", ME_ERROR_LOG_ONLY, path);
457 return(-1);
458 }
459 std::vector<char> contents(pos);
460 ifs.seekg(0, ios::beg);
461 ifs.read(&contents[0], pos);
462
463 /* Decrypt data the with AWS */
464
465 Aws::Utils::ByteBuffer input((unsigned char *)contents.data(), pos);
466 Aws::Utils::ByteBuffer plaintext;
467
468 if (decrypt(input, &plaintext))
469 {
470 return -1;
471 }
472
473 size_t len = plaintext.GetLength();
474
475 if (len > sizeof(info->data))
476 {
477 my_printf_error(ER_UNKNOWN_ERROR, "AWS KMS plugin: encoding key too large for %s", ME_ERROR_LOG_ONLY, path);
478 return(ENCRYPTION_KEY_BUFFER_TOO_SMALL);
479 }
480 memcpy(info->data, plaintext.GetUnderlyingData(), len);
481 info->length= (unsigned int)len;
482 return(0);
483 }
484
485
486 int aws_generate_encrypted_key(Aws::Utils::ByteBuffer *result)
487 {
488 if (!master_key_id[0])
489 {
490 my_printf_error(ER_UNKNOWN_ERROR,
491 "Can't generate encryption key, because 'aws_key_management_master_key_id' parameter is not set",
492 MYF(0));
493 return(-1);
494 }
495 GenerateDataKeyWithoutPlaintextRequest request;
496 request.SetKeyId(master_key_id);
497 request.SetKeySpec(DataKeySpecMapper::GetDataKeySpecForName(key_spec_names[key_spec]));
498
499 GenerateDataKeyWithoutPlaintextOutcome outcome;
500 outcome= client->GenerateDataKeyWithoutPlaintext(request);
501 if (!outcome.IsSuccess())
502 {
503 print_kms_error("GenerateDataKeyWithoutPlaintext", outcome.GetError());
504 return(-1);
505 }
506 *result = outcome.GetResult().GetCiphertextBlob();
507 return 0;
508 }
509
510
511 static int generate_encrypted_key(Aws::Utils::ByteBuffer *output)
512 {
513 #if WITH_AWS_MOCK
514 if(mock)
515 return mock_generate_encrypted_key(output);
516 #endif
517 return aws_generate_encrypted_key(output);
518 }
519
520 /* Generate a new datakey and store it a file */
521 static int generate_and_save_datakey(uint keyid, uint version)
522 {
523 Aws::Utils::ByteBuffer byteBuffer;
524
525 if (generate_encrypted_key(&byteBuffer))
526 return -1;
527
528 string out;
529 char filename[20];
530 format_keyfile_name(filename, sizeof(filename), keyid, version);
531 int fd= open(filename, O_WRONLY |O_CREAT|O_BINARY, IF_WIN(_S_IREAD, S_IRUSR| S_IRGRP| S_IROTH));
532 if (fd < 0)
533 {
534 my_printf_error(ER_UNKNOWN_ERROR, "AWS KMS plugin: Can't create file %s", ME_ERROR_LOG_ONLY, filename);
535 return(-1);
536 }
537 unsigned int len= (unsigned int)byteBuffer.GetLength();
538 if (write(fd, byteBuffer.GetUnderlyingData(), len) != len)
539 {
540 my_printf_error(ER_UNKNOWN_ERROR, "AWS KMS plugin: can't write to %s", ME_ERROR_LOG_ONLY, filename);
541 close(fd);
542 unlink(filename);
543 return(-1);
544 }
545 close(fd);
546 my_printf_error(ER_UNKNOWN_ERROR, "AWS KMS plugin: generated encrypted datakey for key id=%u, version=%u", ME_ERROR_LOG_ONLY | ME_NOTE,
547 keyid, version);
548 return(0);
549 }
550
551 /* Key rotation for a single key */
552 static int rotate_single_key(uint key_id)
553 {
554 uint ver;
555 ver= latest_version_cache[key_id];
556
557 if (!ver)
558 {
559 my_printf_error(ER_UNKNOWN_ERROR, "key %u does not exist", MYF(ME_WARNING), key_id);
560 return -1;
561 }
562 else if (generate_and_save_datakey(key_id, ver + 1))
563 {
564 my_printf_error(ER_UNKNOWN_ERROR, "Could not generate datakey for key id= %u, ver= %u",
565 MYF(ME_WARNING), key_id, ver);
566 return -1;
567 }
568 else
569 {
570 KEY_INFO info;
571 info.key_id= key_id;
572 info.key_version = ver + 1;
573 if (load_key(&info))
574 {
575 my_printf_error(ER_UNKNOWN_ERROR, "Could not load datakey for key id= %u, ver= %u",
576 MYF(ME_WARNING), key_id, ver);
577 return -1;
578 }
579 }
580 return 0;
581 }
582
583 /* Key rotation for all key ids */
584 static int rotate_all_keys()
585 {
586 int ret= 0;
587 for (map<uint, uint>::iterator it= latest_version_cache.begin(); it != latest_version_cache.end(); it++)
588 {
589 ret= rotate_single_key(it->first);
590 if (ret)
591 break;
592 }
593 return ret;
594 }
595
596 static void update_rotate(MYSQL_THD, struct st_mysql_sys_var *, void *, const void *val)
597 {
598 if (!master_key_id[0])
599 {
600 my_printf_error(ER_UNKNOWN_ERROR,
601 "aws_key_management_master_key_id must be set to generate new data keys", MYF(ME_WARNING));
602 return;
603 }
604 mtx.lock();
605 rotate_key= *(int *)val;
606 switch (rotate_key)
607 {
608 case 0:
609 break;
610 case -1:
611 rotate_all_keys();
612 break;
613 default:
614 rotate_single_key(rotate_key);
615 break;
616 }
617 rotate_key= 0;
618 mtx.unlock();
619 }
620
621 static unsigned int get_key(
622 unsigned int key_id,
623 unsigned int version,
624 unsigned char* dstbuf,
625 unsigned int* buflen)
626 {
627 KEY_INFO info;
628
629 mtx.lock();
630 info= key_info_cache[KEY_ID_AND_VERSION(key_id, version)];
631 if (info.length == 0 && !info.load_failed)
632 {
633 info.key_id= key_id;
634 info.key_version= version;
635 load_key(&info);
636 }
637 mtx.unlock();
638 if (info.load_failed)
639 return(ENCRYPTION_KEY_VERSION_INVALID);
640 if (*buflen < info.length)
641 {
642 *buflen= info.length;
643 return(ENCRYPTION_KEY_BUFFER_TOO_SMALL);
644 }
645 *buflen= info.length;
646 memcpy(dstbuf, info.data, info.length);
647 return(0);
648 }
649
650
651 /* Plugin defs */
652 struct st_mariadb_encryption aws_key_management_plugin= {
653 MariaDB_ENCRYPTION_INTERFACE_VERSION,
654 get_latest_key_version,
655 get_key,
656 // use default encrypt/decrypt functions
657 0, 0, 0, 0, 0
658 };
659
660
661 static TYPELIB key_spec_typelib =
662 {
663 array_elements(key_spec_names) - 1, "",
664 key_spec_names, NULL
665 };
666
667 const char *log_level_names[] =
668 {
669 "Off",
670 "Fatal",
671 "Error",
672 "Warn",
673 "Info",
674 "Debug",
675 "Trace",
676 0
677 };
678
679 static TYPELIB log_level_typelib =
680 {
681 array_elements(log_level_names) - 1, "",
682 log_level_names, NULL
683 };
684
685 static MYSQL_SYSVAR_STR(master_key_id, master_key_id,
686 PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_MEMALLOC,
687 "Key id for master encryption key. Used to create new datakeys. If not set, no new keys will be created",
688 NULL, NULL, "");
689
690 static MYSQL_SYSVAR_ENUM(key_spec, key_spec,
691 PLUGIN_VAR_RQCMDARG,
692 "Encryption algorithm used to create new keys.",
693 NULL, NULL, 0, &key_spec_typelib);
694
695
696 static MYSQL_SYSVAR_ENUM(log_level, log_level,
697 PLUGIN_VAR_RQCMDARG,
698 "Logging for AWS API",
699 NULL, NULL, 0, &log_level_typelib);
700
701
702 static MYSQL_SYSVAR_INT(rotate_key, rotate_key,
703 PLUGIN_VAR_RQCMDARG,
704 "Set this variable to key id to perform rotation of the key. Specify -1 to rotate all keys",
705 NULL, update_rotate, 0, -1, INT_MAX, 1);
706
707
708 static MYSQL_SYSVAR_INT(request_timeout, request_timeout,
709 PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY,
710 "Timeout in milliseconds for create HTTPS connection or execute AWS request. Specify 0 to use SDK default.",
711 NULL, NULL, 0, 0, INT_MAX, 1);
712
713 static MYSQL_SYSVAR_STR(region, region,
714 PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY,
715 "AWS region. For example us-east-1, or eu-central-1. If no value provided, SDK default is used.",
716 NULL, NULL, "");
717
718 #if WITH_AWS_MOCK
719 static MYSQL_SYSVAR_BOOL(mock, mock,
720 PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY,
721 "Mock AWS KMS calls (for testing).",
722 NULL, NULL, 0);
723 #endif
724
725 static struct st_mysql_sys_var* settings[]= {
726 MYSQL_SYSVAR(master_key_id),
727 MYSQL_SYSVAR(key_spec),
728 MYSQL_SYSVAR(rotate_key),
729 MYSQL_SYSVAR(log_level),
730 MYSQL_SYSVAR(request_timeout),
731 MYSQL_SYSVAR(region),
732 #if WITH_AWS_MOCK
733 MYSQL_SYSVAR(mock),
734 #endif
735 NULL
736 };
737
738 /*
739 Plugin library descriptor
740 */
741 maria_declare_plugin(aws_key_management)
742 {
743 MariaDB_ENCRYPTION_PLUGIN,
744 &aws_key_management_plugin,
745 "aws_key_management",
746 "MariaDB Corporation",
747 "AWS key management plugin",
748 PLUGIN_LICENSE_GPL,
749 plugin_init,
750 plugin_deinit,
751 0x0100,
752 NULL,
753 settings,
754 "1.0",
755 MariaDB_PLUGIN_MATURITY_STABLE
756 }
757 maria_declare_plugin_end;
758