mirror of https://github.com/ceph/ceph
581 lines
17 KiB
C++
581 lines
17 KiB
C++
// -*- 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/algorithm/string/predicate.hpp>
|
|
#include <boost/config/warning_disable.hpp>
|
|
#include <boost/fusion/adapted/struct/adapt_struct.hpp>
|
|
#include <boost/fusion/include/adapt_struct.hpp>
|
|
#include <boost/fusion/include/std_pair.hpp>
|
|
#include <boost/phoenix.hpp>
|
|
#include <boost/spirit/include/qi.hpp>
|
|
#include <boost/spirit/include/qi_uint.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
|
|
|
|
std::ostream& operator<<(std::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;
|
|
}
|
|
|
|
std::ostream& operator<<(std::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;
|
|
}
|
|
|
|
std::ostream& operator<<(std::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.module.empty()) {
|
|
out << " module " << maybe_quote_string(m.module);
|
|
} else if (!m.command.empty()) {
|
|
out << " command " << maybe_quote_string(m.command);
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
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, module)
|
|
(std::string, profile)
|
|
(std::string, command)
|
|
(kvmap, arguments)
|
|
(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(std::ostream *err) 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;
|
|
}
|
|
|
|
if (profile == "osd") {
|
|
// this is a documented profile (so we need to accept it as valid), but it
|
|
// currently doesn't do anything
|
|
return;
|
|
}
|
|
|
|
if (profile == "mds") {
|
|
// this is a documented profile (so we need to accept it as valid), but it
|
|
// currently doesn't do anything
|
|
return;
|
|
}
|
|
|
|
if (profile == "rbd" || profile == "rbd-read-only") {
|
|
Arguments filtered_arguments;
|
|
for (auto& [key, constraint] : arguments) {
|
|
if (key == "pool" || key == "namespace") {
|
|
filtered_arguments[key] = std::move(constraint);
|
|
} else {
|
|
if (err != nullptr) {
|
|
*err << "profile '" << profile << "' does not recognize key '" << key
|
|
<< "'";
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
mgr_rwxa_t perms = mgr_rwxa_t{MGR_CAP_R};
|
|
if (profile == "rbd") {
|
|
perms = mgr_rwxa_t{MGR_CAP_R | MGR_CAP_W};
|
|
}
|
|
|
|
// allow all 'rbd_support' commands (restricted by optional
|
|
// pool/namespace constraints)
|
|
profile_grants.push_back({{}, "rbd_support", {}, {},
|
|
std::move(filtered_arguments), perms});
|
|
return;
|
|
}
|
|
|
|
if (err != nullptr) {
|
|
*err << "unrecognized profile '" << profile << "'";
|
|
}
|
|
}
|
|
|
|
bool MgrCapGrant::validate_arguments(
|
|
const std::map<std::string, std::string>& 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<std::string, std::string>& args) const {
|
|
if (!profile.empty()) {
|
|
expand_profile(nullptr);
|
|
mgr_rwxa_t a;
|
|
for (auto& grant : profile_grants) {
|
|
a = a | grant.get_allowed(cct, name, s, m, c, args);
|
|
}
|
|
return a;
|
|
}
|
|
|
|
if (!service.empty()) {
|
|
if (service != s) {
|
|
return mgr_rwxa_t{};
|
|
}
|
|
return allow;
|
|
}
|
|
|
|
if (!module.empty()) {
|
|
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;
|
|
}
|
|
|
|
if (!command.empty()) {
|
|
if (command != c) {
|
|
return mgr_rwxa_t{};
|
|
}
|
|
if (!validate_arguments(args)) {
|
|
return mgr_rwxa_t{};
|
|
}
|
|
return mgr_rwxa_t{MGR_CAP_ANY};
|
|
}
|
|
|
|
return allow;
|
|
}
|
|
|
|
std::ostream& operator<<(std::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& module,
|
|
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 << " "
|
|
<< "module=" << module << " "
|
|
<< "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, module, 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(ceph::buffer::list& bl) const {
|
|
// remain backwards compatible w/ MgrCap
|
|
ENCODE_START(4, 4, bl);
|
|
encode(text, bl);
|
|
ENCODE_FINISH(bl);
|
|
}
|
|
|
|
void MgrCap::decode(ceph::buffer::list::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(ceph::Formatter *f) const {
|
|
f->dump_string("text", text);
|
|
}
|
|
|
|
void MgrCap::generate_test_instances(std::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");
|
|
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
|
|
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())
|
|
>> 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(std::string())
|
|
>> qi::attr(std::map<std::string, MgrCapGrantConstraint>())
|
|
>> 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())
|
|
>> -(spaces >> lit("with") >> spaces >> kv_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())
|
|
>> -(spaces >> kv_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::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 |
|
|
module_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()> module_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, std::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;
|
|
|
|
std::stringstream profile_err;
|
|
for (auto& g : grants) {
|
|
g.parse_network();
|
|
|
|
if (!g.profile.empty()) {
|
|
g.expand_profile(&profile_err);
|
|
}
|
|
}
|
|
|
|
if (!profile_err.str().empty()) {
|
|
if (err != nullptr) {
|
|
*err << "mgr capability parse failed during profile evaluation: "
|
|
<< profile_err.str();
|
|
}
|
|
return false;
|
|
}
|
|
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;
|
|
}
|