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