]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
mon: support regex-based restrictions on command caps
authorJason Dillaman <dillaman@redhat.com>
Mon, 26 Jun 2017 20:38:15 +0000 (16:38 -0400)
committerJason Dillaman <dillaman@redhat.com>
Fri, 21 Jul 2017 18:29:37 +0000 (14:29 -0400)
Signed-off-by: Jason Dillaman <dillaman@redhat.com>
src/mon/MonCap.cc
src/mon/MonCap.h
src/test/mon/moncap.cc

index 0bfc83d170eafbaecf5a141ad0f77c793a9c281d..7a26b6825ad43c0d1959337f5f9fa24b953a1c0a 100644 (file)
@@ -27,6 +27,9 @@
 
 #include <algorithm>
 
+#include <boost/regex.hpp>
+#include "include/assert.h"
+
 static inline bool is_not_alnum_space(char c)
 {
   return !(isalpha(c) || isdigit(c) || (c == '-') || (c == '_'));
@@ -60,10 +63,17 @@ ostream& operator<<(ostream& out, const mon_rwxa_t& p)
 
 ostream& operator<<(ostream& out, const StringConstraint& c)
 {
-  if (c.prefix.length())
-    return out << "prefix " << c.prefix;
-  else
+  switch (c.match_type) {
+  case StringConstraint::MATCH_TYPE_EQUAL:
     return out << "value " << c.value;
+  case StringConstraint::MATCH_TYPE_PREFIX:
+    return out << "prefix " << c.value;
+  case StringConstraint::MATCH_TYPE_REGEX:
+    return out << "regex " << c.value;
+  default:
+    break;
+  }
+  return out;
 }
 
 ostream& operator<<(ostream& out, const MonCapGrant& m)
@@ -79,10 +89,22 @@ ostream& operator<<(ostream& out, const MonCapGrant& m)
       for (map<string,StringConstraint>::const_iterator p = m.command_args.begin();
           p != m.command_args.end();
           ++p) {
-       if (p->second.value.length())
-         out << " " << maybe_quote_string(p->first) << "=" << maybe_quote_string(p->second.value);
-       else
-         out << " " << maybe_quote_string(p->first) << " prefix " << maybe_quote_string(p->second.prefix);
+        switch (p->second.match_type) {
+        case StringConstraint::MATCH_TYPE_EQUAL:
+         out << " " << maybe_quote_string(p->first) << "="
+              << maybe_quote_string(p->second.value);
+          break;
+        case StringConstraint::MATCH_TYPE_PREFIX:
+         out << " " << maybe_quote_string(p->first) << " prefix "
+              << maybe_quote_string(p->second.value);
+          break;
+        case StringConstraint::MATCH_TYPE_REGEX:
+         out << " " << maybe_quote_string(p->first) << " regex "
+              << maybe_quote_string(p->second.value);
+          break;
+        default:
+          break;
+        }
       }
     }
   }
@@ -108,8 +130,8 @@ BOOST_FUSION_ADAPT_STRUCT(MonCapGrant,
                          (mon_rwxa_t, allow))
 
 BOOST_FUSION_ADAPT_STRUCT(StringConstraint,
-                         (std::string, value)
-                         (std::string, prefix))
+                          (StringConstraint::MatchType, match_type)
+                         (std::string, value))
 
 // </magic>
 
@@ -176,26 +198,25 @@ void MonCapGrant::expand_profile_mon(const EntityName& name) const
     profile_grants.push_back(MonCapGrant("osd", MON_CAP_R | MON_CAP_W));
     profile_grants.push_back(MonCapGrant("auth", MON_CAP_R | MON_CAP_X));
     profile_grants.push_back(MonCapGrant("config-key", MON_CAP_R | MON_CAP_W));
-    string prefix = string("daemon-private/mgr/");
-    profile_grants.push_back(MonCapGrant("config-key get", "key",
-                                        StringConstraint("", prefix)));
-    profile_grants.push_back(MonCapGrant("config-key put", "key",
-                                        StringConstraint("", prefix)));
-    profile_grants.push_back(MonCapGrant("config-key exists", "key",
-                                        StringConstraint("", prefix)));
-    profile_grants.push_back(MonCapGrant("config-key delete", "key",
-                                        StringConstraint("", prefix)));
+    StringConstraint constraint(StringConstraint::MATCH_TYPE_PREFIX,
+                                "daemon-private/mgr/");
+    profile_grants.push_back(MonCapGrant("config-key get", "key", constraint));
+    profile_grants.push_back(MonCapGrant("config-key put", "key", constraint));
+    profile_grants.push_back(MonCapGrant("config-key exists", "key", constraint));
+    profile_grants.push_back(MonCapGrant("config-key delete", "key", constraint));
   }
   if (profile == "osd" || profile == "mds" || profile == "mon" ||
       profile == "mgr") {
+    StringConstraint constraint(StringConstraint::MATCH_TYPE_PREFIX,
+                                string("daemon-private/") + stringify(name) +
+                                string("/"));
     string prefix = string("daemon-private/") + stringify(name) + string("/");
-    profile_grants.push_back(MonCapGrant("config-key get", "key", StringConstraint("", prefix)));
-    profile_grants.push_back(MonCapGrant("config-key put", "key", StringConstraint("", prefix)));
-    profile_grants.push_back(MonCapGrant("config-key exists", "key", StringConstraint("", prefix)));
-    profile_grants.push_back(MonCapGrant("config-key delete", "key", StringConstraint("", prefix)));
+    profile_grants.push_back(MonCapGrant("config-key get", "key", constraint));
+    profile_grants.push_back(MonCapGrant("config-key put", "key", constraint));
+    profile_grants.push_back(MonCapGrant("config-key exists", "key", constraint));
+    profile_grants.push_back(MonCapGrant("config-key delete", "key", constraint));
   }
   if (profile == "bootstrap-osd") {
-    string prefix = "dm-crypt/osd";
     profile_grants.push_back(MonCapGrant("mon", MON_CAP_R));  // read monmap
     profile_grants.push_back(MonCapGrant("osd", MON_CAP_R));  // read osdmap
     profile_grants.push_back(MonCapGrant("mon getmap"));
@@ -206,27 +227,36 @@ void MonCapGrant::expand_profile_mon(const EntityName& name) const
     profile_grants.push_back(MonCapGrant("osd", MON_CAP_R));  // read osdmap
     profile_grants.push_back(MonCapGrant("mon getmap"));
     profile_grants.push_back(MonCapGrant("auth get-or-create"));  // FIXME: this can expose other mds keys
-    profile_grants.back().command_args["entity"] = StringConstraint("", "mds.");
-    profile_grants.back().command_args["caps_mon"] = StringConstraint("allow profile mds", "");
-    profile_grants.back().command_args["caps_osd"] = StringConstraint("allow rwx", "");
-    profile_grants.back().command_args["caps_mds"] = StringConstraint("allow", "");
+    profile_grants.back().command_args["entity"] = StringConstraint(
+      StringConstraint::MATCH_TYPE_PREFIX, "mds.");
+    profile_grants.back().command_args["caps_mon"] = StringConstraint(
+      StringConstraint::MATCH_TYPE_EQUAL, "allow profile mds");
+    profile_grants.back().command_args["caps_osd"] = StringConstraint(
+      StringConstraint::MATCH_TYPE_EQUAL, "allow rwx");
+    profile_grants.back().command_args["caps_mds"] = StringConstraint(
+      StringConstraint::MATCH_TYPE_EQUAL, "allow");
   }
   if (profile == "bootstrap-mgr") {
     profile_grants.push_back(MonCapGrant("mon", MON_CAP_R));  // read monmap
     profile_grants.push_back(MonCapGrant("osd", MON_CAP_R));  // read osdmap
     profile_grants.push_back(MonCapGrant("mon getmap"));
     profile_grants.push_back(MonCapGrant("auth get-or-create"));  // FIXME: this can expose other mgr keys
-    profile_grants.back().command_args["entity"] = StringConstraint("", "mgr.");
-    profile_grants.back().command_args["caps_mon"] = StringConstraint("allow profile mgr", "");
+    profile_grants.back().command_args["entity"] = StringConstraint(
+      StringConstraint::MATCH_TYPE_PREFIX, "mgr.");
+    profile_grants.back().command_args["caps_mon"] = StringConstraint(
+      StringConstraint::MATCH_TYPE_EQUAL, "allow profile mgr");
   }
   if (profile == "bootstrap-rgw") {
     profile_grants.push_back(MonCapGrant("mon", MON_CAP_R));  // read monmap
     profile_grants.push_back(MonCapGrant("osd", MON_CAP_R));  // read osdmap
     profile_grants.push_back(MonCapGrant("mon getmap"));
     profile_grants.push_back(MonCapGrant("auth get-or-create"));  // FIXME: this can expose other mds keys
-    profile_grants.back().command_args["entity"] = StringConstraint("", "client.rgw.");
-    profile_grants.back().command_args["caps_mon"] = StringConstraint("allow rw", "");
-    profile_grants.back().command_args["caps_osd"] = StringConstraint("allow rwx", "");
+    profile_grants.back().command_args["entity"] = StringConstraint(
+      StringConstraint::MATCH_TYPE_PREFIX, "client.rgw.");
+    profile_grants.back().command_args["caps_mon"] = StringConstraint(
+      StringConstraint::MATCH_TYPE_EQUAL, "allow rw");
+    profile_grants.back().command_args["caps_osd"] = StringConstraint(
+      StringConstraint::MATCH_TYPE_EQUAL, "allow rwx");
   }
   if (profile == "fs-client") {
     profile_grants.push_back(MonCapGrant("mon", MON_CAP_R));
@@ -275,14 +305,25 @@ mon_rwxa_t MonCapGrant::get_allowed(CephContext *cct,
       // argument must be present if a constraint exists
       if (q == c_args.end())
        return 0;
-      if (p->second.value.length()) {
-       // match value
+      switch (p->second.match_type) {
+      case StringConstraint::MATCH_TYPE_EQUAL:
        if (p->second.value != q->second)
          return 0;
-      } else {
-       // match prefix
-       if (q->second.find(p->second.prefix) != 0)
+        break;
+      case StringConstraint::MATCH_TYPE_PREFIX:
+       if (q->second.find(p->second.value) != 0)
          return 0;
+        break;
+      case StringConstraint::MATCH_TYPE_REGEX:
+        {
+         boost::regex pattern(p->second.value,
+                               boost::regex::basic | boost::regex::no_except);
+          if (pattern.empty() || !boost::regex_match(q->second, pattern))
+           return 0;
+        }
+        break;
+      default:
+        break;
       }
     }
     return MON_CAP_ALL;
@@ -427,9 +468,12 @@ struct MonCapParser : qi::grammar<Iterator, MonCap()>
     spaces = +(lit(' ') | lit('\n') | lit('\t'));
 
     // command := command[=]cmd [k1=v1 k2=v2 ...]
-    str_match = '=' >> str >> qi::attr(string());
-    str_prefix = spaces >> lit("prefix") >> spaces >> qi::attr(string()) >> str;
-    kv_pair = str >> (str_match | str_prefix);
+    str_match = '=' >> qi::attr(StringConstraint::MATCH_TYPE_EQUAL) >> str;
+    str_prefix = spaces >> lit("prefix") >> spaces >>
+                 qi::attr(StringConstraint::MATCH_TYPE_PREFIX) >> str;
+    str_regex = spaces >> lit("regex") >> spaces >>
+                 qi::attr(StringConstraint::MATCH_TYPE_REGEX) >> str;
+    kv_pair = str >> (str_match | str_prefix | str_regex);
     kv_map %= kv_pair >> *(spaces >> kv_pair);
     command_match = -spaces >> lit("allow") >> spaces >> lit("command") >> (lit('=') | spaces)
                            >> qi::attr(string()) >> qi::attr(string())
@@ -481,7 +525,7 @@ struct MonCapParser : qi::grammar<Iterator, MonCap()>
   qi::rule<Iterator, string()> unquoted_word;
   qi::rule<Iterator, string()> str;
 
-  qi::rule<Iterator, StringConstraint()> str_match, str_prefix;
+  qi::rule<Iterator, StringConstraint()> str_match, str_prefix, str_regex;
   qi::rule<Iterator, pair<string, StringConstraint>()> kv_pair;
   qi::rule<Iterator, map<string, StringConstraint>()> kv_map;
 
index 46acb1e42b5fae68319ae1b10479a3ac42cb0398..1ff4b831ed1de428273145cff396529d006ca096 100644 (file)
@@ -35,12 +35,20 @@ struct mon_rwxa_t {
 ostream& operator<<(ostream& out, const mon_rwxa_t& p);
 
 struct StringConstraint {
+  enum MatchType {
+    MATCH_TYPE_NONE,
+    MATCH_TYPE_EQUAL,
+    MATCH_TYPE_PREFIX,
+    MATCH_TYPE_REGEX
+  };
+
+  MatchType match_type = MATCH_TYPE_NONE;
   string value;
-  string prefix;
 
   StringConstraint() {}
-  StringConstraint(string a, string b)
-    : value(std::move(a)), prefix(std::move(b)) {}
+  StringConstraint(MatchType match_type, string value)
+    : match_type(match_type), value(value) {
+  }
 };
 
 ostream& operator<<(ostream& out, const StringConstraint& c);
index 8b55719fe16e77620880d4017fb07e1d7a024ca1..f78e0e20774d8cb979ea5d72ffd43d2aae9c278a 100644 (file)
@@ -43,6 +43,8 @@ const char *parse_good[] = {
   "allow command abc with arg=foo arg2=bar",
   "allow command abc with arg=foo arg2 prefix bar arg3 prefix baz",
   "allow command abc with arg=foo arg2 prefix \"bar bingo\" arg3 prefix baz",
+  "allow command abc with arg regex \"^[0-9a-z.]*$\"",
+  "allow command abc with arg regex \"\(invaluid regex\"",
   "allow service foo x",
   "allow service foo x; allow service bar x",
   "allow service foo w ;allow service bar x",
@@ -238,3 +240,19 @@ TEST(MonCap, ProfileOSD) {
                             name, "", "config-key delete", ca, true, true, true));
 }
 
+TEST(MonCap, CommandRegEx) {
+  MonCap cap;
+  ASSERT_FALSE(cap.is_allow_all());
+  ASSERT_TRUE(cap.parse("allow command abc with arg regex \"^[0-9a-z.]*$\"", NULL));
+
+  EntityName name;
+  name.from_str("osd.123");
+  ASSERT_TRUE(cap.is_capable(nullptr, CEPH_ENTITY_TYPE_OSD, name, "",
+                             "abc", {{"arg", "12345abcde"}}, true, true, true));
+  ASSERT_FALSE(cap.is_capable(nullptr, CEPH_ENTITY_TYPE_OSD, name, "",
+                              "abc", {{"arg", "~!@#$"}}, true, true, true));
+
+  ASSERT_TRUE(cap.parse("allow command abc with arg regex \"[*\"", NULL));
+  ASSERT_FALSE(cap.is_capable(nullptr, CEPH_ENTITY_TYPE_OSD, name, "",
+                              "abc", {{"arg", ""}}, true, true, true));
+}