Merge pull request #8064 from WuerthPhoenix/feature/v1-actions-execute-command-8034

WIP implement stub for /v1/actions/execute-command
This commit is contained in:
Alexander Aleksandrovič Klimov 2020-07-08 11:45:17 +02:00 committed by GitHub
commit 86f74e2fe5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 308 additions and 4 deletions

View File

@ -8,12 +8,16 @@
#include "icinga/checkcommand.hpp"
#include "icinga/eventcommand.hpp"
#include "icinga/notificationcommand.hpp"
#include "icinga/clusterevents.hpp"
#include "remote/apiaction.hpp"
#include "remote/apilistener.hpp"
#include "remote/filterutility.hpp"
#include "remote/pkiutility.hpp"
#include "remote/httputility.hpp"
#include "base/utility.hpp"
#include "base/convert.hpp"
#include "base/defer.hpp"
#include "remote/actionshandler.hpp"
#include <fstream>
using namespace icinga;
@ -552,8 +556,245 @@ Dictionary::Ptr ApiActions::GenerateTicket(const ConfigObject::Ptr&,
+ cn + "'.", additional);
}
Value ApiActions::GetSingleObjectByNameUsingPermissions(const String& type, const String& objectName, const ApiUser::Ptr& user)
{
Dictionary::Ptr queryParams = new Dictionary();
queryParams->Set("type", type);
queryParams->Set(type.ToLower(), objectName);
QueryDescription qd;
qd.Types.insert(type);
qd.Permission = "objects/query/" + type;
std::vector<Value> objs;
try {
objs = FilterUtility::GetFilterTargets(qd, queryParams, user);
} catch (const std::exception& ex) {
Log(LogWarning, "ApiActions") << DiagnosticInformation(ex);
return nullptr;
}
if (objs.empty())
return nullptr;
return objs.at(0);
};
Dictionary::Ptr ApiActions::ExecuteCommand(const ConfigObject::Ptr& object,
const Dictionary::Ptr& params)
{
return ApiActions::CreateResult(501, "Not implemented");
ApiListener::Ptr listener = ApiListener::GetInstance();
if (!listener)
BOOST_THROW_EXCEPTION(std::invalid_argument("No ApiListener instance configured."));
/* Get command_type */
String command_type = "EventCommand";
if (params->Contains("command_type"))
command_type = HttpUtility::GetLastParameter(params, "command_type");
/* Validate command_type */
if (command_type != "EventCommand" && command_type != "CheckCommand" && command_type != "NotificationCommand")
return ApiActions::CreateResult(400, "Invalid command_type '" + command_type + "'.");
Checkable::Ptr checkable = dynamic_pointer_cast<Checkable>(object);
if (!checkable)
return ApiActions::CreateResult(404, "Can't start a command execution for a non-existent object.");
/* Get TTL param */
if (!params->Contains("ttl"))
return ApiActions::CreateResult(400, "Parameter ttl is required.");
double ttl = HttpUtility::GetLastParameter(params, "ttl");
if (ttl <= 0)
return ApiActions::CreateResult(400, "Parameter ttl must be greater than 0.");
ObjectLock oLock (checkable);
Host::Ptr host;
Service::Ptr service;
tie(host, service) = GetHostService(checkable);
String endpoint = "$command_endpoint$";
if (params->Contains("endpoint"))
endpoint = HttpUtility::GetLastParameter(params, "endpoint");
MacroProcessor::ResolverList resolvers;
if (params->Contains("macros")) {
Value macros = HttpUtility::GetLastParameter(params, "macros");
if (macros.IsObjectType<Dictionary>()) {
resolvers.emplace_back("override", macros);
}
else
return ApiActions::CreateResult(400, "Parameter macros must be a dictionary.");
}
if (service)
resolvers.emplace_back("service", service);
resolvers.emplace_back("host", host);
resolvers.emplace_back("icinga", IcingaApplication::GetInstance());
String resolved_endpoint = MacroProcessor::ResolveMacros(
endpoint, resolvers, checkable->GetLastCheckResult(),
nullptr, MacroProcessor::EscapeCallback(), nullptr, false
);
if (!ActionsHandler::AuthenticatedApiUser)
BOOST_THROW_EXCEPTION(std::invalid_argument("Can't find API user."));
/* Get endpoint */
Endpoint::Ptr endpointPtr = GetSingleObjectByNameUsingPermissions(Endpoint::GetTypeName(), resolved_endpoint, ActionsHandler::AuthenticatedApiUser);
if (!endpointPtr)
return ApiActions::CreateResult(404, "Can't find a valid endpoint for '" + resolved_endpoint + "'.");
/* Get command */
String command;
if (!params->Contains("command")) {
if (command_type == "CheckCommand" ) {
command = "$check_command$";
} else if (command_type == "EventCommand") {
command = "$event_command$";
} else if (command_type == "NotificationCommand") {
command = "$notification_command$";
}
} else {
command = HttpUtility::GetLastParameter(params, "command");
}
/* Resolve command macro */
String resolved_command = MacroProcessor::ResolveMacros(
command, resolvers, checkable->GetLastCheckResult(), nullptr,
MacroProcessor::EscapeCallback(), nullptr, false
);
CheckResult::Ptr cr = checkable->GetLastCheckResult();
/* Check if resolved_command exists and it is of type command_type */
Dictionary::Ptr execMacros = new Dictionary();
MacroResolver::OverrideMacros = execMacros;
Defer o ([]() {
MacroResolver::OverrideMacros = nullptr;
});
if (command_type == "CheckCommand") {
CheckCommand::Ptr cmd = GetSingleObjectByNameUsingPermissions(CheckCommand::GetTypeName(), resolved_command, ActionsHandler::AuthenticatedApiUser);
if (!cmd)
return ApiActions::CreateResult(404, "Can't find a valid " + command_type + " for '" + resolved_command + "'.");
else
cmd->Execute(checkable, cr, execMacros, false);
} else if (command_type == "EventCommand") {
EventCommand::Ptr cmd = GetSingleObjectByNameUsingPermissions(EventCommand::GetTypeName(), resolved_command, ActionsHandler::AuthenticatedApiUser);
if (!cmd)
return ApiActions::CreateResult(404, "Can't find a valid " + command_type + " for '" + resolved_command + "'.");
else
cmd->Execute(checkable, execMacros, false);
} else if (command_type == "NotificationCommand") {
NotificationCommand::Ptr cmd = GetSingleObjectByNameUsingPermissions(NotificationCommand::GetTypeName(), resolved_command, ActionsHandler::AuthenticatedApiUser);
if (!cmd)
return ApiActions::CreateResult(404, "Can't find a valid " + command_type + " for '" + resolved_command + "'.");
else {
/* Get user */
String user_string = "";
if (params->Contains("user"))
user_string = HttpUtility::GetLastParameter(params, "user");
/* Resolve user macro */
String resolved_user = MacroProcessor::ResolveMacros(
user_string, resolvers, checkable->GetLastCheckResult(), nullptr,
MacroProcessor::EscapeCallback(), nullptr, false
);
User::Ptr user = GetSingleObjectByNameUsingPermissions(User::GetTypeName(), resolved_user, ActionsHandler::AuthenticatedApiUser);
if (!user)
return ApiActions::CreateResult(404, "Can't find a valid user for '" + resolved_user + "'.");
/* Get notification */
String notification_string = "";
if (params->Contains("notification"))
notification_string = HttpUtility::GetLastParameter(params, "notification");
/* Resolve notification macro */
String resolved_notification = MacroProcessor::ResolveMacros(
notification_string, resolvers, checkable->GetLastCheckResult(), nullptr,
MacroProcessor::EscapeCallback(), nullptr, false
);
Notification::Ptr notification = GetSingleObjectByNameUsingPermissions(Notification::GetTypeName(), resolved_notification, ActionsHandler::AuthenticatedApiUser);
if (!notification)
return ApiActions::CreateResult(404, "Can't find a valid notification for '" + resolved_notification + "'.");
cmd->Execute(notification, user, cr, NotificationType::NotificationCustom,
ActionsHandler::AuthenticatedApiUser->GetName(), "", execMacros, false);
}
}
/* This generates a UUID */
String uuid = Utility::NewUniqueID();
/* Create the deadline */
double deadline = Utility::GetTime() + ttl;
/* Update executions */
Dictionary::Ptr pending_execution = new Dictionary();
pending_execution->Set("pending", true);
pending_execution->Set("deadline", deadline);
Dictionary::Ptr executions = checkable->GetExecutions();
if (!executions)
executions = new Dictionary();
executions->Set(uuid, pending_execution);
checkable->SetExecutions(executions);
/* Broadcast the update */
Dictionary::Ptr executionsToBroadcast = new Dictionary();
executionsToBroadcast->Set(uuid, pending_execution);
Dictionary::Ptr updateParams = new Dictionary();
updateParams->Set("host", host->GetName());
if (service)
updateParams->Set("service", service->GetShortName());
updateParams->Set("executions", executionsToBroadcast);
Dictionary::Ptr updateMessage = new Dictionary();
updateMessage->Set("jsonrpc", "2.0");
updateMessage->Set("method", "event::UpdateExecutions");
updateMessage->Set("params", updateParams);
MessageOrigin::Ptr origin = new MessageOrigin();
listener->RelayMessage(origin, checkable, updateMessage, true);
/* Create execution parameters */
Dictionary::Ptr execParams = new Dictionary();
execParams->Set("command_type", command_type);
execParams->Set("command", resolved_command);
execParams->Set("host", host->GetName());
if (service)
execParams->Set("service", service->GetShortName());
/*
* If the host/service object specifies the 'check_timeout' attribute,
* forward this to the remote endpoint to limit the command execution time.
*/
if (!checkable->GetCheckTimeout().IsEmpty())
execParams->Set("check_timeout", checkable->GetCheckTimeout());
execParams->Set("source", uuid);
execParams->Set("deadline", deadline);
execParams->Set("macros", execMacros);
/* Execute command */
bool local = endpointPtr == Endpoint::GetLocalEndpoint();
if (local) {
ClusterEvents::ExecuteCommandAPIHandler(origin, execParams);
} else {
Dictionary::Ptr execMessage = new Dictionary();
execMessage->Set("jsonrpc", "2.0");
execMessage->Set("method", "event::ExecuteCommand");
execMessage->Set("params", execParams);
listener->SyncSendMessage(endpointPtr, execMessage);
}
Dictionary::Ptr result = new Dictionary();
result->Set("checkable", checkable->GetName());
result->Set("execution", uuid);
return ApiActions::CreateResult(202, "Accepted", result);
}

View File

@ -6,6 +6,7 @@
#include "icinga/i2-icinga.hpp"
#include "base/configobject.hpp"
#include "base/dictionary.hpp"
#include "remote/apiuser.hpp"
namespace icinga
{
@ -33,6 +34,7 @@ public:
private:
static Dictionary::Ptr CreateResult(int code, const String& status, const Dictionary::Ptr& additional = nullptr);
static Value GetSingleObjectByNameUsingPermissions(const String& type, const String& value, const ApiUser::Ptr& user);
};
}

View File

@ -77,7 +77,12 @@ void ClusterEvents::EnqueueCheck(const MessageOrigin::Ptr& origin, const Diction
void ClusterEvents::ExecuteCheckFromQueue(const MessageOrigin::Ptr& origin, const Dictionary::Ptr& params) {
Endpoint::Ptr sourceEndpoint = origin->FromClient->GetEndpoint();
Endpoint::Ptr sourceEndpoint;
if (origin->FromClient) {
sourceEndpoint = origin->FromClient->GetEndpoint();
} else if (origin->IsLocal()){
sourceEndpoint = Endpoint::GetLocalEndpoint();
}
if (!sourceEndpoint || (origin->FromZone && !Zone::GetLocalZone()->IsChildOf(origin->FromZone))) {
Log(LogNotice, "ClusterEvents")

View File

@ -949,5 +949,44 @@ Value ClusterEvents::ExecutedCommandAPIHandler(const MessageOrigin::Ptr& origin,
Value ClusterEvents::UpdateExecutionsAPIHandler(const MessageOrigin::Ptr& origin, const Dictionary::Ptr& params)
{
return "Not implemented";
Endpoint::Ptr endpoint = origin->FromClient->GetEndpoint();
if (!endpoint) {
Log(LogNotice, "ClusterEvents")
<< "Discarding 'update executions API handler' message from '" << origin->FromClient->GetIdentity()
<< "': Invalid endpoint origin (client not allowed).";
return Empty;
}
Host::Ptr host = Host::GetByName(params->Get("host"));
if (!host)
return Empty;
Checkable::Ptr checkable;
if (params->Contains("service"))
checkable = host->GetServiceByShortName(params->Get("service"));
else
checkable = host;
if (!checkable)
return Empty;
ObjectLock oLock (checkable);
if (origin->FromZone && !origin->FromZone->CanAccessObject(checkable)) {
Log(LogNotice, "ClusterEvents")
<< "Discarding 'update executions API handler' message for checkable '" << checkable->GetName()
<< "' from '" << origin->FromClient->GetIdentity() << "': Unauthorized access.";
return Empty;
}
Dictionary::Ptr executions = checkable->GetExecutions();
if (!executions)
executions = new Dictionary();
Dictionary::Ptr newExecutions = params->Get("executions");
newExecutions->CopyTo(executions);
checkable->SetExecutions(executions);
return Empty;
}

View File

@ -15,6 +15,8 @@
using namespace icinga;
thread_local Dictionary::Ptr MacroResolver::OverrideMacros;
Value MacroProcessor::ResolveMacros(const Value& str, const ResolverList& resolvers,
const CheckResult::Ptr& cr, String *missingMacro,
const MacroProcessor::EscapeCallback& escapeFn, const Dictionary::Ptr& resolvedMacros,

View File

@ -21,6 +21,8 @@ class MacroResolver
public:
DECLARE_PTR_TYPEDEFS(MacroResolver);
static thread_local Dictionary::Ptr OverrideMacros;
virtual bool ResolveMacro(const String& macro, const CheckResult::Ptr& cr, Value *result) const = 0;
};

View File

@ -29,6 +29,9 @@ void PluginCheckTask::ScriptFunc(const Checkable::Ptr& checkable, const CheckRes
tie(host, service) = GetHostService(checkable);
MacroProcessor::ResolverList resolvers;
if (MacroResolver::OverrideMacros)
resolvers.emplace_back("override", MacroResolver::OverrideMacros);
if (service)
resolvers.emplace_back("service", service);
resolvers.emplace_back("host", host);

View File

@ -4,12 +4,15 @@
#include "remote/httputility.hpp"
#include "remote/filterutility.hpp"
#include "remote/apiaction.hpp"
#include "base/defer.hpp"
#include "base/exception.hpp"
#include "base/logger.hpp"
#include <set>
using namespace icinga;
thread_local ApiUser::Ptr ActionsHandler::AuthenticatedApiUser;
REGISTER_URLHANDLER("/v1/actions", ActionsHandler);
bool ActionsHandler::HandleRequest(
@ -71,6 +74,11 @@ bool ActionsHandler::HandleRequest(
bool verbose = false;
ActionsHandler::AuthenticatedApiUser = user;
Defer a ([]() {
ActionsHandler::AuthenticatedApiUser = nullptr;
});
if (params)
verbose = HttpUtility::GetLastParameter(params, "verbose");
@ -94,7 +102,7 @@ bool ActionsHandler::HandleRequest(
int statusCode = 500;
for (const Dictionary::Ptr& res : results) {
if (res->Contains("code") && res->Get("code") == 200) {
if (res->Contains("code") && res->Get("code") >= 200 && res->Get("code") <= 299) {
statusCode = 200;
break;
}

View File

@ -13,6 +13,8 @@ class ActionsHandler final : public HttpHandler
public:
DECLARE_PTR_TYPEDEFS(ActionsHandler);
static thread_local ApiUser::Ptr AuthenticatedApiUser;
bool HandleRequest(
AsioTlsStream& stream,
const ApiUser::Ptr& user,