From cb534e0049afc4f9259812a6624744bbdbaabafa Mon Sep 17 00:00:00 2001 From: Jason Dillaman Date: Fri, 11 Oct 2019 12:44:01 -0400 Subject: [PATCH] mgr: support optional arguments for module and profile caps This allows an optional, arbitrary key/value constraint clauses to be appended to "profile XYZ" and "allow module XYZ" caps. A module can then provide additional validatation against these meta-arguments. Example: profile rbd pool=rbd allow module rbd_support with pool=rbd Signed-off-by: Jason Dillaman --- doc/rados/operations/user-management.rst | 4 +- src/mgr/MgrCap.cc | 106 +++++++++++++---------- src/mgr/MgrCap.h | 33 ++++--- src/test/mgr/test_mgrcap.cc | 4 + 4 files changed, 88 insertions(+), 59 deletions(-) diff --git a/doc/rados/operations/user-management.rst b/doc/rados/operations/user-management.rst index 9cd295dbbc86a..ba15fb851fb04 100644 --- a/doc/rados/operations/user-management.rst +++ b/doc/rados/operations/user-management.rst @@ -149,7 +149,7 @@ Capability syntax follows the form:: mgr 'allow {access-spec} [network {network/prefix}]' - mgr 'profile {name} [network {network/prefix}]' + mgr 'profile {name} [{key1} {match-type} {value1} ...] [network {network/prefix}]' Manager capabilities can also be specified for specific commands, all commands exported by a built-in manager service, or all commands @@ -159,7 +159,7 @@ Capability syntax follows the form:: mgr 'allow service {service-name} {access-spec} [network {network/prefix}]' - mgr 'allow module {module-name} {access-spec} [network {network/prefix}]' + mgr 'allow module {module-name} [with {key1} {match-type} {value1} ...] {access-spec} [network {network/prefix}]' The ``{access-spec}`` syntax is as follows: :: diff --git a/src/mgr/MgrCap.cc b/src/mgr/MgrCap.cc index 0e9f36118f445..37a256f3fe473 100644 --- a/src/mgr/MgrCap.cc +++ b/src/mgr/MgrCap.cc @@ -86,19 +86,20 @@ ostream& operator<<(ostream& out, const MgrCapGrant& m) { out << " module " << maybe_quote_string(m.module); } 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.arguments.empty()) { + out << (!m.profile.empty() ? "" : " with"); + for (auto& [key, constraint] : m.arguments) { + out << " " << maybe_quote_string(key) << constraint; } } + if (m.allow != 0) { + out << " " << m.allow; + } + if (m.network.size()) { out << " network " << m.network; } @@ -115,7 +116,7 @@ BOOST_FUSION_ADAPT_STRUCT(MgrCapGrant, (std::string, module) (std::string, profile) (std::string, command) - (kvmap, command_args) + (kvmap, arguments) (mgr_rwxa_t, allow) (std::string, network)) @@ -155,15 +156,52 @@ void MgrCapGrant::expand_profile() const { } } +bool MgrCapGrant::validate_arguments( + const std::map& args) const { + for (auto& [key, constraint] : arguments) { + auto q = args.find(key); + + // argument must be present if a constraint exists + if (q == args.end()) { + return false; + } + + switch (constraint.match_type) { + case MgrCapGrantConstraint::MATCH_TYPE_EQUAL: + if (constraint.value != q->second) + return false; + break; + case MgrCapGrantConstraint::MATCH_TYPE_PREFIX: + if (q->second.find(constraint.value) != 0) + return false; + break; + case MgrCapGrantConstraint::MATCH_TYPE_REGEX: + try { + std::regex pattern(constraint.value, std::regex::extended); + if (!std::regex_match(q->second, pattern)) { + return false; + } + } catch(const std::regex_error&) { + return false; + } + break; + default: + return false; + } + } + + return true; +} + mgr_rwxa_t MgrCapGrant::get_allowed( CephContext *cct, EntityName name, const std::string& s, const std::string& m, const std::string& c, - const std::map& c_args) const { + const std::map& args) const { if (!profile.empty()) { expand_profile(); mgr_rwxa_t a; for (auto& grant : profile_grants) { - a = a | grant.get_allowed(cct, name, s, m, c, c_args); + a = a | grant.get_allowed(cct, name, s, m, c, args); } return a; } @@ -179,6 +217,11 @@ mgr_rwxa_t MgrCapGrant::get_allowed( if (module != m) { return mgr_rwxa_t{}; } + + // don't test module arguments when validating a specific command + if (c.empty() && !validate_arguments(args)) { + return mgr_rwxa_t{}; + } return allow; } @@ -186,37 +229,8 @@ mgr_rwxa_t MgrCapGrant::get_allowed( 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; - } + if (!validate_arguments(args)) { + return mgr_rwxa_t{}; } return mgr_rwxa_t{MGR_CAP_ANY}; } @@ -345,6 +359,10 @@ void MgrCap::generate_test_instances(list& ls) { 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"); + ls.push_back(new MgrCap); + ls.back()->parse("allow module bar with k1=v1 k2=v2 x"); + ls.push_back(new MgrCap); + ls.back()->parse("profile rbd pool=rbd"); } // grammar @@ -412,7 +430,7 @@ struct MgrCapParser : qi::grammar { >> str >> qi::attr(std::string()) >> qi::attr(std::string()) - >> qi::attr(map()) + >> -(spaces >> lit("with") >> spaces >> kv_map) >> spaces >> rwxa >> -(spaces >> lit("network") >> spaces >> network_str); @@ -423,7 +441,7 @@ struct MgrCapParser : qi::grammar { >> qi::attr(std::string()) >> str >> qi::attr(std::string()) - >> qi::attr(std::map()) + >> -(spaces >> kv_map) >> qi::attr(0) >> -(spaces >> lit("network") >> spaces >> network_str); diff --git a/src/mgr/MgrCap.h b/src/mgr/MgrCap.h index 96b5b96934bea..dc14ac2d2a910 100644 --- a/src/mgr/MgrCap.h +++ b/src/mgr/MgrCap.h @@ -63,26 +63,30 @@ struct MgrCapGrant { * - a service allow ('allow service mds rw') * - this will match against a specific service and the r/w/x flags. * - * - a module allow ('allow module rbd_support rw') + * - a module allow ('allow module rbd_support rw, allow module rbd_support with pool=rbd rw') * - this will match against a specific python add-on module and the r/w/x * flags. * - * - a profile ('profile read-only') + * - a profile ('profile read-only, profile rbd pool=rbd') * - 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. + * this includes the command name (the prefix string) + * + * The command, module, and profile caps can also accept an optional + * key/value map. If not provided, all command arguments and module + * meta-arguments are allowed. If a key/value pair is specified, that + * argument must be present and must match the provided constraint. */ + typedef std::map Arguments; + std::string service; std::string module; std::string profile; std::string command; - std::map command_args; + Arguments arguments; // restrict by network std::string network; @@ -107,13 +111,16 @@ struct MgrCapGrant { std::string&& module, std::string&& profile, std::string&& command, - std::map&& command_args, + Arguments&& arguments, mgr_rwxa_t allow) : service(std::move(service)), module(std::move(module)), profile(std::move(profile)), command(std::move(command)), - command_args(std::move(command_args)), allow(allow) { + arguments(std::move(arguments)), allow(allow) { } + bool validate_arguments( + const std::map& arguments) const; + /** * check if given request parameters match our constraints * @@ -122,7 +129,7 @@ struct MgrCapGrant { * @param service service (if any) * @param module module (if any) * @param command command (if any) - * @param command_args command args (if any) + * @param arguments profile/module/command args (if any) * @return bits we allow */ mgr_rwxa_t get_allowed( @@ -131,7 +138,7 @@ struct MgrCapGrant { const std::string& service, const std::string& module, const std::string& command, - const std::map& command_args) const; + const std::map& arguments) const; bool is_allow_all() const { return (allow == MGR_CAP_ANY && @@ -168,7 +175,7 @@ struct MgrCap { * @param service service name * @param module module name * @param command command id - * @param command_args + * @param arguments * @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 @@ -179,7 +186,7 @@ struct MgrCap { const std::string& service, const std::string& module, const std::string& command, - const std::map& command_args, + const std::map& arguments, bool op_may_read, bool op_may_write, bool op_may_exec, const entity_addr_t& addr) const; diff --git a/src/test/mgr/test_mgrcap.cc b/src/test/mgr/test_mgrcap.cc index 8dc21561a2ab5..c6c73b885ba68 100644 --- a/src/test/mgr/test_mgrcap.cc +++ b/src/test/mgr/test_mgrcap.cc @@ -57,12 +57,14 @@ const char *parse_good[] = { "allow module=foo x", "allow module foo_foo r", "allow module \" foo \" w", + "allow module foo with arg1=value1 x", "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 rbd pool=ABC namespace=NS", "profile \"mds-bootstrap\", profile foo", "allow * network 1.2.3.4/24", "allow * network ::1/128", @@ -95,6 +97,7 @@ const char *parse_identity[] = { "profile osd-bootstrap", "profile mds-bootstrap, allow *", "profile \"foo bar\", allow *", + "profile rbd namespace=NS pool=ABC", "allow command abc", "allow command \"a b c\"", "allow command abc with arg=foo", @@ -111,6 +114,7 @@ const char *parse_identity[] = { "allow service \" foo \" w, allow service bar r", "allow module foo x", "allow module \" foo_foo \" r", + "allow module foo with arg1=value1 x", "allow command abc with arg=foo arg2=bar, allow service foo r", 0 }; -- 2.39.5