#include "PyFormatter.h"
#include "common/debug.h"
+#include "mon/MonCommand.h"
#include "ActivePyModule.h"
+#include "MgrSession.h"
#define dout_context g_ceph_context
}
int ActivePyModule::handle_command(
+ const ModuleCommand& module_command,
+ const MgrSession& session,
const cmdmap_t &cmdmap,
const bufferlist &inbuf,
std::stringstream *ds,
string instr;
inbuf.copy(0, inbuf.length(), instr);
+ ceph_assert(m_session == nullptr);
+ m_command_perms = module_command.perm;
+ m_session = &session;
+
auto pResult = PyObject_CallMethod(pClassInstance,
const_cast<char*>("_handle_command"), const_cast<char*>("s#O"),
instr.c_str(), instr.length(), py_cmd);
+ m_command_perms.clear();
+ m_session = nullptr;
Py_DECREF(py_cmd);
int r = 0;
checks->merge(health_checks);
}
+bool ActivePyModule::is_authorized(
+ const std::map<std::string, std::string>& arguments) const {
+ if (m_session == nullptr) {
+ return false;
+ }
+
+ // No need to pass command prefix here since that would have already been
+ // tested before command invokation. Instead, only test for service/module
+ // arguments as defined by the module itself.
+ MonCommand mon_command {"", "", "", m_command_perms};
+ return m_session->caps.is_capable(nullptr, m_session->entity_name, "py",
+ py_module->get_name(), "", arguments,
+ mon_command.requires_perm('r'),
+ mon_command.requires_perm('w'),
+ mon_command.requires_perm('x'),
+ m_session->get_peer_addr());
+}
class ActivePyModule;
class ActivePyModules;
+class MgrSession;
+class ModuleCommand;
class ActivePyModule : public PyModuleRunner
{
// Optional, URI exposed by plugins that implement serve()
std::string uri;
+ std::string m_command_perms;
+ const MgrSession* m_session = nullptr;
+
public:
ActivePyModule(const PyModuleRef &py_module_,
LogChannelRef clog_)
std::string *err);
int handle_command(
+ const ModuleCommand& module_command,
+ const MgrSession& session,
const cmdmap_t &cmdmap,
const bufferlist &inbuf,
std::stringstream *ds,
{
return uri;
}
+
+ bool is_authorized(const std::map<std::string, std::string>& arguments) const;
+
};
std::string handle_pyerror();
}
int ActivePyModules::handle_command(
- std::string const &module_name,
+ const ModuleCommand& module_command,
+ const MgrSession& session,
const cmdmap_t &cmdmap,
const bufferlist &inbuf,
std::stringstream *ds,
std::stringstream *ss)
{
lock.lock();
- auto mod_iter = modules.find(module_name);
+ auto mod_iter = modules.find(module_command.module_name);
if (mod_iter == modules.end()) {
- *ss << "Module '" << module_name << "' is not available";
+ *ss << "Module '" << module_command.module_name << "' is not available";
lock.unlock();
return -ENOENT;
}
lock.unlock();
- return mod_iter->second->handle_command(cmdmap, inbuf, ds, ss);
+ return mod_iter->second->handle_command(module_command, session, cmdmap,
+ inbuf, ds, ss);
}
void ActivePyModules::get_health_checks(health_check_map_t *checks)
class health_check_map_t;
class DaemonServer;
+class MgrSession;
+class ModuleCommand;
class PyModuleRegistry;
class ActivePyModules
void set_uri(const std::string& module_name, const std::string &uri);
int handle_command(
- const std::string &module_name,
+ const ModuleCommand& module_command,
+ const MgrSession& session,
const cmdmap_t &cmdmap,
const bufferlist &inbuf,
std::stringstream *ds,
return self->py_modules->get_osd_perf_counters(query_id);
}
+static PyObject*
+ceph_is_authorized(BaseMgrModule *self, PyObject *args)
+{
+ PyObject *args_dict = NULL;
+ if (!PyArg_ParseTuple(args, "O:ceph_is_authorized", &args_dict)) {
+ return nullptr;
+ }
+
+ if (!PyDict_Check(args_dict)) {
+ derr << __func__ << " arg not a dict" << dendl;
+ Py_RETURN_FALSE;
+ }
+
+ std::map<std::string, std::string> arguments;
+
+ PyObject *args_list = PyDict_Items(args_dict);
+ for (int i = 0; i < PyList_Size(args_list); ++i) {
+ PyObject *kv = PyList_GET_ITEM(args_list, i);
+
+ char *arg_key = nullptr;
+ char *arg_value = nullptr;
+ if (!PyArg_ParseTuple(kv, "ss:pair", &arg_key, &arg_value)) {
+ derr << __func__ << " dict item " << i << " not a size 2 tuple" << dendl;
+ continue;
+ }
+
+ arguments[arg_key] = arg_value;
+ }
+
+ if (self->this_module->is_authorized(arguments)) {
+ Py_RETURN_TRUE;
+ }
+
+ Py_RETURN_FALSE;
+}
+
PyMethodDef BaseMgrModule_methods[] = {
{"_ceph_get", (PyCFunction)ceph_state_get, METH_VARARGS,
"Get a cluster object"},
{"_ceph_get_osd_perf_counters", (PyCFunction)ceph_get_osd_perf_counters,
METH_VARARGS, "Get osd perf counters"},
+ {"_ceph_is_authorized", (PyCFunction)ceph_is_authorized,
+ METH_VARARGS, "Verify the current session caps are valid"},
+
{NULL, NULL, 0, NULL}
};
bool DaemonServer::_allowed_command(
MgrSession *s,
+ const string &service,
const string &module,
const string &prefix,
const cmdmap_t& cmdmap,
bool capable = s->caps.is_capable(
g_ceph_context,
s->entity_name,
- module, "", prefix, param_str_map,
+ service, module, prefix, param_str_map,
cmd_r, cmd_w, cmd_x,
s->get_peer_addr());
}
}
+void DaemonServer::log_access_denied(
+ std::shared_ptr<CommandContext>& cmdctx,
+ MgrSession* session, std::stringstream& ss) {
+ dout(1) << " access denied" << dendl;
+ audit_clog->info() << "from='" << session->inst << "' "
+ << "entity='" << session->entity_name << "' "
+ << "cmd=" << cmdctx->cmd << ": access denied";
+ ss << "access denied: does your client key have mgr caps? "
+ "See http://docs.ceph.com/docs/master/mgr/administrator/"
+ "#client-authentication";
+}
+
bool DaemonServer::_handle_command(
std::shared_ptr<CommandContext>& cmdctx)
{
const MonCommand *mgr_cmd = _get_mgrcommand(prefix, mgr_commands);
_generate_command_map(cmdctx->cmdmap, param_str_map);
- bool is_allowed;
+ bool is_allowed = false;
+ ModuleCommand py_command;
if (!mgr_cmd) {
- MonCommand py_command = {"", "", "py", "rw"};
- is_allowed = _allowed_command(session, py_command.module,
- prefix, cmdctx->cmdmap, param_str_map, &py_command);
+ // Resolve the command to the name of the module that will
+ // handle it (if the command exists)
+ auto py_commands = py_modules.get_py_commands();
+ for (const auto &pyc : py_commands) {
+ auto pyc_prefix = cmddesc_get_prefix(pyc.cmdstring);
+ if (pyc_prefix == prefix) {
+ py_command = pyc;
+ break;
+ }
+ }
+
+ MonCommand pyc = {"", "", "py", py_command.perm};
+ is_allowed = _allowed_command(session, "py", py_command.module_name,
+ prefix, cmdctx->cmdmap, param_str_map,
+ &pyc);
} else {
// validate user's permissions for requested command
- is_allowed = _allowed_command(session, mgr_cmd->module,
+ is_allowed = _allowed_command(session, mgr_cmd->module, "",
prefix, cmdctx->cmdmap, param_str_map, mgr_cmd);
}
+
if (!is_allowed) {
- dout(1) << " access denied" << dendl;
- audit_clog->info() << "from='" << session->inst << "' "
- << "entity='" << session->entity_name << "' "
- << "cmd=" << cmdctx->cmd << ": access denied";
- ss << "access denied: does your client key have mgr caps? "
- "See http://docs.ceph.com/docs/master/mgr/administrator/"
- "#client-authentication";
+ log_access_denied(cmdctx, session, ss);
cmdctx->reply(-EACCES, ss);
return true;
}
}
}
- // Resolve the command to the name of the module that will
- // handle it (if the command exists)
- std::string handler_name;
- auto py_commands = py_modules.get_py_commands();
- for (const auto &pyc : py_commands) {
- auto pyc_prefix = cmddesc_get_prefix(pyc.cmdstring);
- if (pyc_prefix == prefix) {
- handler_name = pyc.module_name;
- break;
- }
- }
-
// Was the command unfound?
- if (handler_name.empty()) {
+ if (py_command.cmdstring.empty()) {
ss << "No handler found for '" << prefix << "'";
dout(4) << "No handler found for '" << prefix << "'" << dendl;
cmdctx->reply(-EINVAL, ss);
}
dout(10) << "passing through " << cmdctx->cmdmap.size() << dendl;
- finisher.queue(new LambdaContext([this, cmdctx, handler_name, prefix](int r_) {
+ finisher.queue(new LambdaContext([this, cmdctx, session, py_command, prefix]
+ (int r_) mutable {
std::stringstream ss;
// Validate that the module is enabled
- PyModuleRef module = py_modules.get_module(handler_name);
+ auto& py_handler_name = py_command.module_name;
+ PyModuleRef module = py_modules.get_module(py_handler_name);
ceph_assert(module);
if (!module->is_enabled()) {
- ss << "Module '" << handler_name << "' is not enabled (required by "
+ ss << "Module '" << py_handler_name << "' is not enabled (required by "
"command '" << prefix << "'): use `ceph mgr module enable "
- << handler_name << "` to enable it";
+ << py_handler_name << "` to enable it";
dout(4) << ss.str() << dendl;
cmdctx->reply(-EOPNOTSUPP, ss);
return;
// Hack: allow the self-test method to run on unhealthy modules.
// Fix this in future by creating a special path for self test rather
// than having the hook be a normal module command.
- std::string self_test_prefix = handler_name + " " + "self-test";
+ std::string self_test_prefix = py_handler_name + " " + "self-test";
// Validate that the module is healthy
bool accept_command;
accept_command = true;
} else {
accept_command = false;
- ss << "Module '" << handler_name << "' has experienced an error and "
+ ss << "Module '" << py_handler_name << "' has experienced an error and "
"cannot handle commands: " << module->get_error_string();
}
} else {
// Module not loaded
accept_command = false;
- ss << "Module '" << handler_name << "' failed to load and "
+ ss << "Module '" << py_handler_name << "' failed to load and "
"cannot handle commands: " << module->get_error_string();
}
std::stringstream ds;
bufferlist inbl = cmdctx->data;
- int r = py_modules.handle_command(handler_name, cmdctx->cmdmap, inbl, &ds, &ss);
+ int r = py_modules.handle_command(py_command, *session, cmdctx->cmdmap,
+ inbl, &ds, &ss);
+ if (r == -EACCES) {
+ log_access_denied(cmdctx, session, ss);
+ }
+
cmdctx->odata.append(ds);
cmdctx->reply(r, ss);
}));
static const MonCommand *_get_mgrcommand(const string &cmd_prefix,
const std::vector<MonCommand> &commands);
bool _allowed_command(
- MgrSession *s, const string &module, const string &prefix,
- const cmdmap_t& cmdmap,
+ MgrSession *s, const string &service, const string &module,
+ const string &prefix, const cmdmap_t& cmdmap,
const map<string,string>& param_str_map,
const MonCommand *this_cmd);
const std::set <std::string> &changed) override;
void schedule_tick(double delay_sec);
+
+ void log_access_denied(std::shared_ptr<CommandContext>& cmdctx,
+ MgrSession* session, std::stringstream& ss);
};
#endif
std::set<std::string> declared_types;
- const entity_addr_t& get_peer_addr() {
+ const entity_addr_t& get_peer_addr() const {
return inst.addr;
}
}
int PyModuleRegistry::handle_command(
- std::string const &module_name,
+ const ModuleCommand& module_command,
+ const MgrSession& session,
const cmdmap_t &cmdmap,
const bufferlist &inbuf,
std::stringstream *ds,
std::stringstream *ss)
{
if (active_modules) {
- return active_modules->handle_command(module_name, cmdmap, inbuf, ds, ss);
+ return active_modules->handle_command(module_command, session, cmdmap,
+ inbuf, ds, ss);
} else {
// We do not expect to be called before active modules is up, but
// it's straightfoward to handle this case so let's do it.
#include "ActivePyModules.h"
#include "StandbyPyModules.h"
+class MgrSession;
+
/**
* This class is responsible for setting up the python runtime environment
* and importing the python modules.
* return EAGAIN.
*/
int handle_command(
- std::string const &module_name,
+ const ModuleCommand& module_command,
+ const MgrSession& session,
const cmdmap_t &cmdmap,
const bufferlist &inbuf,
std::stringstream *ds,
"""
return self._ceph_get_osd_perf_counters(query_id)
+ def is_authorized(self, arguments):
+ """
+ Verifies that the current session caps permit executing the py service
+ or current module with the provided arguments. This provides a generic
+ way to allow modules to restrict by more fine-grained controls (e.g.
+ pools).
+
+ :param arguments: dict of key/value arguments to test
+ """
+ return self._ceph_is_authorized(arguments)
+
class PersistentStoreDict(object):
def __init__(self, mgr, prefix):