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