icinga2/lib/remote/configpackageshandler.cpp
Yonas Habteab ce3275d27f Disallow stage deletions during reload
Once the new worker process has read the config, it also includes a
`include */include.conf` statement within the config packages root
directory, and from there on we must not allow to delete any stage
directory from the config package. Otherwise, when the worker actually
evaluates that include statement, it will fail to find the directory
where the include file is located, or the `active.conf` file, which is
included from each stage's `include.conf` file, thus causing the worker
fail.

Co-Authored-By: Johannes Schmidt <johannes.schmidt@icinga.com>
2025-07-24 16:02:30 +02:00

194 lines
5.3 KiB
C++

/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
#include "remote/configpackageshandler.hpp"
#include "remote/configpackageutility.hpp"
#include "remote/configobjectslock.hpp"
#include "remote/httputility.hpp"
#include "remote/filterutility.hpp"
#include "base/exception.hpp"
using namespace icinga;
REGISTER_URLHANDLER("/v1/config/packages", ConfigPackagesHandler);
bool ConfigPackagesHandler::HandleRequest(
const WaitGroup::Ptr&,
AsioTlsStream& stream,
const ApiUser::Ptr& user,
boost::beast::http::request<boost::beast::http::string_body>& request,
const Url::Ptr& url,
boost::beast::http::response<boost::beast::http::string_body>& response,
const Dictionary::Ptr& params,
boost::asio::yield_context& yc,
HttpServerConnection& server
)
{
namespace http = boost::beast::http;
if (url->GetPath().size() > 4)
return false;
if (request.method() == http::verb::get)
HandleGet(user, request, url, response, params);
else if (request.method() == http::verb::post)
HandlePost(user, request, url, response, params);
else if (request.method() == http::verb::delete_)
HandleDelete(user, request, url, response, params);
else
return false;
return true;
}
void ConfigPackagesHandler::HandleGet(
const ApiUser::Ptr& user,
boost::beast::http::request<boost::beast::http::string_body>& request,
const Url::Ptr& url,
boost::beast::http::response<boost::beast::http::string_body>& response,
const Dictionary::Ptr& params
)
{
namespace http = boost::beast::http;
FilterUtility::CheckPermission(user, "config/query");
std::vector<String> packages;
try {
packages = ConfigPackageUtility::GetPackages();
} catch (const std::exception& ex) {
HttpUtility::SendJsonError(response, params, 500, "Could not retrieve packages.",
DiagnosticInformation(ex));
return;
}
ArrayData results;
{
std::unique_lock<std::mutex> lock(ConfigPackageUtility::GetStaticPackageMutex());
for (const String& package : packages) {
String activeStage;
try {
activeStage = ConfigPackageUtility::GetActiveStage(package);
} catch (const std::exception&) { } /* Should never happen. */
results.emplace_back(new Dictionary({
{ "name", package },
{ "stages", Array::FromVector(ConfigPackageUtility::GetStages(package)) },
{ "active-stage", activeStage }
}));
}
}
Dictionary::Ptr result = new Dictionary({
{ "results", new Array(std::move(results)) }
});
response.result(http::status::ok);
HttpUtility::SendJsonBody(response, params, result);
}
void ConfigPackagesHandler::HandlePost(
const ApiUser::Ptr& user,
boost::beast::http::request<boost::beast::http::string_body>& request,
const Url::Ptr& url,
boost::beast::http::response<boost::beast::http::string_body>& response,
const Dictionary::Ptr& params
)
{
namespace http = boost::beast::http;
FilterUtility::CheckPermission(user, "config/modify");
if (url->GetPath().size() >= 4)
params->Set("package", url->GetPath()[3]);
String packageName = HttpUtility::GetLastParameter(params, "package");
if (!ConfigPackageUtility::ValidatePackageName(packageName)) {
HttpUtility::SendJsonError(response, params, 400, "Invalid package name '" + packageName + "'.");
return;
}
ConfigObjectsSharedLock configObjectsSharedLock(std::try_to_lock);
if (!configObjectsSharedLock) {
HttpUtility::SendJsonError(response, params, 503, "Icinga is reloading");
return;
}
try {
std::unique_lock<std::mutex> lock(ConfigPackageUtility::GetStaticPackageMutex());
ConfigPackageUtility::CreatePackage(packageName);
} catch (const std::exception& ex) {
HttpUtility::SendJsonError(response, params, 500, "Could not create package '" + packageName + "'.",
DiagnosticInformation(ex));
return;
}
Dictionary::Ptr result1 = new Dictionary({
{ "code", 200 },
{ "package", packageName },
{ "status", "Created package." }
});
Dictionary::Ptr result = new Dictionary({
{ "results", new Array({ result1 }) }
});
response.result(http::status::ok);
HttpUtility::SendJsonBody(response, params, result);
}
void ConfigPackagesHandler::HandleDelete(
const ApiUser::Ptr& user,
boost::beast::http::request<boost::beast::http::string_body>& request,
const Url::Ptr& url,
boost::beast::http::response<boost::beast::http::string_body>& response,
const Dictionary::Ptr& params
)
{
namespace http = boost::beast::http;
FilterUtility::CheckPermission(user, "config/modify");
if (url->GetPath().size() >= 4)
params->Set("package", url->GetPath()[3]);
String packageName = HttpUtility::GetLastParameter(params, "package");
if (!ConfigPackageUtility::ValidatePackageName(packageName)) {
HttpUtility::SendJsonError(response, params, 400, "Invalid package name '" + packageName + "'.");
return;
}
ConfigObjectsSharedLock lock(std::try_to_lock);
if (!lock) {
HttpUtility::SendJsonError(response, params, 503, "Icinga is reloading");
return;
}
try {
ConfigPackageUtility::DeletePackage(packageName);
} catch (const std::exception& ex) {
HttpUtility::SendJsonError(response, params, 500, "Failed to delete package '" + packageName + "'.",
DiagnosticInformation(ex));
return;
}
Dictionary::Ptr result1 = new Dictionary({
{ "code", 200 },
{ "package", packageName },
{ "status", "Deleted package." }
});
Dictionary::Ptr result = new Dictionary({
{ "results", new Array({ result1 }) }
});
response.result(http::status::ok);
HttpUtility::SendJsonBody(response, params, result);
}