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