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
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
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".
*/
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;
}
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)
{
std::ifstream ifs(source.CStr(), std::ios::binary);

View File

@ -123,6 +123,7 @@ public:
static bool PathExists(const String& path);
static void RemoveDirRecursive(const String& path);
static void CopyFile(const String& source, const String& target);
static Value LoadJsonFile(const String& path);
@ -130,10 +131,10 @@ public:
private:
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<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. */
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";
if (Utility::PathExists(zonesVarDir))
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)
return false;

View File

@ -22,9 +22,11 @@ mkclass_target(zone.ti zone.tcpp zone.thpp)
set(remote_SOURCES
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
jsonrpc.cpp jsonrpcconnection.cpp jsonrpcconnection-heartbeat.cpp
httputility.cpp jsonrpc.cpp jsonrpcconnection.cpp jsonrpcconnection-heartbeat.cpp
messageorigin.cpp zone.cpp zone.thpp
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>";
response.WriteBody(msg.CStr(), msg.GetLength());
} 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();

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 */