]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr: add new 'allow module' cap to MgrCap
authorJason Dillaman <dillaman@redhat.com>
Fri, 11 Oct 2019 15:25:56 +0000 (11:25 -0400)
committerJason Dillaman <dillaman@redhat.com>
Thu, 9 Jan 2020 18:59:36 +0000 (13:59 -0500)
This allows specific python add-on modules to be whitelisted instead
of manually adding each command exported by the module.

  allow module {module-name} {access-spec}

Signed-off-by: Jason Dillaman <dillaman@redhat.com>
(cherry picked from commit 3463613bd43d5fb7f8094c3b9b8a3163325ff196)

doc/rados/operations/user-management.rst
src/mgr/DaemonServer.cc
src/mgr/MgrCap.cc
src/mgr/MgrCap.h
src/test/mgr/test_mgrcap.cc

index 1b3b3882a1327525ee316db80a9c0a2725bc9978..01357ce96144ea093e579a7fea20b60ba1b35359 100644 (file)
@@ -144,6 +144,35 @@ Capability syntax follows the form::
   the use of this capability is restricted to clients connecting from
   this network.
 
+- **Manager Caps:** Manager (``ceph-mgr``) capabilities include
+  ``r``, ``w``, ``x`` access settings or ``profile {name}``. For example: ::
+
+       mgr 'allow {access-spec} [network {network/prefix}]'
+
+       mgr 'profile {name} [network {network/prefix}]'
+
+  Manager capabilities can also be specified for specific commands,
+  all commands exported by a built-in manager service, or all commands
+  exported by a specific add-on module. For example: ::
+
+        mgr 'allow command "{command-prefix}" [with {key1} {match-type} {value1} ...] [network {network/prefix}]'
+
+        mgr 'allow service {service-name} {access-spec} [network {network/prefix}]'
+
+        mgr 'allow module {module-name} {access-spec} [network {network/prefix}]'
+
+  The ``{access-spec}`` syntax is as follows: ::
+
+        * | all | [r][w][x]
+
+  The ``{service-name}`` is one of the following: ::
+
+        mgr | osd | pg | py
+
+  The ``{match-type}`` is one of the following: ::
+
+        = | prefix | regex
+
 - **Metadata Server Caps:** For administrators, use ``allow *``.  For all
   other users, such as CephFS clients, consult :doc:`/cephfs/client-auth`
 
index 1ea16fb5eb13fbbba44633a473ef135358417021..2c6c2aee6128f49a3ce6e23eccf474663ed09543 100644 (file)
@@ -700,7 +700,7 @@ bool DaemonServer::_allowed_command(
   bool capable = s->caps.is_capable(
     g_ceph_context,
     s->entity_name,
-    module, prefix, param_str_map,
+    module, "", prefix, param_str_map,
     cmd_r, cmd_w, cmd_x,
     s->get_peer_addr());
 
index 619a0593c738d623fa2f5d0d75c1d2a728635360..0e9f36118f445be476041ecd373704d21691a7cc 100644 (file)
@@ -82,6 +82,8 @@ ostream& operator<<(ostream& out, const MgrCapGrant& m) {
     out << "allow";
     if (!m.service.empty()) {
       out << " service " << maybe_quote_string(m.service);
+    } else if (!m.module.empty()) {
+      out << " module " << maybe_quote_string(m.module);
     } else if (!m.command.empty()) {
       out << " command " << maybe_quote_string(m.command);
       if (!m.command_args.empty()) {
@@ -110,6 +112,7 @@ typedef std::map<std::string, MgrCapGrantConstraint> kvmap;
 
 BOOST_FUSION_ADAPT_STRUCT(MgrCapGrant,
                           (std::string, service)
+                          (std::string, module)
                           (std::string, profile)
                           (std::string, command)
                           (kvmap, command_args)
@@ -135,32 +138,32 @@ void MgrCapGrant::expand_profile() const {
 
   if (profile == "read-only") {
     // grants READ-ONLY caps MGR-wide
-    profile_grants.push_back({{}, {}, {}, {}, mgr_rwxa_t{MGR_CAP_R}});
+    profile_grants.push_back({{}, {}, {}, {}, {}, mgr_rwxa_t{MGR_CAP_R}});
     return;
   }
 
   if (profile == "read-write") {
     // grants READ-WRITE caps MGR-wide
-    profile_grants.push_back({{}, {}, {}, {},
+    profile_grants.push_back({{}, {}, {}, {}, {},
                               mgr_rwxa_t{MGR_CAP_R | MGR_CAP_W}});
     return;
   }
 
   if (profile == "crash") {
-    profile_grants.push_back({{}, {}, "crash post", {}, {}});
+    profile_grants.push_back({{}, {}, {}, "crash post", {}, {}});
     return;
   }
 }
 
 mgr_rwxa_t MgrCapGrant::get_allowed(
     CephContext *cct, EntityName name, const std::string& s,
-    const std::string& c,
+    const std::string& m, const std::string& c,
     const std::map<std::string, std::string>& c_args) const {
   if (!profile.empty()) {
     expand_profile();
     mgr_rwxa_t a;
     for (auto& grant : profile_grants) {
-      a = a | grant.get_allowed(cct, name, s, c, c_args);
+      a = a | grant.get_allowed(cct, name, s, m, c, c_args);
     }
     return a;
   }
@@ -172,6 +175,13 @@ mgr_rwxa_t MgrCapGrant::get_allowed(
     return allow;
   }
 
+  if (!module.empty()) {
+    if (module != m) {
+      return mgr_rwxa_t{};
+    }
+    return allow;
+  }
+
   if (!command.empty()) {
     if (command != c) {
       return mgr_rwxa_t{};
@@ -238,7 +248,7 @@ bool MgrCap::is_allow_all() const {
 
 void MgrCap::set_allow_all() {
   grants.clear();
-  grants.push_back({{}, {}, {}, {}, mgr_rwxa_t{MGR_CAP_ANY}});
+  grants.push_back({{}, {}, {}, {}, {}, mgr_rwxa_t{MGR_CAP_ANY}});
   text = "allow *";
 }
 
@@ -246,12 +256,14 @@ bool MgrCap::is_capable(
     CephContext *cct,
     EntityName name,
     const std::string& service,
+    const std::string& module,
     const std::string& command,
     const std::map<std::string, std::string>& command_args,
     bool op_may_read, bool op_may_write, bool op_may_exec,
     const entity_addr_t& addr) const {
   if (cct) {
     ldout(cct, 20) << "is_capable service=" << service << " "
+                   << "module=" << module << " "
                    << "command=" << command
                    << (op_may_read ? " read":"")
                    << (op_may_write ? " write":"")
@@ -283,7 +295,7 @@ bool MgrCap::is_capable(
     }
 
     // check enumerated caps
-    allow = allow | grant.get_allowed(cct, name, service, command,
+    allow = allow | grant.get_allowed(cct, name, service, module, command,
                                       command_args);
     if ((!op_may_read || (allow & MGR_CAP_R)) &&
         (!op_may_write || (allow & MGR_CAP_W)) &&
@@ -376,7 +388,9 @@ struct MgrCapParser : qi::grammar<Iterator, MgrCap()> {
 
     // command := command[=]cmd [k1=v1 k2=v2 ...]
     command_match = -spaces >> lit("allow") >> spaces >> lit("command") >> (lit('=') | spaces)
-                            >> qi::attr(std::string()) >> qi::attr(std::string())
+                            >> qi::attr(std::string())
+                            >> qi::attr(std::string())
+                            >> qi::attr(std::string())
                             >> str
                             >> -(spaces >> lit("with") >> spaces >> kv_map)
                             >> qi::attr(0)
@@ -384,15 +398,29 @@ struct MgrCapParser : qi::grammar<Iterator, MgrCap()> {
 
     // service foo rwxa
     service_match %= -spaces >> lit("allow") >> spaces >> lit("service") >> (lit('=') | spaces)
-                             >> str >> qi::attr(std::string()) >> qi::attr(std::string())
+                             >> str
+                             >> qi::attr(std::string())
+                             >> qi::attr(std::string())
+                             >> qi::attr(std::string())
                              >> qi::attr(map<std::string, MgrCapGrantConstraint>())
                              >> spaces >> rwxa
                              >> -(spaces >> lit("network") >> spaces >> network_str);
 
+    // module foo rwxa
+    module_match %= -spaces >> lit("allow") >> spaces >> lit("module") >> (lit('=') | spaces)
+                            >> qi::attr(std::string())
+                            >> str
+                            >> qi::attr(std::string())
+                            >> qi::attr(std::string())
+                            >> qi::attr(map<std::string, MgrCapGrantConstraint>())
+                            >> spaces >> rwxa
+                            >> -(spaces >> lit("network") >> spaces >> network_str);
+
     // profile foo
     profile_match %= -spaces >> -(lit("allow") >> spaces)
                              >> lit("profile") >> (lit('=') | spaces)
                              >> qi::attr(std::string())
+                             >> qi::attr(std::string())
                              >> str
                              >> qi::attr(std::string())
                              >> qi::attr(std::map<std::string, MgrCapGrantConstraint>())
@@ -401,7 +429,10 @@ struct MgrCapParser : qi::grammar<Iterator, MgrCap()> {
 
     // rwxa
     rwxa_match %= -spaces >> lit("allow") >> spaces
-                          >> qi::attr(std::string()) >> qi::attr(std::string()) >> qi::attr(std::string())
+                          >> qi::attr(std::string())
+                          >> qi::attr(std::string())
+                          >> qi::attr(std::string())
+                          >> qi::attr(std::string())
                           >> qi::attr(std::map<std::string,MgrCapGrantConstraint>())
                           >> rwxa
                           >> -(spaces >> lit("network") >> spaces >> network_str);
@@ -418,7 +449,8 @@ struct MgrCapParser : qi::grammar<Iterator, MgrCap()> {
         );
 
     // grant := allow ...
-    grant = -spaces >> (rwxa_match | profile_match | service_match | command_match) >> -spaces;
+    grant = -spaces >> (rwxa_match | profile_match | service_match |
+                        module_match | command_match) >> -spaces;
 
     // mgrcap := grant [grant ...]
     grants %= (grant % (*lit(' ') >> (lit(';') | lit(',')) >> *lit(' ')));
@@ -438,6 +470,7 @@ struct MgrCapParser : qi::grammar<Iterator, MgrCap()> {
   qi::rule<Iterator, MgrCapGrant()> rwxa_match;
   qi::rule<Iterator, MgrCapGrant()> command_match;
   qi::rule<Iterator, MgrCapGrant()> service_match;
+  qi::rule<Iterator, MgrCapGrant()> module_match;
   qi::rule<Iterator, MgrCapGrant()> profile_match;
   qi::rule<Iterator, MgrCapGrant()> grant;
   qi::rule<Iterator, std::vector<MgrCapGrant>()> grants;
index b0385773549a16ade5ecbc5bea82eab33552a1f9..96b5b96934bea49e761419ece2fb7bd165ec6541 100644 (file)
@@ -63,6 +63,10 @@ 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')
+   *    - this will match against a specific python add-on module and the r/w/x
+   *      flags.
+   *
    *  - a profile ('profile read-only')
    *    - this will match against specific MGR-enforced semantics of what
    *      this type of user should need to do.  examples include 'read-write',
@@ -75,6 +79,7 @@ struct MgrCapGrant {
    *      argument must be present and equal or match a prefix.
    */
   std::string service;
+  std::string module;
   std::string profile;
   std::string command;
   std::map<std::string, MgrCapGrantConstraint> command_args;
@@ -99,13 +104,14 @@ struct MgrCapGrant {
 
   MgrCapGrant() : allow(0) {}
   MgrCapGrant(std::string&& service,
+              std::string&& module,
               std::string&& profile,
               std::string&& command,
               std::map<std::string, MgrCapGrantConstraint>&& command_args,
               mgr_rwxa_t allow)
-    : service(std::move(service)), profile(std::move(profile)),
-      command(std::move(command)), command_args(std::move(command_args)),
-      allow(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) {
   }
 
   /**
@@ -114,6 +120,7 @@ struct MgrCapGrant {
    * @param cct context
    * @param name entity name
    * @param service service (if any)
+   * @param module module (if any)
    * @param command command (if any)
    * @param command_args command args (if any)
    * @return bits we allow
@@ -122,12 +129,14 @@ struct MgrCapGrant {
       CephContext *cct,
       EntityName name,
       const std::string& service,
+      const std::string& module,
       const std::string& command,
       const std::map<std::string, std::string>& command_args) const;
 
   bool is_allow_all() const {
     return (allow == MGR_CAP_ANY &&
             service.empty() &&
+            module.empty() &&
             profile.empty() &&
             command.empty());
   }
@@ -157,6 +166,7 @@ struct MgrCap {
    * what the capability has specified.
    *
    * @param service service name
+   * @param module module name
    * @param command command id
    * @param command_args
    * @param op_may_read whether the operation may need to read
@@ -167,6 +177,7 @@ struct MgrCap {
   bool is_capable(CephContext *cct,
                  EntityName name,
                  const std::string& service,
+                 const std::string& module,
                  const std::string& command,
                  const std::map<std::string, std::string>& command_args,
                  bool op_may_read, bool op_may_write, bool op_may_exec,
index 2b997f532c616d1c6a43a5a160ab7a62f2295fbe..8dc21561a2ab5991da9bbf7dc8ce8a6dd631d53d 100644 (file)
@@ -53,6 +53,10 @@ const char *parse_good[] = {
   "allow service foo_foo r, allow service bar r",
   "allow service foo-foo r, allow service bar r",
   "allow service \" foo \" w, allow service bar r",
+  "allow module foo x",
+  "allow module=foo x",
+  "allow module foo_foo r",
+  "allow module \" foo \" w",
   "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\"",
@@ -105,6 +109,8 @@ const char *parse_identity[] = {
   "allow service foo_foo r, allow service bar r",
   "allow service foo-foo r, allow service bar r",
   "allow service \" foo \" w, allow service bar r",
+  "allow module foo x",
+  "allow module \" foo_foo \" r",
   "allow command abc with arg=foo arg2=bar, allow service foo r",
   0
 };
@@ -193,8 +199,8 @@ TEST(MgrCap, AllowAll) {
 
   ASSERT_TRUE(cap.parse("allow *", nullptr));
   ASSERT_TRUE(cap.is_allow_all());
-  ASSERT_TRUE(cap.is_capable(nullptr, {}, "foo", "asdf", {}, true, true, true,
-                             {}));
+  ASSERT_TRUE(cap.is_capable(nullptr, {}, "foo", "", "asdf", {}, true, true,
+                             true, {}));
 
   MgrCap cap2;
   ASSERT_FALSE(cap2.is_allow_all());
@@ -212,12 +218,12 @@ TEST(MgrCap, Network) {
   b.parse("192.168.2.3");
   c.parse("192.167.2.3");
 
-  ASSERT_TRUE(cap.is_capable(nullptr, {}, "foo", "asdf", {}, true, true, true,
-                             a));
-  ASSERT_TRUE(cap.is_capable(nullptr, {}, "foo", "asdf", {}, true, true, true,
-                            b));
-  ASSERT_FALSE(cap.is_capable(nullptr, {}, "foo", "asdf", {}, true, true, true,
-                            c));
+  ASSERT_TRUE(cap.is_capable(nullptr, {}, "foo", "", "asdf", {}, true, true,
+                             true, a));
+  ASSERT_TRUE(cap.is_capable(nullptr, {}, "foo", "", "asdf", {}, true, true,
+                             true, b));
+  ASSERT_FALSE(cap.is_capable(nullptr, {}, "foo", "", "asdf", {}, true, true,
+                              true, c));
 }
 
 TEST(MgrCap, CommandRegEx) {
@@ -228,12 +234,27 @@ TEST(MgrCap, CommandRegEx) {
 
   EntityName name;
   name.from_str("osd.123");
-  ASSERT_TRUE(cap.is_capable(nullptr, name, "", "abc", {{"arg", "12345abcde"}},
-                             true, true, true, {}));
-  ASSERT_FALSE(cap.is_capable(nullptr, name, "", "abc", {{"arg", "~!@#$"}},
+  ASSERT_TRUE(cap.is_capable(nullptr, name, "", "", "abc",
+                             {{"arg", "12345abcde"}}, true, true, true, {}));
+  ASSERT_FALSE(cap.is_capable(nullptr, name, "", "", "abc", {{"arg", "~!@#$"}},
                               true, true, true, {}));
 
   ASSERT_TRUE(cap.parse("allow command abc with arg regex \"[*\"", nullptr));
-  ASSERT_FALSE(cap.is_capable(nullptr, name, "", "abc", {{"arg", ""}}, true,
+  ASSERT_FALSE(cap.is_capable(nullptr, name, "", "", "abc", {{"arg", ""}}, true,
                               true, true, {}));
 }
+
+TEST(MgrCap, Module) {
+  MgrCap cap;
+  ASSERT_FALSE(cap.is_allow_all());
+  ASSERT_TRUE(cap.parse("allow module abc r, allow module bcd w", nullptr));
+
+  ASSERT_FALSE(cap.is_capable(nullptr, {}, "", "abc", "", {}, true, true, false,
+                              {}));
+  ASSERT_TRUE(cap.is_capable(nullptr, {}, "", "abc", "", {}, true, false, false,
+                             {}));
+  ASSERT_FALSE(cap.is_capable(nullptr, {}, "", "bcd", "", {}, true, true, false,
+                              {}));
+  ASSERT_TRUE(cap.is_capable(nullptr, {}, "", "bcd", "", {}, false, true, false,
+                             {}));
+}