summaryrefslogtreecommitdiffstats
path: root/src/mds/MDSAuthCaps.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/mds/MDSAuthCaps.cc')
-rw-r--r--src/mds/MDSAuthCaps.cc434
1 files changed, 434 insertions, 0 deletions
diff --git a/src/mds/MDSAuthCaps.cc b/src/mds/MDSAuthCaps.cc
new file mode 100644
index 00000000..949ac62c
--- /dev/null
+++ b/src/mds/MDSAuthCaps.cc
@@ -0,0 +1,434 @@
+// -*- 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 <string_view>
+
+#include <errno.h>
+#include <fcntl.h>
+
+#include <boost/spirit/include/qi.hpp>
+#include <boost/spirit/include/phoenix_operator.hpp>
+#include <boost/spirit/include/phoenix.hpp>
+
+#include "common/debug.h"
+#include "MDSAuthCaps.h"
+#include "include/ipaddr.h"
+
+#define dout_subsys ceph_subsys_mds
+
+#undef dout_prefix
+#define dout_prefix *_dout << "MDSAuthCap "
+
+using std::ostream;
+using std::string;
+namespace qi = boost::spirit::qi;
+namespace ascii = boost::spirit::ascii;
+namespace phoenix = boost::phoenix;
+
+template <typename Iterator>
+struct MDSCapParser : qi::grammar<Iterator, MDSAuthCaps()>
+{
+ MDSCapParser() : MDSCapParser::base_type(mdscaps)
+ {
+ using qi::char_;
+ using qi::int_;
+ using qi::uint_;
+ 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_./-");
+ network_str %= +char_("/.:a-fA-F0-9][");
+
+ // match := [path=<path>] [uid=<uid> [gids=<gid>[,<gid>...]]
+ path %= (spaces >> lit("path") >> lit('=') >> (quoted_path | unquoted_path));
+ uid %= (spaces >> lit("uid") >> lit('=') >> uint_);
+ uintlist %= (uint_ % lit(','));
+ gidlist %= -(spaces >> lit("gids") >> lit('=') >> uintlist);
+ match = -(
+ (uid >> gidlist)[_val = phoenix::construct<MDSCapMatch>(_1, _2)] |
+ (path >> uid >> gidlist)[_val = phoenix::construct<MDSCapMatch>(_1, _2, _3)] |
+ (path)[_val = phoenix::construct<MDSCapMatch>(_1)]);
+
+ // capspec = * | r[w][p][s]
+ capspec = spaces >> (
+ lit("*")[_val = MDSCapSpec(MDSCapSpec::ALL)]
+ |
+ lit("all")[_val = MDSCapSpec(MDSCapSpec::ALL)]
+ |
+ (lit("rwps"))[_val = MDSCapSpec(MDSCapSpec::RWPS)]
+ |
+ (lit("rwp"))[_val = MDSCapSpec(MDSCapSpec::RWP)]
+ |
+ (lit("rws"))[_val = MDSCapSpec(MDSCapSpec::RWS)]
+ |
+ (lit("rw"))[_val = MDSCapSpec(MDSCapSpec::RW)]
+ |
+ (lit("r"))[_val = MDSCapSpec(MDSCapSpec::READ)]
+ );
+
+ grant = lit("allow") >> (capspec >> match >>
+ -(spaces >> lit("network") >> spaces >> network_str))
+ [_val = phoenix::construct<MDSCapGrant>(_1, _2, _3)];
+ grants %= (grant % (*lit(' ') >> (lit(';') | lit(',')) >> *lit(' ')));
+ mdscaps = grants [_val = phoenix::construct<MDSAuthCaps>(_1)];
+ }
+ qi::rule<Iterator> spaces;
+ qi::rule<Iterator, string()> quoted_path, unquoted_path, network_str;
+ qi::rule<Iterator, MDSCapSpec()> capspec;
+ qi::rule<Iterator, string()> path;
+ qi::rule<Iterator, uint32_t()> uid;
+ qi::rule<Iterator, std::vector<uint32_t>() > uintlist;
+ qi::rule<Iterator, std::vector<uint32_t>() > gidlist;
+ qi::rule<Iterator, MDSCapMatch()> match;
+ qi::rule<Iterator, MDSCapGrant()> grant;
+ qi::rule<Iterator, std::vector<MDSCapGrant>()> grants;
+ qi::rule<Iterator, MDSAuthCaps()> mdscaps;
+};
+
+void MDSCapMatch::normalize_path()
+{
+ // drop any leading /
+ while (path.length() && path[0] == '/') {
+ path = path.substr(1);
+ }
+
+ // drop dup //
+ // drop .
+ // drop ..
+}
+
+bool MDSCapMatch::match(std::string_view target_path,
+ const int caller_uid,
+ const int caller_gid,
+ const vector<uint64_t> *caller_gid_list) const
+{
+ if (uid != MDS_AUTH_UID_ANY) {
+ if (uid != caller_uid)
+ return false;
+ if (!gids.empty()) {
+ bool gid_matched = false;
+ if (std::find(gids.begin(), gids.end(), caller_gid) != gids.end())
+ gid_matched = true;
+ if (caller_gid_list) {
+ for (auto i = caller_gid_list->begin(); i != caller_gid_list->end(); ++i) {
+ if (std::find(gids.begin(), gids.end(), *i) != gids.end()) {
+ gid_matched = true;
+ break;
+ }
+ }
+ }
+ if (!gid_matched)
+ return false;
+ }
+ }
+
+ if (!match_path(target_path)) {
+ return false;
+ }
+
+ return true;
+}
+
+bool MDSCapMatch::match_path(std::string_view target_path) const
+{
+ if (path.length()) {
+ if (target_path.find(path) != 0)
+ return false;
+ // if path doesn't already have a trailing /, make sure the target
+ // does so that path=/foo doesn't match target_path=/food
+ if (target_path.length() > path.length() &&
+ path[path.length()-1] != '/' &&
+ target_path[path.length()] != '/')
+ return false;
+ }
+
+ return true;
+}
+
+void MDSCapGrant::parse_network()
+{
+ network_valid = ::parse_network(network.c_str(), &network_parsed,
+ &network_prefix);
+}
+
+/**
+ * Is the client *potentially* able to access this path? Actual
+ * permission will depend on uids/modes in the full is_capable.
+ */
+bool MDSAuthCaps::path_capable(std::string_view inode_path) const
+{
+ for (const auto &i : grants) {
+ if (i.match.match_path(inode_path)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/**
+ * 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(std::string_view inode_path,
+ uid_t inode_uid, gid_t inode_gid,
+ unsigned inode_mode,
+ uid_t caller_uid, gid_t caller_gid,
+ const vector<uint64_t> *caller_gid_list,
+ unsigned mask,
+ uid_t new_uid, gid_t new_gid,
+ const entity_addr_t& addr) const
+{
+ if (cct)
+ ldout(cct, 10) << __func__ << " inode(path /" << inode_path
+ << " owner " << inode_uid << ":" << inode_gid
+ << " mode 0" << std::oct << inode_mode << std::dec
+ << ") by caller " << caller_uid << ":" << caller_gid
+// << "[" << caller_gid_list << "]";
+ << " mask " << mask
+ << " new " << new_uid << ":" << new_gid
+ << " cap: " << *this << dendl;
+
+ for (std::vector<MDSCapGrant>::const_iterator i = grants.begin();
+ i != grants.end();
+ ++i) {
+ if (i->network.size() &&
+ (!i->network_valid ||
+ !network_contains(i->network_parsed,
+ i->network_prefix,
+ addr))) {
+ continue;
+ }
+
+ if (i->match.match(inode_path, caller_uid, caller_gid, caller_gid_list) &&
+ i->spec.allows(mask & (MAY_READ|MAY_EXECUTE), mask & MAY_WRITE)) {
+ // we have a match; narrow down GIDs to those specifically allowed here
+ vector<uint64_t> gids;
+ if (std::find(i->match.gids.begin(), i->match.gids.end(), caller_gid) !=
+ i->match.gids.end()) {
+ gids.push_back(caller_gid);
+ }
+ if (caller_gid_list) {
+ std::set_intersection(i->match.gids.begin(), i->match.gids.end(),
+ caller_gid_list->begin(), caller_gid_list->end(),
+ std::back_inserter(gids));
+ std::sort(gids.begin(), gids.end());
+ }
+
+
+ // Spec is non-allowing if caller asked for set pool but spec forbids it
+ if (mask & MAY_SET_VXATTR) {
+ if (!i->spec.allow_set_vxattr()) {
+ continue;
+ }
+ }
+
+ if (mask & MAY_SNAPSHOT) {
+ if (!i->spec.allow_snapshot()) {
+ continue;
+ }
+ }
+
+ // check unix permissions?
+ if (i->match.uid == MDSCapMatch::MDS_AUTH_UID_ANY) {
+ return true;
+ }
+
+ // chown/chgrp
+ if (mask & MAY_CHOWN) {
+ if (new_uid != caller_uid || // you can't chown to someone else
+ inode_uid != caller_uid) { // you can't chown from someone else
+ continue;
+ }
+ }
+ if (mask & MAY_CHGRP) {
+ // you can only chgrp *to* one of your groups... if you own the file.
+ if (inode_uid != caller_uid ||
+ std::find(gids.begin(), gids.end(), new_gid) ==
+ gids.end()) {
+ continue;
+ }
+ }
+
+ if (inode_uid == caller_uid) {
+ if ((!(mask & MAY_READ) || (inode_mode & S_IRUSR)) &&
+ (!(mask & MAY_WRITE) || (inode_mode & S_IWUSR)) &&
+ (!(mask & MAY_EXECUTE) || (inode_mode & S_IXUSR))) {
+ return true;
+ }
+ } else if (std::find(gids.begin(), gids.end(),
+ inode_gid) != gids.end()) {
+ if ((!(mask & MAY_READ) || (inode_mode & S_IRGRP)) &&
+ (!(mask & MAY_WRITE) || (inode_mode & S_IWGRP)) &&
+ (!(mask & MAY_EXECUTE) || (inode_mode & S_IXGRP))) {
+ return true;
+ }
+ } else {
+ if ((!(mask & MAY_READ) || (inode_mode & S_IROTH)) &&
+ (!(mask & MAY_WRITE) || (inode_mode & S_IWOTH)) &&
+ (!(mask & MAY_EXECUTE) || (inode_mode & S_IXOTH))) {
+ return true;
+ }
+ }
+ }
+ }
+
+ return false;
+}
+
+void MDSAuthCaps::set_allow_all()
+{
+ grants.clear();
+ grants.push_back(MDSCapGrant(MDSCapSpec(MDSCapSpec::ALL), MDSCapMatch(),
+ {}));
+}
+
+bool MDSAuthCaps::parse(CephContext *c, std::string_view str, ostream *err)
+{
+ // Special case for legacy caps
+ if (str == "allow") {
+ grants.clear();
+ grants.push_back(MDSCapGrant(MDSCapSpec(MDSCapSpec::RWPS), MDSCapMatch(),
+ {}));
+ return true;
+ }
+
+ auto iter = str.begin();
+ auto end = str.end();
+ MDSCapParser<decltype(iter)> g;
+
+ bool r = qi::phrase_parse(iter, end, g, ascii::space, *this);
+ cct = c; // set after parser self-assignment
+ if (r && iter == end) {
+ for (auto& grant : grants) {
+ std::sort(grant.match.gids.begin(), grant.match.gids.end());
+ grant.parse_network();
+ }
+ return true;
+ } else {
+ // Make sure no grants are kept after parsing failed!
+ grants.clear();
+
+ if (err)
+ *err << "mds capability parse failed, stopped at '"
+ << std::string(iter, end)
+ << "' of '" << str << "'";
+ return false;
+ }
+}
+
+
+bool MDSAuthCaps::allow_all() const
+{
+ for (std::vector<MDSCapGrant>::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.length()) {
+ out << "path=\"/" << match.path << "\"";
+ if (match.uid != MDSCapMatch::MDS_AUTH_UID_ANY) {
+ out << " ";
+ }
+ }
+ if (match.uid != MDSCapMatch::MDS_AUTH_UID_ANY) {
+ out << "uid=" << match.uid;
+ if (!match.gids.empty()) {
+ out << " gids=";
+ for (std::vector<gid_t>::const_iterator p = match.gids.begin();
+ p != match.gids.end();
+ ++p) {
+ if (p != match.gids.begin())
+ out << ',';
+ out << *p;
+ }
+ }
+ }
+
+ return out;
+}
+
+
+ostream &operator<<(ostream &out, const MDSCapSpec &spec)
+{
+ if (spec.allow_all()) {
+ out << "*";
+ } else {
+ if (spec.allow_read()) {
+ out << "r";
+ }
+ if (spec.allow_write()) {
+ out << "w";
+ }
+ if (spec.allow_set_vxattr()) {
+ out << "p";
+ }
+ if (spec.allow_snapshot()) {
+ out << "s";
+ }
+ }
+
+ return out;
+}
+
+
+ostream &operator<<(ostream &out, const MDSCapGrant &grant)
+{
+ out << "allow ";
+ out << grant.spec;
+ if (!grant.match.is_match_all()) {
+ out << " " << grant.match;
+ }
+ if (grant.network.size()) {
+ out << " network " << grant.network;
+ }
+ 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;
+}
+