+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
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
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,
--- /dev/null
+// -*- 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;
+}
--- /dev/null
+// -*- 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
#include "common/RefCountedObj.h"
#include "common/entity_name.h"
#include "msg/msg_types.h"
-#include "mon/MonCap.h"
+#include "MgrCap.h"
/**
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;
-// -*- 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
*
* 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>
#include "include/ceph_assert.h"
#include "mds/MDSAuthCaps.h"
+#include "mgr/MgrCap.h"
#include "osd/OSDCap.h"
#define dout_subsys ceph_subsys_mon
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") {
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
+# 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")
--- /dev/null
+// -*- 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, {}));
+}