1 /* Copyright (c) 2017, 2019, Oracle and/or its affiliates. All rights reserved.
2
3 This program is free software; you can redistribute it and/or modify
4 it under the terms of the GNU General Public License, version 2.0,
5 as published by the Free Software Foundation.
6
7 This program is also distributed with certain software (including
8 but not limited to OpenSSL) that is licensed under separate terms,
9 as designated in a particular file or component or in included license
10 documentation. The authors of MySQL hereby grant you an additional
11 permission to link the program and your derivative works with the
12 separately licensed software that they have included with MySQL.
13
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License, version 2.0, for more details.
18
19 You should have received a copy of the GNU General Public License
20 along with this program; if not, write to the Free Software
21 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
22
23 #include "migrate_keyring.h"
24 #include "my_default.h" // my_getopt_use_args_separator
25 #include "mysql/components/services/log_builtins.h"
26 #include "mysqld.h"
27 #include "mysqld_error.h"
28 #include "sql_plugin.h" // plugin_early_load_one
29 #include "violite.h"
30
31 using std::string;
32
Migrate_keyring()33 Migrate_keyring::Migrate_keyring() {
34 m_source_plugin_handle = nullptr;
35 m_destination_plugin_handle = nullptr;
36 mysql = nullptr;
37 }
38
39 /**
40 This function does the following:
41 1. Read command line arguments specific to migration operation
42 2. Get plugin_dir value.
43 3. Get a connection handle by connecting to server.
44
45 @param [in] argc Pointer to argc of original program
46 @param [in] argv Pointer to argv of original program
47 @param [in] source_plugin Pointer to source plugin option
48 @param [in] destination_plugin Pointer to destination plugin option
49 @param [in] user User to login to server
50 @param [in] host Host on which to connect to server
51 @param [in] password Password used to connect to server
52 @param [in] socket The socket file to use for connection
53 @param [in] port Port number to use for connection
54
55 @return 0 Success
56 @return 1 Failure
57
58 */
init(int argc,char ** argv,char * source_plugin,char * destination_plugin,char * user,char * host,char * password,char * socket,ulong port)59 bool Migrate_keyring::init(int argc, char **argv, char *source_plugin,
60 char *destination_plugin, char *user, char *host,
61 char *password, char *socket, ulong port) {
62 DBUG_TRACE;
63
64 std::size_t found = std::string::npos;
65 string equal("=");
66 string so(".so");
67 string dll(".dll");
68 const string compression_method("zlib,zstd,uncompressed");
69
70 if (!source_plugin) {
71 my_error(ER_KEYRING_MIGRATION_FAILURE, MYF(0),
72 "Invalid --keyring-migration-source option.");
73 return true;
74 }
75 if (!destination_plugin) {
76 my_error(ER_KEYRING_MIGRATION_FAILURE, MYF(0),
77 "Invalid --keyring-migration-destination option.");
78 return true;
79 }
80 m_source_plugin_option = source_plugin;
81 m_destination_plugin_option = destination_plugin;
82
83 /* extract plugin name from the specified source plugin option */
84 if ((found = m_source_plugin_option.find(equal)) != std::string::npos)
85 m_source_plugin_name = m_source_plugin_option.substr(0, found);
86 else if ((found = m_source_plugin_option.find(so)) != std::string::npos)
87 m_source_plugin_name = m_source_plugin_option.substr(0, found);
88 else if ((found = m_source_plugin_option.find(dll)) != std::string::npos)
89 m_source_plugin_name = m_source_plugin_option.substr(0, found);
90 else {
91 LogErr(ERROR_LEVEL, ER_KEYRING_MIGRATE_FAILED,
92 "Invalid source plugin option value.");
93 return true;
94 }
95
96 /* extract plugin name from the specified destination plugin option */
97 if ((found = m_destination_plugin_option.find(equal)) != std::string::npos)
98 m_destination_plugin_name = m_destination_plugin_option.substr(0, found);
99 else if ((found = m_destination_plugin_option.find(so)) != std::string::npos)
100 m_destination_plugin_name = m_destination_plugin_option.substr(0, found);
101 else if ((found = m_destination_plugin_option.find(dll)) != std::string::npos)
102 m_destination_plugin_name = m_destination_plugin_option.substr(0, found);
103 else {
104 LogErr(ERROR_LEVEL, ER_KEYRING_MIGRATE_FAILED,
105 "Invalid destination plugin option value.");
106 return true;
107 }
108
109 /* if connect options are provided then initiate connection */
110 if (migrate_connect_options) {
111 ssl_start();
112 /* initiate connection */
113 mysql = mysql_init(nullptr);
114 server_extn.m_user_data = nullptr;
115 server_extn.m_before_header = nullptr;
116 server_extn.m_after_header = nullptr;
117 server_extn.compress_ctx.algorithm = MYSQL_UNCOMPRESSED;
118
119 mysql_extension_set_server_extn(mysql, &server_extn);
120 /* set default compression method */
121 mysql_options(mysql, MYSQL_OPT_COMPRESSION_ALGORITHMS,
122 compression_method.c_str());
123 enum mysql_ssl_mode ssl_mode = SSL_MODE_REQUIRED;
124 mysql_options(mysql, MYSQL_OPT_SSL_MODE, &ssl_mode);
125 mysql_options(mysql, MYSQL_OPT_CONNECT_ATTR_RESET, nullptr);
126 mysql_options4(mysql, MYSQL_OPT_CONNECT_ATTR_ADD, "program_name", "mysqld");
127 mysql_options4(mysql, MYSQL_OPT_CONNECT_ATTR_ADD, "_client_role",
128 "keyring_migration_tool");
129
130 if (!mysql_real_connect(mysql, host, user, password, "", port, socket, 0)) {
131 LogErr(ERROR_LEVEL, ER_KEYRING_MIGRATE_FAILED,
132 "Connection to server failed.");
133 return true;
134 }
135 }
136
137 m_argc = argc;
138 m_argv = new char *[m_argc + 2 + 1]; // 2 extra options + nullptr
139 for (int cnt = 0; cnt < m_argc; ++cnt) {
140 m_argv[cnt] = argv[cnt];
141 }
142
143 // add internal loose options
144 // --loose_<source_plugin_name>_open_mode=1,
145 // --loose_<source_plugin_name>_load_early=1,
146 // --loose_<destination_plugin_name>_load_early=1,
147 // open mode should disable writing on source keyring plugin
148 // load early should inform plugin that it's working in migration mode
149 size_t loose_option_count = 1;
150 m_internal_option[0] = "--loose_" + m_source_plugin_name + "_open_mode=1";
151 if (m_source_plugin_name == "keyring_hashicorp" ||
152 m_destination_plugin_name == "keyring_hashicorp") {
153 loose_option_count++;
154 m_internal_option[1] = "--loose_keyring_hashicorp_load_early=1";
155 }
156
157 // add internal options to the argument vector
158 for (size_t i = 0; i < loose_option_count; i++) {
159 m_argv[m_argc] = const_cast<char *>(m_internal_option[i].c_str());
160 m_argc++;
161 }
162
163 // null terminate and leave
164 m_argv[m_argc] = nullptr;
165 return false;
166 }
167
168 /**
169 This function does the following in sequence:
170 1. Disable access to keyring service APIs.
171 2. Load source plugin.
172 3. Load destination plugin.
173 4. Fetch all keys from source plugin and upon
174 sucess store in destination plugin.
175 5. Enable access to keyring service APIs.
176 6. Unload source plugin.
177 7. Unload destination plugin.
178
179 NOTE: In case there is any error while fetching keys from source plugin,
180 this function would remove all keys stored as part of fetch.
181
182 @return 0 Success
183 @return 1 Failure
184 */
execute()185 bool Migrate_keyring::execute() {
186 DBUG_TRACE;
187
188 char **tmp_m_argv;
189
190 /* Disable access to keyring service APIs */
191 if (migrate_connect_options && disable_keyring_operations()) goto error;
192
193 /* Load source plugin. */
194 if (load_plugin(enum_plugin_type::SOURCE_PLUGIN)) {
195 LogErr(ERROR_LEVEL, ER_KEYRING_MIGRATE_FAILED,
196 "Failed to initialize source keyring");
197 goto error;
198 }
199
200 /* Load destination plugin. */
201 if (load_plugin(enum_plugin_type::DESTINATION_PLUGIN)) {
202 LogErr(ERROR_LEVEL, ER_KEYRING_MIGRATE_FAILED,
203 "Failed to initialize destination keyring");
204 goto error;
205 }
206
207 /* skip program name */
208 m_argc--;
209 /* We use a tmp ptr instead of m_argv since if the latter gets changed, we
210 * lose access to the alloced mem and hence there would be leak */
211 tmp_m_argv = m_argv + 1;
212 /* check for invalid options */
213 if (m_argc > 1) {
214 struct my_option no_opts[] = {{nullptr, 0, nullptr, nullptr, nullptr,
215 nullptr, GET_NO_ARG, NO_ARG, 0, 0, 0,
216 nullptr, 0, nullptr}};
217 my_getopt_skip_unknown = false;
218 my_getopt_use_args_separator = true;
219 if (handle_options(&m_argc, &tmp_m_argv, no_opts, nullptr)) return true;
220
221 if (m_argc > 1) {
222 LogErr(WARNING_LEVEL, ER_KEYRING_MIGRATION_EXTRA_OPTIONS);
223 return true;
224 }
225 }
226
227 /* Fetch all keys from source plugin and store into destination plugin. */
228 if (fetch_and_store_keys()) goto error;
229
230 /* Enable access to keyring service APIs */
231 if (migrate_connect_options) enable_keyring_operations();
232 return false;
233
234 error:
235 /*
236 Enable keyring_operations in case of error
237 */
238 if (migrate_connect_options) enable_keyring_operations();
239 return true;
240 }
241
242 /**
243 Load plugin.
244
245 @param [in] plugin_type Indicates what plugin to be loaded
246
247 @return 0 Success
248 @return 1 Failure
249 */
load_plugin(enum_plugin_type plugin_type)250 bool Migrate_keyring::load_plugin(enum_plugin_type plugin_type) {
251 DBUG_TRACE;
252
253 char *keyring_plugin = nullptr;
254 char *plugin_name = nullptr;
255 bool is_source_plugin = false;
256
257 if (plugin_type == enum_plugin_type::SOURCE_PLUGIN) is_source_plugin = true;
258
259 if (is_source_plugin) {
260 keyring_plugin = const_cast<char *>(m_source_plugin_option.c_str());
261 plugin_name = const_cast<char *>(m_source_plugin_name.c_str());
262 } else {
263 keyring_plugin = const_cast<char *>(m_destination_plugin_option.c_str());
264 plugin_name = const_cast<char *>(m_destination_plugin_name.c_str());
265 }
266
267 if (plugin_early_load_one(&m_argc, m_argv, keyring_plugin))
268 goto error;
269 else {
270 /* set plugin handle */
271 plugin_ref plugin;
272 plugin = my_plugin_lock_by_name(nullptr, to_lex_cstring(plugin_name),
273 MYSQL_KEYRING_PLUGIN);
274 if (plugin == nullptr) goto error;
275
276 if (is_source_plugin)
277 m_source_plugin_handle = (st_mysql_keyring *)plugin_decl(plugin)->info;
278 else
279 m_destination_plugin_handle =
280 (st_mysql_keyring *)plugin_decl(plugin)->info;
281
282 plugin_unlock(nullptr, plugin);
283 }
284 return false;
285
286 error:
287 if (is_source_plugin)
288 LogErr(ERROR_LEVEL, ER_KEYRING_MIGRATE_FAILED,
289 "Failed to load source keyring plugin.");
290 else
291 LogErr(ERROR_LEVEL, ER_KEYRING_MIGRATE_FAILED,
292 "Failed to load destination keyring plugin.");
293 return true;
294 }
295
296 /**
297 This function does the following in sequence:
298 1. Initialize key iterator which will make iterator to position itself
299 inorder to fetch a key.
300 2. Using iterator get key ID and user name.
301 3. Fetch the key information using key ID and user name.
302 4. Store the fetched key into destination plugin.
303 5. In case of errors remove keys from destination plugin.
304
305 @return 0 Success
306 @return 1 Failure
307 */
fetch_and_store_keys()308 bool Migrate_keyring::fetch_and_store_keys() {
309 DBUG_TRACE;
310
311 bool error = false;
312 char key_id[MAX_KEY_LEN] = {0};
313 char user_id[USERNAME_LENGTH] = {0};
314 void *key = nullptr;
315 size_t key_len = 0;
316 char *key_type = nullptr;
317 void *key_iterator = nullptr;
318
319 m_source_plugin_handle->mysql_key_iterator_init(&key_iterator);
320 if (key_iterator == nullptr) {
321 LogErr(ERROR_LEVEL, ER_KEYRING_MIGRATE_FAILED,
322 "Initializing source keyring iterator failed.");
323 return true;
324 }
325 while (!error) {
326 if (m_source_plugin_handle->mysql_key_iterator_get_key(key_iterator, key_id,
327 user_id))
328 break;
329
330 /* using key_info metadata fetch the actual key */
331 if (m_source_plugin_handle->mysql_key_fetch(key_id, &key_type, user_id,
332 &key, &key_len)) {
333 /* fetch failed */
334 string errmsg =
335 "Fetching key (" + string(key_id) + ") from source plugin failed.";
336 LogErr(ERROR_LEVEL, ER_KEYRING_MIGRATE_FAILED, errmsg.c_str());
337 error = true;
338 } else /* store the fetched key into destination plugin */
339 {
340 if (m_destination_plugin_handle->mysql_key_store(key_id, key_type,
341 user_id, key, key_len)) {
342 string errmsg = "Storing key (" + string(key_id) +
343 ") into destination plugin failed.";
344 LogErr(ERROR_LEVEL, ER_KEYRING_MIGRATE_FAILED, errmsg.c_str());
345 error = true;
346 } else {
347 /*
348 keep track of keys stored in successfully so that they can be
349 removed in case of error.
350 */
351 Key_info ki(key_id, user_id);
352 m_source_keys.push_back(ki);
353 }
354 }
355 if (key) my_free((char *)key);
356 if (key_type) my_free(key_type);
357 }
358 if (error) {
359 /* something went wrong remove keys from destination plugin. */
360 while (m_source_keys.size()) {
361 Key_info ki = m_source_keys.back();
362 if (m_destination_plugin_handle->mysql_key_remove(ki.m_key_id.c_str(),
363 ki.m_user_id.c_str())) {
364 string errmsg = "Removing key (" + string(ki.m_key_id.c_str()) +
365 ") from destination plugin failed.";
366 LogErr(ERROR_LEVEL, ER_KEYRING_MIGRATE_FAILED, errmsg.c_str());
367 }
368 m_source_keys.pop_back();
369 }
370 }
371 m_source_plugin_handle->mysql_key_iterator_deinit(key_iterator);
372 return error;
373 }
374
375 /**
376 Disable variable @@keyring_operations.
377
378 @return 0 Success
379 @return 1 Failure
380 */
disable_keyring_operations()381 bool Migrate_keyring::disable_keyring_operations() {
382 DBUG_TRACE;
383 const char query[] = "SET GLOBAL KEYRING_OPERATIONS=0";
384 if (mysql && mysql_real_query(mysql, query, strlen(query))) {
385 LogErr(ERROR_LEVEL, ER_KEYRING_MIGRATE_FAILED,
386 "Failed to disable keyring_operations variable.");
387 return true;
388 }
389 return false;
390 }
391
392 /**
393 Enable variable @@keyring_operations.
394
395 @return 0 Success
396 @return 1 Failure
397 */
enable_keyring_operations()398 bool Migrate_keyring::enable_keyring_operations() {
399 DBUG_TRACE;
400 const char query[] = "SET GLOBAL KEYRING_OPERATIONS=1";
401 if (mysql && mysql_real_query(mysql, query, strlen(query))) {
402 LogErr(ERROR_LEVEL, ER_KEYRING_MIGRATE_FAILED,
403 "Failed to enable keyring_operations variable.");
404 return true;
405 }
406 return false;
407 }
408
409 /**
410 Standard destructor to close connection handle.
411 */
~Migrate_keyring()412 Migrate_keyring::~Migrate_keyring() {
413 if (mysql) {
414 delete[] m_argv;
415 m_argv = nullptr;
416 mysql_close(mysql);
417 mysql = nullptr;
418 if (migrate_connect_options) vio_end();
419 }
420 }
421