]> granicus.if.org Git - icinga2/commitdiff
Cli: Add 'agent wizard' command
authorMichael Friedrich <michael.friedrich@netways.de>
Thu, 30 Oct 2014 15:19:51 +0000 (16:19 +0100)
committerMichael Friedrich <michael.friedrich@netways.de>
Thu, 30 Oct 2014 16:55:34 +0000 (17:55 +0100)
refs #7465

lib/cli/agentsetupcommand.cpp
lib/cli/agentutility.cpp
lib/cli/agentwizardcommand.cpp
lib/cli/pkiutility.cpp

index 82cf1e6a03cce338ccce04597d2bc6265b997501..5c7221125a08ca85befc2666255c1bfacdc8a0b8 100644 (file)
@@ -165,6 +165,8 @@ int AgentSetupCommand::SetupMaster(const boost::program_options::variables_map&
        /* does not overwrite existing files! */
        Utility::CopyFile(ca, target_ca);
 
+       //TODO: Fix permissions for CA dir (root -> icinga)
+
        /* read zones.conf and update with zone + endpoint information */
 
        Log(LogInformation, "cli", "Generating zone and object configuration.");
index 7a10fc9ecde72746bf0d1866593c66b00c11f506..922a32f136e3fcd77875c241a4caa3eb78e656ed 100644 (file)
@@ -252,17 +252,25 @@ int AgentUtility::GenerateAgentIcingaConfig(const std::vector<std::string>& endp
 
                Dictionary::Ptr my_master_endpoint = make_shared<Dictionary>();
 
-               if (tokens.size() > 1)
-                       my_master_endpoint->Set("host", tokens[1]);
+               if (tokens.size() > 1) {
+                       String host = tokens[1];
+                       host.Trim();
+                       my_master_endpoint->Set("host", host);
+               }
 
-               if (tokens.size() > 2)
-                       my_master_endpoint->Set("port", tokens[2]);
+               if (tokens.size() > 2) {
+                       String port = tokens[2];
+                       port.Trim();
+                       my_master_endpoint->Set("port", port);
+               }
 
-               my_master_endpoint->Set("__name", tokens[0]);
+               String cn = tokens[0];
+               cn.Trim();
+               my_master_endpoint->Set("__name", cn);
                my_master_endpoint->Set("__type", "Endpoint");
 
                /* save endpoint in master zone */
-               my_master_zone_members->Add(tokens[0]);
+               my_master_zone_members->Add(cn);
 
                my_config->Add(my_master_endpoint);
        }
index 222b6bf0b19113a1c76bb12ef4a7c76692ec4b19..0ab2b7184aec3fa8a4d4ebae3fe13e99c9f93088 100644 (file)
  ******************************************************************************/
 
 #include "cli/agentwizardcommand.hpp"
+#include "cli/agentutility.hpp"
+#include "cli/pkiutility.hpp"
+#include "cli/featureutility.hpp"
 #include "base/logger.hpp"
+#include "base/console.hpp"
 #include "base/application.hpp"
+#include "base/tlsutility.hpp"
 #include <boost/foreach.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>
 
@@ -49,8 +56,6 @@ String AgentWizardCommand::GetShortDescription(void) const
  */
 int AgentWizardCommand::Run(const boost::program_options::variables_map& vm, const std::vector<std::string>& ap) const
 {
-       Log(LogWarning, "cli", "TODO: Not implemented yet.");
-
        /*
         * The wizard will get all information from the user,
         * and then call all required functions.
@@ -66,7 +71,6 @@ int AgentWizardCommand::Run(const boost::program_options::variables_map& vm, con
 
        //TODO: Add sort of bash completion to path input?
 
-
        /* 0. master or agent setup?
         * 1. Ticket
         * 2. Master information for autosigning
@@ -82,5 +86,406 @@ int AgentWizardCommand::Run(const boost::program_options::variables_map& vm, con
         * 12. reload icinga2, or tell the user to
         */
 
+       std::string answer;
+       bool is_agent_setup = true;
+
+       std::cout << "Please specify if this is an agent setup ('no' installs a master setup) [Y/n]: ";
+       std::getline (std::cin, answer);
+
+       boost::algorithm::to_lower(answer);
+
+       if (Utility::Match("^n*", answer))
+               is_agent_setup = false;
+
+
+       if (is_agent_setup) {
+               /* agent setup part */
+               std::cout << "Starting the Agent setup routine...\n";
+
+               /* CN */
+               std::cout << "Please specifiy the common name (CN) [" << Utility::GetFQDN() << "]: ";
+
+               std::getline(std::cin, answer);
+               boost::algorithm::to_lower(answer);
+
+               if (answer.empty())
+                       answer = Utility::GetFQDN();
+
+               String cn = answer;
+               cn.Trim();
+
+               //TODO: Ask for endpoint config instead, and use that for master_host/port
+               std::vector<std::string> endpoints;
+
+               String endpoint_buffer;
+
+               std::cout << "Please specify the master endpoint(s) this agent should connect to:\n";
+               String master_endpoint_name;
+
+wizard_endpoint_loop_start:
+
+               std::cout << "Master CN: ";
+
+               std::getline(std::cin, answer);
+               boost::algorithm::to_lower(answer);
+
+               if(answer.empty()) {
+                       Log(LogWarning, "cli", "Master CN is required! Please retry.");
+                       goto wizard_endpoint_loop_start;
+               }
+
+               endpoint_buffer = answer;
+               endpoint_buffer.Trim();
+
+               std::cout << "Master endpoint host: ";
+
+               std::getline(std::cin, answer);
+               boost::algorithm::to_lower(answer);
+
+               if (!answer.empty()) {
+                       String tmp = answer;
+                       tmp.Trim();
+                       endpoint_buffer += "," + tmp;
+                       master_endpoint_name = tmp; //store the endpoint name for later
+               }
+
+               std::cout << "Master endpoint port: ";
+
+               std::getline(std::cin, answer);
+               boost::algorithm::to_lower(answer);
+
+               if (!answer.empty()) {
+                       String tmp = answer;
+                       tmp.Trim();
+                       endpoint_buffer += "," + answer;
+               }
+
+
+               endpoints.push_back(endpoint_buffer);
+
+               std::cout << "Add more master endpoints? [y/N]";
+               std::getline (std::cin, answer);
+
+               boost::algorithm::to_lower(answer);
+
+               if (Utility::Match("^y*", answer))
+                       goto wizard_endpoint_loop_start;
+
+
+               std::cout << "Please specify the master connection for auto-signing:\n";
+
+wizard_master_host:
+               std::cout << "Host [" << master_endpoint_name << "]: ";
+
+               std::getline(std::cin, answer);
+               boost::algorithm::to_lower(answer);
+
+               if (answer.empty() && !master_endpoint_name.IsEmpty())
+                       answer = master_endpoint_name;
+
+               if (answer.empty() && master_endpoint_name.IsEmpty())
+                       goto wizard_master_host;
+
+               String master_host = answer;
+               master_host.Trim();
+
+               std::cout << "Port [5665]: ";
+
+               std::getline(std::cin, answer);
+               boost::algorithm::to_lower(answer);
+
+               if (answer.empty())
+                       answer = "5665";
+
+               String master_port = answer;
+               master_port.Trim();
+
+               /* workaround for fetching the master cert - TODO */
+               String agent_cert = PkiUtility::GetPkiPath() + "/" + cn + ".crt";
+               String agent_key = PkiUtility::GetPkiPath() + "/" + cn + ".key";
+
+               //new-ca, new-cert
+               PkiUtility::NewCa();
+
+               if (PkiUtility::NewCert(cn, agent_key, Empty, agent_cert) > 0) {
+                       Log(LogCritical, "cli")
+                           << "Failed to create new self-signed certificate for CN '" << cn << "'. Please try again.";
+                       return 1;
+               }
+
+               /* store ca in /etc/icinga2/pki */
+               //TODO FIX chown
+               String ca = PkiUtility::GetLocalCaPath() + "/ca.crt";
+               String pki_path = PkiUtility::GetPkiPath();
+
+               String target_ca = pki_path + "/ca.crt";
+
+               Utility::CopyFile(ca, target_ca);
+
+               //save-cert and store the master certificate somewhere
+
+               std::cout << "Generating self-signed certifiate:\n";
+
+
+               std::cout << "Fetching public certificate from master ("
+                   << master_host << ", " << master_port << "):\n";
+
+               String trusted_cert = PkiUtility::GetPkiPath() + "/trusted-master.crt";
+
+               if (PkiUtility::SaveCert(master_host, master_port, agent_key, agent_cert, trusted_cert) > 0) {
+                       Log(LogCritical, "cli")
+                           << "Failed to fetch trusted master certificate. Please try again.";
+                       return 1;
+               }
+
+               std::cout << "Stored trusted master certificate in '" << trusted_cert << "'.\n";
+
+wizard_ticket:
+               std::cout << "Please specify the request ticket generated on your Icinga 2 master.\n"
+                   << "(Hint: '# icinga2 pki ticket --cn <this-CN>'): ";
+
+               std::getline(std::cin, answer);
+               boost::algorithm::to_lower(answer);
+
+               if (answer.empty())
+                       goto wizard_ticket;
+
+               String ticket = answer;
+               ticket.Trim();
+
+               std::cout << "Processing self-signed certificate request. Ticket '" << ticket << "'.\n";
+
+               if (PkiUtility::RequestCertificate(master_host, master_port, agent_key, agent_cert, ca, trusted_cert, ticket) > 0) {
+                       Log(LogCritical, "cli")
+                           << "Failed to fetch signed certificate from master '" << master_host << ", "
+                           << master_port <<"'. Please try again.";
+                       return 1;
+               }
+
+               /* apilistener config */
+               std::cout << "Please specify the API bind host/port (optional):\n";
+               std::cout << "Bind Host []: ";
+
+               std::getline(std::cin, answer);
+               boost::algorithm::to_lower(answer);
+
+               String bind_host = answer;
+               bind_host.Trim();
+
+               std::cout << "Bind Port []: ";
+
+               std::getline(std::cin, answer);
+               boost::algorithm::to_lower(answer);
+
+               String bind_port = answer;
+               bind_port.Trim();
+
+               std::cout << "Enabling the APIlistener feature.\n";
+
+               std::vector<std::string> enable;
+               enable.push_back("api");
+               FeatureUtility::EnableFeatures(enable);
+
+               String apipath = FeatureUtility::GetFeaturesAvailablePath() + "/api.conf";
+               AgentUtility::CreateBackupFile(apipath);
+
+               String apipathtmp = apipath + ".tmp";
+
+               std::ofstream fp;
+               fp.open(apipathtmp.CStr(), std::ofstream::out | std::ofstream::trunc);
+
+               fp << "/**\n"
+                   << " * The API listener is used for distributed monitoring setups.\n"
+                   << " */\n"
+                   << "object ApiListener \"api\" {\n"
+                   << "  cert_path = SysconfDir + \"/icinga2/pki/\" + NodeName + \".crt\"\n"
+                   << "  key_path = SysconfDir + \"/icinga2/pki/\" + NodeName + \".key\"\n"
+                   << "  ca_path = SysconfDir + \"/icinga2/pki/ca.crt\"\n";
+
+               if (!bind_host.IsEmpty())
+                       fp << "  bind_host = \"" << bind_host << "\"\n";
+               if (!bind_port.IsEmpty())
+                       fp << "  bind_port = " << bind_port << "\n";
+
+               fp << "\n"
+                   << "  ticket_salt = TicketSalt\n"
+                   << "}\n";
+
+               fp.close();
+
+#ifdef _WIN32
+               _unlink(apipath.CStr());
+#endif /* _WIN32 */
+
+               if (rename(apipathtmp.CStr(), apipath.CStr()) < 0) {
+                       BOOST_THROW_EXCEPTION(posix_error()
+                           << boost::errinfo_api_function("rename")
+                           << boost::errinfo_errno(errno)
+                           << boost::errinfo_file_name(apipathtmp));
+               }
+
+               /* apilistener config */
+               std::cout << "Generating local zones.conf.\n";
+
+               AgentUtility::GenerateAgentIcingaConfig(endpoints, cn);
+
+               if (cn != Utility::GetFQDN()) {
+                       Log(LogWarning, "cli")
+                           << "CN '" << cn << "' does not match the default FQDN '" << Utility::GetFQDN() << "'. Requires update for NodeName constant in constants.conf!";
+               }
+
+               std::cout << "Updating constants.conf\n";
+
+               AgentUtility::CreateBackupFile(Application::GetSysconfDir() + "/icinga2/constants.conf");
+
+               AgentUtility::UpdateConstant("NodeName", cn);
+
+       } else {
+               /* master setup */
+               std::cout << "Starting the Master setup routine...\n";
+
+               /* CN */
+               std::cout << "Please specifiy the common name (CN) (leave blank for default FQDN): ";
+
+               std::getline(std::cin, answer);
+               boost::algorithm::to_lower(answer);
+
+               if (answer.empty())
+                       answer = Utility::GetFQDN();
+
+               String cn = answer;
+               cn.Trim();
+
+               if (PkiUtility::NewCa() > 0) {
+                       Log(LogWarning, "cli", "Found CA, skipping and using the existing one.");
+               }
+
+               String pki_path = PkiUtility::GetPkiPath();
+
+               if (!Utility::MkDirP(pki_path, 0700)) {
+                       Log(LogCritical, "cli")
+                           << "Could not create local pki directory '" << pki_path << "'.";
+                       return 1;
+               }
+
+               String key = pki_path + "/" + cn + ".key";
+               String csr = pki_path + "/" + cn + ".csr";
+
+               if (PkiUtility::NewCert(cn, key, csr, "") > 0) {
+                       Log(LogCritical, "cli", "Failed to create self-signed certificate");
+                       return 1;
+               }
+
+               /* Sign the CSR with the CA key */
+
+               String cert = pki_path + "/" + cn + ".crt";
+
+               if (PkiUtility::SignCsr(csr, cert) != 0) {
+                       Log(LogCritical, "cli", "Could not sign CSR.");
+                       return 1;
+               }
+
+               /* Copy CA certificate to /etc/icinga2/pki */
+
+               String ca = PkiUtility::GetLocalCaPath() + "/ca.crt";
+               String target_ca = pki_path + "/ca.crt";
+
+               Log(LogInformation, "cli")
+                   << "Copying CA certificate to '" << target_ca << "'.";
+
+               /* does not overwrite existing files! */
+               Utility::CopyFile(ca, target_ca);
+
+               //TODO: Fix permissions for CA dir (root -> icinga)
+
+               AgentUtility::GenerateAgentMasterIcingaConfig(cn);
+
+               /* apilistener config */
+               std::cout << "Please specify the API bind host/port (optional):\n";
+               std::cout << "Bind Host []: ";
+
+               std::getline(std::cin, answer);
+               boost::algorithm::to_lower(answer);
+
+               String bind_host = answer;
+               bind_host.Trim();
+
+               std::cout << "Bind Port []: ";
+
+               std::getline(std::cin, answer);
+               boost::algorithm::to_lower(answer);
+
+               String bind_port = answer;
+               bind_port.Trim();
+
+               std::cout << "Enabling the APIlistener feature.\n";
+
+               std::vector<std::string> enable;
+               enable.push_back("api");
+               FeatureUtility::EnableFeatures(enable);
+
+               String apipath = FeatureUtility::GetFeaturesAvailablePath() + "/api.conf";
+               AgentUtility::CreateBackupFile(apipath);
+
+               String apipathtmp = apipath + ".tmp";
+
+               std::ofstream fp;
+               fp.open(apipathtmp.CStr(), std::ofstream::out | std::ofstream::trunc);
+
+               fp << "/**\n"
+                   << " * The API listener is used for distributed monitoring setups.\n"
+                   << " */\n"
+                   << "object ApiListener \"api\" {\n"
+                   << "  cert_path = SysconfDir + \"/icinga2/pki/\" + NodeName + \".crt\"\n"
+                   << "  key_path = SysconfDir + \"/icinga2/pki/\" + NodeName + \".key\"\n"
+                   << "  ca_path = SysconfDir + \"/icinga2/pki/ca.crt\"\n";
+
+               if (!bind_host.IsEmpty())
+                       fp << "  bind_host = \"" << bind_host << "\"\n";
+               if (!bind_port.IsEmpty())
+                       fp << "  bind_port = " << bind_port << "\n";
+
+               fp << "\n"
+                   << "  ticket_salt = TicketSalt\n"
+                   << "}\n";
+
+               fp.close();
+
+#ifdef _WIN32
+               _unlink(apipath.CStr());
+#endif /* _WIN32 */
+
+               if (rename(apipathtmp.CStr(), apipath.CStr()) < 0) {
+                       BOOST_THROW_EXCEPTION(posix_error()
+                           << boost::errinfo_api_function("rename")
+                           << boost::errinfo_errno(errno)
+                           << boost::errinfo_file_name(apipathtmp));
+               }
+
+               /* 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 update for NodeName constant in constants.conf!";
+               }
+
+               Log(LogInformation, "cli", "Updating constants.conf.");
+
+               AgentUtility::CreateBackupFile(Application::GetSysconfDir() + "/icinga2/constants.conf");
+
+               AgentUtility::UpdateConstant("NodeName", cn);
+
+               String salt = RandomString(16);
+
+               AgentUtility::UpdateConstant("TicketSalt", salt);
+
+               Log(LogInformation, "cli")
+                   << "Edit the api feature config file '" << apipath << "' and set a secure 'ticket_salt' attribute.";
+       }
+
+       std::cout << "Now restart your Icinga 2 agent to finish the installation!\n";
+
+       std::cout << "If you encounter problems or bugs, please do not hesitate to\n"
+           << "get in touch with the community at https://support.icinga.org" << std::endl;
+
        return 0;
 }
index 6e07ac579bd162123388d32697b58c86e4dd9ac9..140a306198c848e7e7cf3e9cadabb322ecf80914 100644 (file)
@@ -245,6 +245,11 @@ int PkiUtility::RequestCertificate(const String& host, const String& port, const
                break;
        }
 
+       if (!response->Contains("result")) {
+               Log(LogCritical, "cli", "Request certificate did not return a valid result. Check the master log for details!");
+               return 1;
+       }
+
        Dictionary::Ptr result = response->Get("result");
 
        if (result->Contains("error")) {