]> granicus.if.org Git - icinga2/blob - lib/cli/nodewizardcommand.cpp
add some object locking to the Dump method (which could theoreticylly suffer from...
[icinga2] / lib / cli / nodewizardcommand.cpp
1 /* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
2
3 #include "cli/nodewizardcommand.hpp"
4 #include "cli/nodeutility.hpp"
5 #include "cli/featureutility.hpp"
6 #include "cli/apisetuputility.hpp"
7 #include "remote/apilistener.hpp"
8 #include "remote/pkiutility.hpp"
9 #include "base/logger.hpp"
10 #include "base/console.hpp"
11 #include "base/application.hpp"
12 #include "base/tlsutility.hpp"
13 #include "base/scriptglobal.hpp"
14 #include "base/exception.hpp"
15 #include <boost/algorithm/string/join.hpp>
16 #include <boost/algorithm/string/replace.hpp>
17 #include <boost/algorithm/string/case_conv.hpp>
18 #include <iostream>
19 #include <string>
20 #include <fstream>
21 #include <vector>
22
23 using namespace icinga;
24 namespace po = boost::program_options;
25
26 REGISTER_CLICOMMAND("node/wizard", NodeWizardCommand);
27
28 String NodeWizardCommand::GetDescription() const
29 {
30         return "Wizard for Icinga 2 node setup.";
31 }
32
33 String NodeWizardCommand::GetShortDescription() const
34 {
35         return "wizard for node setup";
36 }
37
38 ImpersonationLevel NodeWizardCommand::GetImpersonationLevel() const
39 {
40         return ImpersonateIcinga;
41 }
42
43 int NodeWizardCommand::GetMaxArguments() const
44 {
45         return -1;
46 }
47
48 void NodeWizardCommand::InitParameters(boost::program_options::options_description& visibleDesc,
49         boost::program_options::options_description& hiddenDesc) const
50 {
51         visibleDesc.add_options()
52                 ("verbose", "increase log level");
53 }
54
55 /**
56  * The entry point for the "node wizard" CLI command.
57  *
58  * @returns An exit status.
59  */
60 int NodeWizardCommand::Run(const boost::program_options::variables_map& vm,
61         const std::vector<std::string>& ap) const
62 {
63         if (!vm.count("verbose"))
64                 Logger::SetConsoleLogSeverity(LogCritical);
65
66         /*
67          * The wizard will get all information from the user,
68          * and then call all required functions.
69          */
70
71         std::cout << ConsoleColorTag(Console_Bold | Console_ForegroundBlue)
72                 << "Welcome to the Icinga 2 Setup Wizard!\n"
73                 << "\n"
74                 << "We will guide you through all required configuration details.\n"
75                 << "\n"
76                 << ConsoleColorTag(Console_Normal);
77
78         /* 0. master or node setup?
79          * 1. Ticket
80          * 2. Master information for autosigning
81          * 3. Trusted cert location
82          * 4. CN to use (defaults to FQDN)
83          * 5. Local CA
84          * 6. New self signed certificate
85          * 7. Request signed certificate from master
86          * 8. copy key information to /var/lib/icinga2/certs
87          * 9. enable ApiListener feature
88          * 10. generate zones.conf with endpoints and zone objects
89          * 11. set NodeName = cn in constants.conf
90          * 12. disable conf.d directory?
91          * 13. reload icinga2, or tell the user to
92          */
93
94         std::string answer;
95         /* master or satellite/client setup */
96         std::cout << ConsoleColorTag(Console_Bold)
97                 << "Please specify if this is a satellite/client setup "
98                 << "('n' installs a master setup)" << ConsoleColorTag(Console_Normal)
99                 << " [Y/n]: ";
100         std::getline (std::cin, answer);
101
102         boost::algorithm::to_lower(answer);
103
104         String choice = answer;
105
106         std::cout << "\n";
107
108         int res = 0;
109
110         if (choice.Contains("n"))
111                 res = MasterSetup();
112         else
113                 res = ClientSetup();
114
115         if (res != 0)
116                 return res;
117
118         std::cout << "\n";
119         std::cout << ConsoleColorTag(Console_Bold | Console_ForegroundGreen)
120                 << "Done.\n\n"
121                 << ConsoleColorTag(Console_Normal);
122
123         std::cout << ConsoleColorTag(Console_Bold | Console_ForegroundRed)
124                 << "Now restart your Icinga 2 daemon to finish the installation!\n"
125                 << ConsoleColorTag(Console_Normal);
126
127         return 0;
128 }
129
130 int NodeWizardCommand::ClientSetup() const
131 {
132         std::string answer;
133         String choice;
134         bool connectToParent = false;
135
136         std::cout << "Starting the Client/Satellite setup routine...\n\n";
137
138         /* CN */
139         std::cout << ConsoleColorTag(Console_Bold)
140                 << "Please specify the common name (CN)"
141                 << ConsoleColorTag(Console_Normal)
142                 << " [" << Utility::GetFQDN() << "]: ";
143
144         std::getline(std::cin, answer);
145
146         if (answer.empty())
147                 answer = Utility::GetFQDN();
148
149         String cn = answer;
150         cn = cn.Trim();
151
152         std::vector<std::string> endpoints;
153
154         String endpointBuffer;
155
156         std::cout << ConsoleColorTag(Console_Bold)
157                 << "\nPlease specify the parent endpoint(s) (master or satellite) where this node should connect to:"
158                 << ConsoleColorTag(Console_Normal) << "\n";
159         String parentEndpointName;
160
161 wizard_endpoint_loop_start:
162
163         std::cout << ConsoleColorTag(Console_Bold)
164                 << "Master/Satellite Common Name" << ConsoleColorTag(Console_Normal)
165                 << " (CN from your master/satellite node): ";
166
167         std::getline(std::cin, answer);
168
169         if (answer.empty()) {
170                 Log(LogWarning, "cli", "Master/Satellite CN is required! Please retry.");
171                 goto wizard_endpoint_loop_start;
172         }
173
174         endpointBuffer = answer;
175         endpointBuffer = endpointBuffer.Trim();
176
177         std::cout << "\nDo you want to establish a connection to the parent node "
178                 << ConsoleColorTag(Console_Bold) << "from this node?"
179                 << ConsoleColorTag(Console_Normal) << " [Y/n]: ";
180
181         std::getline (std::cin, answer);
182         boost::algorithm::to_lower(answer);
183         choice = answer;
184
185         String parentEndpointPort = "5665";
186
187         if (choice.Contains("n")) {
188                 connectToParent = false;
189
190                 Log(LogWarning, "cli", "Node to master/satellite connection setup skipped");
191                 std::cout << "Connection setup skipped. Please configure your parent node to\n"
192                         << "connect to this node by setting the 'host' attribute for the node Endpoint object.\n";
193
194         } else  {
195                 connectToParent = true;
196
197                 std::cout << ConsoleColorTag(Console_Bold)
198                         << "Please specify the master/satellite connection information:"
199                         << ConsoleColorTag(Console_Normal) << "\n"
200                         << ConsoleColorTag(Console_Bold) << "Master/Satellite endpoint host"
201                         << ConsoleColorTag(Console_Normal) << " (IP address or FQDN): ";
202
203                 std::getline(std::cin, answer);
204
205                 if (answer.empty()) {
206                         Log(LogWarning, "cli", "Please enter the parent endpoint (master/satellite) connection information.");
207                         goto wizard_endpoint_loop_start;
208                 }
209
210                 String tmp = answer;
211                 tmp = tmp.Trim();
212
213                 endpointBuffer += "," + tmp;
214                 parentEndpointName = tmp;
215
216                 std::cout << ConsoleColorTag(Console_Bold)
217                         << "Master/Satellite endpoint port" << ConsoleColorTag(Console_Normal)
218                         << " [" << parentEndpointPort << "]: ";
219
220                 std::getline(std::cin, answer);
221
222                 if (!answer.empty())
223                         parentEndpointPort = answer;
224
225                 endpointBuffer += "," + parentEndpointPort.Trim();
226         }
227
228         endpoints.push_back(endpointBuffer);
229
230         std::cout << ConsoleColorTag(Console_Bold) << "\nAdd more master/satellite endpoints?"
231                 << ConsoleColorTag(Console_Normal) << " [y/N]: ";
232         std::getline (std::cin, answer);
233
234         boost::algorithm::to_lower(answer);
235
236         choice = answer;
237
238         if (choice.Contains("y"))
239                 goto wizard_endpoint_loop_start;
240
241         /* Extract parent node information. */
242         String parentHost, parentPort;
243
244         for (const String& endpoint : endpoints) {
245                 std::vector<String> tokens = endpoint.Split(",");
246
247                 if (tokens.size() > 1)
248                         parentHost = tokens[1];
249
250                 if (tokens.size() > 2)
251                         parentPort = tokens[2];
252         }
253
254         /* workaround for fetching the master cert */
255         String certsDir = ApiListener::GetCertsDir();
256         Utility::MkDirP(certsDir, 0700);
257
258         String user = Configuration::RunAsUser;
259         String group = Configuration::RunAsGroup;
260
261         if (!Utility::SetFileOwnership(certsDir, user, group)) {
262                 Log(LogWarning, "cli")
263                         << "Cannot set ownership for user '" << user
264                         << "' group '" << group
265                         << "' on file '" << certsDir << "'. Verify it yourself!";
266         }
267
268         String nodeCert = certsDir + "/" + cn + ".crt";
269         String nodeKey = certsDir + "/" + cn + ".key";
270
271         if (Utility::PathExists(nodeKey))
272                 NodeUtility::CreateBackupFile(nodeKey, true);
273         if (Utility::PathExists(nodeCert))
274                 NodeUtility::CreateBackupFile(nodeCert);
275
276         if (PkiUtility::NewCert(cn, nodeKey, Empty, nodeCert) > 0) {
277                 Log(LogCritical, "cli")
278                         << "Failed to create new self-signed certificate for CN '"
279                         << cn << "'. Please try again.";
280                 return 1;
281         }
282
283         /* fix permissions: root -> icinga daemon user */
284         if (!Utility::SetFileOwnership(nodeKey, user, group)) {
285                 Log(LogWarning, "cli")
286                         << "Cannot set ownership for user '" << user
287                         << "' group '" << group
288                         << "' on file '" << nodeKey << "'. Verify it yourself!";
289         }
290
291         std::shared_ptr<X509> trustedParentCert;
292
293         /* Check whether we should connect to the parent node and present its trusted certificate. */
294         if (connectToParent) {
295                 //save-cert and store the master certificate somewhere
296                 Log(LogInformation, "cli")
297                         << "Fetching public certificate from master ("
298                         << parentHost << ", " << parentPort << "):\n";
299
300                 trustedParentCert = PkiUtility::FetchCert(parentHost, parentPort);
301                 if (!trustedParentCert) {
302                         Log(LogCritical, "cli", "Peer did not present a valid certificate.");
303                         return 1;
304                 }
305
306                 std::cout << ConsoleColorTag(Console_Bold) << "Parent certificate information:\n"
307                         << ConsoleColorTag(Console_Normal) << PkiUtility::GetCertificateInformation(trustedParentCert)
308                         << ConsoleColorTag(Console_Bold) << "\nIs this information correct?"
309                         << ConsoleColorTag(Console_Normal) << " [y/N]: ";
310
311                 std::getline (std::cin, answer);
312                 boost::algorithm::to_lower(answer);
313                 if (answer != "y") {
314                         Log(LogWarning, "cli", "Process aborted.");
315                         return 1;
316                 }
317
318                 Log(LogInformation, "cli", "Received trusted parent certificate.\n");
319         }
320
321 wizard_ticket:
322         String nodeCA = certsDir + "/ca.crt";
323         String ticket;
324
325         /* Check whether we can connect to the parent node and fetch the client and CA certificate. */
326         if (connectToParent) {
327                 std::cout << ConsoleColorTag(Console_Bold)
328                         << "\nPlease specify the request ticket generated on your Icinga 2 master "
329                         << ConsoleColorTag(Console_Normal) << "(optional)"
330                         << ConsoleColorTag(Console_Bold) << "."
331                         << ConsoleColorTag(Console_Normal) << "\n"
332                         << " (Hint: # icinga2 pki ticket --cn '" << cn << "'): ";
333
334                 std::getline(std::cin, answer);
335
336                 if (answer.empty()) {
337                         std::cout << ConsoleColorTag(Console_Bold) << "\n"
338                                 << "No ticket was specified. Please approve the certificate signing request manually\n"
339                                 << "on the master (see 'icinga2 ca list' and 'icinga2 ca sign --help' for details)."
340                                 << ConsoleColorTag(Console_Normal) << "\n";
341                 }
342
343                 ticket = answer;
344                 ticket = ticket.Trim();
345
346                 if (ticket.IsEmpty()) {
347                         Log(LogInformation, "cli")
348                                 << "Requesting certificate without a ticket.";
349                 } else {
350                         Log(LogInformation, "cli")
351                                 << "Requesting certificate with ticket '" << ticket << "'.";
352                 }
353
354                 if (Utility::PathExists(nodeCA))
355                         NodeUtility::CreateBackupFile(nodeCA);
356                 if (Utility::PathExists(nodeCert))
357                         NodeUtility::CreateBackupFile(nodeCert);
358
359                 if (PkiUtility::RequestCertificate(parentHost, parentPort, nodeKey,
360                         nodeCert, nodeCA, trustedParentCert, ticket) > 0) {
361                         Log(LogCritical, "cli")
362                                 << "Failed to fetch signed certificate from master '"
363                                 << parentHost << ", "
364                                 << parentPort << "'. Please try again.";
365                         goto wizard_ticket;
366                 }
367
368                 /* fix permissions (again) when updating the signed certificate */
369                 if (!Utility::SetFileOwnership(nodeCert, user, group)) {
370                         Log(LogWarning, "cli")
371                                 << "Cannot set ownership for user '" << user
372                                 << "' group '" << group << "' on file '"
373                                 << nodeCert << "'. Verify it yourself!";
374                 }
375         } else {
376                 /* We cannot retrieve the parent certificate.
377                  * Tell the user to manually copy the ca.crt file
378                  * into DataDir + "/certs"
379                  */
380
381                 std::cout <<  ConsoleColorTag(Console_Bold)
382                         << "\nNo connection to the parent node was specified.\n\n"
383                         << "Please copy the public CA certificate from your master/satellite\n"
384                         << "into '" << nodeCA << "' before starting Icinga 2.\n"
385                         << ConsoleColorTag(Console_Normal);
386
387                 if (Utility::PathExists(nodeCA)) {
388                         std::cout <<  ConsoleColorTag(Console_Bold)
389                                 << "\nFound public CA certificate in '" << nodeCA << "'.\n"
390                                 << "Please verify that it is the same as on your master/satellite.\n"
391                                 << ConsoleColorTag(Console_Normal);
392                 }
393
394         }
395
396         /* apilistener config */
397         std::cout << ConsoleColorTag(Console_Bold)
398                 << "Please specify the API bind host/port "
399                 << ConsoleColorTag(Console_Normal) << "(optional)"
400                 << ConsoleColorTag(Console_Bold) << ":\n";
401
402         std::cout << ConsoleColorTag(Console_Bold)
403                 << "Bind Host" << ConsoleColorTag(Console_Normal) << " []: ";
404
405         std::getline(std::cin, answer);
406
407         String bindHost = answer;
408         bindHost = bindHost.Trim();
409
410         std::cout << ConsoleColorTag(Console_Bold)
411                 << "Bind Port" << ConsoleColorTag(Console_Normal) << " []: ";
412
413         std::getline(std::cin, answer);
414
415         String bindPort = answer;
416         bindPort = bindPort.Trim();
417
418         std::cout << ConsoleColorTag(Console_Bold) << "\n"
419                 << "Accept config from parent node?" << ConsoleColorTag(Console_Normal)
420                 << " [y/N]: ";
421         std::getline(std::cin, answer);
422         boost::algorithm::to_lower(answer);
423         choice = answer;
424
425         String acceptConfig = choice.Contains("y") ? "true" : "false";
426
427         std::cout << ConsoleColorTag(Console_Bold)
428                 << "Accept commands from parent node?" << ConsoleColorTag(Console_Normal)
429                 << " [y/N]: ";
430         std::getline(std::cin, answer);
431         boost::algorithm::to_lower(answer);
432         choice = answer;
433
434         String acceptCommands = choice.Contains("y") ? "true" : "false";
435
436         std::cout << "\n";
437
438         std::cout << ConsoleColorTag(Console_Bold | Console_ForegroundGreen)
439                 << "Reconfiguring Icinga...\n"
440                 << ConsoleColorTag(Console_Normal);
441
442         /* disable the notifications feature on client nodes */
443         Log(LogInformation, "cli", "Disabling the Notification feature.");
444
445         FeatureUtility::DisableFeatures({ "notification" });
446
447         Log(LogInformation, "cli", "Enabling the ApiListener feature.");
448
449         FeatureUtility::EnableFeatures({ "api" });
450
451         String apiConfPath = FeatureUtility::GetFeaturesAvailablePath() + "/api.conf";
452         NodeUtility::CreateBackupFile(apiConfPath);
453
454         std::fstream fp;
455         String tempApiConfPath = Utility::CreateTempFile(apiConfPath + ".XXXXXX", 0644, fp);
456
457         fp << "/**\n"
458                 << " * The API listener is used for distributed monitoring setups.\n"
459                 << " */\n"
460                 << "object ApiListener \"api\" {\n"
461                 << "  accept_config = " << acceptConfig << "\n"
462                 << "  accept_commands = " << acceptCommands << "\n";
463
464         if (!bindHost.IsEmpty())
465                 fp << "  bind_host = \"" << bindHost << "\"\n";
466         if (!bindPort.IsEmpty())
467                 fp << "  bind_port = " << bindPort << "\n";
468
469         fp << "}\n";
470
471         fp.close();
472
473 #ifdef _WIN32
474         _unlink(apiConfPath.CStr());
475 #endif /* _WIN32 */
476
477         if (rename(tempApiConfPath.CStr(), apiConfPath.CStr()) < 0) {
478                 BOOST_THROW_EXCEPTION(posix_error()
479                         << boost::errinfo_api_function("rename")
480                         << boost::errinfo_errno(errno)
481                         << boost::errinfo_file_name(tempApiConfPath));
482         }
483
484         /* Zones configuration. */
485         Log(LogInformation, "cli", "Generating local zones.conf.");
486
487         /* Setup command hardcodes this as FQDN */
488         String endpointName = cn;
489
490         /* Different local zone name. */
491         std::cout << "\nLocal zone name [" + endpointName + "]: ";
492         std::getline(std::cin, answer);
493
494         if (answer.empty())
495                 answer = endpointName;
496
497         String zoneName = answer;
498         zoneName = zoneName.Trim();
499
500         /* Different parent zone name. */
501         std::cout << "Parent zone name [master]: ";
502         std::getline(std::cin, answer);
503
504         if (answer.empty())
505                 answer = "master";
506
507         String parentZoneName = answer;
508         parentZoneName = parentZoneName.Trim();
509
510         /* Global zones. */
511         std::vector<String> globalZones { "global-templates", "director-global" };
512
513         std::cout << "\nDefault global zones: " << boost::algorithm::join(globalZones, " ");
514         std::cout << "\nDo you want to specify additional global zones? [y/N]: ";
515
516         std::getline(std::cin, answer);
517         boost::algorithm::to_lower(answer);
518         choice = answer;
519
520 wizard_global_zone_loop_start:
521         if (choice.Contains("y")) {
522                 std::cout << "\nPlease specify the name of the global Zone: ";
523
524                 std::getline(std::cin, answer);
525
526                 if (answer.empty()) {
527                         std::cout << "\nName of the global Zone is required! Please retry.";
528                         goto wizard_global_zone_loop_start;
529                 }
530
531                 String globalZoneName = answer;
532                 globalZoneName = globalZoneName.Trim();
533
534                 if (std::find(globalZones.begin(), globalZones.end(), globalZoneName) != globalZones.end()) {
535                         std::cout << "The global zone '" << globalZoneName << "' is already specified."
536                                 << " Please retry.";
537                         goto wizard_global_zone_loop_start;
538                 }
539
540                 globalZones.push_back(globalZoneName);
541
542                 std::cout << "\nDo you want to specify another global zone? [y/N]: ";
543
544                 std::getline(std::cin, answer);
545                 boost::algorithm::to_lower(answer);
546                 choice = answer;
547
548                 if (choice.Contains("y"))
549                         goto wizard_global_zone_loop_start;
550         } else
551                 Log(LogInformation, "cli", "No additional global Zones have been specified");
552
553         /* Generate node configuration. */
554         NodeUtility::GenerateNodeIcingaConfig(endpointName, zoneName, parentZoneName, endpoints, globalZones);
555
556         if (cn != Utility::GetFQDN()) {
557                 Log(LogWarning, "cli")
558                         << "CN '" << cn << "' does not match the default FQDN '"
559                         << Utility::GetFQDN() << "'. Requires update for NodeName constant in constants.conf!";
560         }
561
562         NodeUtility::UpdateConstant("NodeName", cn);
563         NodeUtility::UpdateConstant("ZoneName", cn);
564
565         if (!ticket.IsEmpty()) {
566                 String ticketPath = ApiListener::GetCertsDir() + "/ticket";
567
568                 String tempTicketPath = Utility::CreateTempFile(ticketPath + ".XXXXXX", 0600, fp);
569
570                 if (!Utility::SetFileOwnership(tempTicketPath, user, group)) {
571                         Log(LogWarning, "cli")
572                                 << "Cannot set ownership for user '" << user
573                                 << "' group '" << group
574                                 << "' on file '" << tempTicketPath << "'. Verify it yourself!";
575                 }
576
577                 fp << ticket;
578
579                 fp.close();
580
581 #ifdef _WIN32
582                 _unlink(ticketPath.CStr());
583 #endif /* _WIN32 */
584
585                 if (rename(tempTicketPath.CStr(), ticketPath.CStr()) < 0) {
586                         BOOST_THROW_EXCEPTION(posix_error()
587                                 << boost::errinfo_api_function("rename")
588                                 << boost::errinfo_errno(errno)
589                                 << boost::errinfo_file_name(tempTicketPath));
590                 }
591         }
592
593         /* If no parent connection was made, the user must supply the ca.crt before restarting Icinga 2.*/
594         if (!connectToParent) {
595                 Log(LogWarning, "cli")
596                         << "No connection to the parent node was specified.\n\n"
597                         << "Please copy the public CA certificate from your master/satellite\n"
598                         << "into '" << nodeCA << "' before starting Icinga 2.\n";
599         } else {
600                 Log(LogInformation, "cli", "Make sure to restart Icinga 2.");
601         }
602
603         /* Disable conf.d inclusion */
604         std::cout << "\nDo you want to disable the inclusion of the conf.d directory [Y/n]: ";
605
606         std::getline(std::cin, answer);
607         boost::algorithm::to_lower(answer);
608         choice = answer;
609
610         if (choice.Contains("n"))
611                 Log(LogInformation, "cli")
612                         << "conf.d directory has not been disabled.";
613         else {
614                 std::cout << ConsoleColorTag(Console_Bold | Console_ForegroundGreen)
615                         << "Disabling the inclusion of the conf.d directory...\n"
616                         << ConsoleColorTag(Console_Normal);
617
618                 if (!NodeUtility::UpdateConfiguration("\"conf.d\"", false, true)) {
619                         std::cout << ConsoleColorTag(Console_Bold | Console_ForegroundRed)
620                                 << "Failed to disable the conf.d inclusion, it may already have been disabled.\n"
621                                 << ConsoleColorTag(Console_Normal);
622                 }
623
624                 /* Satellite/Clients should not include the api-users.conf file.
625                  * The configuration should instead be managed via config sync or automation tools.
626                  */
627         }
628
629         return 0;
630 }
631
632 int NodeWizardCommand::MasterSetup() const
633 {
634         std::string answer;
635         String choice;
636
637         std::cout << ConsoleColorTag(Console_Bold) << "Starting the Master setup routine...\n\n";
638
639         /* CN */
640         std::cout << ConsoleColorTag(Console_Bold)
641                 << "Please specify the common name" << ConsoleColorTag(Console_Normal)
642                 << " (CN) [" << Utility::GetFQDN() << "]: ";
643
644         std::getline(std::cin, answer);
645
646         if (answer.empty())
647                 answer = Utility::GetFQDN();
648
649         String cn = answer;
650         cn = cn.Trim();
651
652         std::cout << ConsoleColorTag(Console_Bold | Console_ForegroundGreen)
653                 << "Reconfiguring Icinga...\n"
654                 << ConsoleColorTag(Console_Normal);
655
656         /* check whether the user wants to generate a new certificate or not */
657         String existing_path = ApiListener::GetCertsDir() + "/" + cn + ".crt";
658
659         std::cout << ConsoleColorTag(Console_Normal)
660                 << "Checking for existing certificates for common name '" << cn << "'...\n";
661
662         if (Utility::PathExists(existing_path)) {
663                 std::cout << "Certificate '" << existing_path << "' for CN '"
664                         << cn << "' already existing. Skipping certificate generation.\n";
665         } else {
666                 std::cout << "Certificates not yet generated. Running 'api setup' now.\n";
667                 ApiSetupUtility::SetupMasterCertificates(cn);
668         }
669
670         std::cout << ConsoleColorTag(Console_Bold)
671                 << "Generating master configuration for Icinga 2.\n"
672                 << ConsoleColorTag(Console_Normal);
673
674         ApiSetupUtility::SetupMasterApiUser();
675
676         if (!FeatureUtility::CheckFeatureEnabled("api"))
677                 ApiSetupUtility::SetupMasterEnableApi();
678         else
679                 std::cout << "'api' feature already enabled.\n";
680
681         /* Setup command hardcodes this as FQDN */
682         String endpointName = cn;
683
684         /* Different zone name. */
685         std::cout << "\nMaster zone name [master]: ";
686         std::getline(std::cin, answer);
687
688         if (answer.empty())
689                 answer = "master";
690
691         String zoneName = answer;
692         zoneName = zoneName.Trim();
693
694         /* Global zones. */
695         std::vector<String> globalZones { "global-templates", "director-global" };
696
697         std::cout << "\nDefault global zones: " << boost::algorithm::join(globalZones, " ");
698         std::cout << "\nDo you want to specify additional global zones? [y/N]: ";
699
700         std::getline(std::cin, answer);
701         boost::algorithm::to_lower(answer);
702         choice = answer;
703
704 wizard_global_zone_loop_start:
705         if (choice.Contains("y")) {
706                 std::cout << "\nPlease specify the name of the global Zone: ";
707
708                 std::getline(std::cin, answer);
709
710                 if (answer.empty()) {
711                         std::cout << "\nName of the global Zone is required! Please retry.";
712                         goto wizard_global_zone_loop_start;
713                 }
714
715                 String globalZoneName = answer;
716                 globalZoneName = globalZoneName.Trim();
717
718                 if (std::find(globalZones.begin(), globalZones.end(), globalZoneName) != globalZones.end()) {
719                         std::cout << "The global zone '" << globalZoneName << "' is already specified."
720                                 << " Please retry.";
721                         goto wizard_global_zone_loop_start;
722                 }
723
724                 globalZones.push_back(globalZoneName);
725
726                 std::cout << "\nDo you want to specify another global zone? [y/N]: ";
727
728                 std::getline(std::cin, answer);
729                 boost::algorithm::to_lower(answer);
730                 choice = answer;
731
732                 if (choice.Contains("y"))
733                         goto wizard_global_zone_loop_start;
734         } else
735                 Log(LogInformation, "cli", "No additional global Zones have been specified");
736
737         /* Generate master configuration. */
738         NodeUtility::GenerateNodeMasterIcingaConfig(endpointName, zoneName, globalZones);
739
740         /* apilistener config */
741         std::cout << ConsoleColorTag(Console_Bold)
742                 << "Please specify the API bind host/port "
743                 << ConsoleColorTag(Console_Normal) << "(optional)"
744                 << ConsoleColorTag(Console_Bold) << ":\n";
745
746         std::cout << ConsoleColorTag(Console_Bold)
747                 << "Bind Host" << ConsoleColorTag(Console_Normal) << " []: ";
748
749         std::getline(std::cin, answer);
750
751         String bindHost = answer;
752         bindHost = bindHost.Trim();
753
754         std::cout << ConsoleColorTag(Console_Bold)
755                 << "Bind Port" << ConsoleColorTag(Console_Normal) << " []: ";
756
757         std::getline(std::cin, answer);
758
759         String bindPort = answer;
760         bindPort = bindPort.Trim();
761
762         /* api feature is always enabled, check above */
763         String apiConfPath = FeatureUtility::GetFeaturesAvailablePath() + "/api.conf";
764         NodeUtility::CreateBackupFile(apiConfPath);
765
766         std::fstream fp;
767         String tempApiConfPath = Utility::CreateTempFile(apiConfPath + ".XXXXXX", 0644, fp);
768
769         fp << "/**\n"
770                 << " * The API listener is used for distributed monitoring setups.\n"
771                 << " */\n"
772                 << "object ApiListener \"api\" {\n";
773
774         if (!bindHost.IsEmpty())
775                 fp << "  bind_host = \"" << bindHost << "\"\n";
776         if (!bindPort.IsEmpty())
777                 fp << "  bind_port = " << bindPort << "\n";
778
779         fp << "\n"
780                 << "  ticket_salt = TicketSalt\n"
781                 << "}\n";
782
783         fp.close();
784
785 #ifdef _WIN32
786         _unlink(apiConfPath.CStr());
787 #endif /* _WIN32 */
788
789         if (rename(tempApiConfPath.CStr(), apiConfPath.CStr()) < 0) {
790                 BOOST_THROW_EXCEPTION(posix_error()
791                         << boost::errinfo_api_function("rename")
792                         << boost::errinfo_errno(errno)
793                         << boost::errinfo_file_name(tempApiConfPath));
794         }
795
796         /* update constants.conf with NodeName = CN + TicketSalt = random value */
797         if (cn != Utility::GetFQDN()) {
798                 Log(LogWarning, "cli")
799                         << "CN '" << cn << "' does not match the default FQDN '"
800                         << Utility::GetFQDN() << "'. Requires an update for the NodeName constant in constants.conf!";
801         }
802
803         Log(LogInformation, "cli", "Updating constants.conf.");
804
805         NodeUtility::CreateBackupFile(NodeUtility::GetConstantsConfPath());
806
807         NodeUtility::UpdateConstant("NodeName", cn);
808         NodeUtility::UpdateConstant("ZoneName", cn);
809
810         String salt = RandomString(16);
811
812         NodeUtility::UpdateConstant("TicketSalt", salt);
813
814         /* Disable conf.d inclusion */
815         std::cout << "\nDo you want to disable the inclusion of the conf.d directory [Y/n]: ";
816
817         std::getline(std::cin, answer);
818         boost::algorithm::to_lower(answer);
819         choice = answer;
820
821         if (choice.Contains("n"))
822                 Log(LogInformation, "cli")
823                         << "conf.d directory has not been disabled.";
824         else {
825                 std::cout << ConsoleColorTag(Console_Bold | Console_ForegroundGreen)
826                         << "Disabling the inclusion of the conf.d directory...\n"
827                         << ConsoleColorTag(Console_Normal);
828
829                 if (!NodeUtility::UpdateConfiguration("\"conf.d\"", false, true)) {
830                         std::cout << ConsoleColorTag(Console_Bold | Console_ForegroundRed)
831                                 << "Failed to disable the conf.d inclusion, it may already have been disabled.\n"
832                                 << ConsoleColorTag(Console_Normal);
833                 }
834
835                 /* Include api-users.conf */
836                 String apiUsersFilePath = Configuration::ConfigDir + "/conf.d/api-users.conf";
837
838                 std::cout << ConsoleColorTag(Console_Bold | Console_ForegroundGreen)
839                         << "Checking if the api-users.conf file exists...\n"
840                         << ConsoleColorTag(Console_Normal);
841
842                 if (Utility::PathExists(apiUsersFilePath)) {
843                         NodeUtility::UpdateConfiguration("\"conf.d/api-users.conf\"", true, false);
844                 } else {
845                         Log(LogWarning, "cli")
846                                 << "Included file '" << apiUsersFilePath << "' does not exist.";
847                 }
848         }
849
850         return 0;
851 }