From: Jason Dillaman Date: Fri, 11 Oct 2019 14:30:03 +0000 (-0400) Subject: mgr: stop re-using MonCap for handling MGR caps X-Git-Tag: v15.1.0~1092^2~11 X-Git-Url: http://git.apps.os.sepia.ceph.com/?a=commitdiff_plain;h=6350bee5c873cb1cef0ca1345b89bff66817e0c1;p=ceph-ci.git mgr: stop re-using MonCap for handling MGR caps Use the existing MonCap as the basis for a new custom MgrCap processor. Signed-off-by: Jason Dillaman --- diff --git a/src/mgr/CMakeLists.txt b/src/mgr/CMakeLists.txt index 499c700210c..79227bcc1d4 100644 --- a/src/mgr/CMakeLists.txt +++ b/src/mgr/CMakeLists.txt @@ -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 @@ -22,7 +25,8 @@ set(mgr_srcs PyModuleRunner.cc PyOSDMap.cc StandbyPyModules.cc - mgr_commands.cc) + mgr_commands.cc + $) add_executable(ceph-mgr ${mgr_srcs}) target_include_directories(ceph-mgr SYSTEM PRIVATE "${Python_INCLUDE_DIRS}") target_link_libraries(ceph-mgr diff --git a/src/mgr/DaemonServer.cc b/src/mgr/DaemonServer.cc index e087f0ea931..2eceda48ba5 100644 --- a/src/mgr/DaemonServer.cc +++ b/src/mgr/DaemonServer.cc @@ -681,7 +681,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 index 00000000000..619a0593c73 --- /dev/null +++ b/src/mgr/MgrCap.cc @@ -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 +#include +#include +#include +#include +#include +#include +#include + +#include "MgrCap.h" +#include "include/stringify.h" +#include "include/ipaddr.h" +#include "common/debug.h" +#include "common/Formatter.h" + +#include +#include + +#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; +} + +// +// fusion lets us easily populate structs via the qi parser. + +typedef std::map 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)) + +// + +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& 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& 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& 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 +struct MgrCapParser : qi::grammar { + 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()) + >> 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()) + >> 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()) + >> 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(_1)]; + } + + qi::rule spaces; + qi::rule rwxa; + qi::rule quoted_string; + qi::rule unquoted_word; + qi::rule str, network_str; + + qi::rule str_match, str_prefix, str_regex; + qi::rule()> kv_pair; + qi::rule()> kv_map; + + qi::rule rwxa_match; + qi::rule command_match; + qi::rule service_match; + qi::rule profile_match; + qi::rule grant; + qi::rule()> grants; + qi::rule mgrcap; +}; + +bool MgrCap::parse(const std::string& str, ostream *err) { + auto iter = str.begin(); + auto end = str.end(); + + MgrCapParser 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 index 00000000000..b0385773549 --- /dev/null +++ b/src/mgr/MgrCap.h @@ -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 + +#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 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 profile_grants; + + void expand_profile() const; + + MgrCapGrant() : allow(0) {} + MgrCapGrant(std::string&& service, + std::string&& profile, + std::string&& command, + std::map&& 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& 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 grants; + + MgrCap() {} + explicit MgrCap(const std::vector &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& 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& ls); +}; +WRITE_CLASS_ENCODER(MgrCap) + +std::ostream& operator<<(std::ostream& out, const MgrCap& cap); + +#endif // CEPH_MGRCAP_H diff --git a/src/mgr/MgrSession.h b/src/mgr/MgrSession.h index aacbeb8209f..0d6ff95716b 100644 --- a/src/mgr/MgrSession.h +++ b/src/mgr/MgrSession.h @@ -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 declared_types; diff --git a/src/mon/AuthMonitor.cc b/src/mon/AuthMonitor.cc index e7fa856dd46..3cb261b412a 100644 --- a/src/mon/AuthMonitor.cc +++ b/src/mon/AuthMonitor.cc @@ -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 @@ -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 @@ -1241,9 +1242,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") { diff --git a/src/mon/CMakeLists.txt b/src/mon/CMakeLists.txt index f1ac49bde56..1382d24a76e 100644 --- a/src/mon/CMakeLists.txt +++ b/src/mon/CMakeLists.txt @@ -23,7 +23,8 @@ set(lib_mon_srcs ConfigKeyService.cc ../mds/MDSAuthCaps.cc ../mgr/mgr_commands.cc - ../osd/OSDCap.cc) + ../osd/OSDCap.cc + $) if(HAVE_GSSAPI) list(APPEND lib_mon_srcs diff --git a/src/test/mgr/CMakeLists.txt b/src/test/mgr/CMakeLists.txt index c212b541924..9e6950d799e 100644 --- a/src/test/mgr/CMakeLists.txt +++ b/src/test/mgr/CMakeLists.txt @@ -1,3 +1,10 @@ +# unittest_mgr_mgrcap +add_executable(unittest_mgr_mgrcap + test_mgrcap.cc + $) +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 index 00000000000..2b997f532c6 --- /dev/null +++ b/src/test/mgr/test_mgrcap.cc @@ -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 + +#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, {})); +}