// -*- 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) 2009-2011 New Dream Network * * 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 #include "OSDCap.h" #include "common/config.h" #include "common/debug.h" #include "include/ipaddr.h" using std::ostream; using std::string; using std::vector; ostream& operator<<(ostream& out, const osd_rwxa_t& p) { if (p == OSD_CAP_ANY) return out << "*"; if (p & OSD_CAP_R) out << "r"; if (p & OSD_CAP_W) out << "w"; if ((p & OSD_CAP_X) == OSD_CAP_X) { out << "x"; } else { if (p & OSD_CAP_CLS_R) out << " class-read"; if (p & OSD_CAP_CLS_W) out << " class-write"; } return out; } ostream& operator<<(ostream& out, const OSDCapSpec& s) { if (s.allow) return out << s.allow; if (s.class_name.length()) { out << "class '" << s.class_name << "'"; if (!s.method_name.empty()) { out << " '" << s.method_name << "'"; } } return out; } ostream& operator<<(ostream& out, const OSDCapPoolNamespace& pns) { if (!pns.pool_name.empty()) { out << "pool " << pns.pool_name << " "; } if (pns.nspace) { out << "namespace "; if (pns.nspace->empty()) { out << "\"\""; } else { out << *pns.nspace; } out << " "; } return out; } ostream& operator<<(ostream &out, const OSDCapPoolTag &pt) { out << "app " << pt.application << " key " << pt.key << " val " << pt.value << " "; return out; } ostream& operator<<(ostream& out, const OSDCapMatch& m) { if (!m.pool_namespace.pool_name.empty() || m.pool_namespace.nspace) { out << m.pool_namespace; } if (!m.pool_tag.application.empty()) { out << m.pool_tag; } if (m.object_prefix.length()) { out << "object_prefix " << m.object_prefix << " "; } return out; } ostream& operator<<(ostream& out, const OSDCapProfile& m) { out << "profile " << m.name; out << m.pool_namespace; return out; } bool OSDCapPoolNamespace::is_match(const std::string& pn, const std::string& ns) const { if (!pool_name.empty()) { if (pool_name != pn) { return false; } } if (nspace) { if (!nspace->empty() && nspace->back() == '*' && boost::starts_with(ns, nspace->substr(0, nspace->length() - 1))) { return true; } if (*nspace != ns) { return false; } } return true; } bool OSDCapPoolNamespace::is_match_all() const { if (!pool_name.empty()) return false; if (nspace) return false; return true; } bool OSDCapPoolTag::is_match(const app_map_t& app_map) const { if (application.empty()) { return true; } auto kv_map = app_map.find(application); if (kv_map == app_map.end()) { return false; } if (!key.compare("*") && !value.compare("*")) { return true; } if (!key.compare("*")) { for (auto it : kv_map->second) { if (it.second == value) { return true; } } return false; } auto kv_val = kv_map->second.find(key); if (kv_val == kv_map->second.end()) { return false; } if (!value.compare("*")) { return true; } return kv_val->second == value; } bool OSDCapPoolTag::is_match_all() const { return application.empty(); } bool OSDCapMatch::is_match(const string& pn, const string& ns, const OSDCapPoolTag::app_map_t& app_map, const string& object) const { if (!pool_namespace.is_match(pn, ns)) { return false; } else if (!pool_tag.is_match(app_map)) { return false; } if (object_prefix.length()) { if (object.find(object_prefix) != 0) return false; } return true; } bool OSDCapMatch::is_match_all() const { if (!pool_namespace.is_match_all()) { return false; } else if (!pool_tag.is_match_all()) { return false; } if (object_prefix.length()) { return false; } return true; } ostream& operator<<(ostream& out, const OSDCapGrant& g) { out << "grant("; if (g.profile.is_valid()) { out << g.profile << " ["; for (auto it = g.profile_grants.cbegin(); it != g.profile_grants.cend(); ++it) { if (it != g.profile_grants.cbegin()) { out << ","; } out << *it; } out << "]"; } else { out << g.match << g.spec; } if (g.network.size()) { out << " network " << g.network; } out << ")"; return out; } void OSDCapGrant::set_network(const string& n) { network = n; network_valid = ::parse_network(n.c_str(), &network_parsed, &network_prefix); } bool OSDCapGrant::allow_all() const { if (profile.is_valid()) { return std::any_of(profile_grants.cbegin(), profile_grants.cend(), [](const OSDCapGrant& grant) { return grant.allow_all(); }); } return (match.is_match_all() && spec.allow_all()); } bool OSDCapGrant::is_capable( const string& pool_name, const string& ns, const OSDCapPoolTag::app_map_t& application_metadata, const string& object, bool op_may_read, bool op_may_write, const std::vector& classes, const entity_addr_t& addr, std::vector* class_allowed) const { osd_rwxa_t allow = 0; if (network.size() && (!network_valid || !network_contains(network_parsed, network_prefix, addr))) { return false; } if (profile.is_valid()) { return std::any_of(profile_grants.cbegin(), profile_grants.cend(), [&](const OSDCapGrant& grant) { return grant.is_capable(pool_name, ns, application_metadata, object, op_may_read, op_may_write, classes, addr, class_allowed); }); } else { if (match.is_match(pool_name, ns, application_metadata, object)) { allow = allow | spec.allow; if ((op_may_read && !(allow & OSD_CAP_R)) || (op_may_write && !(allow & OSD_CAP_W))) { return false; } if (!classes.empty()) { // check 'allow *' if (spec.allow_all()) { return true; } // compare this grant to each class in the operation for (size_t i = 0; i < classes.size(); ++i) { // check 'allow class foo [method_name]' if (!spec.class_name.empty() && classes[i].class_name == spec.class_name && (spec.method_name.empty() || classes[i].method_name == spec.method_name)) { (*class_allowed)[i] = true; continue; } // check 'allow x | class-{rw}': must be on allow list if (!classes[i].allowed) { continue; } if ((classes[i].read && !(allow & OSD_CAP_CLS_R)) || (classes[i].write && !(allow & OSD_CAP_CLS_W))) { continue; } (*class_allowed)[i] = true; } if (!std::all_of(class_allowed->cbegin(), class_allowed->cend(), [](bool v) { return v; })) { return false; } } return true; } } return false; } void OSDCapGrant::expand_profile() { if (profile.name == "read-only") { // grants READ-ONLY caps to the OSD profile_grants.emplace_back(OSDCapMatch(profile.pool_namespace), OSDCapSpec(osd_rwxa_t(OSD_CAP_R))); return; } if (profile.name == "read-write") { // grants READ-WRITE caps to the OSD profile_grants.emplace_back(OSDCapMatch(profile.pool_namespace), OSDCapSpec(osd_rwxa_t(OSD_CAP_R | OSD_CAP_W))); } if (profile.name == "rbd") { // RBD read-write grant profile_grants.emplace_back(OSDCapMatch(string(), "rbd_info"), OSDCapSpec(osd_rwxa_t(OSD_CAP_R))); profile_grants.emplace_back(OSDCapMatch(string(), "rbd_children"), OSDCapSpec(osd_rwxa_t(OSD_CAP_CLS_R))); profile_grants.emplace_back(OSDCapMatch(string(), "rbd_mirroring"), OSDCapSpec(osd_rwxa_t(OSD_CAP_CLS_R))); profile_grants.emplace_back(OSDCapMatch(profile.pool_namespace.pool_name), OSDCapSpec("rbd", "metadata_list")); profile_grants.emplace_back(OSDCapMatch(profile.pool_namespace), OSDCapSpec(osd_rwxa_t(OSD_CAP_R | OSD_CAP_W | OSD_CAP_X))); } if (profile.name == "rbd-read-only") { // RBD read-only grant profile_grants.emplace_back(OSDCapMatch(profile.pool_namespace), OSDCapSpec(osd_rwxa_t(OSD_CAP_R | OSD_CAP_CLS_R))); profile_grants.emplace_back(OSDCapMatch(profile.pool_namespace, "rbd_header."), OSDCapSpec("rbd", "child_attach")); profile_grants.emplace_back(OSDCapMatch(profile.pool_namespace, "rbd_header."), OSDCapSpec("rbd", "child_detach")); } } bool OSDCap::allow_all() const { for (auto &grant : grants) { if (grant.allow_all()) { return true; } } return false; } void OSDCap::set_allow_all() { grants.clear(); grants.push_back(OSDCapGrant(OSDCapMatch(), OSDCapSpec(OSD_CAP_ANY))); } bool OSDCap::is_capable(const string& pool_name, const string& ns, const OSDCapPoolTag::app_map_t& application_metadata, const string& object, bool op_may_read, bool op_may_write, const std::vector& classes, const entity_addr_t& addr) const { std::vector class_allowed(classes.size(), false); for (auto &grant : grants) { if (grant.is_capable(pool_name, ns, application_metadata, object, op_may_read, op_may_write, classes, addr, &class_allowed)) { return true; } } return false; } // grammar namespace qi = boost::spirit::qi; namespace ascii = boost::spirit::ascii; namespace phoenix = boost::phoenix; template struct OSDCapParser : qi::grammar { OSDCapParser() : OSDCapParser::base_type(osdcap) { 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; quoted_string %= lexeme['"' >> +(char_ - '"') >> '"'] | lexeme['\'' >> +(char_ - '\'') >> '\'']; equoted_string %= lexeme['"' >> *(char_ - '"') >> '"'] | lexeme['\'' >> *(char_ - '\'') >> '\'']; unquoted_word %= +char_("a-zA-Z0-9_./-"); str %= quoted_string | unquoted_word; estr %= equoted_string | unquoted_word; network_str %= +char_("/.:a-fA-F0-9]["); spaces = +ascii::space; wildcard = (lit('*') | lit("all")) [_val = "*"]; pool_name %= -(spaces >> lit("pool") >> (lit('=') | spaces) >> str); nspace %= (spaces >> lit("namespace") >> (lit('=') | spaces) >> estr >> -char_('*')); // match := [pool[=] [namespace[=]]] [object_prefix ] object_prefix %= -(spaces >> lit("object_prefix") >> spaces >> str); pooltag %= (spaces >> lit("tag") >> spaces >> str // application >> spaces >> (wildcard | str) // key >> -spaces >> lit('=') >> -spaces >> (wildcard | str)); // value match = ( pooltag [_val = phoenix::construct(_1)] | (nspace >> pooltag) [_val = phoenix::construct(_1, _2)] | (pool_name >> nspace >> object_prefix) [_val = phoenix::construct(_1, _2, _3)] | (pool_name >> object_prefix) [_val = phoenix::construct(_1, _2)] ); // rwxa := * | [r][w][x] [class-read] [class-write] rwxa = (spaces >> wildcard[_val = OSD_CAP_ANY]) | ( eps[_val = 0] >> ( spaces >> ( lit('r')[_val |= OSD_CAP_R] || lit('w')[_val |= OSD_CAP_W] || lit('x')[_val |= OSD_CAP_X] )) || ( (spaces >> lit("class-read")[_val |= OSD_CAP_CLS_R]) || (spaces >> lit("class-write")[_val |= OSD_CAP_CLS_W]) )); // capspec := * | rwx | class [] class_name %= (spaces >> lit("class") >> spaces >> str); method_name %= -(spaces >> str); capspec = ( (rwxa) [_val = phoenix::construct(_1)] | (class_name >> method_name) [_val = phoenix::construct(_1, _2)]); // profile := profile [pool[=] [namespace[=]]] profile_name %= (lit("profile") >> (lit('=') | spaces) >> str); profile = ( (profile_name >> pool_name >> nspace) [_val = phoenix::construct(_1, _2, _3)] | (profile_name >> pool_name) [_val = phoenix::construct(_1, _2)]); // grant := allow match capspec grant = (*ascii::blank >> ((lit("allow") >> capspec >> match >> -(spaces >> lit("network") >> spaces >> network_str)) [_val = phoenix::construct(_2, _1, _3)] | (lit("allow") >> match >> capspec >> -(spaces >> lit("network") >> spaces >> network_str)) [_val = phoenix::construct(_1, _2, _3)] | (profile >> -(spaces >> lit("network") >> spaces >> network_str)) [_val = phoenix::construct(_1, _2)] ) >> *ascii::blank); // osdcap := grant [grant ...] grants %= (grant % (lit(';') | lit(','))); osdcap = grants [_val = phoenix::construct(_1)]; } qi::rule spaces; qi::rule rwxa; qi::rule quoted_string, equoted_string; qi::rule unquoted_word; qi::rule str, estr, network_str; qi::rule wildcard; qi::rule class_name; qi::rule method_name; qi::rule capspec; qi::rule pool_name; qi::rule nspace; qi::rule object_prefix; qi::rule pooltag; qi::rule match; qi::rule profile_name; qi::rule profile; qi::rule grant; qi::rule()> grants; qi::rule osdcap; }; bool OSDCap::parse(const string& str, ostream *err) { OSDCapParser g; string::const_iterator iter = str.begin(); string::const_iterator end = str.end(); bool r = qi::phrase_parse(iter, end, g, ascii::space, *this); if (r && iter == end) return true; // Make sure no grants are kept after parsing failed! grants.clear(); if (err) *err << "osd capability parse failed, stopped at '" << std::string(iter, end) << "' of '" << str << "'"; return false; }