From: John Spray Date: Wed, 24 Sep 2014 10:37:34 +0000 (+0100) Subject: mds: introduce MDS auth caps X-Git-Tag: v0.88~97^2~13 X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=e8f3f6b91c6789fff0d3298e559b2314b83002df;p=ceph.git mds: introduce MDS auth caps Signed-off-by: John Spray --- diff --git a/src/mds/MDS.cc b/src/mds/MDS.cc index d981433234c1..1c803a811b2c 100644 --- a/src/mds/MDS.cc +++ b/src/mds/MDS.cc @@ -805,8 +805,9 @@ void MDS::check_ops_in_flight() /* This function DOES put the passed message before returning*/ void MDS::handle_command(MCommand *m) { - // FIXME authorize based on m->get_connection() - + Session *session = static_cast(m->get_connection()->get_priv()); + assert(session != NULL); + int r = 0; cmdmap_t cmdmap; std::stringstream ss; @@ -815,7 +816,15 @@ void MDS::handle_command(MCommand *m) Context *run_after = NULL; - if (m->cmd.empty()) { + if (!session->auth_caps.allow_all()) { + // TODO: enforce allow_all check only for 'w' commands + dout(1) << __func__ + << ": received command from client without `tell` capability: " + << m->get_connection()->peer_addr << dendl; + + ss << "permission denied"; + r = -EPERM; + } else if (m->cmd.empty()) { ss << "no command given"; outs = ss.str(); } else if (!cmdmap_from_json(m->cmd, &cmdmap, ss)) { @@ -825,12 +834,10 @@ void MDS::handle_command(MCommand *m) r = _handle_command(cmdmap, m->get_data(), &outbl, &outs, &run_after); } - if (m->get_connection()) { - MCommandReply *reply = new MCommandReply(r, outs); - reply->set_tid(m->get_tid()); - reply->set_data(outbl); - m->get_connection()->send_message(reply); - } + MCommandReply *reply = new MCommandReply(r, outs); + reply->set_tid(m->get_tid()); + reply->set_data(outbl); + m->get_connection()->send_message(reply); if (run_after) { run_after->complete(0); @@ -2539,6 +2546,8 @@ bool MDS::ms_verify_authorizer(Connection *con, int peer_type, << ", new/authorizing con " << con << dendl; con->set_priv(s->get()); + + // Wait until we fully accept the connection before setting // s->connection. In particular, if there are multiple incoming // connection attempts, they will all get their authorizer @@ -2552,6 +2561,25 @@ bool MDS::ms_verify_authorizer(Connection *con, int peer_type, // messenger.) } + bufferlist::iterator p = caps_info.caps.begin(); + string auth_cap_str; + try { + ::decode(auth_cap_str, p); + + dout(10) << __func__ << ": parsing auth_cap_str='" << auth_cap_str << "'" << dendl; + std::ostringstream errstr; + int parse_success = s->auth_caps.parse(auth_cap_str, &errstr); + if (parse_success == false) { + dout(1) << __func__ << ": auth cap parse error: " << errstr.str() + << " parsing '" << auth_cap_str << "'" << dendl; + } + } catch (buffer::error& e) { + // Assume legacy auth, defaults to: + // * permit all filesystem ops + // * permit no `tell` ops + dout(1) << __func__ << ": cannot decode auth caps bl of length " << caps_info.caps.length() << dendl; + } + /* s->caps.set_allow_all(caps_info.allow_all); diff --git a/src/mds/MDSAuthCaps.cc b/src/mds/MDSAuthCaps.cc new file mode 100644 index 000000000000..de97a0c102ed --- /dev/null +++ b/src/mds/MDSAuthCaps.cc @@ -0,0 +1,210 @@ +// -*- 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) 2014 Red Hat + * + * 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 "MDSAuthCaps.h" + +using std::ostream; +using std::string; +namespace qi = boost::spirit::qi; +namespace ascii = boost::spirit::ascii; +namespace phoenix = boost::phoenix; + +const std::string MDSCapMatch::MDS_AUTH_PATH_ROOT = "/"; + +template +struct MDSCapParser : qi::grammar +{ + MDSCapParser() : MDSCapParser::base_type(mdscaps) + { + using qi::char_; + using qi::int_; + using qi::lexeme; + using qi::alnum; + using qi::_val; + using qi::_1; + using qi::_2; + using qi::_3; + using qi::eps; + using qi::lit; + + spaces = +(lit(' ') | lit('\n') | lit('\t')); + + quoted_path %= + lexeme[lit("\"") >> *(char_ - '"') >> '"'] | + lexeme[lit("'") >> *(char_ - '\'') >> '\'']; + unquoted_path %= +char_("a-zA-Z0-9_.-/"); + + // match := [path=] [uid=] + uid %= (spaces >> lit("uid") >> lit('=') >> int_); + path %= (spaces >> lit("path") >> lit('=') >> (quoted_path | unquoted_path)); + match = -( + (uid)[_val = phoenix::construct(_1)] | + (path >> uid)[_val = phoenix::construct(_1, _2)] | + (path)[_val = phoenix::construct(_1)]); + + // capspec = * | r[w] + capspec = spaces >> ( + lit("*")[_val = MDSCapSpec(true, true, true)] + | + (lit("rw"))[_val = MDSCapSpec(true, true, false)] + | + (lit("r"))[_val = MDSCapSpec(true, false, false)] + ); + + grant = lit("allow") >> (capspec >> match)[_val = phoenix::construct(_1, _2)]; + grants %= (grant % (*lit(' ') >> (lit(';') | lit(',')) >> *lit(' '))); + mdscaps = grants [_val = phoenix::construct(_1)]; + } + qi::rule spaces; + qi::rule quoted_path, unquoted_path; + qi::rule capspec; + qi::rule path; + qi::rule uid; + qi::rule match; + qi::rule grant; + qi::rule()> grants; + qi::rule mdscaps; +}; + + +/** + * For a given filesystem path, query whether this capability carries` + * authorization to read or write. + * + * This is true if any of the 'grant' clauses in the capability match the + * requested path + op. + * + */ +bool MDSAuthCaps::is_capable(const std::string &path, int uid, bool may_read, bool may_write) const +{ + for (std::vector::const_iterator i = grants.begin(); i != grants.end(); ++i) { + if (i->match.match(path, uid)) { + if ((may_read && !i->spec.read) || + (may_write && !i->spec.write)) { + continue; + } else { + return true; + } + } + } + + return false; +} + +bool MDSAuthCaps::parse(const std::string& str, ostream *err) +{ + // Special case for legacy caps + if (str == "allow") { + grants.clear(); + grants.push_back(MDSCapGrant(MDSCapSpec(true, true, false), MDSCapMatch())); + return true; + } + + MDSCapParser g; + std::string::const_iterator iter = str.begin(); + std::string::const_iterator end = str.end(); + + bool r = qi::phrase_parse(iter, end, g, ascii::space, *this); + if (r && iter == end) { + return true; + } else { + // Make sure no grants are kept after parsing failed! + grants.clear(); + + if (err) + *err << "osdcap parse failed, stopped at '" << std::string(iter, end) + << "' of '" << str << "'\n"; + return false; + } +} + + +bool MDSAuthCaps::allow_all() const +{ + for (std::vector::const_iterator i = grants.begin(); i != grants.end(); ++i) { + if (i->match.is_match_all() && i->spec.allow_all()) { + return true; + } + } + + return false; +} + + +ostream &operator<<(ostream &out, const MDSCapMatch &match) +{ + if (match.path != MDSCapMatch::MDS_AUTH_PATH_ROOT) { + out << "path=\"" << match.path << "\""; + } + if (match.path != MDSCapMatch::MDS_AUTH_PATH_ROOT && match.uid != MDSCapMatch::MDS_AUTH_UID_ANY) { + out << " "; + } + if (match.uid != MDSCapMatch::MDS_AUTH_UID_ANY) { + out << "uid=" << match.uid; + } + + return out; +} + + +ostream &operator<<(ostream &out, const MDSCapSpec &spec) +{ + if (spec.any) { + out << "*"; + } else { + if (spec.read) { + out << "r"; + } + if (spec.write) { + out << "w"; + } + } + + return out; +} + + +ostream &operator<<(ostream &out, const MDSCapGrant &grant) +{ + out << "allow "; + out << grant.spec; + if (!grant.match.is_match_all()) { + out << " " << grant.match; + } + + return out; +} + + +ostream &operator<<(ostream &out, const MDSAuthCaps &cap) +{ + out << "MDSAuthCaps["; + for (size_t i = 0; i < cap.grants.size(); ++i) { + out << cap.grants[i]; + if (i < cap.grants.size() - 1) { + out << ", "; + } + } + out << "]"; + + return out; +} + diff --git a/src/mds/MDSAuthCaps.h b/src/mds/MDSAuthCaps.h new file mode 100644 index 000000000000..2e54d079cfa2 --- /dev/null +++ b/src/mds/MDSAuthCaps.h @@ -0,0 +1,86 @@ +// -*- 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) 2014 Red Hat + * + * 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. + * + */ + + +#ifndef MDS_AUTH_CAPS_H +#define MDS_AUTH_CAPS_H + +#include +#include +#include + + +struct MDSCapSpec { + bool read; + bool write; + bool any; + + MDSCapSpec() : write(false), any(false) {} + MDSCapSpec(bool r_, bool w_, bool a_) : read(r_), write(w_), any(a_) {} + + bool allow_all() const {return any;} +}; + +struct MDSCapMatch { + static const int MDS_AUTH_UID_ANY = -1; + static const std::string MDS_AUTH_PATH_ROOT; + + int uid; // Require UID to be equal to this, if !=MDS_AUTH_UID_ANY + std::string path; // Require path to be child of this (may be "/" for any) + + MDSCapMatch() : uid(MDS_AUTH_UID_ANY), path(MDS_AUTH_PATH_ROOT) {} + MDSCapMatch(int uid_) : uid(uid_), path(MDS_AUTH_PATH_ROOT) {} + MDSCapMatch(std::string path_) : uid(MDS_AUTH_UID_ANY), path(path_) {} + MDSCapMatch(std::string path_, int uid_) : uid(uid_), path(path_) {} + + bool is_match_all() const + { + return uid == MDS_AUTH_UID_ANY && path == "/"; + } + + bool match(const std::string &target_path, const int target_uid) const { + return (target_path.find(path) == 0 && (target_uid == uid || uid == MDS_AUTH_UID_ANY)); + } +}; + +struct MDSCapGrant { + MDSCapSpec spec; + MDSCapMatch match; + + MDSCapGrant(const MDSCapSpec &spec_, const MDSCapMatch &match_) : spec(spec_), match(match_) {} + MDSCapGrant() {} +}; + +class MDSAuthCaps +{ + protected: + std::vector grants; + + public: + bool parse(const std::string &str, std::ostream *err); + MDSAuthCaps() {} + MDSAuthCaps(const std::vector &grants_) : grants(grants_) {} + + bool allow_all() const; + bool is_capable(const std::string &path, int uid, bool may_read, bool may_write) const; + friend std::ostream &operator<<(std::ostream &out, const MDSAuthCaps &cap); +}; + + +std::ostream &operator<<(std::ostream &out, const MDSCapMatch &match); +std::ostream &operator<<(std::ostream &out, const MDSCapSpec &spec); +std::ostream &operator<<(std::ostream &out, const MDSCapGrant &grant); +std::ostream &operator<<(std::ostream &out, const MDSAuthCaps &cap); + +#endif // MDS_AUTH_CAPS_H diff --git a/src/mds/Makefile.am b/src/mds/Makefile.am index 71f76d5c010a..8bed0e15b11f 100644 --- a/src/mds/Makefile.am +++ b/src/mds/Makefile.am @@ -26,6 +26,7 @@ libmds_la_SOURCES = \ mds/snap.cc \ mds/SessionMap.cc \ mds/MDSContext.cc \ + mds/MDSAuthCaps.cc \ mds/MDLog.cc \ common/TrackedOp.cc libmds_la_LIBADD = $(LIBOSDC) @@ -53,6 +54,7 @@ noinst_HEADERS += \ mds/MDS.h \ mds/Beacon.h \ mds/MDSContext.h \ + mds/MDSAuthCaps.h \ mds/MDSMap.h \ mds/MDSTable.h \ mds/MDSTableServer.h \ diff --git a/src/mds/SessionMap.h b/src/mds/SessionMap.h index b000c5ec227c..4c38d3c10826 100644 --- a/src/mds/SessionMap.h +++ b/src/mds/SessionMap.h @@ -25,6 +25,7 @@ using std::set; #include "include/elist.h" #include "include/interval_set.h" #include "mdstypes.h" +#include "mds/MDSAuthCaps.h" class CInode; struct MDRequestImpl; @@ -81,6 +82,7 @@ private: int importing_count; friend class SessionMap; + // Human (friendly) name is soft state generated from client metadata void _update_human_name(); std::string human_name; @@ -98,6 +100,8 @@ public: session_info_t info; ///< durable bits + MDSAuthCaps auth_caps; + ConnectionRef connection; xlist::item item_session_list;