]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr: support optional arguments for module and profile caps
authorJason Dillaman <dillaman@redhat.com>
Fri, 11 Oct 2019 16:44:01 +0000 (12:44 -0400)
committerJason Dillaman <dillaman@redhat.com>
Tue, 29 Oct 2019 12:35:03 +0000 (08:35 -0400)
This allows an optional, arbitrary key/value constraint clauses to
be appended to "profile XYZ" and "allow module XYZ" caps. A module
can then provide additional validatation against these meta-arguments.

Example:
profile rbd pool=rbd
allow module rbd_support with pool=rbd

Signed-off-by: Jason Dillaman <dillaman@redhat.com>
doc/rados/operations/user-management.rst
src/mgr/MgrCap.cc
src/mgr/MgrCap.h
src/test/mgr/test_mgrcap.cc

index 9cd295dbbc86aeba42979fe417f7ae91599c2a36..ba15fb851fb047e5ea2b914b56dadfff2f64a5c2 100644 (file)
@@ -149,7 +149,7 @@ Capability syntax follows the form::
 
        mgr 'allow {access-spec} [network {network/prefix}]'
 
-       mgr 'profile {name} [network {network/prefix}]'
+       mgr 'profile {name} [{key1} {match-type} {value1} ...] [network {network/prefix}]'
 
   Manager capabilities can also be specified for specific commands,
   all commands exported by a built-in manager service, or all commands
@@ -159,7 +159,7 @@ Capability syntax follows the form::
 
         mgr 'allow service {service-name} {access-spec} [network {network/prefix}]'
 
-        mgr 'allow module {module-name} {access-spec} [network {network/prefix}]'
+        mgr 'allow module {module-name} [with {key1} {match-type} {value1} ...] {access-spec} [network {network/prefix}]'
 
   The ``{access-spec}`` syntax is as follows: ::
 
index 0e9f36118f445be476041ecd373704d21691a7cc..37a256f3fe473bd5c9c2211483c700f58bc37ab2 100644 (file)
@@ -86,19 +86,20 @@ ostream& operator<<(ostream& out, const MgrCapGrant& m) {
       out << " module " << maybe_quote_string(m.module);
     } else if (!m.command.empty()) {
       out << " command " << maybe_quote_string(m.command);
-      if (!m.command_args.empty()) {
-        out << " with";
-        for (auto& [key, constraint] : m.command_args) {
-          out << " " << maybe_quote_string(key) << constraint;
-        }
-      }
     }
+  }
 
-    if (m.allow != 0) {
-      out << " " << m.allow;
+  if (!m.arguments.empty()) {
+    out << (!m.profile.empty() ? "" : " with");
+    for (auto& [key, constraint] : m.arguments) {
+      out << " " << maybe_quote_string(key) << constraint;
     }
   }
 
+  if (m.allow != 0) {
+    out << " " << m.allow;
+  }
+
   if (m.network.size()) {
     out << " network " << m.network;
   }
@@ -115,7 +116,7 @@ BOOST_FUSION_ADAPT_STRUCT(MgrCapGrant,
                           (std::string, module)
                           (std::string, profile)
                           (std::string, command)
-                          (kvmap, command_args)
+                          (kvmap, arguments)
                           (mgr_rwxa_t, allow)
                           (std::string, network))
 
@@ -155,15 +156,52 @@ void MgrCapGrant::expand_profile() const {
   }
 }
 
+bool MgrCapGrant::validate_arguments(
+      const std::map<std::string, std::string>& args) const {
+  for (auto& [key, constraint] : arguments) {
+    auto q = args.find(key);
+
+    // argument must be present if a constraint exists
+    if (q == args.end()) {
+      return false;
+    }
+
+    switch (constraint.match_type) {
+    case MgrCapGrantConstraint::MATCH_TYPE_EQUAL:
+      if (constraint.value != q->second)
+        return false;
+      break;
+    case MgrCapGrantConstraint::MATCH_TYPE_PREFIX:
+      if (q->second.find(constraint.value) != 0)
+        return false;
+      break;
+    case MgrCapGrantConstraint::MATCH_TYPE_REGEX:
+      try {
+        std::regex pattern(constraint.value, std::regex::extended);
+        if (!std::regex_match(q->second, pattern)) {
+          return false;
+        }
+      } catch(const std::regex_error&) {
+        return false;
+      }
+      break;
+    default:
+      return false;
+    }
+  }
+
+  return true;
+}
+
 mgr_rwxa_t MgrCapGrant::get_allowed(
     CephContext *cct, EntityName name, const std::string& s,
     const std::string& m, const std::string& c,
-    const std::map<std::string, std::string>& c_args) const {
+    const std::map<std::string, std::string>& args) const {
   if (!profile.empty()) {
     expand_profile();
     mgr_rwxa_t a;
     for (auto& grant : profile_grants) {
-      a = a | grant.get_allowed(cct, name, s, m, c, c_args);
+      a = a | grant.get_allowed(cct, name, s, m, c, args);
     }
     return a;
   }
@@ -179,6 +217,11 @@ mgr_rwxa_t MgrCapGrant::get_allowed(
     if (module != m) {
       return mgr_rwxa_t{};
     }
+
+    // don't test module arguments when validating a specific command
+    if (c.empty() && !validate_arguments(args)) {
+      return mgr_rwxa_t{};
+    }
     return allow;
   }
 
@@ -186,37 +229,8 @@ mgr_rwxa_t MgrCapGrant::get_allowed(
     if (command != c) {
       return mgr_rwxa_t{};
     }
-
-    for (auto& [key, constraint] : command_args) {
-      auto q = c_args.find(key);
-
-      // argument must be present if a constraint exists
-      if (q == c_args.end()) {
-        return mgr_rwxa_t{};
-      }
-
-      switch (constraint.match_type) {
-      case MgrCapGrantConstraint::MATCH_TYPE_EQUAL:
-        if (constraint.value != q->second)
-          return mgr_rwxa_t{};
-        break;
-      case MgrCapGrantConstraint::MATCH_TYPE_PREFIX:
-        if (q->second.find(constraint.value) != 0)
-          return mgr_rwxa_t{};
-        break;
-      case MgrCapGrantConstraint::MATCH_TYPE_REGEX:
-        try {
-          std::regex pattern(constraint.value, std::regex::extended);
-          if (!std::regex_match(q->second, pattern)) {
-            return mgr_rwxa_t{};
-          }
-        } catch(const std::regex_error&) {
-          return mgr_rwxa_t{};
-        }
-        break;
-      default:
-        break;
-      }
+    if (!validate_arguments(args)) {
+      return mgr_rwxa_t{};
     }
     return mgr_rwxa_t{MGR_CAP_ANY};
   }
@@ -345,6 +359,10 @@ void MgrCap::generate_test_instances(list<MgrCap*>& ls) {
   ls.back()->parse("allow command bar with k1=v1 x");
   ls.push_back(new MgrCap);
   ls.back()->parse("allow command bar with k1=v1 k2=v2 x");
+  ls.push_back(new MgrCap);
+  ls.back()->parse("allow module bar with k1=v1 k2=v2 x");
+  ls.push_back(new MgrCap);
+  ls.back()->parse("profile rbd pool=rbd");
 }
 
 // grammar
@@ -412,7 +430,7 @@ struct MgrCapParser : qi::grammar<Iterator, MgrCap()> {
                             >> str
                             >> qi::attr(std::string())
                             >> qi::attr(std::string())
-                            >> qi::attr(map<std::string, MgrCapGrantConstraint>())
+                            >> -(spaces >> lit("with") >> spaces >> kv_map)
                             >> spaces >> rwxa
                             >> -(spaces >> lit("network") >> spaces >> network_str);
 
@@ -423,7 +441,7 @@ struct MgrCapParser : qi::grammar<Iterator, MgrCap()> {
                              >> qi::attr(std::string())
                              >> str
                              >> qi::attr(std::string())
-                             >> qi::attr(std::map<std::string, MgrCapGrantConstraint>())
+                             >> -(spaces >> kv_map)
                              >> qi::attr(0)
                              >> -(spaces >> lit("network") >> spaces >> network_str);
 
index 96b5b96934bea49e761419ece2fb7bd165ec6541..dc14ac2d2a910233d4ea9bd9ed1c0dff71fd4bf4 100644 (file)
@@ -63,26 +63,30 @@ struct MgrCapGrant {
    *  - a service allow ('allow service mds rw')
    *    - this will match against a specific service and the r/w/x flags.
    *
-   *  - a module allow ('allow module rbd_support rw')
+   *  - a module allow ('allow module rbd_support rw, allow module rbd_support with pool=rbd rw')
    *    - this will match against a specific python add-on module and the r/w/x
    *      flags.
    *
-   *  - a profile ('profile read-only')
+   *  - a profile ('profile read-only, profile rbd pool=rbd')
    *    - this will match against specific MGR-enforced semantics of what
    *      this type of user should need to do.  examples include 'read-write',
    *      'read-only', 'crash'.
    *
    *  - a command ('allow command foo', 'allow command bar with arg1=val1 arg2 prefix val2')
-   *      this includes the command name (the prefix string), and a set
-   *      of key/value pairs that constrain use of that command.  if no pairs
-   *      are specified, any arguments are allowed; if a pair is specified, that
-   *      argument must be present and equal or match a prefix.
+   *      this includes the command name (the prefix string)
+   *
+   *  The command, module, and profile caps can also accept an optional
+   *  key/value map. If not provided, all command arguments and module
+   *  meta-arguments are allowed. If a key/value pair is specified, that
+   *  argument must be present and must match the provided constraint.
    */
+  typedef std::map<std::string, MgrCapGrantConstraint> Arguments;
+
   std::string service;
   std::string module;
   std::string profile;
   std::string command;
-  std::map<std::string, MgrCapGrantConstraint> command_args;
+  Arguments arguments;
 
   // restrict by network
   std::string network;
@@ -107,13 +111,16 @@ struct MgrCapGrant {
               std::string&& module,
               std::string&& profile,
               std::string&& command,
-              std::map<std::string, MgrCapGrantConstraint>&& command_args,
+              Arguments&& arguments,
               mgr_rwxa_t allow)
     : service(std::move(service)), module(std::move(module)),
       profile(std::move(profile)), command(std::move(command)),
-      command_args(std::move(command_args)), allow(allow) {
+      arguments(std::move(arguments)), allow(allow) {
   }
 
+  bool validate_arguments(
+      const std::map<std::string, std::string>& arguments) const;
+
   /**
    * check if given request parameters match our constraints
    *
@@ -122,7 +129,7 @@ struct MgrCapGrant {
    * @param service service (if any)
    * @param module module (if any)
    * @param command command (if any)
-   * @param command_args command args (if any)
+   * @param arguments profile/module/command args (if any)
    * @return bits we allow
    */
   mgr_rwxa_t get_allowed(
@@ -131,7 +138,7 @@ struct MgrCapGrant {
       const std::string& service,
       const std::string& module,
       const std::string& command,
-      const std::map<std::string, std::string>& command_args) const;
+      const std::map<std::string, std::string>& arguments) const;
 
   bool is_allow_all() const {
     return (allow == MGR_CAP_ANY &&
@@ -168,7 +175,7 @@ struct MgrCap {
    * @param service service name
    * @param module module name
    * @param command command id
-   * @param command_args
+   * @param arguments
    * @param op_may_read whether the operation may need to read
    * @param op_may_write whether the operation may need to write
    * @param op_may_exec whether the operation may exec
@@ -179,7 +186,7 @@ struct MgrCap {
                  const std::string& service,
                  const std::string& module,
                  const std::string& command,
-                 const std::map<std::string, std::string>& command_args,
+                 const std::map<std::string, std::string>& arguments,
                  bool op_may_read, bool op_may_write, bool op_may_exec,
                  const entity_addr_t& addr) const;
 
index 8dc21561a2ab5991da9bbf7dc8ce8a6dd631d53d..c6c73b885ba689e0890b2222c31f943f3fb2ee89 100644 (file)
@@ -57,12 +57,14 @@ const char *parse_good[] = {
   "allow module=foo x",
   "allow module foo_foo r",
   "allow module \" foo \" w",
+  "allow module foo with arg1=value1 x",
   "allow command abc with arg=foo arg2=bar, allow service foo r",
   "allow command abc.def with arg=foo arg2=bar, allow service foo r",
   "allow command \"foo bar\" with arg=\"baz\"",
   "allow command \"foo bar\" with arg=\"baz.xx\"",
   "allow command \"foo bar\" with arg = \"baz.xx\"",
   "profile osd",
+  "profile rbd pool=ABC namespace=NS",
   "profile \"mds-bootstrap\", profile foo",
   "allow * network 1.2.3.4/24",
   "allow * network ::1/128",
@@ -95,6 +97,7 @@ const char *parse_identity[] = {
   "profile osd-bootstrap",
   "profile mds-bootstrap, allow *",
   "profile \"foo bar\", allow *",
+  "profile rbd namespace=NS pool=ABC",
   "allow command abc",
   "allow command \"a b c\"",
   "allow command abc with arg=foo",
@@ -111,6 +114,7 @@ const char *parse_identity[] = {
   "allow service \" foo \" w, allow service bar r",
   "allow module foo x",
   "allow module \" foo_foo \" r",
+  "allow module foo with arg1=value1 x",
   "allow command abc with arg=foo arg2=bar, allow service foo r",
   0
 };