diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 12:34:54 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 12:34:54 +0000 |
commit | 0915b3ef56dfac3113cce55a59a5765dc94976be (patch) | |
tree | a8fea11d50b4f083e1bf0f90025ece7f0824784a /lib/cli/nodewizardcommand.cpp | |
parent | Initial commit. (diff) | |
download | icinga2-0915b3ef56dfac3113cce55a59a5765dc94976be.tar.xz icinga2-0915b3ef56dfac3113cce55a59a5765dc94976be.zip |
Adding upstream version 2.13.6.upstream/2.13.6upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'lib/cli/nodewizardcommand.cpp')
-rw-r--r-- | lib/cli/nodewizardcommand.cpp | 824 |
1 files changed, 824 insertions, 0 deletions
diff --git a/lib/cli/nodewizardcommand.cpp b/lib/cli/nodewizardcommand.cpp new file mode 100644 index 0000000..abf87d8 --- /dev/null +++ b/lib/cli/nodewizardcommand.cpp @@ -0,0 +1,824 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include "cli/nodewizardcommand.hpp" +#include "cli/nodeutility.hpp" +#include "cli/featureutility.hpp" +#include "cli/apisetuputility.hpp" +#include "remote/apilistener.hpp" +#include "remote/pkiutility.hpp" +#include "base/logger.hpp" +#include "base/console.hpp" +#include "base/application.hpp" +#include "base/tlsutility.hpp" +#include "base/scriptglobal.hpp" +#include "base/exception.hpp" +#include <boost/algorithm/string/join.hpp> +#include <boost/algorithm/string/replace.hpp> +#include <boost/algorithm/string/case_conv.hpp> +#include <iostream> +#include <string> +#include <fstream> +#include <vector> + +using namespace icinga; +namespace po = boost::program_options; + +REGISTER_CLICOMMAND("node/wizard", NodeWizardCommand); + +String NodeWizardCommand::GetDescription() const +{ + return "Wizard for Icinga 2 node setup."; +} + +String NodeWizardCommand::GetShortDescription() const +{ + return "wizard for node setup"; +} + +ImpersonationLevel NodeWizardCommand::GetImpersonationLevel() const +{ + return ImpersonateIcinga; +} + +int NodeWizardCommand::GetMaxArguments() const +{ + return -1; +} + +void NodeWizardCommand::InitParameters(boost::program_options::options_description& visibleDesc, + boost::program_options::options_description& hiddenDesc) const +{ + visibleDesc.add_options() + ("verbose", "increase log level"); +} + +/** + * The entry point for the "node wizard" CLI command. + * + * @returns An exit status. + */ +int NodeWizardCommand::Run(const boost::program_options::variables_map& vm, + const std::vector<std::string>& ap) const +{ + if (!vm.count("verbose")) + Logger::SetConsoleLogSeverity(LogCritical); + + /* + * The wizard will get all information from the user, + * and then call all required functions. + */ + + std::cout << ConsoleColorTag(Console_Bold | Console_ForegroundBlue) + << "Welcome to the Icinga 2 Setup Wizard!\n" + << "\n" + << "We will guide you through all required configuration details.\n" + << "\n" + << ConsoleColorTag(Console_Normal); + + /* 0. master or node setup? + * 1. Ticket + * 2. Master information for autosigning + * 3. Trusted cert location + * 4. CN to use (defaults to FQDN) + * 5. Local CA + * 6. New self signed certificate + * 7. Request signed certificate from master + * 8. copy key information to /var/lib/icinga2/certs + * 9. enable ApiListener feature + * 10. generate zones.conf with endpoints and zone objects + * 11. set NodeName = cn and ZoneName in constants.conf + * 12. disable conf.d directory? + * 13. reload icinga2, or tell the user to + */ + + std::string answer; + /* master or satellite/agent setup */ + std::cout << ConsoleColorTag(Console_Bold) + << "Please specify if this is an agent/satellite setup " + << "('n' installs a master setup)" << ConsoleColorTag(Console_Normal) + << " [Y/n]: "; + std::getline (std::cin, answer); + + boost::algorithm::to_lower(answer); + + String choice = answer; + + std::cout << "\n"; + + int res = 0; + + if (choice.Contains("n")) + res = MasterSetup(); + else + res = AgentSatelliteSetup(); + + if (res != 0) + return res; + + std::cout << "\n"; + std::cout << ConsoleColorTag(Console_Bold | Console_ForegroundGreen) + << "Done.\n\n" + << ConsoleColorTag(Console_Normal); + + std::cout << ConsoleColorTag(Console_Bold | Console_ForegroundRed) + << "Now restart your Icinga 2 daemon to finish the installation!\n" + << ConsoleColorTag(Console_Normal); + + return 0; +} + +int NodeWizardCommand::AgentSatelliteSetup() const +{ + std::string answer; + String choice; + bool connectToParent = false; + + std::cout << "Starting the Agent/Satellite setup routine...\n\n"; + + /* CN */ + std::cout << ConsoleColorTag(Console_Bold) + << "Please specify the common name (CN)" + << ConsoleColorTag(Console_Normal) + << " [" << Utility::GetFQDN() << "]: "; + + std::getline(std::cin, answer); + + if (answer.empty()) + answer = Utility::GetFQDN(); + + String cn = answer; + cn = cn.Trim(); + + std::vector<std::string> endpoints; + + String endpointBuffer; + + std::cout << ConsoleColorTag(Console_Bold) + << "\nPlease specify the parent endpoint(s) (master or satellite) where this node should connect to:" + << ConsoleColorTag(Console_Normal) << "\n"; + String parentEndpointName; + +wizard_endpoint_loop_start: + + std::cout << ConsoleColorTag(Console_Bold) + << "Master/Satellite Common Name" << ConsoleColorTag(Console_Normal) + << " (CN from your master/satellite node): "; + + std::getline(std::cin, answer); + + if (answer.empty()) { + Log(LogWarning, "cli", "Master/Satellite CN is required! Please retry."); + goto wizard_endpoint_loop_start; + } + + endpointBuffer = answer; + endpointBuffer = endpointBuffer.Trim(); + + std::cout << "\nDo you want to establish a connection to the parent node " + << ConsoleColorTag(Console_Bold) << "from this node?" + << ConsoleColorTag(Console_Normal) << " [Y/n]: "; + + std::getline (std::cin, answer); + boost::algorithm::to_lower(answer); + choice = answer; + + String parentEndpointPort = "5665"; + + if (choice.Contains("n")) { + connectToParent = false; + + Log(LogWarning, "cli", "Node to master/satellite connection setup skipped"); + std::cout << "Connection setup skipped. Please configure your parent node to\n" + << "connect to this node by setting the 'host' attribute for the node Endpoint object.\n"; + + } else { + connectToParent = true; + + std::cout << ConsoleColorTag(Console_Bold) + << "Please specify the master/satellite connection information:" + << ConsoleColorTag(Console_Normal) << "\n" + << ConsoleColorTag(Console_Bold) << "Master/Satellite endpoint host" + << ConsoleColorTag(Console_Normal) << " (IP address or FQDN): "; + + std::getline(std::cin, answer); + + if (answer.empty()) { + Log(LogWarning, "cli", "Please enter the parent endpoint (master/satellite) connection information."); + goto wizard_endpoint_loop_start; + } + + String tmp = answer; + tmp = tmp.Trim(); + + endpointBuffer += "," + tmp; + parentEndpointName = tmp; + + std::cout << ConsoleColorTag(Console_Bold) + << "Master/Satellite endpoint port" << ConsoleColorTag(Console_Normal) + << " [" << parentEndpointPort << "]: "; + + std::getline(std::cin, answer); + + if (!answer.empty()) + parentEndpointPort = answer; + + endpointBuffer += "," + parentEndpointPort.Trim(); + } + + endpoints.push_back(endpointBuffer); + + std::cout << ConsoleColorTag(Console_Bold) << "\nAdd more master/satellite endpoints?" + << ConsoleColorTag(Console_Normal) << " [y/N]: "; + std::getline (std::cin, answer); + + boost::algorithm::to_lower(answer); + + choice = answer; + + if (choice.Contains("y")) + goto wizard_endpoint_loop_start; + + /* Extract parent node information. */ + String parentHost, parentPort; + + for (const String& endpoint : endpoints) { + std::vector<String> tokens = endpoint.Split(","); + + if (tokens.size() > 1) + parentHost = tokens[1]; + + if (tokens.size() > 2) + parentPort = tokens[2]; + } + + /* workaround for fetching the master cert */ + String certsDir = ApiListener::GetCertsDir(); + Utility::MkDirP(certsDir, 0700); + + String user = Configuration::RunAsUser; + String group = Configuration::RunAsGroup; + + if (!Utility::SetFileOwnership(certsDir, user, group)) { + Log(LogWarning, "cli") + << "Cannot set ownership for user '" << user + << "' group '" << group + << "' on file '" << certsDir << "'. Verify it yourself!"; + } + + String nodeCert = certsDir + "/" + cn + ".crt"; + String nodeKey = certsDir + "/" + cn + ".key"; + + if (Utility::PathExists(nodeKey)) + NodeUtility::CreateBackupFile(nodeKey, true); + if (Utility::PathExists(nodeCert)) + NodeUtility::CreateBackupFile(nodeCert); + + if (PkiUtility::NewCert(cn, nodeKey, Empty, nodeCert) > 0) { + Log(LogCritical, "cli") + << "Failed to create new self-signed certificate for CN '" + << cn << "'. Please try again."; + return 1; + } + + /* fix permissions: root -> icinga daemon user */ + if (!Utility::SetFileOwnership(nodeKey, user, group)) { + Log(LogWarning, "cli") + << "Cannot set ownership for user '" << user + << "' group '" << group + << "' on file '" << nodeKey << "'. Verify it yourself!"; + } + + std::shared_ptr<X509> trustedParentCert; + + /* Check whether we should connect to the parent node and present its trusted certificate. */ + if (connectToParent) { + //save-cert and store the master certificate somewhere + Log(LogInformation, "cli") + << "Fetching public certificate from master (" + << parentHost << ", " << parentPort << "):\n"; + + trustedParentCert = PkiUtility::FetchCert(parentHost, parentPort); + if (!trustedParentCert) { + Log(LogCritical, "cli", "Peer did not present a valid certificate."); + return 1; + } + + std::cout << ConsoleColorTag(Console_Bold) << "Parent certificate information:\n" + << ConsoleColorTag(Console_Normal) << PkiUtility::GetCertificateInformation(trustedParentCert) + << ConsoleColorTag(Console_Bold) << "\nIs this information correct?" + << ConsoleColorTag(Console_Normal) << " [y/N]: "; + + std::getline (std::cin, answer); + boost::algorithm::to_lower(answer); + if (answer != "y") { + Log(LogWarning, "cli", "Process aborted."); + return 1; + } + + Log(LogInformation, "cli", "Received trusted parent certificate.\n"); + } + +wizard_ticket: + String nodeCA = certsDir + "/ca.crt"; + String ticket; + + /* Check whether we can connect to the parent node and fetch the client and CA certificate. */ + if (connectToParent) { + std::cout << ConsoleColorTag(Console_Bold) + << "\nPlease specify the request ticket generated on your Icinga 2 master " + << ConsoleColorTag(Console_Normal) << "(optional)" + << ConsoleColorTag(Console_Bold) << "." + << ConsoleColorTag(Console_Normal) << "\n" + << " (Hint: # icinga2 pki ticket --cn '" << cn << "'): "; + + std::getline(std::cin, answer); + + if (answer.empty()) { + std::cout << ConsoleColorTag(Console_Bold) << "\n" + << "No ticket was specified. Please approve the certificate signing request manually\n" + << "on the master (see 'icinga2 ca list' and 'icinga2 ca sign --help' for details)." + << ConsoleColorTag(Console_Normal) << "\n"; + } + + ticket = answer; + ticket = ticket.Trim(); + + if (ticket.IsEmpty()) { + Log(LogInformation, "cli") + << "Requesting certificate without a ticket."; + } else { + Log(LogInformation, "cli") + << "Requesting certificate with ticket '" << ticket << "'."; + } + + if (Utility::PathExists(nodeCA)) + NodeUtility::CreateBackupFile(nodeCA); + if (Utility::PathExists(nodeCert)) + NodeUtility::CreateBackupFile(nodeCert); + + if (PkiUtility::RequestCertificate(parentHost, parentPort, nodeKey, + nodeCert, nodeCA, trustedParentCert, ticket) > 0) { + Log(LogCritical, "cli") + << "Failed to fetch signed certificate from master '" + << parentHost << ", " + << parentPort << "'. Please try again."; + goto wizard_ticket; + } + + /* fix permissions (again) when updating the signed certificate */ + if (!Utility::SetFileOwnership(nodeCert, user, group)) { + Log(LogWarning, "cli") + << "Cannot set ownership for user '" << user + << "' group '" << group << "' on file '" + << nodeCert << "'. Verify it yourself!"; + } + } else { + /* We cannot retrieve the parent certificate. + * Tell the user to manually copy the ca.crt file + * into DataDir + "/certs" + */ + + std::cout << ConsoleColorTag(Console_Bold) + << "\nNo connection to the parent node was specified.\n\n" + << "Please copy the public CA certificate from your master/satellite\n" + << "into '" << nodeCA << "' before starting Icinga 2.\n" + << ConsoleColorTag(Console_Normal); + + if (Utility::PathExists(nodeCA)) { + std::cout << ConsoleColorTag(Console_Bold) + << "\nFound public CA certificate in '" << nodeCA << "'.\n" + << "Please verify that it is the same as on your master/satellite.\n" + << ConsoleColorTag(Console_Normal); + } + + } + + /* apilistener config */ + std::cout << ConsoleColorTag(Console_Bold) + << "Please specify the API bind host/port " + << ConsoleColorTag(Console_Normal) << "(optional)" + << ConsoleColorTag(Console_Bold) << ":\n"; + + std::cout << ConsoleColorTag(Console_Bold) + << "Bind Host" << ConsoleColorTag(Console_Normal) << " []: "; + + std::getline(std::cin, answer); + + String bindHost = answer; + bindHost = bindHost.Trim(); + + std::cout << ConsoleColorTag(Console_Bold) + << "Bind Port" << ConsoleColorTag(Console_Normal) << " []: "; + + std::getline(std::cin, answer); + + String bindPort = answer; + bindPort = bindPort.Trim(); + + std::cout << ConsoleColorTag(Console_Bold) << "\n" + << "Accept config from parent node?" << ConsoleColorTag(Console_Normal) + << " [y/N]: "; + std::getline(std::cin, answer); + boost::algorithm::to_lower(answer); + choice = answer; + + String acceptConfig = choice.Contains("y") ? "true" : "false"; + + std::cout << ConsoleColorTag(Console_Bold) + << "Accept commands from parent node?" << ConsoleColorTag(Console_Normal) + << " [y/N]: "; + std::getline(std::cin, answer); + boost::algorithm::to_lower(answer); + choice = answer; + + String acceptCommands = choice.Contains("y") ? "true" : "false"; + + std::cout << "\n"; + + std::cout << ConsoleColorTag(Console_Bold | Console_ForegroundGreen) + << "Reconfiguring Icinga...\n" + << ConsoleColorTag(Console_Normal); + + /* disable the notifications feature on agent/satellite nodes */ + Log(LogInformation, "cli", "Disabling the Notification feature."); + + FeatureUtility::DisableFeatures({ "notification" }); + + Log(LogInformation, "cli", "Enabling the ApiListener feature."); + + FeatureUtility::EnableFeatures({ "api" }); + + String apiConfPath = FeatureUtility::GetFeaturesAvailablePath() + "/api.conf"; + NodeUtility::CreateBackupFile(apiConfPath); + + std::fstream fp; + String tempApiConfPath = Utility::CreateTempFile(apiConfPath + ".XXXXXX", 0644, fp); + + fp << "/**\n" + << " * The API listener is used for distributed monitoring setups.\n" + << " */\n" + << "object ApiListener \"api\" {\n" + << " accept_config = " << acceptConfig << "\n" + << " accept_commands = " << acceptCommands << "\n"; + + if (!bindHost.IsEmpty()) + fp << " bind_host = \"" << bindHost << "\"\n"; + if (!bindPort.IsEmpty()) + fp << " bind_port = " << bindPort << "\n"; + + fp << "}\n"; + + fp.close(); + + Utility::RenameFile(tempApiConfPath, apiConfPath); + + /* Zones configuration. */ + Log(LogInformation, "cli", "Generating local zones.conf."); + + /* Setup command hardcodes this as FQDN */ + String endpointName = cn; + + /* Different local zone name. */ + std::cout << "\nLocal zone name [" + endpointName + "]: "; + std::getline(std::cin, answer); + + if (answer.empty()) + answer = endpointName; + + String zoneName = answer; + zoneName = zoneName.Trim(); + + /* Different parent zone name. */ + std::cout << "Parent zone name [master]: "; + std::getline(std::cin, answer); + + if (answer.empty()) + answer = "master"; + + String parentZoneName = answer; + parentZoneName = parentZoneName.Trim(); + + /* Global zones. */ + std::vector<String> globalZones { "global-templates", "director-global" }; + + std::cout << "\nDefault global zones: " << boost::algorithm::join(globalZones, " "); + std::cout << "\nDo you want to specify additional global zones? [y/N]: "; + + std::getline(std::cin, answer); + boost::algorithm::to_lower(answer); + choice = answer; + +wizard_global_zone_loop_start: + if (choice.Contains("y")) { + std::cout << "\nPlease specify the name of the global Zone: "; + + std::getline(std::cin, answer); + + if (answer.empty()) { + std::cout << "\nName of the global Zone is required! Please retry."; + goto wizard_global_zone_loop_start; + } + + String globalZoneName = answer; + globalZoneName = globalZoneName.Trim(); + + if (std::find(globalZones.begin(), globalZones.end(), globalZoneName) != globalZones.end()) { + std::cout << "The global zone '" << globalZoneName << "' is already specified." + << " Please retry."; + goto wizard_global_zone_loop_start; + } + + globalZones.push_back(globalZoneName); + + std::cout << "\nDo you want to specify another global zone? [y/N]: "; + + std::getline(std::cin, answer); + boost::algorithm::to_lower(answer); + choice = answer; + + if (choice.Contains("y")) + goto wizard_global_zone_loop_start; + } else + Log(LogInformation, "cli", "No additional global Zones have been specified"); + + /* Generate node configuration. */ + NodeUtility::GenerateNodeIcingaConfig(endpointName, zoneName, parentZoneName, endpoints, globalZones); + + if (endpointName != Utility::GetFQDN()) { + Log(LogWarning, "cli") + << "CN/Endpoint name '" << endpointName << "' does not match the default FQDN '" + << Utility::GetFQDN() << "'. Requires update for NodeName constant in constants.conf!"; + } + + NodeUtility::UpdateConstant("NodeName", endpointName); + NodeUtility::UpdateConstant("ZoneName", zoneName); + + if (!ticket.IsEmpty()) { + String ticketPath = ApiListener::GetCertsDir() + "/ticket"; + + String tempTicketPath = Utility::CreateTempFile(ticketPath + ".XXXXXX", 0600, fp); + + if (!Utility::SetFileOwnership(tempTicketPath, user, group)) { + Log(LogWarning, "cli") + << "Cannot set ownership for user '" << user + << "' group '" << group + << "' on file '" << tempTicketPath << "'. Verify it yourself!"; + } + + fp << ticket; + + fp.close(); + + Utility::RenameFile(tempTicketPath, ticketPath); + } + + /* If no parent connection was made, the user must supply the ca.crt before restarting Icinga 2.*/ + if (!connectToParent) { + Log(LogWarning, "cli") + << "No connection to the parent node was specified.\n\n" + << "Please copy the public CA certificate from your master/satellite\n" + << "into '" << nodeCA << "' before starting Icinga 2.\n"; + } else { + Log(LogInformation, "cli", "Make sure to restart Icinga 2."); + } + + /* Disable conf.d inclusion */ + std::cout << "\nDo you want to disable the inclusion of the conf.d directory [Y/n]: "; + + std::getline(std::cin, answer); + boost::algorithm::to_lower(answer); + choice = answer; + + if (choice.Contains("n")) + Log(LogInformation, "cli") + << "conf.d directory has not been disabled."; + else { + std::cout << ConsoleColorTag(Console_Bold | Console_ForegroundGreen) + << "Disabling the inclusion of the conf.d directory...\n" + << ConsoleColorTag(Console_Normal); + + if (!NodeUtility::UpdateConfiguration("\"conf.d\"", false, true)) { + std::cout << ConsoleColorTag(Console_Bold | Console_ForegroundRed) + << "Failed to disable the conf.d inclusion, it may already have been disabled.\n" + << ConsoleColorTag(Console_Normal); + } + + /* Satellite/Agents should not include the api-users.conf file. + * The configuration should instead be managed via config sync or automation tools. + */ + } + + return 0; +} + +int NodeWizardCommand::MasterSetup() const +{ + std::string answer; + String choice; + + std::cout << ConsoleColorTag(Console_Bold) << "Starting the Master setup routine...\n\n"; + + /* CN */ + std::cout << ConsoleColorTag(Console_Bold) + << "Please specify the common name" << ConsoleColorTag(Console_Normal) + << " (CN) [" << Utility::GetFQDN() << "]: "; + + std::getline(std::cin, answer); + + if (answer.empty()) + answer = Utility::GetFQDN(); + + String cn = answer; + cn = cn.Trim(); + + std::cout << ConsoleColorTag(Console_Bold | Console_ForegroundGreen) + << "Reconfiguring Icinga...\n" + << ConsoleColorTag(Console_Normal); + + /* check whether the user wants to generate a new certificate or not */ + String existing_path = ApiListener::GetCertsDir() + "/" + cn + ".crt"; + + std::cout << ConsoleColorTag(Console_Normal) + << "Checking for existing certificates for common name '" << cn << "'...\n"; + + if (Utility::PathExists(existing_path)) { + std::cout << "Certificate '" << existing_path << "' for CN '" + << cn << "' already existing. Skipping certificate generation.\n"; + } else { + std::cout << "Certificates not yet generated. Running 'api setup' now.\n"; + ApiSetupUtility::SetupMasterCertificates(cn); + } + + std::cout << ConsoleColorTag(Console_Bold) + << "Generating master configuration for Icinga 2.\n" + << ConsoleColorTag(Console_Normal); + + ApiSetupUtility::SetupMasterApiUser(); + + if (!FeatureUtility::CheckFeatureEnabled("api")) + ApiSetupUtility::SetupMasterEnableApi(); + else + std::cout << "'api' feature already enabled.\n"; + + /* Setup command hardcodes this as FQDN */ + String endpointName = cn; + + /* Different zone name. */ + std::cout << "\nMaster zone name [master]: "; + std::getline(std::cin, answer); + + if (answer.empty()) + answer = "master"; + + String zoneName = answer; + zoneName = zoneName.Trim(); + + /* Global zones. */ + std::vector<String> globalZones { "global-templates", "director-global" }; + + std::cout << "\nDefault global zones: " << boost::algorithm::join(globalZones, " "); + std::cout << "\nDo you want to specify additional global zones? [y/N]: "; + + std::getline(std::cin, answer); + boost::algorithm::to_lower(answer); + choice = answer; + +wizard_global_zone_loop_start: + if (choice.Contains("y")) { + std::cout << "\nPlease specify the name of the global Zone: "; + + std::getline(std::cin, answer); + + if (answer.empty()) { + std::cout << "\nName of the global Zone is required! Please retry."; + goto wizard_global_zone_loop_start; + } + + String globalZoneName = answer; + globalZoneName = globalZoneName.Trim(); + + if (std::find(globalZones.begin(), globalZones.end(), globalZoneName) != globalZones.end()) { + std::cout << "The global zone '" << globalZoneName << "' is already specified." + << " Please retry."; + goto wizard_global_zone_loop_start; + } + + globalZones.push_back(globalZoneName); + + std::cout << "\nDo you want to specify another global zone? [y/N]: "; + + std::getline(std::cin, answer); + boost::algorithm::to_lower(answer); + choice = answer; + + if (choice.Contains("y")) + goto wizard_global_zone_loop_start; + } else + Log(LogInformation, "cli", "No additional global Zones have been specified"); + + /* Generate master configuration. */ + NodeUtility::GenerateNodeMasterIcingaConfig(endpointName, zoneName, globalZones); + + /* apilistener config */ + std::cout << ConsoleColorTag(Console_Bold) + << "Please specify the API bind host/port " + << ConsoleColorTag(Console_Normal) << "(optional)" + << ConsoleColorTag(Console_Bold) << ":\n"; + + std::cout << ConsoleColorTag(Console_Bold) + << "Bind Host" << ConsoleColorTag(Console_Normal) << " []: "; + + std::getline(std::cin, answer); + + String bindHost = answer; + bindHost = bindHost.Trim(); + + std::cout << ConsoleColorTag(Console_Bold) + << "Bind Port" << ConsoleColorTag(Console_Normal) << " []: "; + + std::getline(std::cin, answer); + + String bindPort = answer; + bindPort = bindPort.Trim(); + + /* api feature is always enabled, check above */ + String apiConfPath = FeatureUtility::GetFeaturesAvailablePath() + "/api.conf"; + NodeUtility::CreateBackupFile(apiConfPath); + + std::fstream fp; + String tempApiConfPath = Utility::CreateTempFile(apiConfPath + ".XXXXXX", 0644, fp); + + fp << "/**\n" + << " * The API listener is used for distributed monitoring setups.\n" + << " */\n" + << "object ApiListener \"api\" {\n"; + + if (!bindHost.IsEmpty()) + fp << " bind_host = \"" << bindHost << "\"\n"; + if (!bindPort.IsEmpty()) + fp << " bind_port = " << bindPort << "\n"; + + fp << "\n" + << " ticket_salt = TicketSalt\n" + << "}\n"; + + fp.close(); + + Utility::RenameFile(tempApiConfPath, apiConfPath); + + /* update constants.conf with NodeName = CN + TicketSalt = random value */ + if (cn != Utility::GetFQDN()) { + Log(LogWarning, "cli") + << "CN '" << cn << "' does not match the default FQDN '" + << Utility::GetFQDN() << "'. Requires an update for the NodeName constant in constants.conf!"; + } + + Log(LogInformation, "cli", "Updating constants.conf."); + + NodeUtility::CreateBackupFile(NodeUtility::GetConstantsConfPath()); + + NodeUtility::UpdateConstant("NodeName", endpointName); + NodeUtility::UpdateConstant("ZoneName", zoneName); + + String salt = RandomString(16); + + NodeUtility::UpdateConstant("TicketSalt", salt); + + /* Disable conf.d inclusion */ + std::cout << "\nDo you want to disable the inclusion of the conf.d directory [Y/n]: "; + + std::getline(std::cin, answer); + boost::algorithm::to_lower(answer); + choice = answer; + + if (choice.Contains("n")) + Log(LogInformation, "cli") + << "conf.d directory has not been disabled."; + else { + std::cout << ConsoleColorTag(Console_Bold | Console_ForegroundGreen) + << "Disabling the inclusion of the conf.d directory...\n" + << ConsoleColorTag(Console_Normal); + + if (!NodeUtility::UpdateConfiguration("\"conf.d\"", false, true)) { + std::cout << ConsoleColorTag(Console_Bold | Console_ForegroundRed) + << "Failed to disable the conf.d inclusion, it may already have been disabled.\n" + << ConsoleColorTag(Console_Normal); + } + + /* Include api-users.conf */ + String apiUsersFilePath = Configuration::ConfigDir + "/conf.d/api-users.conf"; + + std::cout << ConsoleColorTag(Console_Bold | Console_ForegroundGreen) + << "Checking if the api-users.conf file exists...\n" + << ConsoleColorTag(Console_Normal); + + if (Utility::PathExists(apiUsersFilePath)) { + NodeUtility::UpdateConfiguration("\"conf.d/api-users.conf\"", true, false); + } else { + Log(LogWarning, "cli") + << "Included file '" << apiUsersFilePath << "' does not exist."; + } + } + + return 0; +} |