Implement config file management for the API

refs #9083

fixes #9102
fixes #9103
fixes #9104

fixes #9705
This commit is contained in:
Michael Friedrich 2015-07-21 16:10:13 +02:00
parent 43ff15cf86
commit fca7a33aac
17 changed files with 1100 additions and 20 deletions

View File

@ -136,16 +136,6 @@ and their generated configuration described in
You can put your own configuration files in the [conf.d](4-configuring-icinga-2.md#conf-d) directory. This You can put your own configuration files in the [conf.d](4-configuring-icinga-2.md#conf-d) directory. This
directive makes sure that all of your own configuration files are included. directive makes sure that all of your own configuration files are included.
/**
* The zones.d directory contains configuration files for satellite
* instances.
*/
include_zones "etc", "zones.d"
Configuration files for satellite instances are managed in 'zones'. This directive ensures
that all configuration files in the `zones.d` directory are included and that the `zones`
attribute for objects defined in this directory is set appropriately.
### <a id="constants-conf"></a> constants.conf ### <a id="constants-conf"></a> constants.conf
The `constants.conf` configuration file can be used to define global constants. The `constants.conf` configuration file can be used to define global constants.

View File

@ -48,9 +48,3 @@ include_recursive "repository.d"
* directory. Each of these files must have the file extension ".conf". * directory. Each of these files must have the file extension ".conf".
*/ */
include_recursive "conf.d" include_recursive "conf.d"
/**
* The zones.d directory contains configuration files for satellite
* instances.
*/
include_zones "etc", "zones.d"

View File

@ -581,6 +581,39 @@ bool Utility::MkDirP(const String& path, int flags)
return ret; return ret;
} }
void Utility::RemoveDirRecursive(const String& path)
{
std::vector<String> paths;
Utility::GlobRecursive(path, "*", boost::bind(&Utility::CollectPaths, _1, boost::ref(paths)), GlobFile | GlobDirectory);
/* This relies on the fact that GlobRecursive lists the parent directory
first before recursing into subdirectories. */
std::reverse(paths.begin(), paths.end());
BOOST_FOREACH(const String& path, paths) {
if (remove(path.CStr()) < 0)
BOOST_THROW_EXCEPTION(posix_error()
<< boost::errinfo_api_function("remove")
<< boost::errinfo_errno(errno)
<< boost::errinfo_file_name(path));
}
#ifndef _WIN32
if (rmdir(path.CStr()) < 0)
#else /* _WIN32 */
if (_rmdir(path.CStr()) < 0)
#endif /* _WIN32 */
BOOST_THROW_EXCEPTION(posix_error()
<< boost::errinfo_api_function("rmdir")
<< boost::errinfo_errno(errno)
<< boost::errinfo_file_name(path));
}
void Utility::CollectPaths(const String& path, std::vector<String>& paths)
{
paths.push_back(path);
}
void Utility::CopyFile(const String& source, const String& target) void Utility::CopyFile(const String& source, const String& target)
{ {
std::ifstream ifs(source.CStr(), std::ios::binary); std::ifstream ifs(source.CStr(), std::ios::binary);

View File

@ -123,6 +123,7 @@ public:
static bool PathExists(const String& path); static bool PathExists(const String& path);
static void RemoveDirRecursive(const String& path);
static void CopyFile(const String& source, const String& target); static void CopyFile(const String& source, const String& target);
static Value LoadJsonFile(const String& path); static Value LoadJsonFile(const String& path);
@ -130,10 +131,10 @@ public:
private: private:
Utility(void); Utility(void);
static void CollectPaths(const String& path, std::vector<String>& paths);
static boost::thread_specific_ptr<String> m_ThreadName; static boost::thread_specific_ptr<String> m_ThreadName;
static boost::thread_specific_ptr<unsigned int> m_RandSeed; static boost::thread_specific_ptr<unsigned int> m_RandSeed;
}; };
} }

View File

@ -85,10 +85,29 @@ bool DaemonUtility::ValidateConfigFiles(const std::vector<std::string>& configs,
* unfortunately moving it there is somewhat non-trivial. */ * unfortunately moving it there is somewhat non-trivial. */
success = true; success = true;
String zonesEtcDir = Application::GetZonesDir();
if (!zonesEtcDir.IsEmpty() && Utility::PathExists(zonesEtcDir))
Utility::Glob(zonesEtcDir + "/*", boost::bind(&IncludeZoneDirRecursive, _1, boost::ref(success)), GlobDirectory);
if (!success)
return false;
String zonesVarDir = Application::GetLocalStateDir() + "/lib/icinga2/api/zones"; String zonesVarDir = Application::GetLocalStateDir() + "/lib/icinga2/api/zones";
if (Utility::PathExists(zonesVarDir)) if (Utility::PathExists(zonesVarDir))
Utility::Glob(zonesVarDir + "/*", boost::bind(&IncludeNonLocalZone, _1, boost::ref(success)), GlobDirectory); Utility::Glob(zonesVarDir + "/*", boost::bind(&IncludeNonLocalZone, _1, boost::ref(success)), GlobDirectory);
if (!success)
return false;
String modulesVarDir = Application::GetLocalStateDir() + "/lib/icinga2/api/modules";
if (Utility::PathExists(modulesVarDir)) {
std::vector<Expression *> expressions;
Utility::Glob(modulesVarDir + "/*/include.conf", boost::bind(&ConfigCompiler::CollectIncludes, boost::ref(expressions), _1, ""), GlobFile);
DictExpression expr(expressions);
if (!ExecuteExpression(&expr))
success = false;
}
if (!success) if (!success)
return false; return false;

View File

@ -22,9 +22,11 @@ mkclass_target(zone.ti zone.tcpp zone.thpp)
set(remote_SOURCES set(remote_SOURCES
apifunction.cpp apilistener.cpp apilistener.thpp apilistener-sync.cpp apifunction.cpp apilistener.cpp apilistener.thpp apilistener-sync.cpp
apiuser.cpp apiuser.thpp authority.cpp base64.cpp endpoint.cpp endpoint.thpp apiuser.cpp apiuser.thpp authority.cpp base64.cpp configfileshandler.cpp
configmoduleshandler.cpp configmoduleutility.cpp configstageshandler.cpp
endpoint.cpp endpoint.thpp
httpchunkedencoding.cpp httpconnection.cpp httpdemohandler.cpp httphandler.cpp httprequest.cpp httpresponse.cpp httpchunkedencoding.cpp httpconnection.cpp httpdemohandler.cpp httphandler.cpp httprequest.cpp httpresponse.cpp
jsonrpc.cpp jsonrpcconnection.cpp jsonrpcconnection-heartbeat.cpp httputility.cpp jsonrpc.cpp jsonrpcconnection.cpp jsonrpcconnection-heartbeat.cpp
messageorigin.cpp zone.cpp zone.thpp messageorigin.cpp zone.cpp zone.thpp
url.cpp url.cpp
) )

View File

@ -0,0 +1,96 @@
/******************************************************************************
* 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 "remote/configfileshandler.hpp"
#include "remote/configmoduleutility.hpp"
#include "remote/httputility.hpp"
#include "base/exception.hpp"
#include <boost/algorithm/string/join.hpp>
#include <fstream>
using namespace icinga;
REGISTER_URLHANDLER("/v1/config/files", ConfigFilesHandler);
void ConfigFilesHandler::HandleRequest(const ApiUser::Ptr& user, HttpRequest& request, HttpResponse& response)
{
if (request.RequestMethod == "GET")
HandleGet(user, request, response);
else
response.SetStatus(400, "Bad request");
}
bool ConfigFilesHandler::CanAlsoHandleUrl(const Url::Ptr& url) const
{
return true;
}
void ConfigFilesHandler::HandleGet(const ApiUser::Ptr& user, HttpRequest& request, HttpResponse& response)
{
Dictionary::Ptr params = HttpUtility::FetchRequestParameters(request);
const std::vector<String>& urlPath = request.RequestUrl->GetPath();
if (urlPath.size() >= 4)
params->Set("module", urlPath[3]);
if (urlPath.size() >= 5)
params->Set("stage", urlPath[4]);
if (urlPath.size() >= 6) {
std::vector<String> tmpPath(urlPath.begin() + 5, urlPath.end());
params->Set("path", boost::algorithm::join(tmpPath, "/"));
}
String moduleName = params->Get("module");
String stageName = params->Get("stage");
if (!ConfigModuleUtility::ValidateName(moduleName) || !ConfigModuleUtility::ValidateName(stageName)) {
response.SetStatus(403, "Forbidden");
return;
}
String relativePath = params->Get("path");
if (ConfigModuleUtility::ContainsDotDot(relativePath)) {
response.SetStatus(403, "Forbidden");
return;
}
String path = ConfigModuleUtility::GetModuleDir() + "/" + moduleName + "/" + stageName + "/" + relativePath;
if (!Utility::PathExists(path)) {
response.SetStatus(404, "File not found");
return;
}
try {
std::ifstream fp(path.CStr(), std::ifstream::in | std::ifstream::binary);
fp.exceptions(std::ifstream::badbit);
String content((std::istreambuf_iterator<char>(fp)), std::istreambuf_iterator<char>());
response.SetStatus(200, "OK");
response.AddHeader("Content-Type", "application/octet-stream");
response.WriteBody(content.CStr(), content.GetLength());
} catch (const std::exception& ex) {
response.SetStatus(503, "Could not read file");
}
}

View File

@ -0,0 +1,42 @@
/******************************************************************************
* 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 CONFIGFILESHANDLER_H
#define CONFIGFILESHANDLER_H
#include "remote/httphandler.hpp"
namespace icinga
{
class I2_REMOTE_API ConfigFilesHandler : public HttpHandler
{
public:
DECLARE_PTR_TYPEDEFS(ConfigFilesHandler);
virtual bool CanAlsoHandleUrl(const Url::Ptr& url) const;
virtual void HandleRequest(const ApiUser::Ptr& user, HttpRequest& request, HttpResponse& response);
private:
void HandleGet(const ApiUser::Ptr& user, HttpRequest& request, HttpResponse& response);
};
}
#endif /* CONFIGFILESHANDLER_H */

View File

@ -0,0 +1,149 @@
/******************************************************************************
* 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 "remote/configmoduleshandler.hpp"
#include "remote/configmoduleutility.hpp"
#include "remote/httputility.hpp"
#include "base/exception.hpp"
using namespace icinga;
REGISTER_URLHANDLER("/v1/config/modules", ConfigModulesHandler);
void ConfigModulesHandler::HandleRequest(const ApiUser::Ptr& user, HttpRequest& request, HttpResponse& response)
{
if (request.RequestMethod == "GET")
HandleGet(user, request, response);
else if (request.RequestMethod == "POST")
HandlePost(user, request, response);
else if (request.RequestMethod == "DELETE")
HandleDelete(user, request, response);
else
response.SetStatus(400, "Bad request");
}
bool ConfigModulesHandler::CanAlsoHandleUrl(const Url::Ptr& url) const
{
if (url->GetPath().size() > 4)
return false;
else
return true;
}
void ConfigModulesHandler::HandleGet(const ApiUser::Ptr& user, HttpRequest& request, HttpResponse& response)
{
std::vector<String> modules = ConfigModuleUtility::GetModules();
Array::Ptr results = new Array();
BOOST_FOREACH(const String& module, modules) {
Dictionary::Ptr moduleInfo = new Dictionary();
moduleInfo->Set("name", module);
moduleInfo->Set("stages", Array::FromVector(ConfigModuleUtility::GetStages(module)));
moduleInfo->Set("active-stage", ConfigModuleUtility::GetActiveStage(module));
results->Add(moduleInfo);
}
Dictionary::Ptr result = new Dictionary();
result->Set("results", results);
response.SetStatus(200, "OK");
HttpUtility::SendJsonBody(response, result);
}
void ConfigModulesHandler::HandlePost(const ApiUser::Ptr& user, HttpRequest& request, HttpResponse& response)
{
Dictionary::Ptr params = HttpUtility::FetchRequestParameters(request);
if (request.RequestUrl->GetPath().size() >= 4)
params->Set("module", request.RequestUrl->GetPath()[3]);
String moduleName = params->Get("module");
if (!ConfigModuleUtility::ValidateName(moduleName)) {
response.SetStatus(403, "Forbidden");
return;
}
int code = 200;
String status = "Created module.";
try {
ConfigModuleUtility::CreateModule(moduleName);
} catch (const std::exception& ex) {
code = 501;
status = "Error: " + DiagnosticInformation(ex);
}
Dictionary::Ptr result1 = new Dictionary();
result1->Set("module", moduleName);
result1->Set("code", code);
result1->Set("status", status);
Array::Ptr results = new Array();
results->Add(result1);
Dictionary::Ptr result = new Dictionary();
result->Set("results", results);
response.SetStatus(code, (code == 200) ? "OK" : "Error");
HttpUtility::SendJsonBody(response, result);
}
void ConfigModulesHandler::HandleDelete(const ApiUser::Ptr& user, HttpRequest& request, HttpResponse& response)
{
Dictionary::Ptr params = HttpUtility::FetchRequestParameters(request);
if (request.RequestUrl->GetPath().size() >= 4)
params->Set("module", request.RequestUrl->GetPath()[3]);
String moduleName = params->Get("module");
if (!ConfigModuleUtility::ValidateName(moduleName)) {
response.SetStatus(403, "Forbidden");
return;
}
int code = 200;
String status = "Deleted module.";
try {
ConfigModuleUtility::DeleteModule(moduleName);
} catch (const std::exception& ex) {
code = 501;
status = "Error: " + DiagnosticInformation(ex);
}
Dictionary::Ptr result1 = new Dictionary();
result1->Set("module", moduleName);
result1->Set("code", code);
result1->Set("status", status);
Array::Ptr results = new Array();
results->Add(result1);
Dictionary::Ptr result = new Dictionary();
result->Set("results", results);
response.SetStatus(code, (code == 200) ? "OK" : "Error");
HttpUtility::SendJsonBody(response, result);
}

View File

@ -0,0 +1,45 @@
/******************************************************************************
* 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 CONFIGMODULESHANDLER_H
#define CONFIGMODULESHANDLER_H
#include "remote/httphandler.hpp"
namespace icinga
{
class I2_REMOTE_API ConfigModulesHandler : public HttpHandler
{
public:
DECLARE_PTR_TYPEDEFS(ConfigModulesHandler);
virtual bool CanAlsoHandleUrl(const Url::Ptr& url) const;
virtual void HandleRequest(const ApiUser::Ptr& user, HttpRequest& request, HttpResponse& response);
private:
void HandleGet(const ApiUser::Ptr& user, HttpRequest& request, HttpResponse& response);
void HandlePost(const ApiUser::Ptr& user, HttpRequest& request, HttpResponse& response);
void HandleDelete(const ApiUser::Ptr& user, HttpRequest& request, HttpResponse& response);
};
}
#endif /* CONFIGMODULESHANDLER_H */

View File

@ -0,0 +1,297 @@
/******************************************************************************
* 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 "remote/configmoduleutility.hpp"
#include "base/application.hpp"
#include "base/exception.hpp"
#include "base/scriptglobal.hpp"
#include "base/utility.hpp"
#include "boost/foreach.hpp"
#include <boost/algorithm/string.hpp>
#include <boost/regex.hpp>
#include <algorithm>
#include <fstream>
using namespace icinga;
String ConfigModuleUtility::GetModuleDir(void)
{
return Application::GetLocalStateDir() + "/lib/icinga2/api/modules";
}
void ConfigModuleUtility::CreateModule(const String& name)
{
String path = GetModuleDir() + "/" + name;
if (Utility::PathExists(path))
BOOST_THROW_EXCEPTION(std::invalid_argument("Module already exists."));
Utility::MkDirP(path, 0700);
WriteModuleConfig(name);
}
void ConfigModuleUtility::DeleteModule(const String& name)
{
String path = GetModuleDir() + "/" + name;
if (!Utility::PathExists(path))
BOOST_THROW_EXCEPTION(std::invalid_argument("Module does not exist."));
Utility::RemoveDirRecursive(path);
Application::RequestRestart();
}
std::vector<String> ConfigModuleUtility::GetModules(void)
{
std::vector<String> modules;
Utility::Glob(GetModuleDir() + "/*", boost::bind(&ConfigModuleUtility::CollectDirNames, _1, boost::ref(modules)), GlobDirectory);
return modules;
}
void ConfigModuleUtility::CollectDirNames(const String& path, std::vector<String>& dirs)
{
String name = Utility::BaseName(path);
dirs.push_back(name);
}
String ConfigModuleUtility::CreateStage(const String& moduleName, const Dictionary::Ptr& files)
{
String stageName = Utility::NewUniqueID();
String path = GetModuleDir() + "/" + moduleName;
if (!Utility::PathExists(path))
BOOST_THROW_EXCEPTION(std::invalid_argument("Module does not exist."));
path += "/" + stageName;
Utility::MkDirP(path, 0700);
WriteStageConfig(moduleName, stageName);
ObjectLock olock(files);
BOOST_FOREACH(const Dictionary::Pair& kv, files) {
if (ContainsDotDot(kv.first))
BOOST_THROW_EXCEPTION(std::invalid_argument("Path must not contain '..'."));
String filePath = path + "/" + kv.first;
Log(LogInformation, "ConfigModuleUtility")
<< "Updating configuration file: " << filePath;
//pass the directory and generate a dir tree, if not existing already
Utility::MkDirP(Utility::DirName(filePath), 0750);
std::ofstream fp(filePath.CStr(), std::ofstream::out | std::ostream::binary | std::ostream::trunc);
fp << kv.second;
fp.close();
}
return stageName;
}
void ConfigModuleUtility::WriteModuleConfig(const String& moduleName)
{
String stageName = GetActiveStage(moduleName);
String includePath = GetModuleDir() + "/" + moduleName + "/include.conf";
std::ofstream fpInclude(includePath.CStr(), std::ofstream::out | std::ostream::binary | std::ostream::trunc);
fpInclude << "include \"*/include.conf\"\n";
fpInclude.close();
String activePath = GetModuleDir() + "/" + moduleName + "/active.conf";
std::ofstream fpActive(activePath.CStr(), std::ofstream::out | std::ostream::binary | std::ostream::trunc);
fpActive << "if (!globals.contains(\"ActiveStages\")) {\n"
<< " globals.ActiveStages = {}\n"
<< "}\n"
<< "\n"
<< "if (globals.contains(\"ActiveStageOverride\")) {\n"
<< " var arr = ActiveStageOverride.split(\":\")\n"
<< " if (arr[0] == \"" << moduleName << "\") {\n"
<< " if (arr.len() < 2) {\n"
<< " log(LogCritical, \"Config\", \"Invalid value for ActiveStageOverride\")\n"
<< " } else {\n"
<< " ActiveStages[\"" << moduleName << "\"] = arr[1]\n"
<< " }\n"
<< " }\n"
<< "}\n"
<< "\n"
<< "if (!ActiveStages.contains(\"" << moduleName << "\")) {\n"
<< " ActiveStages[\"" << moduleName << "\"] = \"" << stageName << "\"\n"
<< "}\n";
fpActive.close();
}
void ConfigModuleUtility::WriteStageConfig(const String& moduleName, const String& stageName)
{
String path = GetModuleDir() + "/" + moduleName + "/" + stageName + "/include.conf";
std::ofstream fp(path.CStr(), std::ofstream::out | std::ostream::binary | std::ostream::trunc);
fp << "include \"../active.conf\"\n"
<< "if (ActiveStages[\"" << moduleName << "\"] == \"" << stageName << "\") {\n"
<< " include_recursive \"conf.d\"\n"
<< " include_zones \"" << moduleName << "\", \"zones.d\"\n"
<< "}\n";
fp.close();
}
void ConfigModuleUtility::ActivateStage(const String& moduleName, const String& stageName)
{
String activeStagePath = GetModuleDir() + "/" + moduleName + "/active-stage";
std::ofstream fpActiveStage(activeStagePath.CStr(), std::ofstream::out | std::ostream::binary | std::ostream::trunc);
fpActiveStage << stageName;
fpActiveStage.close();
WriteModuleConfig(moduleName);
}
void ConfigModuleUtility::TryActivateStageCallback(const ProcessResult& pr, const String& moduleName, const String& stageName)
{
String logFile = GetModuleDir() + "/" + moduleName + "/" + stageName + "/startup.log";
std::ofstream fpLog(logFile.CStr(), std::ofstream::out | std::ostream::binary | std::ostream::trunc);
fpLog << pr.Output;
fpLog.close();
String statusFile = GetModuleDir() + "/" + moduleName + "/" + stageName + "/status";
std::ofstream fpStatus(statusFile.CStr(), std::ofstream::out | std::ostream::binary | std::ostream::trunc);
fpStatus << pr.ExitStatus;
fpStatus.close();
/* validation went fine, activate stage and reload */
if (pr.ExitStatus == 0) {
ActivateStage(moduleName, stageName);
Application::RequestRestart();
} else {
Log(LogCritical, "ConfigModuleUtility")
<< "Config validation failed for module '"
<< moduleName << "' and stage '" << stageName << "'.";
}
}
void ConfigModuleUtility::AsyncTryActivateStage(const String& moduleName, const String& stageName)
{
// prepare arguments
Array::Ptr args = new Array();
args->Add(Application::GetExePath("icinga2"));
args->Add("daemon");
args->Add("--validate");
args->Add("--define");
args->Add("ActiveStageOverride=" + moduleName + ":" + stageName);
Process::Ptr process = new Process(Process::PrepareCommand(args));
process->SetTimeout(300);
process->Run(boost::bind(&TryActivateStageCallback, _1, moduleName, stageName));
}
void ConfigModuleUtility::DeleteStage(const String& moduleName, const String& stageName)
{
String path = GetModuleDir() + "/" + moduleName + "/" + stageName;
if (!Utility::PathExists(path))
BOOST_THROW_EXCEPTION(std::invalid_argument("Stage does not exist."));
if (GetActiveStage(moduleName) == stageName)
BOOST_THROW_EXCEPTION(std::invalid_argument("Active stage cannot be deleted."));
Utility::RemoveDirRecursive(path);
}
std::vector<String> ConfigModuleUtility::GetStages(const String& moduleName)
{
std::vector<String> stages;
Utility::Glob(GetModuleDir() + "/" + moduleName + "/*", boost::bind(&ConfigModuleUtility::CollectDirNames, _1, boost::ref(stages)), GlobDirectory);
return stages;
}
String ConfigModuleUtility::GetActiveStage(const String& moduleName)
{
String path = GetModuleDir() + "/" + moduleName + "/active-stage";
std::ifstream fp;
fp.open(path.CStr());
String stage;
std::getline(fp, stage.GetData());
stage.Trim();
fp.close();
if (fp.fail())
return "";
return stage;
}
std::vector<std::pair<String, bool> > ConfigModuleUtility::GetFiles(const String& moduleName, const String& stageName)
{
std::vector<std::pair<String, bool> > paths;
Utility::GlobRecursive(GetModuleDir() + "/" + moduleName + "/" + stageName, "*", boost::bind(&ConfigModuleUtility::CollectPaths, _1, boost::ref(paths)), GlobDirectory | GlobFile);
return paths;
}
void ConfigModuleUtility::CollectPaths(const String& path, std::vector<std::pair<String, bool> >& paths)
{
#ifndef _WIN32
struct stat statbuf;
int rc = lstat(path.CStr(), &statbuf);
if (rc < 0)
BOOST_THROW_EXCEPTION(posix_error()
<< boost::errinfo_api_function("lstat")
<< boost::errinfo_errno(errno)
<< boost::errinfo_file_name(path));
#else /* _WIN32 */
struct _stat statbuf;
int rc = _stat(path.CStr(), &statbuf);
if (rc < 0)
BOOST_THROW_EXCEPTION(posix_error()
<< boost::errinfo_api_function("_stat")
<< boost::errinfo_errno(errno)
<< boost::errinfo_file_name(path));
#endif /* _WIN32 */
paths.push_back(std::make_pair(path, S_ISDIR(statbuf.st_mode)));
}
bool ConfigModuleUtility::ContainsDotDot(const String& path)
{
std::vector<String> tokens;
boost::algorithm::split(tokens, path, boost::is_any_of("/\\"));
BOOST_FOREACH(const String& part, tokens) {
if (part == "..")
return true;
}
return false;
}
bool ConfigModuleUtility::ValidateName(const String& name)
{
if (name.IsEmpty())
return false;
/* check for path injection */
if (ContainsDotDot(name))
return false;
boost::regex expr("^[^a-zA-Z0-9_\\-]*$", boost::regex::icase);
boost::smatch what;
return (!boost::regex_search(name.GetData(), what, expr));
}

View File

@ -0,0 +1,72 @@
/******************************************************************************
* 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 CONFIGMODULEUTILITY_H
#define CONFIGMODULEUTILITY_H
#include "remote/i2-remote.hpp"
#include "base/application.hpp"
#include "base/dictionary.hpp"
#include "base/process.hpp"
#include "base/string.hpp"
#include <vector>
namespace icinga
{
/**
* Helper functions.
*
* @ingroup remote
*/
class I2_REMOTE_API ConfigModuleUtility
{
public:
static String GetModuleDir(void);
static void CreateModule(const String& name);
static void DeleteModule(const String& name);
static std::vector<String> GetModules(void);
static String CreateStage(const String& moduleName, const Dictionary::Ptr& files);
static void DeleteStage(const String& moduleName, const String& stageName);
static std::vector<String> GetStages(const String& moduleName);
static String GetActiveStage(const String& moduleName);
static void ActivateStage(const String& moduleName, const String& stageName);
static void AsyncTryActivateStage(const String& moduleName, const String& stageName);
static std::vector<std::pair<String, bool> > GetFiles(const String& moduleName, const String& stageName);
static bool ContainsDotDot(const String& path);
static bool ValidateName(const String& name);
private:
static void CollectDirNames(const String& path, std::vector<String>& dirs);
static void CollectPaths(const String& path, std::vector<std::pair<String, bool> >& paths);
static void WriteModuleConfig(const String& moduleName);
static void WriteStageConfig(const String& moduleName, const String& stageName);
static void TryActivateStageCallback(const ProcessResult& pr, const String& moduleName, const String& stageName);
};
}
#endif /* CONFIGMODULEUTILITY_H */

View File

@ -0,0 +1,184 @@
/******************************************************************************
* 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 "remote/configstageshandler.hpp"
#include "remote/configmoduleutility.hpp"
#include "remote/httputility.hpp"
#include "base/application.hpp"
#include "base/exception.hpp"
#include <boost/foreach.hpp>
using namespace icinga;
REGISTER_URLHANDLER("/v1/config/stages", ConfigStagesHandler);
void ConfigStagesHandler::HandleRequest(const ApiUser::Ptr& user, HttpRequest& request, HttpResponse& response)
{
if (request.RequestMethod == "GET")
HandleGet(user, request, response);
else if (request.RequestMethod == "POST")
HandlePost(user, request, response);
else if (request.RequestMethod == "DELETE")
HandleDelete(user, request, response);
else
response.SetStatus(400, "Bad request");
}
bool ConfigStagesHandler::CanAlsoHandleUrl(const Url::Ptr& url) const
{
if (url->GetPath().size() > 5)
return false;
else
return true;
}
void ConfigStagesHandler::HandleGet(const ApiUser::Ptr& user, HttpRequest& request, HttpResponse& response)
{
Dictionary::Ptr params = HttpUtility::FetchRequestParameters(request);
if (request.RequestUrl->GetPath().size() >= 4)
params->Set("module", request.RequestUrl->GetPath()[3]);
if (request.RequestUrl->GetPath().size() >= 5)
params->Set("stage", request.RequestUrl->GetPath()[4]);
String moduleName = params->Get("module");
String stageName = params->Get("stage");
if (!ConfigModuleUtility::ValidateName(moduleName) || !ConfigModuleUtility::ValidateName(stageName)) {
response.SetStatus(403, "Forbidden");
return;
}
Array::Ptr results = new Array();
std::vector<std::pair<String, bool> > paths = ConfigModuleUtility::GetFiles(moduleName, stageName);
String prefixPath = ConfigModuleUtility::GetModuleDir() + "/" + moduleName + "/" + stageName + "/";
typedef std::pair<String, bool> kv_pair;
BOOST_FOREACH(const kv_pair& kv, paths) {
Dictionary::Ptr stageInfo = new Dictionary();
stageInfo->Set("type", (kv.second ? "directory" : "file"));
stageInfo->Set("name", kv.first.SubStr(prefixPath.GetLength()));
results->Add(stageInfo);
}
Dictionary::Ptr result = new Dictionary();
result->Set("results", results);
response.SetStatus(200, "OK");
HttpUtility::SendJsonBody(response, result);
}
void ConfigStagesHandler::HandlePost(const ApiUser::Ptr& user, HttpRequest& request, HttpResponse& response)
{
Dictionary::Ptr params = HttpUtility::FetchRequestParameters(request);
if (request.RequestUrl->GetPath().size() >= 4)
params->Set("module", request.RequestUrl->GetPath()[3]);
String moduleName = params->Get("module");
if (!ConfigModuleUtility::ValidateName(moduleName)) {
response.SetStatus(403, "Forbidden");
return;
}
Dictionary::Ptr files = params->Get("files");
int code = 200;
String status = "Created stage.";
String stageName;
try {
if (!files)
BOOST_THROW_EXCEPTION(std::invalid_argument("Parameter 'files' must be specified."));
stageName = ConfigModuleUtility::CreateStage(moduleName, files);
/* validate the config. on success, activate stage and reload */
ConfigModuleUtility::AsyncTryActivateStage(moduleName, stageName);
} catch (const std::exception& ex) {
code = 501;
status = "Error: " + DiagnosticInformation(ex);
}
Dictionary::Ptr result1 = new Dictionary();
result1->Set("module", moduleName);
result1->Set("stage", stageName);
result1->Set("code", code);
result1->Set("status", status);
Array::Ptr results = new Array();
results->Add(result1);
Dictionary::Ptr result = new Dictionary();
result->Set("results", results);
response.SetStatus(code, (code == 200) ? "OK" : "Error");
HttpUtility::SendJsonBody(response, result);
}
void ConfigStagesHandler::HandleDelete(const ApiUser::Ptr& user, HttpRequest& request, HttpResponse& response)
{
Dictionary::Ptr params = HttpUtility::FetchRequestParameters(request);
if (request.RequestUrl->GetPath().size() >= 4)
params->Set("module", request.RequestUrl->GetPath()[3]);
if (request.RequestUrl->GetPath().size() >= 5)
params->Set("stage", request.RequestUrl->GetPath()[4]);
String moduleName = params->Get("module");
String stageName = params->Get("stage");
if (!ConfigModuleUtility::ValidateName(moduleName) || !ConfigModuleUtility::ValidateName(stageName)) {
response.SetStatus(403, "Forbidden");
return;
}
int code = 200;
String status = "Deleted stage.";
try {
ConfigModuleUtility::DeleteStage(moduleName, stageName);
} catch (const std::exception& ex) {
code = 501;
status = "Error: " + DiagnosticInformation(ex);
}
Dictionary::Ptr result1 = new Dictionary();
result1->Set("module", moduleName);
result1->Set("stage", stageName);
result1->Set("code", code);
result1->Set("status", status);
Array::Ptr results = new Array();
results->Add(result1);
Dictionary::Ptr result = new Dictionary();
result->Set("results", results);
response.SetStatus(code, (code == 200) ? "OK" : "Error");
HttpUtility::SendJsonBody(response, result);
}

View File

@ -0,0 +1,45 @@
/******************************************************************************
* 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 CONFIGSTAGESHANDLER_H
#define CONFIGSTAGESHANDLER_H
#include "remote/httphandler.hpp"
namespace icinga
{
class I2_REMOTE_API ConfigStagesHandler : public HttpHandler
{
public:
DECLARE_PTR_TYPEDEFS(ConfigStagesHandler);
virtual bool CanAlsoHandleUrl(const Url::Ptr& url) const;
virtual void HandleRequest(const ApiUser::Ptr& user, HttpRequest& request, HttpResponse& response);
private:
void HandleGet(const ApiUser::Ptr& user, HttpRequest& request, HttpResponse& response);
void HandlePost(const ApiUser::Ptr& user, HttpRequest& request, HttpResponse& response);
void HandleDelete(const ApiUser::Ptr& user, HttpRequest& request, HttpResponse& response);
};
}
#endif /* CONFIGSTAGESHANDLER_H */

View File

@ -154,7 +154,14 @@ void HttpConnection::ProcessMessageAsync(HttpRequest& request)
String msg = "<h1>Unauthorized</h1>"; String msg = "<h1>Unauthorized</h1>";
response.WriteBody(msg.CStr(), msg.GetLength()); response.WriteBody(msg.CStr(), msg.GetLength());
} else { } else {
HttpHandler::ProcessRequest(user, request, response); try {
HttpHandler::ProcessRequest(user, request, response);
} catch (const std::exception& ex) {
response.SetStatus(503, "Unhandled exception");
response.AddHeader("Content-Type", "text/plain");
String errorInfo = DiagnosticInformation(ex);
response.WriteBody(errorInfo.CStr(), errorInfo.GetLength());
}
} }
response.Finish(); response.Finish();

View File

@ -0,0 +1,59 @@
/******************************************************************************
* 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 "remote/httputility.hpp"
#include "base/json.hpp"
#include <boost/foreach.hpp>
using namespace icinga;
Dictionary::Ptr HttpUtility::FetchRequestParameters(HttpRequest& request)
{
Dictionary::Ptr result;
String body;
char buffer[1024];
size_t count;
while ((count = request.ReadBody(buffer, sizeof(buffer))) > 0)
body += String(buffer, buffer + count);
if (!body.IsEmpty())
result = JsonDecode(body);
if (!result)
result = new Dictionary();
typedef std::pair<String, Value> kv_pair;
BOOST_FOREACH(const kv_pair& kv, request.RequestUrl->GetQuery()) {
result->Set(kv.first, kv.second);
}
return result;
}
void HttpUtility::SendJsonBody(HttpResponse& response, const Value& val)
{
response.AddHeader("Content-Type", "application/json");
String body = JsonEncode(val);
response.WriteBody(body.CStr(), body.GetLength());
}

View File

@ -0,0 +1,45 @@
/******************************************************************************
* 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 HTTPUTILITY_H
#define HTTPUTILITY_H
#include "remote/httprequest.hpp"
#include "remote/httpresponse.hpp"
#include "base/dictionary.hpp"
namespace icinga
{
/**
* Helper functions.
*
* @ingroup remote
*/
class I2_REMOTE_API HttpUtility
{
public:
static Dictionary::Ptr FetchRequestParameters(HttpRequest& request);
static void SendJsonBody(HttpResponse& response, const Value& val);
};
}
#endif /* HTTPUTILITY_H */