]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
osd: primitive cephx osd profile cap support
authorJason Dillaman <dillaman@redhat.com>
Thu, 29 Jun 2017 01:20:09 +0000 (21:20 -0400)
committerJason Dillaman <dillaman@redhat.com>
Fri, 21 Jul 2017 18:29:37 +0000 (14:29 -0400)
The two new example profiles are read-only and read-write

Signed-off-by: Jason Dillaman <dillaman@redhat.com>
src/osd/OSDCap.cc
src/osd/OSDCap.h
src/test/osd/osdcap.cc

index 25da5e2b69a5dd76f6115a638fffa5bc8686e291..c759ab76825d549b7a1c3a713a09b98f9db5847e 100644 (file)
@@ -25,7 +25,7 @@ using std::ostream;
 using std::vector;
 
 ostream& operator<<(ostream& out, const osd_rwxa_t& p)
-{ 
+{
   if (p == OSD_CAP_ANY)
     return out << "*";
 
@@ -53,42 +53,79 @@ ostream& operator<<(ostream& out, const OSDCapSpec& s)
   return out;
 }
 
+ostream& operator<<(ostream& out, const OSDCapPoolNamespace& pns)
+{
+  if (!pns.pool_name.empty()) {
+    out << "pool " << pns.pool_name << " ";
+  }
+  if (pns.nspace) {
+    out << "namespace ";
+    if (pns.nspace->empty()) {
+      out << "\"\"";
+    } else {
+      out << *pns.nspace;
+    }
+    out << " ";
+  }
+  return out;
+}
+
 ostream& operator<<(ostream& out, const OSDCapMatch& m)
 {
   if (m.auid != -1LL) {
     out << "auid " << m.auid << " ";
+  } else {
+    out << m.pool_namespace;
   }
+
   if (m.object_prefix.length()) {
     out << "object_prefix " << m.object_prefix << " ";
   }
-  if (m.pool_name.length()) {
-    out << "pool " << m.pool_name << " ";
-  }
-  if (m.is_nspace) {
-    out << "namespace ";
-    if (m.nspace.length() == 0)
-      out << "\"\"";
-    else
-      out << m.nspace;
-    out << " ";
-  }
   return out;
 }
 
-bool OSDCapMatch::is_match(const string& pn, const string& ns, int64_t pool_auid, const string& object) const
+ostream& operator<<(ostream& out, const OSDCapProfile& m)
 {
-  if (auid >= 0) {
-    if (auid != pool_auid)
+  out << "profile " << m.name << " ";
+  out << m.pool_namespace;
+  return out;
+}
+
+bool OSDCapPoolNamespace::is_match(const std::string& pn,
+                                   const std::string& ns) const
+{
+  if (!pool_name.empty()) {
+    if (pool_name != pn) {
       return false;
+    }
   }
-  if (pool_name.length()) {
-    if (pool_name != pn)
+  if (nspace) {
+    if (*nspace != ns) {
       return false;
+    }
   }
-  if (is_nspace) {
-    if (nspace != ns)
+  return true;
+}
+
+bool OSDCapPoolNamespace::is_match_all() const
+{
+  if (!pool_name.empty())
+    return false;
+  if (nspace)
+    return false;
+  return true;
+}
+
+bool OSDCapMatch::is_match(const string& pn, const string& ns,
+                           int64_t pool_auid, const string& object) const
+{
+  if (auid >= 0) {
+    if (auid != pool_auid)
       return false;
+  } else if (!pool_namespace.is_match(pn, ns)) {
+    return false;
   }
+
   if (object_prefix.length()) {
     if (object.find(object_prefix) != 0)
       return false;
@@ -98,77 +135,91 @@ bool OSDCapMatch::is_match(const string& pn, const string& ns, int64_t pool_auid
 
 bool OSDCapMatch::is_match_all() const
 {
-  if (auid >= 0)
-    return false;
-  if (pool_name.length())
+  if (auid >= 0) {
     return false;
-  if (is_nspace)
+  } else if (!pool_namespace.is_match_all()) {
     return false;
-  if (object_prefix.length())
+  }
+
+  if (object_prefix.length()) {
     return false;
+  }
   return true;
 }
 
 ostream& operator<<(ostream& out, const OSDCapGrant& g)
 {
-  return out << "grant(" << g.match << g.spec << ")";
+  out << "grant(";
+  if (g.profile.is_valid()) {
+    out << g.profile;
+  } else {
+    out << g.match << g.spec;
+  }
+  out << ")";
+  return out;
 }
 
-
-bool OSDCap::allow_all() const
+bool OSDCapGrant::allow_all() const
 {
-  for (vector<OSDCapGrant>::const_iterator p = grants.begin(); p != grants.end(); ++p)
-    if (p->match.is_match_all() && p->spec.allow_all())
-      return true;
-  return false;
-}
+  if (profile.is_valid()) {
+    expand_profile();
+    return std::any_of(profile_grants.cbegin(), profile_grants.cend(),
+                       [](const OSDCapGrant& grant) {
+        return grant.allow_all();
+      });
+  }
 
-void OSDCap::set_allow_all()
-{
-  grants.clear();
-  grants.push_back(OSDCapGrant(OSDCapMatch(), OSDCapSpec(OSD_CAP_ANY)));
+  return (match.is_match_all() && spec.allow_all());
 }
 
-bool OSDCap::is_capable(const string& pool_name, const string& ns, int64_t pool_auid,
-                       const string& object, bool op_may_read, bool op_may_write,
-                       const std::vector<OpRequest::ClassInfo>& classes) const
+bool OSDCapGrant::is_capable(const string& pool_name, const string& ns,
+                             int64_t pool_auid, const string& object,
+                             bool op_may_read, bool op_may_write,
+                             const std::vector<OpRequest::ClassInfo>& classes,
+                             std::vector<bool>* class_allowed) const
 {
-  const size_t num_classes = classes.size();
-  std::vector<bool> class_allowed(num_classes, false);
   osd_rwxa_t allow = 0;
-  for (vector<OSDCapGrant>::const_iterator p = grants.begin();
-       p != grants.end(); ++p) {
-    if (p->match.is_match(pool_name, ns, pool_auid, object)) {
-      allow = allow | p->spec.allow;
+  if (profile.is_valid()) {
+    expand_profile();
+    return std::any_of(profile_grants.cbegin(), profile_grants.cend(),
+                       [&](const OSDCapGrant& grant) {
+        return grant.is_capable(pool_name, ns, pool_auid, object, op_may_read,
+                                op_may_write, classes, class_allowed);
+      });
+  } else {
+    if (match.is_match(pool_name, ns, pool_auid, object)) {
+      allow = allow | spec.allow;
       if ((op_may_read && !(allow & OSD_CAP_R)) ||
-          (op_may_write && !(allow & OSD_CAP_W)))
-        continue;
-      if (num_classes) {
+          (op_may_write && !(allow & OSD_CAP_W))) {
+        return false;
+      }
+      if (!classes.empty()) {
         // check 'allow *'
-        if (p->spec.allow_all())
+        if (spec.allow_all()) {
           return true;
+        }
+
         // compare this grant to each class in the operation
-        for (size_t i = 0; i < num_classes; i++) {
+        for (size_t i = 0; i < classes.size(); ++i) {
           // check 'allow class foo'
-          if (classes[i].name == p->spec.class_name &&
-              !p->spec.class_name.empty()) {
-            class_allowed[i] = true;
+          if (!spec.class_name.empty() && classes[i].name == spec.class_name) {
+            (*class_allowed)[i] = true;
             continue;
           }
           // check 'allow x | class-{rw}': must be on whitelist
-          if (!classes[i].whitelisted)
+          if (!classes[i].whitelisted) {
             continue;
+          }
           if ((classes[i].read && !(allow & OSD_CAP_CLS_R)) ||
               (classes[i].write && !(allow & OSD_CAP_CLS_W))) {
             continue;
           }
-          class_allowed[i] = true;
+          (*class_allowed)[i] = true;
+        }
+        if (!std::all_of(class_allowed->cbegin(), class_allowed->cend(),
+              [](bool v) { return v; })) {
+          return false;
         }
-        if (std::all_of(class_allowed.cbegin(), class_allowed.cend(),
-              [](bool v) { return v; }))
-          return true;
-        else
-          continue;
       }
       return true;
     }
@@ -176,6 +227,57 @@ bool OSDCap::is_capable(const string& pool_name, const string& ns, int64_t pool_
   return false;
 }
 
+void OSDCapGrant::expand_profile() const
+{
+  // only generate this list once
+  if (!profile_grants.empty()) {
+    return;
+  }
+
+  if (profile.name == "read-only") {
+    // grants READ-ONLY caps to the OSD
+    profile_grants.emplace_back(OSDCapMatch(profile.pool_namespace),
+                                OSDCapSpec(osd_rwxa_t(OSD_CAP_R)));
+    return;
+  }
+  if (profile.name == "read-write") {
+    // grants READ-WRITE caps to the OSD
+    profile_grants.emplace_back(OSDCapMatch(profile.pool_namespace),
+                                OSDCapSpec(osd_rwxa_t(OSD_CAP_R | OSD_CAP_W)));
+  }
+}
+
+bool OSDCap::allow_all() const
+{
+  for (auto &grant : grants) {
+    if (grant.allow_all()) {
+      return true;
+    }
+  }
+  return false;
+}
+
+void OSDCap::set_allow_all()
+{
+  grants.clear();
+  grants.push_back(OSDCapGrant(OSDCapMatch(), OSDCapSpec(OSD_CAP_ANY)));
+}
+
+bool OSDCap::is_capable(const string& pool_name, const string& ns,
+                        int64_t pool_auid, const string& object,
+                        bool op_may_read, bool op_may_write,
+                       const std::vector<OpRequest::ClassInfo>& classes) const
+{
+  std::vector<bool> class_allowed(classes.size(), false);
+  for (auto &grant : grants) {
+    if (grant.is_capable(pool_name, ns, pool_auid, object, op_may_read,
+                         op_may_write, classes, &class_allowed)) {
+      return true;
+    }
+  }
+  return false;
+}
+
 
 // grammar
 namespace qi = boost::spirit::qi;
@@ -210,16 +312,17 @@ struct OSDCapParser : qi::grammar<Iterator, OSDCap()>
 
     spaces = +ascii::space;
 
-
-    // match := [pool[=]<poolname> [namespace[=]<namespace>] | auid <123>] [object_prefix <prefix>]
     pool_name %= -(spaces >> lit("pool") >> (lit('=') | spaces) >> str);
     nspace %= (spaces >> lit("namespace") >> (lit('=') | spaces) >> estr);
+
+    // match := [pool[=]<poolname> [namespace[=]<namespace>] | auid <123>] [object_prefix <prefix>]
     auid %= (spaces >> lit("auid") >> spaces >> int_);
     object_prefix %= -(spaces >> lit("object_prefix") >> spaces >> str);
 
-    match = ( (auid >> object_prefix)                 [_val = phoenix::construct<OSDCapMatch>(_1, _2)] |
-             (pool_name >> nspace >> object_prefix)   [_val = phoenix::construct<OSDCapMatch>(_1, _2, _3)] |
-             (pool_name >> object_prefix)             [_val = phoenix::construct<OSDCapMatch>(_1, _2)]);
+    match = (
+      (auid >> object_prefix)                 [_val = phoenix::construct<OSDCapMatch>(_1, _2)] |
+      (pool_name >> nspace >> object_prefix)  [_val = phoenix::construct<OSDCapMatch>(_1, _2, _3)] |
+      (pool_name >> object_prefix)            [_val = phoenix::construct<OSDCapMatch>(_1, _2)]);
 
     // rwxa := * | [r][w][x] [class-read] [class-write]
     rwxa =
@@ -234,19 +337,27 @@ struct OSDCapParser : qi::grammar<Iterator, OSDCap()>
          (spaces >> lit("class-write")[_val |= OSD_CAP_CLS_W]) ));
 
     // capspec := * | rwx | class <name> [classcap]
-    capspec =
-      rwxa                                            [_val = phoenix::construct<OSDCapSpec>(_1)] |
-      ( spaces >> lit("class") >> spaces >> ((str >> spaces >> str)   [_val = phoenix::construct<OSDCapSpec>(_1, _2)] |
-                        str                          [_val = phoenix::construct<OSDCapSpec>(_1, string())] ));
+    class_name %= (spaces >> lit("class") >> spaces >> str);
+    class_cap %= -(spaces >> str);
+    capspec = (
+      (rwxa)                    [_val = phoenix::construct<OSDCapSpec>(_1)] |
+      (class_name >> class_cap) [_val = phoenix::construct<OSDCapSpec>(_1, _2)]);
+
+    // profile := profile <name> [pool[=]<pool> [namespace[=]<namespace>]]
+    profile_name %= (spaces >> lit("profile") >> spaces >> str);
+    profile = (
+      (profile_name >> pool_name >> nspace) [_val = phoenix::construct<OSDCapProfile>(_1, _2, _3)] |
+      (profile_name >> pool_name)           [_val = phoenix::construct<OSDCapProfile>(_1, _2)]);
 
     // grant := allow match capspec
     grant = (*ascii::blank >> lit("allow") >>
-            ((capspec >> match)       [_val = phoenix::construct<OSDCapGrant>(_2, _1)] |
-             (match >> capspec)       [_val = phoenix::construct<OSDCapGrant>(_1, _2)]) >>
-            *ascii::blank);
+            ((capspec >> match) [_val = phoenix::construct<OSDCapGrant>(_2, _1)] |
+             (match >> capspec) [_val = phoenix::construct<OSDCapGrant>(_1, _2)] |
+              (profile)          [_val = phoenix::construct<OSDCapGrant>(_1)]
+             ) >> *ascii::blank);
     // osdcap := grant [grant ...]
     grants %= (grant % (lit(';') | lit(',')));
-    osdcap = grants  [_val = phoenix::construct<OSDCap>(_1)]; 
+    osdcap = grants  [_val = phoenix::construct<OSDCap>(_1)];
   }
   qi::rule<Iterator> spaces;
   qi::rule<Iterator, unsigned()> rwxa;
@@ -254,11 +365,15 @@ struct OSDCapParser : qi::grammar<Iterator, OSDCap()>
   qi::rule<Iterator, string()> unquoted_word;
   qi::rule<Iterator, string()> str, estr;
   qi::rule<Iterator, int()> auid;
+  qi::rule<Iterator, string()> class_name;
+  qi::rule<Iterator, string()> class_cap;
   qi::rule<Iterator, OSDCapSpec()> capspec;
   qi::rule<Iterator, string()> pool_name;
   qi::rule<Iterator, string()> nspace;
   qi::rule<Iterator, string()> object_prefix;
   qi::rule<Iterator, OSDCapMatch()> match;
+  qi::rule<Iterator, string()> profile_name;
+  qi::rule<Iterator, OSDCapProfile()> profile;
   qi::rule<Iterator, OSDCapGrant()> grant;
   qi::rule<Iterator, std::vector<OSDCapGrant>()> grants;
   qi::rule<Iterator, OSDCap()> osdcap;
index 8ce808052eb5f64c75ed1275496d187846cf6a7f..2c9ffa27c80a9e578edec9463ba5465b8e741418 100644 (file)
@@ -1,4 +1,4 @@
-// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- 
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
 // vim: ts=8 sw=2 smarttab
 /*
  * Ceph - scalable distributed file system
@@ -7,10 +7,10 @@
  *
  * This is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
- * License version 2.1, as published by the Free Software 
+ * License version 2.1, as published by the Free Software
  * Foundation.  See file COPYING.
- * 
- * OSDCaps: Hold the capabilities associated with a single authenticated 
+ *
+ * OSDCaps: Hold the capabilities associated with a single authenticated
  * user key. These are specified by text strings of the form
  * "allow r" (which allows reading anything on the OSD)
  * "allow rwx auid foo" (which allows full access to listed auids)
@@ -33,6 +33,10 @@ using std::ostream;
 #include "include/types.h"
 #include "OpRequest.h"
 
+#include <list>
+#include <vector>
+#include <boost/optional.hpp>
+
 static const __u8 OSD_CAP_R     = (1 << 1);      // read
 static const __u8 OSD_CAP_W     = (1 << 2);      // write
 static const __u8 OSD_CAP_CLS_R = (1 << 3);      // class read
@@ -74,22 +78,39 @@ struct OSDCapSpec {
 
 ostream& operator<<(ostream& out, const OSDCapSpec& s);
 
+struct OSDCapPoolNamespace {
+  std::string pool_name;
+  boost::optional<std::string> nspace = boost::none;
+
+  OSDCapPoolNamespace() {
+  }
+  OSDCapPoolNamespace(const std::string& pool_name,
+                      const boost::optional<std::string>& nspace = boost::none)
+    : pool_name(pool_name), nspace(nspace) {
+  }
+
+  bool is_match(const std::string& pn, const std::string& ns) const;
+  bool is_match_all() const;
+};
+
+ostream& operator<<(ostream& out, const OSDCapPoolNamespace& pns);
+
 
 struct OSDCapMatch {
   // auid and pool_name/nspace are mutually exclusive
-  int64_t auid;
-  std::string pool_name;
-  bool is_nspace;      // true if nspace is defined; false if not constrained.
-  std::string nspace;
-
+  int64_t auid = CEPH_AUTH_UID_DEFAULT;
+  OSDCapPoolNamespace pool_namespace;
   std::string object_prefix;
 
-  OSDCapMatch() : auid(CEPH_AUTH_UID_DEFAULT), is_nspace(false) {}
-  OSDCapMatch(std::string pl, std::string pre) :
-       auid(CEPH_AUTH_UID_DEFAULT), pool_name(pl), is_nspace(false), object_prefix(pre) {}
-  OSDCapMatch(std::string pl, std::string ns, std::string pre) :
-       auid(CEPH_AUTH_UID_DEFAULT), pool_name(pl), is_nspace(true), nspace(ns), object_prefix(pre) {}
-  OSDCapMatch(uint64_t auid, std::string pre) : auid(auid), is_nspace(false), object_prefix(pre) {}
+  OSDCapMatch() {}
+  OSDCapMatch(const OSDCapPoolNamespace& pns) : pool_namespace(pns) {}
+  OSDCapMatch(const std::string& pl, const std::string& pre)
+    : pool_namespace(pl), object_prefix(pre) {}
+  OSDCapMatch(const std::string& pl, const std::string& ns,
+              const std::string& pre)
+    : pool_namespace(pl, ns), object_prefix(pre) {}
+  OSDCapMatch(uint64_t auid, const std::string& pre)
+    : auid(auid), object_prefix(pre) {}
 
   /**
    * check if given request parameters match our constraints
@@ -100,19 +121,54 @@ struct OSDCapMatch {
    * @param object object name
    * @return true if we match, false otherwise
    */
-  bool is_match(const std::string& pool_name, const std::string& nspace_name, int64_t pool_auid, const std::string& object) const;
+  bool is_match(const std::string& pool_name, const std::string& nspace_name,
+                int64_t pool_auid, const std::string& object) const;
   bool is_match_all() const;
 };
 
 ostream& operator<<(ostream& out, const OSDCapMatch& m);
 
 
+struct OSDCapProfile {
+  std::string name;
+  OSDCapPoolNamespace pool_namespace;
+
+  OSDCapProfile() {
+  }
+  OSDCapProfile(const std::string& name,
+                const std::string& pool_name,
+                const boost::optional<std::string>& nspace = boost::none)
+    : name(name), pool_namespace(pool_name, nspace) {
+  }
+
+  inline bool is_valid() const {
+    return !name.empty();
+  }
+};
+
+ostream& operator<<(ostream& out, const OSDCapProfile& m);
+
 struct OSDCapGrant {
   OSDCapMatch match;
   OSDCapSpec spec;
+  OSDCapProfile profile;
+
+  // explicit grants that a profile grant expands to; populated as
+  // needed by expand_profile() and cached here.
+  mutable std::list<OSDCapGrant> profile_grants;
 
   OSDCapGrant() {}
-  OSDCapGrant(OSDCapMatch m, OSDCapSpec s) : match(m), spec(s) {}
+  OSDCapGrant(const OSDCapMatch& m, const OSDCapSpec& s) : match(m), spec(s) {}
+  OSDCapGrant(const OSDCapProfile& profile) : profile(profile) {
+  }
+
+  bool allow_all() const;
+  bool is_capable(const string& pool_name, const string& ns, int64_t pool_auid,
+                  const string& object, bool op_may_read, bool op_may_write,
+                  const std::vector<OpRequest::ClassInfo>& classes,
+                  std::vector<bool>* class_allowed) const;
+
+  void expand_profile() const;
 };
 
 ostream& operator<<(ostream& out, const OSDCapGrant& g);
index 95dc40ba556936047eaf19c95d6bf088d0a706d5..7080bdeff3340edd3cb53ccc05075cf5c42702b3 100644 (file)
@@ -70,6 +70,7 @@ const char *parse_good[] = {
   "allow pool foo namespace=nfoo rwx; allow pool bar namespace nbar object_prefix rbd r",
   "allow pool foo namespace=\"\" rwx; allow pool bar namespace='' object_prefix rbd r",
   "allow pool foo namespace \"\" rwx; allow pool bar namespace '' object_prefix rbd r",
+  "allow profile abc, allow profile abc pool=bar, allow profile abc pool=bar namespace=foo",
   0
 };
 
@@ -1006,3 +1007,13 @@ TEST(OSDCap, AllowClassMultiRWX) {
   ASSERT_FALSE(cap.is_capable("bar", "", 0, "foo", false, false, {{"foo", false, false, false}, {"bar", false, true, false}}));
   ASSERT_FALSE(cap.is_capable("bar", "", 0, "foo", false, false, {{"foo", false, false, false}, {"bar", false, false, false}}));
 }
+
+TEST(OSDCap, AllowProfile) {
+  OSDCap cap;
+  ASSERT_TRUE(cap.parse("allow profile read-only, allow profile read-write pool abc", NULL));
+  ASSERT_FALSE(cap.allow_all());
+  ASSERT_FALSE(cap.is_capable("foo", "", 0, "asdf", true, true, {}));
+  ASSERT_TRUE(cap.is_capable("foo", "", 0, "asdf", true, false, {}));
+  ASSERT_TRUE(cap.is_capable("abc", "", 0, "asdf", false, true, {}));
+}
+