]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr: stop re-using MonCap for handling MGR caps
authorJason Dillaman <dillaman@redhat.com>
Fri, 11 Oct 2019 14:30:03 +0000 (10:30 -0400)
committerJason Dillaman <dillaman@redhat.com>
Thu, 9 Jan 2020 18:59:36 +0000 (13:59 -0500)
Use the existing MonCap as the basis for a new custom MgrCap
processor.

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

src/mgr/CMakeLists.txt
src/mgr/DaemonServer.cc
src/mgr/MgrCap.cc [new file with mode: 0644]
src/mgr/MgrCap.h [new file with mode: 0644]
src/mgr/MgrSession.h
src/mon/AuthMonitor.cc
src/mon/CMakeLists.txt
src/test/mgr/CMakeLists.txt
src/test/mgr/test_mgrcap.cc [new file with mode: 0644]

index 252fb3e5e317f25f9119813e31ffbc2a22d1b438..180e39bc1c45fd6105aa404270a1994545e44452 100644 (file)
@@ -1,3 +1,6 @@
+add_library(mgr_cap_obj OBJECT
+  MgrCap.cc)
+
 set(mgr_srcs
   ${CMAKE_SOURCE_DIR}/src/ceph_mgr.cc
   ${CMAKE_SOURCE_DIR}/src/mon/PGMap.cc
@@ -20,7 +23,8 @@ set(mgr_srcs
   PyModuleRunner.cc
   PyOSDMap.cc
   StandbyPyModules.cc
-  mgr_commands.cc)
+  mgr_commands.cc
+  $<TARGET_OBJECTS:mgr_cap_obj>)
 add_executable(ceph-mgr ${mgr_srcs})
 target_include_directories(ceph-mgr SYSTEM PRIVATE "${PYTHON_INCLUDE_DIRS}")
 target_link_libraries(ceph-mgr
index 0bb21d8587d4dab742e2e575fb112d1fc8d8a275..1ea16fb5eb13fbbba44633a473ef135358417021 100644 (file)
@@ -699,7 +699,6 @@ bool DaemonServer::_allowed_command(
 
   bool capable = s->caps.is_capable(
     g_ceph_context,
-    CEPH_ENTITY_TYPE_MGR,
     s->entity_name,
     module, prefix, param_str_map,
     cmd_r, cmd_w, cmd_x,
diff --git a/src/mgr/MgrCap.cc b/src/mgr/MgrCap.cc
new file mode 100644 (file)
index 0000000..619a059
--- /dev/null
@@ -0,0 +1,473 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+/*
+ * Ceph - scalable distributed file system
+ *
+ * Copyright (C) 2013 Inktank
+ *
+ * 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
+ * Foundation.  See file COPYING.
+ *
+ */
+
+#include <boost/config/warning_disable.hpp>
+#include <boost/spirit/include/qi_uint.hpp>
+#include <boost/spirit/include/qi.hpp>
+#include <boost/fusion/include/std_pair.hpp>
+#include <boost/spirit/include/phoenix.hpp>
+#include <boost/fusion/adapted/struct/adapt_struct.hpp>
+#include <boost/fusion/include/adapt_struct.hpp>
+#include <boost/algorithm/string/predicate.hpp>
+
+#include "MgrCap.h"
+#include "include/stringify.h"
+#include "include/ipaddr.h"
+#include "common/debug.h"
+#include "common/Formatter.h"
+
+#include <algorithm>
+#include <regex>
+
+#include "include/ceph_assert.h"
+
+static inline bool is_not_alnum_space(char c) {
+  return !(isalpha(c) || isdigit(c) || (c == '-') || (c == '_'));
+}
+
+static std::string maybe_quote_string(const std::string& str) {
+  if (find_if(str.begin(), str.end(), is_not_alnum_space) == str.end())
+    return str;
+  return std::string("\"") + str + std::string("\"");
+}
+
+#define dout_subsys ceph_subsys_mgr
+
+ostream& operator<<(ostream& out, const mgr_rwxa_t& p) {
+  if (p == MGR_CAP_ANY)
+    return out << "*";
+
+  if (p & MGR_CAP_R)
+    out << "r";
+  if (p & MGR_CAP_W)
+    out << "w";
+  if (p & MGR_CAP_X)
+    out << "x";
+  return out;
+}
+
+ostream& operator<<(ostream& out, const MgrCapGrantConstraint& c) {
+  switch (c.match_type) {
+  case MgrCapGrantConstraint::MATCH_TYPE_EQUAL:
+    out << "=";
+    break;
+  case MgrCapGrantConstraint::MATCH_TYPE_PREFIX:
+    out << " prefix ";
+    break;
+  case MgrCapGrantConstraint::MATCH_TYPE_REGEX:
+    out << " regex ";
+    break;
+  default:
+    break;
+  }
+  out << maybe_quote_string(c.value);
+  return out;
+}
+
+ostream& operator<<(ostream& out, const MgrCapGrant& m) {
+  if (!m.profile.empty()) {
+    out << "profile " << maybe_quote_string(m.profile);
+  } else {
+    out << "allow";
+    if (!m.service.empty()) {
+      out << " service " << maybe_quote_string(m.service);
+    } 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.network.size()) {
+    out << " network " << m.network;
+  }
+  return out;
+}
+
+// <magic>
+//  fusion lets us easily populate structs via the qi parser.
+
+typedef std::map<std::string, MgrCapGrantConstraint> kvmap;
+
+BOOST_FUSION_ADAPT_STRUCT(MgrCapGrant,
+                          (std::string, service)
+                          (std::string, profile)
+                          (std::string, command)
+                          (kvmap, command_args)
+                          (mgr_rwxa_t, allow)
+                          (std::string, network))
+
+BOOST_FUSION_ADAPT_STRUCT(MgrCapGrantConstraint,
+                          (MgrCapGrantConstraint::MatchType, match_type)
+                          (std::string, value))
+
+// </magic>
+
+void MgrCapGrant::parse_network() {
+  network_valid = ::parse_network(network.c_str(), &network_parsed,
+                                  &network_prefix);
+}
+
+void MgrCapGrant::expand_profile() const {
+  // only generate this list once
+  if (!profile_grants.empty()) {
+    return;
+  }
+
+  if (profile == "read-only") {
+    // grants READ-ONLY caps MGR-wide
+    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({{}, {}, {}, {},
+                              mgr_rwxa_t{MGR_CAP_R | MGR_CAP_W}});
+    return;
+  }
+
+  if (profile == "crash") {
+    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::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);
+    }
+    return a;
+  }
+
+  if (!service.empty()) {
+    if (service != s) {
+      return mgr_rwxa_t{};
+    }
+    return allow;
+  }
+
+  if (!command.empty()) {
+    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;
+      }
+    }
+    return mgr_rwxa_t{MGR_CAP_ANY};
+  }
+
+  return allow;
+}
+
+ostream& operator<<(ostream&out, const MgrCap& m) {
+  bool first = true;
+  for (auto& grant : m.grants) {
+    if (!first) {
+      out << ", ";
+    }
+    first = false;
+
+    out << grant;
+  }
+  return out;
+}
+
+bool MgrCap::is_allow_all() const {
+  for (auto& grant : grants) {
+    if (grant.is_allow_all()) {
+      return true;
+    }
+  }
+  return false;
+}
+
+void MgrCap::set_allow_all() {
+  grants.clear();
+  grants.push_back({{}, {}, {}, {}, mgr_rwxa_t{MGR_CAP_ANY}});
+  text = "allow *";
+}
+
+bool MgrCap::is_capable(
+    CephContext *cct,
+    EntityName name,
+    const std::string& service,
+    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 << " "
+                   << "command=" << command
+                   << (op_may_read ? " read":"")
+                   << (op_may_write ? " write":"")
+                   << (op_may_exec ? " exec":"")
+                   << " addr " << addr
+                   << " on cap " << *this
+                   << dendl;
+  }
+
+  mgr_rwxa_t allow;
+  for (auto& grant : grants) {
+    if (cct)
+      ldout(cct, 20) << " allow so far " << allow << ", doing grant " << grant
+                     << dendl;
+
+    if (grant.network.size() &&
+        (!grant.network_valid ||
+         !network_contains(grant.network_parsed,
+                           grant.network_prefix,
+                           addr))) {
+      continue;
+    }
+
+    if (grant.is_allow_all()) {
+      if (cct) {
+        ldout(cct, 20) << " allow all" << dendl;
+      }
+      return true;
+    }
+
+    // check enumerated caps
+    allow = allow | grant.get_allowed(cct, name, service, command,
+                                      command_args);
+    if ((!op_may_read || (allow & MGR_CAP_R)) &&
+        (!op_may_write || (allow & MGR_CAP_W)) &&
+        (!op_may_exec || (allow & MGR_CAP_X))) {
+      if (cct) {
+        ldout(cct, 20) << " match" << dendl;
+      }
+      return true;
+    }
+  }
+  return false;
+}
+
+void MgrCap::encode(bufferlist& bl) const {
+  // remain backwards compatible w/ MgrCap
+  ENCODE_START(4, 4, bl);
+  encode(text, bl);
+  ENCODE_FINISH(bl);
+}
+
+void MgrCap::decode(bufferlist::const_iterator& bl) {
+  // remain backwards compatible w/ MgrCap
+  std::string s;
+  DECODE_START(4, bl);
+  decode(s, bl);
+  DECODE_FINISH(bl);
+  parse(s, NULL);
+}
+
+void MgrCap::dump(Formatter *f) const {
+  f->dump_string("text", text);
+}
+
+void MgrCap::generate_test_instances(list<MgrCap*>& ls) {
+  ls.push_back(new MgrCap);
+  ls.push_back(new MgrCap);
+  ls.back()->parse("allow *");
+  ls.push_back(new MgrCap);
+  ls.back()->parse("allow rwx");
+  ls.push_back(new MgrCap);
+  ls.back()->parse("allow service foo x");
+  ls.push_back(new MgrCap);
+  ls.back()->parse("allow command bar x");
+  ls.push_back(new MgrCap);
+  ls.back()->parse("allow service foo r, allow command bar x");
+  ls.push_back(new MgrCap);
+  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");
+}
+
+// grammar
+namespace qi = boost::spirit::qi;
+namespace ascii = boost::spirit::ascii;
+namespace phoenix = boost::phoenix;
+
+template <typename Iterator>
+struct MgrCapParser : qi::grammar<Iterator, MgrCap()> {
+  MgrCapParser() : MgrCapParser::base_type(mgrcap) {
+    using qi::char_;
+    using qi::int_;
+    using qi::ulong_long;
+    using qi::lexeme;
+    using qi::alnum;
+    using qi::_val;
+    using qi::_1;
+    using qi::_2;
+    using qi::_3;
+    using qi::eps;
+    using qi::lit;
+
+    quoted_string %=
+      lexeme['"' >> +(char_ - '"') >> '"'] |
+      lexeme['\'' >> +(char_ - '\'') >> '\''];
+    unquoted_word %= +char_("a-zA-Z0-9_./-");
+    str %= quoted_string | unquoted_word;
+    network_str %= +char_("/.:a-fA-F0-9][");
+
+    spaces = +(lit(' ') | lit('\n') | lit('\t'));
+
+    // key <=|prefix|regex> value[ ...]
+    str_match = -spaces >> lit('=') >> -spaces >>
+                qi::attr(MgrCapGrantConstraint::MATCH_TYPE_EQUAL) >> str;
+    str_prefix = spaces >> lit("prefix") >> spaces >>
+                 qi::attr(MgrCapGrantConstraint::MATCH_TYPE_PREFIX) >> str;
+    str_regex = spaces >> lit("regex") >> spaces >>
+                 qi::attr(MgrCapGrantConstraint::MATCH_TYPE_REGEX) >> str;
+    kv_pair = str >> (str_match | str_prefix | str_regex);
+    kv_map %= kv_pair >> *(spaces >> kv_pair);
+
+    // 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())
+                            >> str
+                            >> -(spaces >> lit("with") >> spaces >> kv_map)
+                            >> qi::attr(0)
+                            >> -(spaces >> lit("network") >> spaces >> network_str);
+
+    // service foo rwxa
+    service_match %= -spaces >> lit("allow") >> spaces >> lit("service") >> (lit('=') | spaces)
+                             >> 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())
+                             >> str
+                             >> qi::attr(std::string())
+                             >> qi::attr(std::map<std::string, MgrCapGrantConstraint>())
+                             >> qi::attr(0)
+                             >> -(spaces >> lit("network") >> spaces >> network_str);
+
+    // rwxa
+    rwxa_match %= -spaces >> lit("allow") >> spaces
+                          >> 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);
+
+    // rwxa := * | [r][w][x]
+    rwxa =
+      (lit("*")[_val = MGR_CAP_ANY]) |
+      (lit("all")[_val = MGR_CAP_ANY]) |
+      ( eps[_val = 0] >>
+        ( lit('r')[_val |= MGR_CAP_R] ||
+          lit('w')[_val |= MGR_CAP_W] ||
+          lit('x')[_val |= MGR_CAP_X]
+          )
+        );
+
+    // grant := allow ...
+    grant = -spaces >> (rwxa_match | profile_match | service_match | command_match) >> -spaces;
+
+    // mgrcap := grant [grant ...]
+    grants %= (grant % (*lit(' ') >> (lit(';') | lit(',')) >> *lit(' ')));
+    mgrcap = grants  [_val = phoenix::construct<MgrCap>(_1)];
+  }
+
+  qi::rule<Iterator> spaces;
+  qi::rule<Iterator, unsigned()> rwxa;
+  qi::rule<Iterator, std::string()> quoted_string;
+  qi::rule<Iterator, std::string()> unquoted_word;
+  qi::rule<Iterator, std::string()> str, network_str;
+
+  qi::rule<Iterator, MgrCapGrantConstraint()> str_match, str_prefix, str_regex;
+  qi::rule<Iterator, std::pair<std::string, MgrCapGrantConstraint>()> kv_pair;
+  qi::rule<Iterator, std::map<std::string, MgrCapGrantConstraint>()> kv_map;
+
+  qi::rule<Iterator, MgrCapGrant()> rwxa_match;
+  qi::rule<Iterator, MgrCapGrant()> command_match;
+  qi::rule<Iterator, MgrCapGrant()> service_match;
+  qi::rule<Iterator, MgrCapGrant()> profile_match;
+  qi::rule<Iterator, MgrCapGrant()> grant;
+  qi::rule<Iterator, std::vector<MgrCapGrant>()> grants;
+  qi::rule<Iterator, MgrCap()> mgrcap;
+};
+
+bool MgrCap::parse(const std::string& str, ostream *err) {
+  auto iter = str.begin();
+  auto end = str.end();
+
+  MgrCapParser<std::string::const_iterator> exp;
+  bool r = qi::parse(iter, end, exp, *this);
+  if (r && iter == end) {
+    text = str;
+    for (auto& g : grants) {
+      g.parse_network();
+    }
+    return true;
+  }
+
+  // Make sure no grants are kept after parsing failed!
+  grants.clear();
+
+  if (err) {
+    if (iter != end)
+      *err << "mgr capability parse failed, stopped at '"
+           << std::string(iter, end) << "' of '" << str << "'";
+    else
+      *err << "mgr capability parse failed, stopped at end of '" << str << "'";
+  }
+
+  return false;
+}
diff --git a/src/mgr/MgrCap.h b/src/mgr/MgrCap.h
new file mode 100644 (file)
index 0000000..b038577
--- /dev/null
@@ -0,0 +1,184 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#ifndef CEPH_MGRCAP_H
+#define CEPH_MGRCAP_H
+
+#include <ostream>
+
+#include "include/types.h"
+#include "common/entity_name.h"
+
+class CephContext;
+
+static const __u8 MGR_CAP_R     = (1 << 1);      // read
+static const __u8 MGR_CAP_W     = (1 << 2);      // write
+static const __u8 MGR_CAP_X     = (1 << 3);      // execute
+static const __u8 MGR_CAP_ANY   = 0xff;          // *
+
+struct mgr_rwxa_t {
+  __u8 val = 0U;
+
+  mgr_rwxa_t() {}
+  explicit mgr_rwxa_t(__u8 v) : val(v) {}
+
+  mgr_rwxa_t& operator=(__u8 v) {
+    val = v;
+    return *this;
+  }
+  operator __u8() const {
+    return val;
+  }
+};
+
+std::ostream& operator<<(std::ostream& out, const mgr_rwxa_t& p);
+
+struct MgrCapGrantConstraint {
+  enum MatchType {
+    MATCH_TYPE_NONE,
+    MATCH_TYPE_EQUAL,
+    MATCH_TYPE_PREFIX,
+    MATCH_TYPE_REGEX
+  };
+
+  MatchType match_type = MATCH_TYPE_NONE;
+  std::string value;
+
+  MgrCapGrantConstraint() {}
+  MgrCapGrantConstraint(MatchType match_type, std::string value)
+    : match_type(match_type), value(value) {
+  }
+};
+
+std::ostream& operator<<(std::ostream& out, const MgrCapGrantConstraint& c);
+
+struct MgrCapGrant {
+  /*
+   * A grant can come in one of four forms:
+   *
+   *  - a blanket allow ('allow rw', 'allow *')
+   *    - this will match against any service and the read/write/exec flags
+   *      in the mgr code.  semantics of what X means are somewhat ad hoc.
+   *
+   *  - a service allow ('allow service mds rw')
+   *    - this will match against a specific service 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',
+   *      '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.
+   */
+  std::string service;
+  std::string profile;
+  std::string command;
+  std::map<std::string, MgrCapGrantConstraint> command_args;
+
+  // restrict by network
+  std::string network;
+
+  // these are filled in by parse_network(), called by MgrCap::parse()
+  entity_addr_t network_parsed;
+  unsigned network_prefix = 0;
+  bool network_valid = true;
+
+  void parse_network();
+
+  mgr_rwxa_t allow;
+
+  // explicit grants that a profile grant expands to; populated as
+  // needed by expand_profile() (via is_match()) and cached here.
+  mutable std::list<MgrCapGrant> profile_grants;
+
+  void expand_profile() const;
+
+  MgrCapGrant() : allow(0) {}
+  MgrCapGrant(std::string&& service,
+              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) {
+  }
+
+  /**
+   * check if given request parameters match our constraints
+   *
+   * @param cct context
+   * @param name entity name
+   * @param service service (if any)
+   * @param command command (if any)
+   * @param command_args command args (if any)
+   * @return bits we allow
+   */
+  mgr_rwxa_t get_allowed(
+      CephContext *cct,
+      EntityName name,
+      const std::string& service,
+      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() &&
+            profile.empty() &&
+            command.empty());
+  }
+};
+
+std::ostream& operator<<(std::ostream& out, const MgrCapGrant& g);
+
+struct MgrCap {
+  std::string text;
+  std::vector<MgrCapGrant> grants;
+
+  MgrCap() {}
+  explicit MgrCap(const std::vector<MgrCapGrant> &g) : grants(g) {}
+
+  std::string get_str() const {
+    return text;
+  }
+
+  bool is_allow_all() const;
+  void set_allow_all();
+  bool parse(const std::string& str, std::ostream *err=NULL);
+
+  /**
+   * check if we are capable of something
+   *
+   * This method actually checks a description of a particular operation against
+   * what the capability has specified.
+   *
+   * @param service service name
+   * @param command command id
+   * @param command_args
+   * @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
+   * @return true if the operation is allowed, false otherwise
+   */
+  bool is_capable(CephContext *cct,
+                 EntityName name,
+                 const std::string& service,
+                 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;
+
+  void encode(ceph::buffer::list& bl) const;
+  void decode(ceph::buffer::list::const_iterator& bl);
+  void dump(ceph::Formatter *f) const;
+  static void generate_test_instances(std::list<MgrCap*>& ls);
+};
+WRITE_CLASS_ENCODER(MgrCap)
+
+std::ostream& operator<<(std::ostream& out, const MgrCap& cap);
+
+#endif // CEPH_MGRCAP_H
index c61a80b63e7e3f19a08197d9f0cdce18ecf855d2..c2ae345cb7f1050e677f4e77d8c24cfa898bc7a4 100644 (file)
@@ -7,7 +7,7 @@
 #include "common/RefCountedObj.h"
 #include "common/entity_name.h"
 #include "msg/msg_types.h"
-#include "mon/MonCap.h"
+#include "MgrCap.h"
 
 
 /**
@@ -20,8 +20,7 @@ struct MgrSession : public RefCountedObject {
 
   int osd_id = -1;  ///< osd id (if an osd)
 
-  // mon caps are suitably generic for mgr
-  MonCap caps;
+  MgrCap caps;
 
   std::set<std::string> declared_types;
 
index 89f88b65cda78d124c6361429f88ecb0b4ce8f92..3940da3441d00bcca423c7015365c939b014d950 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,9 +7,9 @@
  *
  * 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.
- * 
+ *
  */
 
 #include <sstream>
@@ -34,6 +34,7 @@
 #include "include/ceph_assert.h"
 
 #include "mds/MDSAuthCaps.h"
+#include "mgr/MgrCap.h"
 #include "osd/OSDCap.h"
 
 #define dout_subsys ceph_subsys_mon
@@ -1240,9 +1241,14 @@ bool AuthMonitor::valid_caps(
     const string& caps,
     ostream *out)
 {
-  if (type == "mon" || type == "mgr") {
-    MonCap tmp;
-    if (!tmp.parse(caps, out)) {
+  if (type == "mon") {
+    MonCap moncap;
+    if (!moncap.parse(caps, out)) {
+      return false;
+    }
+  } else if (type == "mgr") {
+    MgrCap mgrcap;
+    if (!mgrcap.parse(caps, out)) {
       return false;
     }
   } else if (type == "osd") {
index 62b41dc2acd9931d3777a8d5a9c489facb3bae40..8172dfd911aa186275aabcda395f02fb852de786 100644 (file)
@@ -22,7 +22,8 @@ set(lib_mon_srcs
   ConfigKeyService.cc
   ../mds/MDSAuthCaps.cc
   ../mgr/mgr_commands.cc
-  ../osd/OSDCap.cc)
+  ../osd/OSDCap.cc
+  $<TARGET_OBJECTS:mgr_cap_obj>)
 
 if(HAVE_GSSAPI)
   list(APPEND lib_mon_srcs
index c212b5419245c419ab1f67f144f24d5bad737edc..9e6950d799ee8d34d8af9e039988d6aa12c5070d 100644 (file)
@@ -1,3 +1,10 @@
+# unittest_mgr_mgrcap
+add_executable(unittest_mgr_mgrcap
+  test_mgrcap.cc
+  $<TARGET_OBJECTS:mgr_cap_obj>)
+add_ceph_unittest(unittest_mgr_mgrcap)
+target_link_libraries(unittest_mgr_mgrcap global)
+
 #scripts
 if(WITH_MGR_DASHBOARD_FRONTEND)
   if(NOT CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64|AARCH64|arm|ARM")
diff --git a/src/test/mgr/test_mgrcap.cc b/src/test/mgr/test_mgrcap.cc
new file mode 100644 (file)
index 0000000..2b997f5
--- /dev/null
@@ -0,0 +1,239 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+/*
+ * Ceph - scalable distributed file system
+ *
+ * Copyright (C) 2012 Inktank
+ *
+ * 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
+ * Foundation.  See file COPYING.
+ *
+ */
+
+#include <iostream>
+
+#include "include/stringify.h"
+#include "mgr/MgrCap.h"
+
+#include "gtest/gtest.h"
+
+const char *parse_good[] = {
+
+  // MgrCapMatch
+  "allow *",
+  "allow r",
+  "allow rwx",
+  "allow  r",
+  " allow rwx",
+  "allow rwx ",
+  " allow rwx ",
+  " allow\t   rwx ",
+  "\tallow\nrwx\t",
+  "allow service=foo x",
+  "allow service=\"froo\" x",
+  "allow profile osd",
+  "allow profile osd-bootstrap",
+  "allow profile \"mds-bootstrap\", allow *",
+  "allow command \"a b c\"",
+  "allow command abc",
+  "allow command abc with arg=foo",
+  "allow command abc with arg=foo arg2=bar",
+  "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",
+  "allow service foo  w , allow service bar x",
+  "allow service foo r , allow service bar x",
+  "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 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 \"mds-bootstrap\", profile foo",
+  "allow * network 1.2.3.4/24",
+  "allow * network ::1/128",
+  "allow * network [aa:bb::1]/128",
+  "allow service=foo x network 1.2.3.4/16",
+  "allow command abc network 1.2.3.4/8",
+  "profile osd network 1.2.3.4/32",
+  "allow profile mon network 1.2.3.4/32",
+  0
+};
+
+TEST(MgrCap, ParseGood) {
+  for (int i=0; parse_good[i]; ++i) {
+    string str = parse_good[i];
+    MgrCap cap;
+    std::cout << "Testing good input: '" << str << "'" << std::endl;
+    ASSERT_TRUE(cap.parse(str, &cout));
+    std::cout << "                                         -> " << cap
+              << std::endl;
+  }
+}
+
+// these should stringify to the input value
+const char *parse_identity[] = {
+  "allow *",
+  "allow r",
+  "allow rwx",
+  "allow service foo x",
+  "profile osd",
+  "profile osd-bootstrap",
+  "profile mds-bootstrap, allow *",
+  "profile \"foo bar\", allow *",
+  "allow command abc",
+  "allow command \"a b c\"",
+  "allow command abc with arg=foo",
+  "allow command abc with arg=foo arg2=bar",
+  "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 service foo x",
+  "allow service foo x, allow service bar x",
+  "allow service foo w, allow service bar x",
+  "allow service foo r, allow service bar x",
+  "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 command abc with arg=foo arg2=bar, allow service foo r",
+  0
+};
+
+TEST(MgrCap, ParseIdentity)
+{
+  for (int i=0; parse_identity[i]; ++i) {
+    string str = parse_identity[i];
+    MgrCap cap;
+    std::cout << "Testing good input: '" << str << "'" << std::endl;
+    ASSERT_TRUE(cap.parse(str, &cout));
+    string out = stringify(cap);
+    ASSERT_EQ(out, str);
+  }
+}
+
+const char *parse_bad[] = {
+  "allow r foo",
+  "allow*",
+  "foo allow *",
+  "profile foo rwx",
+  "profile",
+  "profile foo bar rwx",
+  "allow profile foo rwx",
+  "allow profile",
+  "allow profile foo bar rwx",
+  "allow service bar",
+  "allow command baz x",
+  "allow r w",
+  "ALLOW r",
+  "allow rwx,",
+  "allow rwx x",
+  "allow r pool foo r",
+  "allow wwx pool taco",
+  "allow wwx pool taco^funny&chars",
+  "allow rwx pool 'weird name''",
+  "allow rwx object_prefix \"beforepool\" pool weird",
+  "allow rwx auid 123 pool asdf",
+  "allow command foo a prefix b",
+  "allow command foo with a prefixb",
+  "allow command foo with a = prefix b",
+  "allow command foo with a prefix b c",
+  0
+};
+
+TEST(MgrCap, ParseBad) {
+  for (int i=0; parse_bad[i]; ++i) {
+    string str = parse_bad[i];
+    MgrCap cap;
+    std::cout << "Testing bad input: '" << str << "'" << std::endl;
+    ASSERT_FALSE(cap.parse(str, &cout));
+  }
+}
+
+TEST(MgrCap, AllowAll) {
+  MgrCap cap;
+  ASSERT_FALSE(cap.is_allow_all());
+
+  ASSERT_TRUE(cap.parse("allow r", nullptr));
+  ASSERT_FALSE(cap.is_allow_all());
+  cap.grants.clear();
+
+  ASSERT_TRUE(cap.parse("allow w", nullptr));
+  ASSERT_FALSE(cap.is_allow_all());
+  cap.grants.clear();
+
+  ASSERT_TRUE(cap.parse("allow x", nullptr));
+  ASSERT_FALSE(cap.is_allow_all());
+  cap.grants.clear();
+
+  ASSERT_TRUE(cap.parse("allow rwx", nullptr));
+  ASSERT_FALSE(cap.is_allow_all());
+  cap.grants.clear();
+
+  ASSERT_TRUE(cap.parse("allow rw", nullptr));
+  ASSERT_FALSE(cap.is_allow_all());
+  cap.grants.clear();
+
+  ASSERT_TRUE(cap.parse("allow rx", nullptr));
+  ASSERT_FALSE(cap.is_allow_all());
+  cap.grants.clear();
+
+  ASSERT_TRUE(cap.parse("allow wx", nullptr));
+  ASSERT_FALSE(cap.is_allow_all());
+  cap.grants.clear();
+
+  ASSERT_TRUE(cap.parse("allow *", nullptr));
+  ASSERT_TRUE(cap.is_allow_all());
+  ASSERT_TRUE(cap.is_capable(nullptr, {}, "foo", "asdf", {}, true, true, true,
+                             {}));
+
+  MgrCap cap2;
+  ASSERT_FALSE(cap2.is_allow_all());
+  cap2.set_allow_all();
+  ASSERT_TRUE(cap2.is_allow_all());
+}
+
+TEST(MgrCap, Network) {
+  MgrCap cap;
+  bool r = cap.parse("allow * network 192.168.0.0/16, allow * network 10.0.0.0/8", nullptr);
+  ASSERT_TRUE(r);
+
+  entity_addr_t a, b, c;
+  a.parse("10.1.2.3");
+  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));
+}
+
+TEST(MgrCap, CommandRegEx) {
+  MgrCap cap;
+  ASSERT_FALSE(cap.is_allow_all());
+  ASSERT_TRUE(cap.parse("allow command abc with arg regex \"^[0-9a-z.]*$\"",
+                        nullptr));
+
+  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", "~!@#$"}},
+                              true, true, true, {}));
+
+  ASSERT_TRUE(cap.parse("allow command abc with arg regex \"[*\"", nullptr));
+  ASSERT_FALSE(cap.is_capable(nullptr, name, "", "abc", {{"arg", ""}}, true,
+                              true, true, {}));
+}