From: Jason Dillaman Date: Mon, 26 Jun 2017 20:38:15 +0000 (-0400) Subject: mon: support regex-based restrictions on command caps X-Git-Tag: v12.1.2~162^2~10 X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=9ceac165037dbd6097e5d9096276e0020e28eef6;p=ceph.git mon: support regex-based restrictions on command caps Signed-off-by: Jason Dillaman --- diff --git a/src/mon/MonCap.cc b/src/mon/MonCap.cc index 0bfc83d170ea..7a26b6825ad4 100644 --- a/src/mon/MonCap.cc +++ b/src/mon/MonCap.cc @@ -27,6 +27,9 @@ #include +#include +#include "include/assert.h" + static inline bool is_not_alnum_space(char c) { return !(isalpha(c) || isdigit(c) || (c == '-') || (c == '_')); @@ -60,10 +63,17 @@ ostream& operator<<(ostream& out, const mon_rwxa_t& p) ostream& operator<<(ostream& out, const StringConstraint& c) { - if (c.prefix.length()) - return out << "prefix " << c.prefix; - else + switch (c.match_type) { + case StringConstraint::MATCH_TYPE_EQUAL: return out << "value " << c.value; + case StringConstraint::MATCH_TYPE_PREFIX: + return out << "prefix " << c.value; + case StringConstraint::MATCH_TYPE_REGEX: + return out << "regex " << c.value; + default: + break; + } + return out; } ostream& operator<<(ostream& out, const MonCapGrant& m) @@ -79,10 +89,22 @@ ostream& operator<<(ostream& out, const MonCapGrant& m) for (map::const_iterator p = m.command_args.begin(); p != m.command_args.end(); ++p) { - if (p->second.value.length()) - out << " " << maybe_quote_string(p->first) << "=" << maybe_quote_string(p->second.value); - else - out << " " << maybe_quote_string(p->first) << " prefix " << maybe_quote_string(p->second.prefix); + switch (p->second.match_type) { + case StringConstraint::MATCH_TYPE_EQUAL: + out << " " << maybe_quote_string(p->first) << "=" + << maybe_quote_string(p->second.value); + break; + case StringConstraint::MATCH_TYPE_PREFIX: + out << " " << maybe_quote_string(p->first) << " prefix " + << maybe_quote_string(p->second.value); + break; + case StringConstraint::MATCH_TYPE_REGEX: + out << " " << maybe_quote_string(p->first) << " regex " + << maybe_quote_string(p->second.value); + break; + default: + break; + } } } } @@ -108,8 +130,8 @@ BOOST_FUSION_ADAPT_STRUCT(MonCapGrant, (mon_rwxa_t, allow)) BOOST_FUSION_ADAPT_STRUCT(StringConstraint, - (std::string, value) - (std::string, prefix)) + (StringConstraint::MatchType, match_type) + (std::string, value)) // @@ -176,26 +198,25 @@ void MonCapGrant::expand_profile_mon(const EntityName& name) const profile_grants.push_back(MonCapGrant("osd", MON_CAP_R | MON_CAP_W)); profile_grants.push_back(MonCapGrant("auth", MON_CAP_R | MON_CAP_X)); profile_grants.push_back(MonCapGrant("config-key", MON_CAP_R | MON_CAP_W)); - string prefix = string("daemon-private/mgr/"); - profile_grants.push_back(MonCapGrant("config-key get", "key", - StringConstraint("", prefix))); - profile_grants.push_back(MonCapGrant("config-key put", "key", - StringConstraint("", prefix))); - profile_grants.push_back(MonCapGrant("config-key exists", "key", - StringConstraint("", prefix))); - profile_grants.push_back(MonCapGrant("config-key delete", "key", - StringConstraint("", prefix))); + StringConstraint constraint(StringConstraint::MATCH_TYPE_PREFIX, + "daemon-private/mgr/"); + profile_grants.push_back(MonCapGrant("config-key get", "key", constraint)); + profile_grants.push_back(MonCapGrant("config-key put", "key", constraint)); + profile_grants.push_back(MonCapGrant("config-key exists", "key", constraint)); + profile_grants.push_back(MonCapGrant("config-key delete", "key", constraint)); } if (profile == "osd" || profile == "mds" || profile == "mon" || profile == "mgr") { + StringConstraint constraint(StringConstraint::MATCH_TYPE_PREFIX, + string("daemon-private/") + stringify(name) + + string("/")); string prefix = string("daemon-private/") + stringify(name) + string("/"); - profile_grants.push_back(MonCapGrant("config-key get", "key", StringConstraint("", prefix))); - profile_grants.push_back(MonCapGrant("config-key put", "key", StringConstraint("", prefix))); - profile_grants.push_back(MonCapGrant("config-key exists", "key", StringConstraint("", prefix))); - profile_grants.push_back(MonCapGrant("config-key delete", "key", StringConstraint("", prefix))); + profile_grants.push_back(MonCapGrant("config-key get", "key", constraint)); + profile_grants.push_back(MonCapGrant("config-key put", "key", constraint)); + profile_grants.push_back(MonCapGrant("config-key exists", "key", constraint)); + profile_grants.push_back(MonCapGrant("config-key delete", "key", constraint)); } if (profile == "bootstrap-osd") { - string prefix = "dm-crypt/osd"; profile_grants.push_back(MonCapGrant("mon", MON_CAP_R)); // read monmap profile_grants.push_back(MonCapGrant("osd", MON_CAP_R)); // read osdmap profile_grants.push_back(MonCapGrant("mon getmap")); @@ -206,27 +227,36 @@ void MonCapGrant::expand_profile_mon(const EntityName& name) const profile_grants.push_back(MonCapGrant("osd", MON_CAP_R)); // read osdmap profile_grants.push_back(MonCapGrant("mon getmap")); profile_grants.push_back(MonCapGrant("auth get-or-create")); // FIXME: this can expose other mds keys - profile_grants.back().command_args["entity"] = StringConstraint("", "mds."); - profile_grants.back().command_args["caps_mon"] = StringConstraint("allow profile mds", ""); - profile_grants.back().command_args["caps_osd"] = StringConstraint("allow rwx", ""); - profile_grants.back().command_args["caps_mds"] = StringConstraint("allow", ""); + profile_grants.back().command_args["entity"] = StringConstraint( + StringConstraint::MATCH_TYPE_PREFIX, "mds."); + profile_grants.back().command_args["caps_mon"] = StringConstraint( + StringConstraint::MATCH_TYPE_EQUAL, "allow profile mds"); + profile_grants.back().command_args["caps_osd"] = StringConstraint( + StringConstraint::MATCH_TYPE_EQUAL, "allow rwx"); + profile_grants.back().command_args["caps_mds"] = StringConstraint( + StringConstraint::MATCH_TYPE_EQUAL, "allow"); } if (profile == "bootstrap-mgr") { profile_grants.push_back(MonCapGrant("mon", MON_CAP_R)); // read monmap profile_grants.push_back(MonCapGrant("osd", MON_CAP_R)); // read osdmap profile_grants.push_back(MonCapGrant("mon getmap")); profile_grants.push_back(MonCapGrant("auth get-or-create")); // FIXME: this can expose other mgr keys - profile_grants.back().command_args["entity"] = StringConstraint("", "mgr."); - profile_grants.back().command_args["caps_mon"] = StringConstraint("allow profile mgr", ""); + profile_grants.back().command_args["entity"] = StringConstraint( + StringConstraint::MATCH_TYPE_PREFIX, "mgr."); + profile_grants.back().command_args["caps_mon"] = StringConstraint( + StringConstraint::MATCH_TYPE_EQUAL, "allow profile mgr"); } if (profile == "bootstrap-rgw") { profile_grants.push_back(MonCapGrant("mon", MON_CAP_R)); // read monmap profile_grants.push_back(MonCapGrant("osd", MON_CAP_R)); // read osdmap profile_grants.push_back(MonCapGrant("mon getmap")); profile_grants.push_back(MonCapGrant("auth get-or-create")); // FIXME: this can expose other mds keys - profile_grants.back().command_args["entity"] = StringConstraint("", "client.rgw."); - profile_grants.back().command_args["caps_mon"] = StringConstraint("allow rw", ""); - profile_grants.back().command_args["caps_osd"] = StringConstraint("allow rwx", ""); + profile_grants.back().command_args["entity"] = StringConstraint( + StringConstraint::MATCH_TYPE_PREFIX, "client.rgw."); + profile_grants.back().command_args["caps_mon"] = StringConstraint( + StringConstraint::MATCH_TYPE_EQUAL, "allow rw"); + profile_grants.back().command_args["caps_osd"] = StringConstraint( + StringConstraint::MATCH_TYPE_EQUAL, "allow rwx"); } if (profile == "fs-client") { profile_grants.push_back(MonCapGrant("mon", MON_CAP_R)); @@ -275,14 +305,25 @@ mon_rwxa_t MonCapGrant::get_allowed(CephContext *cct, // argument must be present if a constraint exists if (q == c_args.end()) return 0; - if (p->second.value.length()) { - // match value + switch (p->second.match_type) { + case StringConstraint::MATCH_TYPE_EQUAL: if (p->second.value != q->second) return 0; - } else { - // match prefix - if (q->second.find(p->second.prefix) != 0) + break; + case StringConstraint::MATCH_TYPE_PREFIX: + if (q->second.find(p->second.value) != 0) return 0; + break; + case StringConstraint::MATCH_TYPE_REGEX: + { + boost::regex pattern(p->second.value, + boost::regex::basic | boost::regex::no_except); + if (pattern.empty() || !boost::regex_match(q->second, pattern)) + return 0; + } + break; + default: + break; } } return MON_CAP_ALL; @@ -427,9 +468,12 @@ struct MonCapParser : qi::grammar spaces = +(lit(' ') | lit('\n') | lit('\t')); // command := command[=]cmd [k1=v1 k2=v2 ...] - str_match = '=' >> str >> qi::attr(string()); - str_prefix = spaces >> lit("prefix") >> spaces >> qi::attr(string()) >> str; - kv_pair = str >> (str_match | str_prefix); + str_match = '=' >> qi::attr(StringConstraint::MATCH_TYPE_EQUAL) >> str; + str_prefix = spaces >> lit("prefix") >> spaces >> + qi::attr(StringConstraint::MATCH_TYPE_PREFIX) >> str; + str_regex = spaces >> lit("regex") >> spaces >> + qi::attr(StringConstraint::MATCH_TYPE_REGEX) >> str; + kv_pair = str >> (str_match | str_prefix | str_regex); kv_map %= kv_pair >> *(spaces >> kv_pair); command_match = -spaces >> lit("allow") >> spaces >> lit("command") >> (lit('=') | spaces) >> qi::attr(string()) >> qi::attr(string()) @@ -481,7 +525,7 @@ struct MonCapParser : qi::grammar qi::rule unquoted_word; qi::rule str; - qi::rule str_match, str_prefix; + qi::rule str_match, str_prefix, str_regex; qi::rule()> kv_pair; qi::rule()> kv_map; diff --git a/src/mon/MonCap.h b/src/mon/MonCap.h index 46acb1e42b5f..1ff4b831ed1d 100644 --- a/src/mon/MonCap.h +++ b/src/mon/MonCap.h @@ -35,12 +35,20 @@ struct mon_rwxa_t { ostream& operator<<(ostream& out, const mon_rwxa_t& p); struct StringConstraint { + enum MatchType { + MATCH_TYPE_NONE, + MATCH_TYPE_EQUAL, + MATCH_TYPE_PREFIX, + MATCH_TYPE_REGEX + }; + + MatchType match_type = MATCH_TYPE_NONE; string value; - string prefix; StringConstraint() {} - StringConstraint(string a, string b) - : value(std::move(a)), prefix(std::move(b)) {} + StringConstraint(MatchType match_type, string value) + : match_type(match_type), value(value) { + } }; ostream& operator<<(ostream& out, const StringConstraint& c); diff --git a/src/test/mon/moncap.cc b/src/test/mon/moncap.cc index 8b55719fe16e..f78e0e20774d 100644 --- a/src/test/mon/moncap.cc +++ b/src/test/mon/moncap.cc @@ -43,6 +43,8 @@ const char *parse_good[] = { "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", @@ -238,3 +240,19 @@ TEST(MonCap, ProfileOSD) { name, "", "config-key delete", ca, true, true, true)); } +TEST(MonCap, CommandRegEx) { + MonCap cap; + ASSERT_FALSE(cap.is_allow_all()); + ASSERT_TRUE(cap.parse("allow command abc with arg regex \"^[0-9a-z.]*$\"", NULL)); + + EntityName name; + name.from_str("osd.123"); + ASSERT_TRUE(cap.is_capable(nullptr, CEPH_ENTITY_TYPE_OSD, name, "", + "abc", {{"arg", "12345abcde"}}, true, true, true)); + ASSERT_FALSE(cap.is_capable(nullptr, CEPH_ENTITY_TYPE_OSD, name, "", + "abc", {{"arg", "~!@#$"}}, true, true, true)); + + ASSERT_TRUE(cap.parse("allow command abc with arg regex \"[*\"", NULL)); + ASSERT_FALSE(cap.is_capable(nullptr, CEPH_ENTITY_TYPE_OSD, name, "", + "abc", {{"arg", ""}}, true, true, true)); +}