]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/DaemonServer: enforce caps
authorSage Weil <sage@redhat.com>
Tue, 7 Mar 2017 20:36:36 +0000 (15:36 -0500)
committerSage Weil <sage@redhat.com>
Wed, 29 Mar 2017 15:39:25 +0000 (11:39 -0400)
Signed-off-by: Sage Weil <sage@redhat.com>
src/mgr/DaemonServer.cc
src/mgr/DaemonServer.h

index 72fb03967884af3c875ac0fcbcc334eff454ce44..b154b8eee2bfcb840c7dc2c7d2eca78ed9d1369e 100644 (file)
@@ -128,6 +128,7 @@ bool DaemonServer::ms_verify_authorizer(Connection *con,
   }
 
   MgrSessionRef s(new MgrSession);
+  s->addr = con->get_peer_addr();
   AuthCapsInfo caps_info;
 
   is_valid = handler->verify_authorizer(
@@ -280,6 +281,11 @@ struct MgrCommand {
   string module;
   string perm;
   string availability;
+
+  bool requires_perm(char p) const {
+    return (perm.find(p) != string::npos);
+  }
+
 } mgr_commands[] = {
 
 #define COMMAND(parsesig, helptext, module, perm, availability) \
@@ -288,6 +294,71 @@ struct MgrCommand {
 #undef COMMAND
 };
 
+void DaemonServer::_generate_command_map(
+  map<string,cmd_vartype>& cmdmap,
+  map<string,string> &param_str_map)
+{
+  for (map<string,cmd_vartype>::const_iterator p = cmdmap.begin();
+       p != cmdmap.end(); ++p) {
+    if (p->first == "prefix")
+      continue;
+    if (p->first == "caps") {
+      vector<string> cv;
+      if (cmd_getval(g_ceph_context, cmdmap, "caps", cv) &&
+         cv.size() % 2 == 0) {
+       for (unsigned i = 0; i < cv.size(); i += 2) {
+         string k = string("caps_") + cv[i];
+         param_str_map[k] = cv[i + 1];
+       }
+       continue;
+      }
+    }
+    param_str_map[p->first] = cmd_vartype_stringify(p->second);
+  }
+}
+
+const MgrCommand *DaemonServer::_get_mgrcommand(
+  const string &cmd_prefix,
+  MgrCommand *cmds,
+  int cmds_size)
+{
+  MgrCommand *this_cmd = NULL;
+  for (MgrCommand *cp = cmds;
+       cp < &cmds[cmds_size]; cp++) {
+    if (cp->cmdstring.compare(0, cmd_prefix.size(), cmd_prefix) == 0) {
+      this_cmd = cp;
+      break;
+    }
+  }
+  return this_cmd;
+}
+
+bool DaemonServer::_allowed_command(
+  MgrSession *s,
+  const string &module,
+  const string &prefix,
+  const map<string,cmd_vartype>& cmdmap,
+  const map<string,string>& param_str_map,
+  const MgrCommand *this_cmd) {
+
+  if (s->entity_name.is_mon()) {
+    // mon is all-powerful.  even when it is forwarding commands on behalf of
+    // old clients; we expect the mon is validating commands before proxying!
+    return true;
+  }
+
+  bool cmd_r = this_cmd->requires_perm('r');
+  bool cmd_w = this_cmd->requires_perm('w');
+  bool cmd_x = this_cmd->requires_perm('x');
+
+  bool capable = s->caps.is_capable(g_ceph_context, s->entity_name,
+                                    module, prefix, param_str_map,
+                                    cmd_r, cmd_w, cmd_x);
+
+  dout(10) << " " << (capable ? "" : "not ") << "capable" << dendl;
+  return capable;
+}
+
 bool DaemonServer::handle_command(MCommand *m)
 {
   int r = 0;
@@ -299,14 +370,20 @@ bool DaemonServer::handle_command(MCommand *m)
 
   cmdmap_t cmdmap;
 
-  // TODO enforce some caps
-  
   // TODO background the call into python land so that we don't
   // block a messenger thread on python code.
 
   ConnectionRef con = m->get_connection();
+  MgrSessionRef session(static_cast<MgrSession*>(con->get_priv()));
+  if (!session) {
+    return true;
+  }
+  session->put(); // SessionRef takes a ref
+
   string format;
   boost::scoped_ptr<Formatter> f;
+  const MgrCommand *mgr_cmd;
+  map<string,string> param_str_map;
 
   if (!cmdmap_from_json(m->cmd, &cmdmap, ss)) {
     r = -EINVAL;
@@ -355,12 +432,37 @@ bool DaemonServer::handle_command(MCommand *m)
     goto out;
   }
 
+  // lookup command
+  mgr_cmd = _get_mgrcommand(prefix, mgr_commands,
+                                              ARRAY_SIZE(mgr_commands));
+  _generate_command_map(cmdmap, param_str_map);
+  if (!mgr_cmd) {
+    ss << "command not supported";
+    r = -EINVAL;
+    goto out;
+  }
+
+  // validate user's permissions for requested command
+  if (!_allowed_command(session.get(), mgr_cmd->module, prefix, cmdmap,
+                        param_str_map, mgr_cmd)) {
+    dout(1) << __func__ << " access denied" << dendl;
+#if 0
+    // FIXME: audit_clog?
+    clog->info() << "from='" << session->addr << "' "
+                << "entity='" << session->entity_name << "' "
+                << "cmd=" << m->cmd << ":  access denied";
+#endif
+    ss << "access denied";
+    r = -EACCES;
+    goto out;
+  }
+
   // -----------
   // PG commands
 
-  else if (prefix == "pg scrub" ||
-          prefix == "pg repair" ||
-          prefix == "pg deep-scrub") {
+  if (prefix == "pg scrub" ||
+      prefix == "pg repair" ||
+      prefix == "pg deep-scrub") {
     string scrubop = prefix.substr(3, string::npos);
     pg_t pgid;
     string pgidstr;
index ee805919d1bbb687186b936837b1ab860c3777ec..5f020ea7a51432a668a08d427962c5a566e32776 100644 (file)
@@ -33,6 +33,7 @@
 class MMgrReport;
 class MMgrOpen;
 class MCommand;
+struct MgrCommand;
 
 /**
  * Session state associated with the Connection.
@@ -40,6 +41,7 @@ class MCommand;
 struct MgrSession : public RefCountedObject {
   uint64_t global_id = 0;
   EntityName entity_name;
+  entity_addr_t addr;
 
   // mon caps are suitably generic for mgr
   MonCap caps;
@@ -76,6 +78,15 @@ protected:
 
   Mutex lock;
 
+  static void _generate_command_map(map<string,cmd_vartype>& cmdmap,
+                                    map<string,string> &param_str_map);
+  static const MgrCommand *_get_mgrcommand(const string &cmd_prefix,
+                                           MgrCommand *cmds, int cmds_size);
+  bool _allowed_command(
+    MgrSession *s, const string &module, const string &prefix,
+    const map<string,cmd_vartype>& cmdmap,
+    const map<string,string>& param_str_map,
+    const MgrCommand *this_cmd);
 
 public:
   int init(uint64_t gid, entity_addr_t client_addr);