From 3463613bd43d5fb7f8094c3b9b8a3163325ff196 Mon Sep 17 00:00:00 2001 From: Jason Dillaman Date: Fri, 11 Oct 2019 11:25:56 -0400 Subject: [PATCH] mgr: add new 'allow module' cap to MgrCap This allows specific python add-on modules to be whitelisted instead of manually adding each command exported by the module. allow module {module-name} {access-spec} Signed-off-by: Jason Dillaman --- doc/rados/operations/user-management.rst | 29 +++++++++++++ src/mgr/DaemonServer.cc | 2 +- src/mgr/MgrCap.cc | 55 +++++++++++++++++++----- src/mgr/MgrCap.h | 17 ++++++-- src/test/mgr/test_mgrcap.cc | 45 +++++++++++++------ 5 files changed, 121 insertions(+), 27 deletions(-) diff --git a/doc/rados/operations/user-management.rst b/doc/rados/operations/user-management.rst index 5665321ff5c..9cd295dbbc8 100644 --- a/doc/rados/operations/user-management.rst +++ b/doc/rados/operations/user-management.rst @@ -144,6 +144,35 @@ Capability syntax follows the form:: the use of this capability is restricted to clients connecting from this network. +- **Manager Caps:** Manager (``ceph-mgr``) capabilities include + ``r``, ``w``, ``x`` access settings or ``profile {name}``. For example: :: + + mgr 'allow {access-spec} [network {network/prefix}]' + + mgr 'profile {name} [network {network/prefix}]' + + Manager capabilities can also be specified for specific commands, + all commands exported by a built-in manager service, or all commands + exported by a specific add-on module. For example: :: + + mgr 'allow command "{command-prefix}" [with {key1} {match-type} {value1} ...] [network {network/prefix}]' + + mgr 'allow service {service-name} {access-spec} [network {network/prefix}]' + + mgr 'allow module {module-name} {access-spec} [network {network/prefix}]' + + The ``{access-spec}`` syntax is as follows: :: + + * | all | [r][w][x] + + The ``{service-name}`` is one of the following: :: + + mgr | osd | pg | py + + The ``{match-type}`` is one of the following: :: + + = | prefix | regex + - **Metadata Server Caps:** For administrators, use ``allow *``. For all other users, such as CephFS clients, consult :doc:`/cephfs/client-auth` diff --git a/src/mgr/DaemonServer.cc b/src/mgr/DaemonServer.cc index 2eceda48ba5..3fd549dcbb8 100644 --- a/src/mgr/DaemonServer.cc +++ b/src/mgr/DaemonServer.cc @@ -682,7 +682,7 @@ bool DaemonServer::_allowed_command( bool capable = s->caps.is_capable( g_ceph_context, s->entity_name, - module, prefix, param_str_map, + module, "", prefix, param_str_map, cmd_r, cmd_w, cmd_x, s->get_peer_addr()); diff --git a/src/mgr/MgrCap.cc b/src/mgr/MgrCap.cc index 619a0593c73..0e9f36118f4 100644 --- a/src/mgr/MgrCap.cc +++ b/src/mgr/MgrCap.cc @@ -82,6 +82,8 @@ ostream& operator<<(ostream& out, const MgrCapGrant& m) { out << "allow"; if (!m.service.empty()) { out << " service " << maybe_quote_string(m.service); + } else if (!m.module.empty()) { + out << " module " << maybe_quote_string(m.module); } else if (!m.command.empty()) { out << " command " << maybe_quote_string(m.command); if (!m.command_args.empty()) { @@ -110,6 +112,7 @@ typedef std::map kvmap; BOOST_FUSION_ADAPT_STRUCT(MgrCapGrant, (std::string, service) + (std::string, module) (std::string, profile) (std::string, command) (kvmap, command_args) @@ -135,32 +138,32 @@ void MgrCapGrant::expand_profile() const { if (profile == "read-only") { // grants READ-ONLY caps MGR-wide - profile_grants.push_back({{}, {}, {}, {}, mgr_rwxa_t{MGR_CAP_R}}); + 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({{}, {}, {}, {}, + profile_grants.push_back({{}, {}, {}, {}, {}, mgr_rwxa_t{MGR_CAP_R | MGR_CAP_W}}); return; } if (profile == "crash") { - profile_grants.push_back({{}, {}, "crash post", {}, {}}); + 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::string& m, 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); + a = a | grant.get_allowed(cct, name, s, m, c, c_args); } return a; } @@ -172,6 +175,13 @@ mgr_rwxa_t MgrCapGrant::get_allowed( return allow; } + if (!module.empty()) { + if (module != m) { + return mgr_rwxa_t{}; + } + return allow; + } + if (!command.empty()) { if (command != c) { return mgr_rwxa_t{}; @@ -238,7 +248,7 @@ bool MgrCap::is_allow_all() const { void MgrCap::set_allow_all() { grants.clear(); - grants.push_back({{}, {}, {}, {}, mgr_rwxa_t{MGR_CAP_ANY}}); + grants.push_back({{}, {}, {}, {}, {}, mgr_rwxa_t{MGR_CAP_ANY}}); text = "allow *"; } @@ -246,12 +256,14 @@ bool MgrCap::is_capable( CephContext *cct, EntityName name, const std::string& service, + const std::string& module, 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 << " " + << "module=" << module << " " << "command=" << command << (op_may_read ? " read":"") << (op_may_write ? " write":"") @@ -283,7 +295,7 @@ bool MgrCap::is_capable( } // check enumerated caps - allow = allow | grant.get_allowed(cct, name, service, command, + allow = allow | grant.get_allowed(cct, name, service, module, command, command_args); if ((!op_may_read || (allow & MGR_CAP_R)) && (!op_may_write || (allow & MGR_CAP_W)) && @@ -376,7 +388,9 @@ struct MgrCapParser : qi::grammar { // 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()) + >> qi::attr(std::string()) + >> qi::attr(std::string()) + >> qi::attr(std::string()) >> str >> -(spaces >> lit("with") >> spaces >> kv_map) >> qi::attr(0) @@ -384,15 +398,29 @@ struct MgrCapParser : qi::grammar { // service foo rwxa service_match %= -spaces >> lit("allow") >> spaces >> lit("service") >> (lit('=') | spaces) - >> str >> qi::attr(std::string()) >> qi::attr(std::string()) + >> str + >> qi::attr(std::string()) + >> qi::attr(std::string()) + >> qi::attr(std::string()) >> qi::attr(map()) >> spaces >> rwxa >> -(spaces >> lit("network") >> spaces >> network_str); + // module foo rwxa + module_match %= -spaces >> lit("allow") >> spaces >> lit("module") >> (lit('=') | spaces) + >> qi::attr(std::string()) + >> 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()) + >> qi::attr(std::string()) >> str >> qi::attr(std::string()) >> qi::attr(std::map()) @@ -401,7 +429,10 @@ struct MgrCapParser : qi::grammar { // rwxa rwxa_match %= -spaces >> lit("allow") >> spaces - >> qi::attr(std::string()) >> qi::attr(std::string()) >> qi::attr(std::string()) + >> qi::attr(std::string()) + >> qi::attr(std::string()) + >> qi::attr(std::string()) + >> qi::attr(std::string()) >> qi::attr(std::map()) >> rwxa >> -(spaces >> lit("network") >> spaces >> network_str); @@ -418,7 +449,8 @@ struct MgrCapParser : qi::grammar { ); // grant := allow ... - grant = -spaces >> (rwxa_match | profile_match | service_match | command_match) >> -spaces; + grant = -spaces >> (rwxa_match | profile_match | service_match | + module_match | command_match) >> -spaces; // mgrcap := grant [grant ...] grants %= (grant % (*lit(' ') >> (lit(';') | lit(',')) >> *lit(' '))); @@ -438,6 +470,7 @@ struct MgrCapParser : qi::grammar { qi::rule rwxa_match; qi::rule command_match; qi::rule service_match; + qi::rule module_match; qi::rule profile_match; qi::rule grant; qi::rule()> grants; diff --git a/src/mgr/MgrCap.h b/src/mgr/MgrCap.h index b0385773549..96b5b96934b 100644 --- a/src/mgr/MgrCap.h +++ b/src/mgr/MgrCap.h @@ -63,6 +63,10 @@ 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') + * - this will match against a specific python add-on module 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', @@ -75,6 +79,7 @@ struct MgrCapGrant { * argument must be present and equal or match a prefix. */ std::string service; + std::string module; std::string profile; std::string command; std::map command_args; @@ -99,13 +104,14 @@ struct MgrCapGrant { MgrCapGrant() : allow(0) {} MgrCapGrant(std::string&& service, + std::string&& module, 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) { + : service(std::move(service)), module(std::move(module)), + profile(std::move(profile)), command(std::move(command)), + command_args(std::move(command_args)), allow(allow) { } /** @@ -114,6 +120,7 @@ struct MgrCapGrant { * @param cct context * @param name entity name * @param service service (if any) + * @param module module (if any) * @param command command (if any) * @param command_args command args (if any) * @return bits we allow @@ -122,12 +129,14 @@ struct MgrCapGrant { CephContext *cct, EntityName name, const std::string& service, + const std::string& module, const std::string& command, const std::map& command_args) const; bool is_allow_all() const { return (allow == MGR_CAP_ANY && service.empty() && + module.empty() && profile.empty() && command.empty()); } @@ -157,6 +166,7 @@ struct MgrCap { * what the capability has specified. * * @param service service name + * @param module module name * @param command command id * @param command_args * @param op_may_read whether the operation may need to read @@ -167,6 +177,7 @@ struct MgrCap { bool is_capable(CephContext *cct, EntityName name, const std::string& service, + const std::string& module, const std::string& command, const std::map& command_args, bool op_may_read, bool op_may_write, bool op_may_exec, diff --git a/src/test/mgr/test_mgrcap.cc b/src/test/mgr/test_mgrcap.cc index 2b997f532c6..8dc21561a2a 100644 --- a/src/test/mgr/test_mgrcap.cc +++ b/src/test/mgr/test_mgrcap.cc @@ -53,6 +53,10 @@ const char *parse_good[] = { "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 module foo x", + "allow module=foo x", + "allow module foo_foo r", + "allow module \" foo \" w", "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\"", @@ -105,6 +109,8 @@ const char *parse_identity[] = { "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 module foo x", + "allow module \" foo_foo \" r", "allow command abc with arg=foo arg2=bar, allow service foo r", 0 }; @@ -193,8 +199,8 @@ TEST(MgrCap, AllowAll) { ASSERT_TRUE(cap.parse("allow *", nullptr)); ASSERT_TRUE(cap.is_allow_all()); - ASSERT_TRUE(cap.is_capable(nullptr, {}, "foo", "asdf", {}, true, true, true, - {})); + ASSERT_TRUE(cap.is_capable(nullptr, {}, "foo", "", "asdf", {}, true, true, + true, {})); MgrCap cap2; ASSERT_FALSE(cap2.is_allow_all()); @@ -212,12 +218,12 @@ TEST(MgrCap, Network) { 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)); + 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) { @@ -228,12 +234,27 @@ TEST(MgrCap, CommandRegEx) { 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", "~!@#$"}}, + 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, + ASSERT_FALSE(cap.is_capable(nullptr, name, "", "", "abc", {{"arg", ""}}, true, true, true, {})); } + +TEST(MgrCap, Module) { + MgrCap cap; + ASSERT_FALSE(cap.is_allow_all()); + ASSERT_TRUE(cap.parse("allow module abc r, allow module bcd w", nullptr)); + + ASSERT_FALSE(cap.is_capable(nullptr, {}, "", "abc", "", {}, true, true, false, + {})); + ASSERT_TRUE(cap.is_capable(nullptr, {}, "", "abc", "", {}, true, false, false, + {})); + ASSERT_FALSE(cap.is_capable(nullptr, {}, "", "bcd", "", {}, true, true, false, + {})); + ASSERT_TRUE(cap.is_capable(nullptr, {}, "", "bcd", "", {}, false, true, false, + {})); +} -- 2.39.5