1 /*
2  Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved.
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, version 2.0,
6  as published by the Free Software Foundation.
7 
8  This program is also distributed with certain software (including
9  but not limited to OpenSSL) that is licensed under separate terms,
10  as designated in a particular file or component or in included license
11  documentation.  The authors of MySQL hereby grant you an additional
12  permission to link the program and your derivative works with the
13  separately licensed software that they have included with MySQL.
14 
15  This program is distributed in the hope that it will be useful,
16  but WITHOUT ANY WARRANTY; without even the implied warranty of
17  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  GNU General Public License for more details.
19 
20  You should have received a copy of the GNU General Public License
21  along with this program; if not, write to the Free Software
22  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
23  */
24 
25 #include <fstream>
26 #include <stdexcept>
27 #include <system_error>
28 
29 #ifdef _WIN32
30 #include <windows.h>
31 #else
32 #include <unistd.h>
33 #endif
34 
35 #include "dim.h"
36 #include "gmock/gmock.h"
37 #include "keyring/keyring_manager.h"
38 #include "mysqlrouter/keyring_info.h"
39 #include "random_generator.h"
40 #include "router_component_test.h"
41 #include "script_generator.h"
42 #include "tcp_port_pool.h"
43 #include "utils.h"
44 
45 /**
46  * @file
47  * @brief Component Tests for the master-key-reader and master-key-writer
48  */
49 
50 using namespace std::chrono_literals;
51 
52 MATCHER_P(FileContentEqual, master_key, "") {
53   std::ifstream file(arg);
54   std::stringstream file_content;
55   file_content << file.rdbuf();
56   return file_content.str() == master_key;
57 }
58 
59 MATCHER_P(FileContentNotEqual, master_key, "") {
60   std::ifstream file(arg);
61   std::stringstream file_content;
62   file_content << file.rdbuf();
63   return file_content.str() != master_key;
64 }
65 
66 class MasterKeyReaderWriterTest : public RouterComponentTest {
67  protected:
SetUp()68   void SetUp() override {
69     RouterComponentTest::SetUp();
70     logging_folder = Path(tmp_dir_.name()).join("log").str();
71 
72     mysql_harness::DIM &dim = mysql_harness::DIM::instance();
73     // RandomGenerator
74     dim.set_RandomGenerator(
75         []() {
76           static mysql_harness::RandomGenerator rg;
77           return &rg;
78         },
79         [](mysql_harness::RandomGeneratorInterface *) {});
80   }
81 
write_to_file(const Path & file_path,const std::string & text)82   void write_to_file(const Path &file_path, const std::string &text) {
83     std::ofstream master_key_file(file_path.str());
84     if (master_key_file.good()) {
85       master_key_file << text;
86     }
87   }
88 
get_metadata_cache_section(unsigned server_port)89   std::string get_metadata_cache_section(unsigned server_port) {
90     return "[metadata_cache:test]\n"
91            "router_id=1\n"
92            "bootstrap_server_addresses=mysql://localhost:" +
93            std::to_string(server_port) +
94            "\n"
95            "user=mysql_router1_user\n"
96            "metadata_cluster=test\n"
97            "ttl=500\n\n";
98   }
99 
get_metadata_cache_routing_section(const std::string & role,const std::string & strategy,unsigned router_port)100   std::string get_metadata_cache_routing_section(const std::string &role,
101                                                  const std::string &strategy,
102                                                  unsigned router_port) {
103     return "[routing:test_default]\n"
104            "bind_port=" +
105            std::to_string(router_port) + "\n" +
106            "destinations=metadata-cache://test/default?role=" + role + "\n" +
107            "protocol=classic\n" + "routing_strategy=" + strategy + "\n";
108   }
109 
init_keyring()110   KeyringInfo init_keyring() {
111     ScriptGenerator script_generator(ProcessManager::get_origin(),
112                                      tmp_dir_.name());
113 
114     KeyringInfo keyring_info;
115     keyring_info.set_master_key_reader(script_generator.get_reader_script());
116     keyring_info.set_master_key_writer(script_generator.get_writer_script());
117     keyring_info.set_keyring_file(Path(tmp_dir_.name()).join("keyring").str());
118 
119     keyring_info.generate_master_key();
120     master_key_ = keyring_info.get_master_key();
121     keyring_info.add_router_id_to_env(1);
122     keyring_info.write_master_key();
123     mysql_harness::init_keyring_with_key(keyring_info.get_keyring_file(),
124                                          keyring_info.get_master_key(), true);
125 
126     mysql_harness::Keyring *keyring = mysql_harness::get_keyring();
127     keyring->store("mysql_router1_user", "password", "root");
128     mysql_harness::flush_keyring();
129     mysql_harness::reset_keyring();
130 
131     return keyring_info;
132   }
133 
get_default_section_map(bool assign_fake_reader=false,bool assign_fake_writer=false)134   std::map<std::string, std::string> get_default_section_map(
135       bool assign_fake_reader = false, bool assign_fake_writer = false) {
136     ScriptGenerator script_generator(ProcessManager::get_origin(),
137                                      tmp_dir_.name());
138     std::map<std::string, std::string> default_section = get_DEFAULT_defaults();
139     default_section["logging_folder"] = logging_folder;
140     default_section["keyring_path"] =
141         Path(tmp_dir_.name()).join("keyring").str();
142 
143     if (assign_fake_reader)
144       default_section["master_key_reader"] =
145           script_generator.get_fake_reader_script();
146     else
147       default_section["master_key_reader"] =
148           script_generator.get_reader_script();
149 
150     if (assign_fake_writer)
151       default_section["master_key_writer"] =
152           script_generator.get_fake_writer_script();
153     else
154       default_section["master_key_writer"] =
155           script_generator.get_writer_script();
156 
157     return default_section;
158   }
159 
160   std::map<std::string, std::string>
get_incorrect_master_key_default_section_map()161   get_incorrect_master_key_default_section_map() {
162     ScriptGenerator script_generator(ProcessManager::get_origin(),
163                                      tmp_dir_.name());
164 
165     auto default_section = get_DEFAULT_defaults();
166     default_section["logging_folder"] = logging_folder;
167     default_section["keyring_path"] =
168         Path(tmp_dir_.name()).join("keyring").str();
169     default_section["master_key_reader"] =
170         script_generator.get_reader_incorrect_master_key_script();
171     default_section["master_key_writer"] = script_generator.get_writer_script();
172 
173     return default_section;
174   }
175 
176   TcpPortPool port_pool_;
177   TempDirectory tmp_dir_;
178   TempDirectory bootstrap_dir_;
179   std::string logging_folder;
180   std::string master_key_;
181 };
182 
183 /**
184  * @test
185  *       verify that when bootstrap is launched using --master-key-reader and
186  *       --master-key-writer options then master key file is not created.
187  */
TEST_F(MasterKeyReaderWriterTest,NoMasterKeyFileWhenBootstrapPassWithMasterKeyReader)188 TEST_F(MasterKeyReaderWriterTest,
189        NoMasterKeyFileWhenBootstrapPassWithMasterKeyReader) {
190   auto server_port = port_pool_.get_next_available();
191   auto &server_mock = launch_mysql_server_mock(
192       get_data_dir().join("bootstrap_gr.js").str(), server_port, false);
193   ASSERT_NO_FATAL_FAILURE(check_port_ready(server_mock, server_port));
194 
195   ScriptGenerator script_generator(ProcessManager::get_origin(),
196                                    tmp_dir_.name());
197 
198   // launch the router in bootstrap mode
199   auto &router = launch_router({
200       "--bootstrap=127.0.0.1:" + std::to_string(server_port),
201       "--report-host",
202       "dont.query.dns",
203       "--directory=" + bootstrap_dir_.name(),
204       "--force",
205       "--master-key-reader=" + script_generator.get_reader_script(),
206       "--master-key-writer=" + script_generator.get_writer_script(),
207   });
208 
209   // add login hook
210   router.register_response("Please enter MySQL password for root: ",
211                            "fake-pass\n");
212 
213   // check if the bootstraping was successful
214   check_exit_code(router, EXIT_SUCCESS, 30000ms);
215   EXPECT_TRUE(router.expect_output(
216       "MySQL Router configured for the InnoDB Cluster 'mycluster'"))
217       << router.get_full_output() << std::endl
218       << "server: " << server_mock.get_full_output();
219 
220   Path tmp(bootstrap_dir_.name());
221   Path master_key_file(tmp.join("mysqlrouter.key").str());
222 
223   ASSERT_FALSE(master_key_file.exists());
224 
225   Path keyring_file(tmp.join("data").join("keyring").str());
226   ASSERT_TRUE(keyring_file.exists());
227 
228   Path dir(tmp_dir_.name());
229   Path data_file(dir.join("master_key").str());
230   ASSERT_TRUE(data_file.exists());
231 }
232 
233 /**
234  * @test
235  *       verify that when bootstrap is launched using --master-key-reader and
236  *       --master-key-writer options then generated config file contains
237  *       entries for master_key_reader and master_key_writer.
238  *       Also, verify that --bootstrap can be specified after --master-key-*
239  *       options (all other tests will use it in the beginning).
240  */
TEST_F(MasterKeyReaderWriterTest,CheckConfigFileWhenBootstrapPassWithMasterKeyReader)241 TEST_F(MasterKeyReaderWriterTest,
242        CheckConfigFileWhenBootstrapPassWithMasterKeyReader) {
243   auto server_port = port_pool_.get_next_available();
244   auto &server_mock = launch_mysql_server_mock(
245       get_data_dir().join("bootstrap_gr.js").str(), server_port, false);
246   ASSERT_NO_FATAL_FAILURE(check_port_ready(server_mock, server_port));
247 
248   ScriptGenerator script_generator(ProcessManager::get_origin(),
249                                    tmp_dir_.name());
250 
251   // launch the router in bootstrap mode
252   auto &router = launch_router({
253       "--directory=" + bootstrap_dir_.name(),
254       "--force",
255       "--master-key-reader=" + script_generator.get_reader_script(),
256       "--master-key-writer=" + script_generator.get_writer_script(),
257       "--report-host",
258       "dont.query.dns",
259       "--bootstrap=127.0.0.1:" + std::to_string(server_port),
260   });
261 
262   // add login hook
263   router.register_response("Please enter MySQL password for root: ",
264                            "fake-pass\n");
265 
266   // check if the bootstraping was successful
267   check_exit_code(router, EXIT_SUCCESS, 30000ms);
268   EXPECT_TRUE(
269       router.expect_output("MySQL Router configured for the "
270                            "InnoDB Cluster 'mycluster'"))
271       << router.get_full_output() << std::endl
272       << "server: " << server_mock.get_full_output();
273 
274   Path tmp(bootstrap_dir_.name());
275   Path config_file(tmp.join("mysqlrouter.conf").str());
276   ASSERT_TRUE(config_file.exists());
277 
278   // read master-key-reader and master-key-writer
279   std::string master_key_reader = "", master_key_writer = "";
280 
281   std::ifstream file(config_file.str());
282   std::istream_iterator<std::string> beg(file), eof;
283   std::vector<std::string> lines(beg, eof);
284 
285   for (const auto &line : lines) {
286     int index = line.find('=');
287     if (line.substr(0, index) == "master_key_reader")
288       master_key_reader = line.substr(index + 1);
289     else if (line.substr(0, index) == "master_key_writer")
290       master_key_writer = line.substr(index + 1);
291   }
292 
293   ASSERT_THAT(master_key_reader,
294               testing::Eq(script_generator.get_reader_script()));
295   ASSERT_THAT(master_key_writer,
296               testing::Eq(script_generator.get_writer_script()));
297 }
298 
299 /**
300  * @test
301  *       verify that when --master-key-reader option is used, but specified
302  * reader cannot be executed, then bootstrap fails and appropriate error message
303  * is printed to standard output.
304  */
TEST_F(MasterKeyReaderWriterTest,BootstrapFailsWhenCannotRunMasterKeyReader)305 TEST_F(MasterKeyReaderWriterTest, BootstrapFailsWhenCannotRunMasterKeyReader) {
306   auto server_port = port_pool_.get_next_available();
307   auto &server_mock = launch_mysql_server_mock(
308       get_data_dir().join("bootstrap_gr.js").str(), server_port, false);
309   ASSERT_NO_FATAL_FAILURE(check_port_ready(server_mock, server_port));
310 
311   ScriptGenerator script_generator(ProcessManager::get_origin(),
312                                    tmp_dir_.name());
313 
314   // launch the router in bootstrap mode
315   auto &router = launch_router(
316       {
317           "--bootstrap=127.0.0.1:" + std::to_string(server_port),
318           "--report-host",
319           "dont.query.dns",
320           "--directory=" + bootstrap_dir_.name(),
321           "--force",
322           "--master-key-reader=" + script_generator.get_fake_reader_script(),
323           "--master-key-writer=" + script_generator.get_writer_script(),
324       },
325       EXIT_FAILURE);
326 
327   // add login hook
328   router.register_response("Please enter MySQL password for root: ",
329                            "fake-pass\n");
330 
331   // check if the bootstraping failed
332   check_exit_code(router, EXIT_FAILURE);
333   EXPECT_TRUE(router.expect_output(
334       "Error: Cannot fetch master key file using master key reader"))
335       << router.get_full_output() << std::endl
336       << "server: " << server_mock.get_full_output();
337 }
338 
339 /**
340  * @test
341  *       verify that when --master-key-writer option is used, but specified
342  * master key writer cannot be executed, then bootstrap fails and appropriate
343  * error message is printed to standard output.
344  */
TEST_F(MasterKeyReaderWriterTest,BootstrapFailsWhenCannotRunMasterKeyWriter)345 TEST_F(MasterKeyReaderWriterTest, BootstrapFailsWhenCannotRunMasterKeyWriter) {
346   auto server_port = port_pool_.get_next_available();
347   auto &server_mock = launch_mysql_server_mock(
348       get_data_dir().join("bootstrap_gr.js").str(), server_port, false);
349   ASSERT_NO_FATAL_FAILURE(check_port_ready(server_mock, server_port));
350 
351   ScriptGenerator script_generator(ProcessManager::get_origin(),
352                                    tmp_dir_.name());
353 
354   // launch the router in bootstrap mode
355   auto &router = launch_router(
356       {
357           "--bootstrap=127.0.0.1:" + std::to_string(server_port),
358           "--report-host",
359           "dont.query.dns",
360           "--directory=" + bootstrap_dir_.name(),
361           "--force",
362           "--master-key-reader=" + script_generator.get_reader_script(),
363           "--master-key-writer=" + script_generator.get_fake_writer_script(),
364       },
365       EXIT_FAILURE);
366 
367   // add login hook
368   router.register_response("Please enter MySQL password for root: ",
369                            "fake-pass\n");
370 
371   // check if the bootstraping failed
372   check_exit_code(router, EXIT_FAILURE);
373   EXPECT_TRUE(router.expect_output(
374       "Error: Cannot write master key file using master key writer"))
375       << router.get_full_output() << std::endl
376       << "server: " << server_mock.get_full_output();
377 }
378 
379 /**
380  * @test
381  *       verify that if keyring file already exists and --master-key-reader
382  * option is used and bootstrap fails, then original keyring file is restored.
383  */
TEST_F(MasterKeyReaderWriterTest,KeyringFileRestoredWhenBootstrapFails)384 TEST_F(MasterKeyReaderWriterTest, KeyringFileRestoredWhenBootstrapFails) {
385   mysql_harness::mkdir(Path(tmp_dir_.name()).join("data").str(), 0777);
386   // create keyring file
387   Path keyring_path(Path(tmp_dir_.name()).join("data").join("keyring").str());
388 
389   write_to_file(keyring_path, "keyring file content");
390 
391   auto server_port = port_pool_.get_next_available();
392   auto &server_mock = launch_mysql_server_mock(
393       get_data_dir().join("bootstrap_gr.js").str(), server_port, false);
394   ASSERT_NO_FATAL_FAILURE(check_port_ready(server_mock, server_port));
395 
396   ScriptGenerator script_generator(ProcessManager::get_origin(),
397                                    tmp_dir_.name());
398 
399   // launch the router in bootstrap mode
400   auto &router = launch_router(
401       {
402           "--bootstrap=127.0.0.1:" + std::to_string(server_port),
403           "--directory=" + bootstrap_dir_.name(),
404           "--force",
405           "--master-key-reader=" + script_generator.get_fake_reader_script(),
406           "--master-key-writer=" + script_generator.get_fake_writer_script(),
407           "--report-host",
408           "dont.query.dns",
409       },
410       EXIT_FAILURE);
411 
412   // add login hook
413   router.register_response("Please enter MySQL password for root: ",
414                            "fake-pass\n");
415 
416   // check if the bootstraping failed
417   check_exit_code(router, EXIT_FAILURE);
418   ASSERT_THAT(keyring_path.str(), FileContentEqual("keyring file content"));
419 }
420 
421 /**
422  * @test
423  *       verify that if --master-key-reader option is used and bootstrap fails,
424  *       then original master key is restored.
425  */
TEST_F(MasterKeyReaderWriterTest,MasterKeyRestoredWhenBootstrapFails)426 TEST_F(MasterKeyReaderWriterTest, MasterKeyRestoredWhenBootstrapFails) {
427   // create file that is used by master-key-reader and master-key-writer
428   Path master_key_path(Path(tmp_dir_.name()).join("master_key").str());
429   write_to_file(master_key_path, "");
430 
431   unsigned server_port = port_pool_.get_next_available();
432   ScriptGenerator script_generator(ProcessManager::get_origin(),
433                                    tmp_dir_.name());
434 
435   // launch the router in bootstrap mode
436   auto &router = launch_router(
437       {
438           "--bootstrap=127.0.0.1:" + std::to_string(server_port),
439           "--connect-timeout=1",
440           "--directory=" + bootstrap_dir_.name(),
441           "--force",
442           "--master-key-reader=" + script_generator.get_reader_script(),
443           "--master-key-writer=" + script_generator.get_writer_script(),
444       },
445       EXIT_FAILURE);
446 
447   // add login hook
448   router.register_response("Please enter MySQL password for root: ",
449                            "fake-pass\n");
450 
451   // check if the bootstraping failed
452   check_exit_code(router, EXIT_FAILURE);
453   ASSERT_THAT(master_key_path.str(), FileContentEqual(""));
454 }
455 
456 /*
457  * @test
458  *       verify that if --master-key-reader option is used and original master
459  * key is empty and bootstrap passes, then new master key is stored using
460  * --master-key-writer.
461  */
TEST_F(MasterKeyReaderWriterTest,IsNewMasterKeyIfReaderReturnsEmptyKeyAndBootstrapPass)462 TEST_F(MasterKeyReaderWriterTest,
463        IsNewMasterKeyIfReaderReturnsEmptyKeyAndBootstrapPass) {
464   write_to_file(Path(tmp_dir_.name()).join("master_key"), "");
465 
466   auto server_port = port_pool_.get_next_available();
467   auto &server_mock = launch_mysql_server_mock(
468       get_data_dir().join("bootstrap_gr.js").str(), server_port, false);
469   ASSERT_NO_FATAL_FAILURE(check_port_ready(server_mock, server_port));
470 
471   ScriptGenerator script_generator(ProcessManager::get_origin(),
472                                    tmp_dir_.name());
473 
474   // launch the router in bootstrap mode
475   auto &router = launch_router({
476       "--bootstrap=127.0.0.1:" + std::to_string(server_port),
477       "--report-host",
478       "dont.query.dns",
479       "--directory=" + bootstrap_dir_.name(),
480       "--force",
481       "--master-key-reader=" + script_generator.get_reader_script(),
482       "--master-key-writer=" + script_generator.get_writer_script(),
483   });
484 
485   // add login hook
486   router.register_response("Please enter MySQL password for root: ",
487                            "fake-pass\n");
488 
489   // check if the bootstraping was successful
490   check_exit_code(router, EXIT_SUCCESS, 30000ms);
491   EXPECT_TRUE(
492       router.expect_output("MySQL Router configured for the "
493                            "InnoDB Cluster 'mycluster'"))
494       << router.get_full_output() << std::endl
495       << "server: " << server_mock.get_full_output();
496 
497   Path dir(tmp_dir_.name());
498   Path data_file(dir.join("master_key").str());
499   ASSERT_TRUE(data_file.exists());
500   ASSERT_THAT(data_file.str(), FileContentNotEqual(""));
501 }
502 
503 /*
504  * @test
505  *       verify that if master key exists and is not empty and bootstrap pass,
506  * then original master key is not overriden.
507  */
TEST_F(MasterKeyReaderWriterTest,DontWriteMasterKeyAtBootstrapIfMasterKeyAlreadyExists)508 TEST_F(MasterKeyReaderWriterTest,
509        DontWriteMasterKeyAtBootstrapIfMasterKeyAlreadyExists) {
510   write_to_file(Path(tmp_dir_.name()).join("master_key"), "master key value");
511 
512   auto server_port = port_pool_.get_next_available();
513   auto &server_mock = launch_mysql_server_mock(
514       get_data_dir().join("bootstrap_gr.js").str(), server_port, false);
515   ASSERT_NO_FATAL_FAILURE(check_port_ready(server_mock, server_port));
516 
517   ScriptGenerator script_generator(ProcessManager::get_origin(),
518                                    tmp_dir_.name());
519 
520   // launch the router in bootstrap mode
521   auto &router = launch_router({
522       "--bootstrap=127.0.0.1:" + std::to_string(server_port),
523       "--report-host",
524       "dont.query.dns",
525       "--directory=" + bootstrap_dir_.name(),
526       "--force",
527       "--master-key-reader=" + script_generator.get_reader_script(),
528       "--master-key-writer=" + script_generator.get_writer_script(),
529   });
530 
531   // add login hook
532   router.register_response("Please enter MySQL password for root: ",
533                            "fake-pass\n");
534 
535   // check if the bootstraping failed
536   check_exit_code(router);
537   ASSERT_THAT(Path(tmp_dir_.name()).join("master_key").str(),
538               FileContentEqual("master key value"));
539 }
540 
541 /**
542  * @test
543  *       verify that when master key returned by master-key-reader is correct,
544  *       then launching the router succeeds
545  *
546  */
TEST_F(MasterKeyReaderWriterTest,ConnectToMetadataServerPass)547 TEST_F(MasterKeyReaderWriterTest, ConnectToMetadataServerPass) {
548   auto server_port = port_pool_.get_next_available();
549   auto router_port = port_pool_.get_next_available();
550   std::string metadata_cache_section = get_metadata_cache_section(server_port);
551   std::string routing_section =
552       get_metadata_cache_routing_section("PRIMARY", "round-robin", router_port);
553 
554   init_keyring();
555 
556   // launch server
557   auto &server = launch_mysql_server_mock(
558       get_data_dir().join("metadata_dynamic_nodes.js").str(), server_port,
559       false);
560   ASSERT_NO_FATAL_FAILURE(check_port_ready(server, server_port));
561 
562   auto default_section_map = get_default_section_map();
563   // launch the router with metadata-cache configuration
564   TempDirectory conf_dir("conf");
565   auto &router = ProcessManager::launch_router({
566       "-c",
567       create_config_file(conf_dir.name(),
568                          metadata_cache_section + routing_section,
569                          &default_section_map),
570   });
571 
572   // in windows waiting for the router's keyring reader takes about 2seconds,
573   // and we need to do 3 rounds
574   ASSERT_NO_FATAL_FAILURE(check_port_ready(router, router_port, 10000ms));
575 
576   auto matcher = [&](const std::string &line) -> bool {
577     return line.find("Connected with metadata server running on") != line.npos;
578   };
579 
580   EXPECT_TRUE(find_in_file(logging_folder + "/mysqlrouter.log", matcher,
581                            std::chrono::milliseconds(10000)));
582 }
583 
584 /**
585  * @test
586  *       verify that when master key returned by master-key-reader is correct
587  *       and then launching the router succeeds, then master-key is not written
588  *       to log files.
589  *
590  */
TEST_F(MasterKeyReaderWriterTest,NoMasterKeyInLogsWhenConnectToMetadataServerPass)591 TEST_F(MasterKeyReaderWriterTest,
592        NoMasterKeyInLogsWhenConnectToMetadataServerPass) {
593   unsigned server_port = port_pool_.get_next_available();
594   unsigned router_port = port_pool_.get_next_available();
595   std::string metadata_cache_section = get_metadata_cache_section(server_port);
596   std::string routing_section =
597       get_metadata_cache_routing_section("PRIMARY", "round-robin", router_port);
598 
599   init_keyring();
600 
601   // launch server
602   auto &server = launch_mysql_server_mock(
603       get_data_dir().join("metadata_dynamic_nodes.js").str(), server_port,
604       false);
605   ASSERT_NO_FATAL_FAILURE(check_port_ready(server, server_port, 10000ms));
606 
607   auto default_section_map = get_default_section_map();
608   // launch the router with metadata-cache configuration
609   TempDirectory conf_dir("conf");
610   auto &router = ProcessManager::launch_router(
611       {"-c", create_config_file(conf_dir.name(),
612                                 metadata_cache_section + routing_section,
613                                 &default_section_map)});
614 
615   // in windows waiting for the router's keyring reader takes about 2seconds,
616   // and we need to do 3 rounds
617   ASSERT_NO_FATAL_FAILURE(check_port_ready(router, router_port, 10000ms));
618 
619   auto matcher = [&, this](const std::string &line) -> bool {
620     return line.find(master_key_) != line.npos;
621   };
622 
623   EXPECT_FALSE(find_in_file(logging_folder + "/mysqlrouter.log", matcher,
624                             std::chrono::milliseconds(1000)))
625       << router.get_full_output();
626 }
627 
628 /**
629  * @test
630  *       verify that when cannot run master-key-reader in order to read master
631  * key then launching the router fails.
632  */
TEST_F(MasterKeyReaderWriterTest,CannotLaunchRouterWhenNoMasterKeyReader)633 TEST_F(MasterKeyReaderWriterTest, CannotLaunchRouterWhenNoMasterKeyReader) {
634   auto server_port = port_pool_.get_next_available();
635   auto router_port = port_pool_.get_next_available();
636   std::string metadata_cache_section = get_metadata_cache_section(server_port);
637   std::string routing_section =
638       get_metadata_cache_routing_section("PRIMARY", "round-robin", router_port);
639 
640   init_keyring();
641 
642   // launch second metadata server
643   auto &server = launch_mysql_server_mock(
644       get_data_dir().join("metadata_dynamic_nodes.js").str(), server_port,
645       false);
646   ASSERT_NO_FATAL_FAILURE(check_port_ready(server, server_port));
647 
648   auto default_section_map = get_default_section_map(true, true);
649   // launch the router with metadata-cache configuration
650   TempDirectory conf_dir("conf");
651   auto &router = ProcessManager::launch_router(
652       {
653           "-c",
654           create_config_file(conf_dir.name(),
655                              metadata_cache_section + routing_section,
656                              &default_section_map),
657       },
658       EXIT_FAILURE);
659 
660   check_exit_code(router, EXIT_FAILURE);
661 }
662 
663 /**
664  * @test
665  *       verify that when password fetched using --master-key-reader is
666  * incorrect, then launching the router fails with appropriate log message.
667  */
TEST_F(MasterKeyReaderWriterTest,CannotLaunchRouterWhenMasterKeyIncorrect)668 TEST_F(MasterKeyReaderWriterTest, CannotLaunchRouterWhenMasterKeyIncorrect) {
669   auto server_port = port_pool_.get_next_available();
670   auto router_port = port_pool_.get_next_available();
671   std::string metadata_cache_section = get_metadata_cache_section(server_port);
672   std::string routing_section =
673       get_metadata_cache_routing_section("PRIMARY", "round-robin", router_port);
674 
675   init_keyring();
676 
677   // launch second metadata server
678   auto &server = launch_mysql_server_mock(
679       get_data_dir().join("metadata_dynamic_nodes.js").str(), server_port,
680       false);
681   ASSERT_NO_FATAL_FAILURE(check_port_ready(server, server_port));
682 
683   auto incorrect_master_key_default_section_map =
684       get_incorrect_master_key_default_section_map();
685   // launch the router with metadata-cache configuration
686   TempDirectory conf_dir("conf");
687   auto &router = ProcessManager::launch_router(
688       {"-c", create_config_file(conf_dir.name(),
689                                 metadata_cache_section + routing_section,
690                                 &incorrect_master_key_default_section_map)},
691       EXIT_FAILURE);
692 
693   check_exit_code(router, EXIT_FAILURE);
694 }
695 /*
696  * These tests are executed only for STANDALONE layout and are not executed for
697  * Windows. Bootstrap for layouts different than STANDALONE use directories to
698  * which tests don't have access (see install_layout.cmake).
699  */
700 Path g_origin_path;
701 #ifndef SKIP_BOOTSTRAP_SYSTEM_DEPLOYMENT_TESTS
702 
703 class MasterKeyReaderWriterSystemDeploymentTest : public RouterComponentTest {
704  protected:
SetUp()705   void SetUp() override {
706     // this test modifies the origin path so we need to restore it
707     ProcessManager::set_origin(g_origin_path);
708     RouterComponentTest::SetUp();
709     init_tmp_dir();
710 
711     set_mysqlrouter_exec(Path(exec_file_));
712   }
713 
TearDown()714   void TearDown() override {
715     RouterComponentTest::TearDown();
716 #ifdef __APPLE__
717     unlink(library_link_file_.c_str());
718 #endif
719   }
720 
write_to_file(const Path & file_path,const std::string & text)721   void write_to_file(const Path &file_path, const std::string &text) {
722     std::ofstream master_key_file(file_path.str());
723     if (master_key_file.good()) {
724       master_key_file << text;
725     }
726   }
727 
728   /*
729    * Create temporary directory that represents system deployment
730    * layout for mysql bootstrap. A mysql executable is copied to
731    * tmp_dir_/stage/bin/ and then an execution permission is assigned to it.
732    *
733    * After the test is completed, the whole temporary directory is deleted.
734    */
init_tmp_dir()735   void init_tmp_dir() {
736     mysql_harness::mkdir(tmp_dir_.name() + "/stage", 0700);
737     mysql_harness::mkdir(tmp_dir_.name() + "/stage/bin", 0700);
738     exec_file_ = tmp_dir_.name() + "/stage/bin/mysqlrouter";
739     mysqlrouter::copy_file(get_mysqlrouter_exec().str(), exec_file_);
740 #ifndef _WIN32
741     chmod(exec_file_.c_str(), 0700);
742 #endif
743 
744     // on MacOS we need to create symlink to library_output_directory
745     // inside our temp dir as mysqlrouter has @loader_path/../lib
746     // hardcoded by MYSQL_ADD_EXECUTABLE
747 #ifdef __APPLE__
748     std::string cur_dir_name = g_origin_path.real_path().dirname().str();
749     const std::string library_output_dir =
750         cur_dir_name + "/library_output_directory";
751 
752     library_link_file_ =
753         std::string(Path(tmp_dir_.name()).real_path().str() + "/stage/lib");
754 
755     if (symlink(library_output_dir.c_str(), library_link_file_.c_str())) {
756       throw std::runtime_error(
757           "Could not create symbolic link to library_output_directory: " +
758           std::to_string(errno));
759     }
760 #endif
761     config_file_ = tmp_dir_.name() + "/stage/mysqlrouter.conf";
762   }
763 
run_server_mock()764   auto &run_server_mock() {
765     const std::string json_stmts = get_data_dir().join("bootstrap_gr.js").str();
766     server_port_ = port_pool_.get_next_available();
767 
768     // launch mock server and wait for it to start accepting connections
769     auto &server_mock = launch_mysql_server_mock(json_stmts, server_port_);
770     return server_mock;
771   }
772 
773   TcpPortPool port_pool_;
774 
775   TempDirectory tmp_dir_;
776   std::string exec_file_;
777   std::string config_file_;
778 #ifdef __APPLE__
779   std::string library_link_file_;
780 #endif
781 
782   uint16_t server_port_;
783 };
784 
785 /**
786  * @test
787  *      Verify if bootstrap with --master-key-reader and --master-key-writer
788  *      and with system deployment layout then master key file
789  * (stage/mysqlrouter.key) is not generated.
790  */
TEST_F(MasterKeyReaderWriterSystemDeploymentTest,BootstrapPass)791 TEST_F(MasterKeyReaderWriterSystemDeploymentTest, BootstrapPass) {
792   auto &server_mock = run_server_mock();
793   ASSERT_NO_FATAL_FAILURE(check_port_ready(server_mock, server_port_));
794 
795   ScriptGenerator script_generator(ProcessManager::get_origin(),
796                                    tmp_dir_.name());
797 
798   // launch the router in bootstrap mode
799   auto &router = launch_router({
800       "--bootstrap=127.0.0.1:" + std::to_string(server_port_),
801       "--report-host",
802       "dont.query.dns",
803       "--master-key-reader=" + script_generator.get_reader_script(),
804       "--master-key-writer=" + script_generator.get_writer_script(),
805   });
806 
807   // add login hook
808   router.register_response("Please enter MySQL password for root: ",
809                            "fake-pass\n");
810 
811   // check if the bootstraping was successful
812   check_exit_code(router, EXIT_SUCCESS);
813 
814   EXPECT_TRUE(
815       router.expect_output("MySQL Router configured for the "
816                            "InnoDB Cluster 'mycluster'"))
817       << router.get_full_output() << std::endl
818       << "server: " << server_mock.get_full_output();
819 
820   Path dir(tmp_dir_.name());
821   Path data_file(dir.join("stage").join("mysqlrouter.key").str());
822   ASSERT_FALSE(data_file.exists());
823 }
824 
825 /**
826  * @test
827  *       verify if bootstrap with --master-key-reader and with system deployment
828  * layout, but specified reader cannot be executed, then bootstrap fails and
829  * appropriate error message is printed to standard output.
830  */
TEST_F(MasterKeyReaderWriterSystemDeploymentTest,BootstrapFailsWhenCannotRunMasterKeyReader)831 TEST_F(MasterKeyReaderWriterSystemDeploymentTest,
832        BootstrapFailsWhenCannotRunMasterKeyReader) {
833   auto &server_mock = run_server_mock();
834   ASSERT_NO_FATAL_FAILURE(check_port_ready(server_mock, server_port_));
835 
836   ScriptGenerator script_generator(ProcessManager::get_origin(),
837                                    tmp_dir_.name());
838 
839   // launch the router in bootstrap mode
840   auto &router = launch_router(
841       {
842           "--bootstrap=127.0.0.1:" + std::to_string(server_port_),
843           "--report-host",
844           "dont.query.dns",
845           "--master-key-reader=" + script_generator.get_fake_reader_script(),
846           "--master-key-writer=" + script_generator.get_writer_script(),
847       },
848       EXIT_FAILURE);
849 
850   // add login hook
851   router.register_response("Please enter MySQL password for root: ",
852                            "fake-pass\n");
853 
854   // check if the bootstraping failed
855   check_exit_code(router, EXIT_FAILURE);
856 
857   EXPECT_TRUE(router.expect_output(
858       "Error: Cannot fetch master key file using master key reader"))
859       << router.get_full_output() << std::endl
860       << "server: " << server_mock.get_full_output();
861 }
862 
863 /**
864  * @test
865  *       verify if bootstrap with --master-key-writer and system deployment
866  * layout, but specified master key writer cannot be executed, then bootstrap
867  * fails and appropriate error message is printed to standard output.
868  */
TEST_F(MasterKeyReaderWriterSystemDeploymentTest,BootstrapFailsWhenCannotRunMasterKeyWriter)869 TEST_F(MasterKeyReaderWriterSystemDeploymentTest,
870        BootstrapFailsWhenCannotRunMasterKeyWriter) {
871   auto &server_mock = run_server_mock();
872   ASSERT_NO_FATAL_FAILURE(check_port_ready(server_mock, server_port_));
873 
874   ScriptGenerator script_generator(ProcessManager::get_origin(),
875                                    tmp_dir_.name());
876 
877   // launch the router in bootstrap mode
878   auto &router = launch_router(
879       {
880           "--bootstrap=127.0.0.1:" + std::to_string(server_port_),
881           "--report-host",
882           "dont.query.dns",
883           "--master-key-reader=" + script_generator.get_reader_script(),
884           "--master-key-writer=" + script_generator.get_fake_writer_script(),
885       },
886       EXIT_FAILURE);
887 
888   // add login hook
889   router.register_response("Please enter MySQL password for root: ",
890                            "fake-pass\n");
891 
892   // check if the bootstraping failed
893   check_exit_code(router, EXIT_FAILURE);
894 
895   EXPECT_TRUE(router.expect_output(
896       "Error: Cannot write master key file using master key writer"))
897       << router.get_full_output() << std::endl
898       << "server: " << server_mock.get_full_output();
899 }
900 
901 /**
902  * @test
903  *       verify that if keyring file already exists and bootstrap with
904  * --master-key-reader and system deployment layout and bootstrap fails, then
905  * original keyring file is restored.
906  */
TEST_F(MasterKeyReaderWriterSystemDeploymentTest,KeyringFileRestoredWhenBootstrapFails)907 TEST_F(MasterKeyReaderWriterSystemDeploymentTest,
908        KeyringFileRestoredWhenBootstrapFails) {
909   mysql_harness::mkdir(Path(tmp_dir_.name()).join("stage").join("data").str(),
910                        0777);
911   // create keyring file
912   Path keyring_path(
913       Path(tmp_dir_.name()).join("stage").join("data").join("keyring").str());
914   // set original keyring file
915   write_to_file(keyring_path, "keyring file content");
916 
917   auto &server_mock = run_server_mock();
918   ASSERT_NO_FATAL_FAILURE(check_port_ready(server_mock, server_port_));
919 
920   ScriptGenerator script_generator(ProcessManager::get_origin(),
921                                    tmp_dir_.name());
922 
923   // launch the router in bootstrap mode
924   auto &router = launch_router(
925       {
926           "--bootstrap=127.0.0.1:" + std::to_string(server_port_),
927           "--connect-timeout=1",
928           "--master-key-reader=" + script_generator.get_fake_reader_script(),
929           "--master-key-writer=" + script_generator.get_fake_writer_script(),
930           "--report-host",
931           "dont.query.dns",
932       },
933       EXIT_FAILURE);
934 
935   // add login hook
936   router.register_response("Please enter MySQL password for root: ",
937                            "fake-pass\n");
938 
939   // check if the bootstraping failed
940   check_exit_code(router, EXIT_FAILURE);
941 
942   ASSERT_THAT(keyring_path.str(), FileContentEqual("keyring file content"));
943 }
944 
945 /**
946  * @test
947  *       verify bootstrap with --master-key-reader and system deployment layout
948  *       and bootstrap fails, then original master key is restored.
949  */
TEST_F(MasterKeyReaderWriterSystemDeploymentTest,MasterKeyRestoredWhenBootstrapFails)950 TEST_F(MasterKeyReaderWriterSystemDeploymentTest,
951        MasterKeyRestoredWhenBootstrapFails) {
952   // create file with master key
953   Path master_key_path(Path(tmp_dir_.name()).join("master_key").str());
954   write_to_file(master_key_path, "");
955 
956   unsigned server_port = port_pool_.get_next_available();
957   ScriptGenerator script_generator(ProcessManager::get_origin(),
958                                    tmp_dir_.name());
959 
960   // launch the router in bootstrap mode
961   auto &router = launch_router(
962       {
963           "--bootstrap=127.0.0.1:" + std::to_string(server_port),
964           "--connect-timeout=1",
965           "--master-key-reader=" + script_generator.get_reader_script(),
966           "--master-key-writer=" + script_generator.get_writer_script(),
967       },
968       EXIT_FAILURE);
969 
970   // add login hook
971   router.register_response("Please enter MySQL password for root: ",
972                            "fake-pass\n");
973 
974   // check if the bootstraping failed
975   check_exit_code(router, EXIT_FAILURE);
976 
977   ASSERT_THAT(master_key_path.str(), FileContentEqual(""));
978 }
979 
980 #endif
981 
main(int argc,char * argv[])982 int main(int argc, char *argv[]) {
983   init_windows_sockets();
984   g_origin_path = Path(argv[0]).dirname();
985   ProcessManager::set_origin(Path(argv[0]).dirname());
986   ::testing::InitGoogleTest(&argc, argv);
987   return RUN_ALL_TESTS();
988 }
989