From: Michael Friedrich Date: Tue, 21 Oct 2014 19:33:21 +0000 (+0200) Subject: Cli: Add basic setup agent calls (unfinished) X-Git-Tag: v2.2.0~300 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=bda94f14f4c720f1c49c13e2eaf0292d46f5b7c6;p=icinga2 Cli: Add basic setup agent calls (unfinished) refs #7423 --- diff --git a/lib/cli/agentsetupcommand.cpp b/lib/cli/agentsetupcommand.cpp index 8593053d6..511c78dbe 100644 --- a/lib/cli/agentsetupcommand.cpp +++ b/lib/cli/agentsetupcommand.cpp @@ -21,12 +21,19 @@ #include "cli/agentutility.hpp" #include "cli/featureutility.hpp" #include "cli/pkiutility.hpp" +#include "config/configcompilercontext.hpp" +#include "config/configcompiler.hpp" +#include "config/configitembuilder.hpp" #include "base/logger.hpp" #include "base/console.hpp" #include "base/application.hpp" +#include "base/dynamictype.hpp" #include +#include #include #include +#include + #include #include #include @@ -52,6 +59,7 @@ void AgentSetupCommand::InitParameters(boost::program_options::options_descripti visibleDesc.add_options() ("zone", po::value(), "The name of the local zone") ("master_zone", po::value(), "The name of the master zone") + ("master_host", po::value(), "The name of the master host for auto-signing the csr") ("endpoint", po::value >(), "Connect to remote endpoint(s) on host,port") ("listen", po::value(), "Listen on host,port") ("ticket", po::value(), "Generated ticket number for this request") @@ -95,7 +103,10 @@ int AgentSetupCommand::Run(const boost::program_options::variables_map& vm, cons int AgentSetupCommand::SetupMaster(const boost::program_options::variables_map& vm, const std::vector& ap) { - /* 1. Generate a new CA, if not already existing */ + /* + * 1. Generate a new CA, if not already existing + */ + Log(LogInformation, "cli") << "Generating new CA."; @@ -103,7 +114,10 @@ int AgentSetupCommand::SetupMaster(const boost::program_options::variables_map& Log(LogWarning, "cli", "Found CA, skipping and using the existing one.\n"); } - /* 2. Generate a self signed certificate */ + /* + * 2. Generate a self signed certificate + */ + Log(LogInformation, "cli", "Generating new self-signed certificate."); String local_pki_path = PkiUtility::GetLocalPkiPath(); @@ -127,7 +141,10 @@ int AgentSetupCommand::SetupMaster(const boost::program_options::variables_map& Log(LogCritical, "cli", "Failed to create self-signed certificate"); } - /* 3. Copy certificates to /etc/icinga2/pki */ + /* + * 3. Copy certificates to /etc/icinga2/pki + */ + String pki_path = PkiUtility::GetPkiPath(); Log(LogInformation, "cli") @@ -144,23 +161,39 @@ int AgentSetupCommand::SetupMaster(const boost::program_options::variables_map& std::cout << ConsoleColorTag(Console_ForegroundRed | Console_Bold) << "PLACEHOLDER" << ConsoleColorTag(Console_Normal) << std::endl; - /* 4. read zones.conf and update with zone + endpoint information */ + /* + * 4. read zones.conf and update with zone + endpoint information + */ + Log(LogInformation, "cli", "Generating zone and object configuration."); std::cout << ConsoleColorTag(Console_ForegroundRed | Console_Bold) << "PLACEHOLDER" << ConsoleColorTag(Console_Normal) << std::endl; - /* 5. enable the ApiListener config (verifiy its data) */ + /* + * 5. enable the ApiListener config (verifiy its data) + */ + Log(LogInformation, "cli", "Enabling the APIListener feature."); String api_path = FeatureUtility::GetFeaturesEnabledPath() + "/api.conf"; + //TODO: verify that the correct attributes are set on the ApiListener object + //by reading the configuration (CompileFile) and fetching the object - std::cout << ConsoleColorTag(Console_ForegroundRed | Console_Bold) << "PLACEHOLDER" << ConsoleColorTag(Console_Normal) << std::endl; + std::vector enable; + enable.push_back("api"); + FeatureUtility::EnableFeatures(enable); + + /* + * 6. tell the user to set a safe salt in api.conf + */ - /* 5. tell the user to set a safe salt in api.conf */ Log(LogInformation, "cli") << "Edit the api feature config file '" << api_path << "' and set a secure 'ticket_salt' attribute."; - /* 6. tell the user to reload icinga2 */ + /* + * 7. tell the user to reload icinga2 + */ + Log(LogInformation, "cli", "Make sure to restart Icinga 2."); return 0; @@ -168,7 +201,10 @@ int AgentSetupCommand::SetupMaster(const boost::program_options::variables_map& int AgentSetupCommand::SetupAgent(const boost::program_options::variables_map& vm, const std::vector& ap) { - /* 1. require ticket number (generated on master) */ + /* + * 1. require ticket number (generated on master) + */ + if (!vm.count("ticket")) { Log(LogCritical, "cli") << "Please pass the ticket number generated on master\n" @@ -181,7 +217,36 @@ int AgentSetupCommand::SetupAgent(const boost::program_options::variables_map& v Log(LogInformation, "cli") << "Verifying ticket '" << ticket << "'."; - /* 2. trusted cert must be passed (retrieved by the user with 'pki save-cert' before) */ + /* + * 2. require master host information for auto-signing requests + */ + + if (!vm.count("master_host")) { + Log(LogCritical, "cli") + << "Please pass the master host connection information for auto-signing using '--master_host '"; + return 1; + } + + std::vector tokens; + boost::algorithm::split(tokens, vm["master_host"].as(), boost::is_any_of(",")); + String master_host; + String master_port = "5665"; + + if (tokens.size() == 1 || tokens.size() == 2) + master_host = tokens[0]; + + if (tokens.size() == 2) + master_port = tokens[1]; + + + Log(LogInformation, "cli") + << "Verifying master host connection information: host '" << master_host << "', port '" << master_port << "'."; + + + /* + * 2. trusted cert must be passed (retrieved by the user with 'pki save-cert' before) + */ + if (!vm.count("trustedcert")) { Log(LogCritical, "cli") << "Please pass the trusted cert retrieved from the master\n" @@ -199,36 +264,178 @@ int AgentSetupCommand::SetupAgent(const boost::program_options::variables_map& v if (vm.count("cn")) cn = vm["cn"].as(); - /* 3. retrieve CN and pass it if requested (defaults to FQDN) */ + String NodeName = cn; + String ZoneName = cn; + + /* + * 3. retrieve CN and pass it (defaults to FQDN) + */ + Log(LogInformation, "cli") - << "Verifying CN (defaults to FQDN): '" << cn << "'."; + << "Using the following CN (defaults to FQDN): '" << cn << "'."; - std::cout << ConsoleColorTag(Console_ForegroundRed | Console_Bold) << "PLACEHOLDER" << ConsoleColorTag(Console_Normal) << std::endl; + /* + * 4. new ca, new cert and pki request a signed certificate from the master + */ + + String local_pki_path = PkiUtility::GetLocalPkiPath(); + + Log(LogInformation, "cli") + << "Generating new CA."; + + if (PkiUtility::NewCa() > 0) { + Log(LogWarning, "cli") + << "Found CA, skipping and using the existing one."; + } + + Log(LogInformation, "cli") + << "Generating a self-signed certificate."; + + String keyfile = local_pki_path + "/" + cn + ".key"; + String certfile = local_pki_path + "/" + cn + ".crt"; + String cafile = PkiUtility::GetLocalCaPath() + "/ca.crt"; + + if (PkiUtility::NewCert(cn, keyfile, Empty, certfile) > 0) { + Log(LogCritical, "cli") + << "Failed to create self-signed certificate"; + } + + /* + * 5. Copy certificates to /etc/icinga2/pki + */ String pki_path = PkiUtility::GetPkiPath(); - /* 4. pki request a signed certificate from the master */ Log(LogInformation, "cli", "Requesting a signed certificate from the master."); - std::cout << ConsoleColorTag(Console_ForegroundRed | Console_Bold) << "PLACEHOLDER" << ConsoleColorTag(Console_Normal) << std::endl; + String port = "5665"; + + PkiUtility::RequestCertificate(master_host, master_port, keyfile, + certfile, cafile, trustedcert, ticket); + + /* + * 6. get public key signed by the master, private key and ca.crt and copy it to /etc/icinga2/pki + */ + + //Log(LogInformation, "cli") + // << "Copying retrieved signed certificate, private key and ca.crt to pki path '" << pki_path << "'."; + + //std::cout << ConsoleColorTag(Console_ForegroundRed | Console_Bold) << "PLACEHOLDER" << ConsoleColorTag(Console_Normal) << std::endl; + + /* + * 7. enable the ApiListener config (verifiy its data) + */ - /* 5. get public key signed by the master, private key and ca.crt and copy it to /etc/icinga2/pki */ Log(LogInformation, "cli") - << "Copying retrieved signed certificate, private key and ca.crt to pki path '" << pki_path << "'."; + << "Enabling the APIListener feature."; - std::cout << ConsoleColorTag(Console_ForegroundRed | Console_Bold) << "PLACEHOLDER" << ConsoleColorTag(Console_Normal) << std::endl; + std::vector enable; + enable.push_back("api"); + FeatureUtility::EnableFeatures(enable); + + String api_path = FeatureUtility::GetFeaturesEnabledPath() + "/api.conf"; + //TODO: verify that the correct attributes are set on the ApiListener object + //by reading the configuration (CompileFile) and fetching the object + + /* + ConfigCompilerContext::GetInstance()->Reset(); + ConfigCompiler::CompileFile(api_path); + + DynamicType::Ptr dt = DynamicType::GetByName("ApiListener"); + + BOOST_FOREACH(const DynamicObject::Ptr& object, dt->GetObjects()) { + std::cout << JsonSerialize(object) << std::endl; + }*/ + + + /* + * 8. generate local zones.conf with zone+endpoint + * TODO: Move that into a function + */ - /* 6. generate local zones.conf with zone+endpoint */ Log(LogInformation, "cli", "Generating zone and object configuration."); - std::cout << ConsoleColorTag(Console_ForegroundRed | Console_Bold) << "PLACEHOLDER" << ConsoleColorTag(Console_Normal) << std::endl; + std::vector endpoints; + if (vm.count("endpoint")) { + endpoints = vm["endpoint"].as >(); + } else { + endpoints.push_back("master-noconnect"); //no endpoint means no connection attempt. fake name required for master endpoint name + } - /* 7. update constants.conf with NodeName = CN */ - Log(LogInformation, "cli", "Updating configuration with NodeName constant."); + String zones_path = Application::GetSysconfDir() + "/icinga2/zones.conf.TESTBYMICHI"; //FIXME - std::cout << ConsoleColorTag(Console_ForegroundRed | Console_Bold) << "PLACEHOLDER" << ConsoleColorTag(Console_Normal) << std::endl; + Array::Ptr my_config = make_shared(); + + Dictionary::Ptr my_master_zone = make_shared(); + Array::Ptr my_master_zone_members = make_shared(); + + BOOST_FOREACH(const std::string& endpoint, endpoints) { + + /* extract all --endpoint arguments and store host,port info */ + std::vector tokens; + boost::algorithm::split(tokens, endpoint, boost::is_any_of(",")); + + Dictionary::Ptr my_master_endpoint = make_shared(); + + if (tokens.size() == 1 || tokens.size() == 2) + my_master_endpoint->Set("host", tokens[0]); + + if (tokens.size() == 2) + my_master_endpoint->Set("port", tokens[1]); + + my_master_endpoint->Set("__name", String(endpoint)); + my_master_endpoint->Set("__type", "Endpoint"); + + /* save endpoint in master zone */ + my_master_zone_members->Add(String(endpoint)); //find a better name + + my_config->Add(my_master_endpoint); + } + + + /* add the master zone to the config */ + my_master_zone->Set("__name", "master"); //hardcoded name + my_master_zone->Set("__type", "Zone"); + my_master_zone->Set("endpoints", my_master_zone_members); + + my_config->Add(my_master_zone); + + /* store the local generated agent configuration */ + Dictionary::Ptr my_endpoint = make_shared(); + Dictionary::Ptr my_zone = make_shared(); + + my_endpoint->Set("__name", NodeName); + my_endpoint->Set("__type", "Endpoint"); + + Array::Ptr my_zone_members = make_shared(); + my_zone_members->Add(NodeName); + + my_zone->Set("__name", NodeName); + my_zone->Set("__type", "Zone"); + my_zone->Set("//this is the local agent", NodeName); + my_zone->Set("endpoints", my_zone_members); + + /* store the local config */ + my_config->Add(my_endpoint); + my_config->Add(my_zone); + + /* write the newly generated configuration */ + AgentUtility::WriteAgentConfigObjects(zones_path, my_config); + + /* + * 9. update constants.conf with NodeName = CN + */ + //Log(LogInformation, "cli") + // << "Updating configuration with NodeName constant."; + + //TODO requires parsing of constants.conf, editing the entry and dumping it again? + + //std::cout << ConsoleColorTag(Console_ForegroundRed | Console_Bold) << "PLACEHOLDER" << ConsoleColorTag(Console_Normal) << std::endl; + + /* + * 10. tell the user to reload icinga2 + */ - /* 8. tell the user to reload icinga2 */ Log(LogInformation, "cli", "Make sure to restart Icinga 2."); return 0; diff --git a/lib/cli/agentutility.cpp b/lib/cli/agentutility.cpp index 39dbed020..b4b36f8be 100644 --- a/lib/cli/agentutility.cpp +++ b/lib/cli/agentutility.cpp @@ -253,3 +253,96 @@ void AgentUtility::CollectAgents(const String& agent_file, std::vector& Log(LogDebug, "cli", "Adding agent: " << agent); agents.push_back(agent); } + +/* + * This is ugly and requires refactoring into a generic config writer class. + * TODO. + */ +bool AgentUtility::WriteAgentConfigObjects(const String& filename, const Array::Ptr& objects) +{ + Log(LogInformation, "cli", "Dumping config items to file '" + filename + "'"); + + Utility::MkDirP(Utility::DirName(filename), 0755); + + String tempPath = filename + ".tmp"; + + std::ofstream fp(tempPath.CStr(), std::ofstream::out | std::ostream::trunc); + + ObjectLock olock(objects); + + BOOST_FOREACH(const Dictionary::Ptr& object, objects) { + String name = object->Get("__name"); + String type = object->Get("__type"); + + SerializeObject(fp, name, type, object); + } + + fp << std::endl; + fp.close(); + +#ifdef _WIN32 + _unlink(filename.CStr()); +#endif /* _WIN32 */ + + if (rename(tempPath.CStr(), filename.CStr()) < 0) { + BOOST_THROW_EXCEPTION(posix_error() + << boost::errinfo_api_function("rename") + << boost::errinfo_errno(errno) + << boost::errinfo_file_name(tempPath)); + } + + return true; +} + +void AgentUtility::SerializeObject(std::ostream& fp, const String& name, const String& type, const Dictionary::Ptr& object) +{ + fp << "object " << type << " \"" << name << "\" {\n"; + BOOST_FOREACH(const Dictionary::Pair& kv, object) { + if (kv.first == "__type" || kv.first == "__name") + continue; + + fp << "\t" << kv.first << " = "; + FormatValue(fp, kv.second); + fp << "\n"; + } + fp << "}\n"; +} + +void AgentUtility::FormatValue(std::ostream& fp, const Value& val) +{ + if (val.IsObjectType()) { + FormatArray(fp, val); + return; + } + + if (val.IsString()) { + fp << "\"" << Convert::ToString(val) << "\""; + return; + } + + fp << Convert::ToString(val); +} + +void AgentUtility::FormatArray(std::ostream& fp, const Array::Ptr& arr) +{ + bool first = true; + + fp << "[ "; + + if (arr) { + ObjectLock olock(arr); + BOOST_FOREACH(const Value& value, arr) { + if (first) + first = false; + else + fp << ", "; + + FormatValue(fp, value); + } + } + + if (!first) + fp << " "; + + fp << "]"; +} diff --git a/lib/cli/agentutility.hpp b/lib/cli/agentutility.hpp index 4f114be4a..599f1ed0a 100644 --- a/lib/cli/agentutility.hpp +++ b/lib/cli/agentutility.hpp @@ -22,6 +22,8 @@ #include "base/i2-base.hpp" #include "base/dictionary.hpp" +#include "base/array.hpp" +#include "base/value.hpp" #include "base/string.hpp" #include @@ -52,10 +54,16 @@ public: static bool GetAgents(std::vector& agents); + static bool WriteAgentConfigObjects(const String& filename, const Array::Ptr& objects); + private: AgentUtility(void); static bool RemoveAgentFile(const String& path); static void CollectAgents(const String& agent_file, std::vector& agents); + + static void SerializeObject(std::ostream& fp, const String& name, const String& type, const Dictionary::Ptr& object); + static void FormatValue(std::ostream& fp, const Value& val); + static void FormatArray(std::ostream& fp, const Array::Ptr& arr); }; } diff --git a/lib/cli/pkiutility.cpp b/lib/cli/pkiutility.cpp index fa1e325ae..76f5cb441 100644 --- a/lib/cli/pkiutility.cpp +++ b/lib/cli/pkiutility.cpp @@ -253,7 +253,7 @@ int PkiUtility::RequestCertificate(const String& host, const String& port, const } Log(LogInformation, "cli") - << "Writing CA certificate to file '" << certfile << "'."; + << "Writing CA certificate to file '" << cafile << "'."; return 0; }