From 7b0b04ba8f3b26d3b61f44ad20cfd918989043d7 Mon Sep 17 00:00:00 2001 From: Michael Friedrich Date: Wed, 24 Jun 2015 15:09:22 +0200 Subject: [PATCH] Implement 'api setup' cli command & add ApiUser config refs #9471 --- lib/cli/CMakeLists.txt | 1 + lib/cli/apisetupcommand.cpp | 75 +++++++++++++++ lib/cli/apisetupcommand.hpp | 47 ++++++++++ lib/cli/apisetuputility.cpp | 182 ++++++++++++++++++++++++++++++++++++ lib/cli/apisetuputility.hpp | 49 ++++++++++ 5 files changed, 354 insertions(+) create mode 100644 lib/cli/apisetupcommand.cpp create mode 100644 lib/cli/apisetupcommand.hpp create mode 100644 lib/cli/apisetuputility.cpp create mode 100644 lib/cli/apisetuputility.hpp diff --git a/lib/cli/CMakeLists.txt b/lib/cli/CMakeLists.txt index eb2073fe3..ba04a9ca6 100644 --- a/lib/cli/CMakeLists.txt +++ b/lib/cli/CMakeLists.txt @@ -16,6 +16,7 @@ # Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. set(cli_SOURCES + apisetupcommand.cpp apisetuputility.cpp nodeaddcommand.cpp nodeblackandwhitelistcommand.cpp nodelistcommand.cpp noderemovecommand.cpp nodesetcommand.cpp nodesetupcommand.cpp nodeupdateconfigcommand.cpp nodewizardcommand.cpp nodeutility.cpp clicommand.cpp diff --git a/lib/cli/apisetupcommand.cpp b/lib/cli/apisetupcommand.cpp new file mode 100644 index 000000000..5bad6d710 --- /dev/null +++ b/lib/cli/apisetupcommand.cpp @@ -0,0 +1,75 @@ +/****************************************************************************** + * Icinga 2 * + * Copyright (C) 2012-2015 Icinga Development Team (http://www.icinga.org) * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License * + * as published by the Free Software Foundation; either version 2 * + * of the License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the Free Software Foundation * + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * + ******************************************************************************/ + +#include "cli/apisetupcommand.hpp" +#include "cli/apisetuputility.hpp" +#include "base/logger.hpp" +#include "base/console.hpp" + +using namespace icinga; +namespace po = boost::program_options; + +REGISTER_CLICOMMAND("api/setup", ApiSetupCommand); + +String ApiSetupCommand::GetDescription(void) const +{ + return "Setup for Icinga 2 API."; +} + +String ApiSetupCommand::GetShortDescription(void) const +{ + return "setup for api"; +} + +ImpersonationLevel ApiSetupCommand::GetImpersonationLevel(void) const +{ + return ImpersonateRoot; +} + +int ApiSetupCommand::GetMaxArguments(void) const +{ + return -1; +} + +/** + * The entry point for the "node wizard" CLI command. + * + * @returns An exit status. + */ +int ApiSetupCommand::Run(const boost::program_options::variables_map& vm, const std::vector& ap) const +{ + /* 1. generate CA & signed certificate + * 2. update password inside api-users.conf for the "root" user + * TODO: + * - setup the api on a client? + */ + + int result = ApiSetupUtility::SetupMaster(); + + if (result > 0) { + Log(LogCritical, "ApiSetup", "Error occured. Bailing out."); + return result; + } + + std::cout << "Done.\n\n"; + + std::cout << "Now restart your Icinga 2 daemon to finish the installation!\n\n"; + + return 0; +} diff --git a/lib/cli/apisetupcommand.hpp b/lib/cli/apisetupcommand.hpp new file mode 100644 index 000000000..c27f172bd --- /dev/null +++ b/lib/cli/apisetupcommand.hpp @@ -0,0 +1,47 @@ +/****************************************************************************** + * Icinga 2 * + * Copyright (C) 2012-2015 Icinga Development Team (http://www.icinga.org) * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License * + * as published by the Free Software Foundation; either version 2 * + * of the License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the Free Software Foundation * + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * + ******************************************************************************/ + +#ifndef APISETUPCOMMAND_H +#define APISETUPCOMMAND_H + +#include "cli/clicommand.hpp" + +namespace icinga +{ + +/** + * The "api setup" command. + * + * @ingroup cli + */ +class ApiSetupCommand : public CLICommand +{ +public: + DECLARE_PTR_TYPEDEFS(ApiSetupCommand); + + virtual String GetDescription(void) const; + virtual String GetShortDescription(void) const; + virtual int GetMaxArguments(void) const; + virtual int Run(const boost::program_options::variables_map& vm, const std::vector& ap) const; + virtual ImpersonationLevel GetImpersonationLevel(void) const; +}; + +} + +#endif /* APISETUPCOMMAND_H */ diff --git a/lib/cli/apisetuputility.cpp b/lib/cli/apisetuputility.cpp new file mode 100644 index 000000000..5c3d912ee --- /dev/null +++ b/lib/cli/apisetuputility.cpp @@ -0,0 +1,182 @@ +/****************************************************************************** + * Icinga 2 * + * Copyright (C) 2012-2015 Icinga Development Team (http://www.icinga.org) * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License * + * as published by the Free Software Foundation; either version 2 * + * of the License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the Free Software Foundation * + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * + ******************************************************************************/ + +#include "cli/apisetuputility.hpp" +#include "cli/pkiutility.hpp" +#include "cli/nodeutility.hpp" +#include "cli/featureutility.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 +#include +#include +#include +#include +#include +#include +#include + +using namespace icinga; + +String ApiSetupUtility::GetConfdPath(void) +{ + return Application::GetSysconfDir() + "/icinga2/conf.d"; +} + +int ApiSetupUtility::SetupMaster(void) +{ + Log(LogInformation, "cli") + << "Generating new CA.\n"; + + String cn = Utility::GetFQDN(); + + 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 user = ScriptGlobal::Get("RunAsUser"); + String group = ScriptGlobal::Get("RunAsGroup"); + + if (!Utility::SetFileOwnership(pki_path, user, group)) { + Log(LogWarning, "cli") + << "Cannot set ownership for user '" << user << "' group '" << group << "' on file '" << pki_path << "'. Verify it yourself!"; + } + + String key = pki_path + "/" + cn + ".key"; + String csr = pki_path + "/" + cn + ".csr"; + + Log(LogInformation, "cli") + << "Generating new CSR in '" << csr << "'.\n"; + + if (Utility::PathExists(key)) + NodeUtility::CreateBackupFile(key, true); + if (Utility::PathExists(csr)) + NodeUtility::CreateBackupFile(csr); + + if (PkiUtility::NewCert(cn, key, csr, "") > 0) { + Log(LogCritical, "cli", "Failed to create certificate signing request."); + return 1; + } + + /* Sign the CSR with the CA key */ + String cert = pki_path + "/" + cn + ".crt"; + + Log(LogInformation, "cli") + << "Signing CSR with CA and writing certificate to '" << cert << "'.\n"; + + if (Utility::PathExists(cert)) + NodeUtility::CreateBackupFile(cert); + + if (PkiUtility::SignCsr(csr, cert) != 0) { + Log(LogCritical, "cli", "Could not sign CSR."); + return 1; + } + + /* Copy CA certificate to /etc/icinga2/pki */ + + String ca_path = PkiUtility::GetLocalCaPath(); + String ca = ca_path + "/ca.crt"; + String ca_key = ca_path + "/ca.key"; + String serial = ca_path + "/serial.txt"; + String target_ca = pki_path + "/ca.crt"; + + Log(LogInformation, "cli") + << "Copying CA certificate to '" << target_ca << "'.\n"; + + if (Utility::PathExists(target_ca)) + NodeUtility::CreateBackupFile(target_ca); + + /* does not overwrite existing files! */ + Utility::CopyFile(ca, target_ca); + + /* fix permissions: root -> icinga daemon user */ + std::vector files; + files.push_back(ca_path); + files.push_back(ca); + files.push_back(ca_key); + files.push_back(serial); + files.push_back(target_ca); + files.push_back(key); + files.push_back(csr); + files.push_back(cert); + + BOOST_FOREACH(const String& file, files) { + if (!Utility::SetFileOwnership(file, user, group)) { + Log(LogWarning, "cli") + << "Cannot set ownership for user '" << user << "' group '" << group << "' on file '" << file << "'. Verify it yourself!"; + } + } + + String api_username = "root"; //TODO make this available as cli parameter? + String api_password = RandomString(8); + String apiuserspath = GetConfdPath() + "/api-users.conf"; + + Log(LogInformation, "cli") + << "Adding new ApiUser '" << api_username << "' in '" << apiuserspath << "'.\n"; + + NodeUtility::CreateBackupFile(apiuserspath); + + String apiuserspathtmp = apiuserspath + ".tmp"; + + std::ofstream fp; + fp.open(apiuserspathtmp.CStr(), std::ofstream::out | std::ofstream::trunc); + + fp << "/**\n" + << " * The API users are used for authentication against the API.\n" + << " */\n" + << "object ApiUser \"" << api_username << "\" {\n" + << " password = \"" << api_password << "\"\n" + << " //client_cn = \"\"\n" + << "}\n"; + + fp.close(); + +#ifdef _WIN32 + _unlink(apiuserspath.CStr()); +#endif /* _WIN32 */ + + if (rename(apiuserspathtmp.CStr(), apiuserspath.CStr()) < 0) { + BOOST_THROW_EXCEPTION(posix_error() + << boost::errinfo_api_function("rename") + << boost::errinfo_errno(errno) + << boost::errinfo_file_name(apiuserspathtmp)); + } + + + Log(LogInformation, "cli", "Enabling the ApiListener feature.\n"); + + std::vector enable; + enable.push_back("api"); + FeatureUtility::EnableFeatures(enable); + + + return 0; +} diff --git a/lib/cli/apisetuputility.hpp b/lib/cli/apisetuputility.hpp new file mode 100644 index 000000000..eb5a1321c --- /dev/null +++ b/lib/cli/apisetuputility.hpp @@ -0,0 +1,49 @@ +/****************************************************************************** + * Icinga 2 * + * Copyright (C) 2012-2015 Icinga Development Team (http://www.icinga.org) * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License * + * as published by the Free Software Foundation; either version 2 * + * of the License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the Free Software Foundation * + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * + ******************************************************************************/ + +#ifndef APISETUPUTILITY_H +#define APISETUPUTILITY_H + +#include "base/i2-base.hpp" +#include "cli/i2-cli.hpp" +#include "base/dictionary.hpp" +#include "base/array.hpp" +#include "base/value.hpp" +#include "base/string.hpp" +#include + +namespace icinga +{ + +/** + * @ingroup cli + */ +class I2_CLI_API ApiSetupUtility +{ +public: + static int SetupMaster(void); + static String GetConfdPath(void); + +private: + ApiSetupUtility(void); +}; + +} + +#endif /* APISETUPUTILITY_H */