]> git.proxmox.com Git - ceph.git/blob - ceph/src/mgr/MgrCap.cc
import new upstream nautilus stable release 14.2.8
[ceph.git] / ceph / src / mgr / MgrCap.cc
1 // -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
2 // vim: ts=8 sw=2 smarttab
3 /*
4 * Ceph - scalable distributed file system
5 *
6 * Copyright (C) 2013 Inktank
7 *
8 * This is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Lesser General Public
10 * License version 2.1, as published by the Free Software
11 * Foundation. See file COPYING.
12 *
13 */
14
15 #include <boost/config/warning_disable.hpp>
16 #include <boost/spirit/include/qi_uint.hpp>
17 #include <boost/spirit/include/qi.hpp>
18 #include <boost/fusion/include/std_pair.hpp>
19 #include <boost/spirit/include/phoenix.hpp>
20 #include <boost/fusion/adapted/struct/adapt_struct.hpp>
21 #include <boost/fusion/include/adapt_struct.hpp>
22 #include <boost/algorithm/string/predicate.hpp>
23
24 #include "MgrCap.h"
25 #include "include/stringify.h"
26 #include "include/ipaddr.h"
27 #include "common/debug.h"
28 #include "common/Formatter.h"
29
30 #include <algorithm>
31 #include <regex>
32
33 #include "include/ceph_assert.h"
34
35 static inline bool is_not_alnum_space(char c) {
36 return !(isalpha(c) || isdigit(c) || (c == '-') || (c == '_'));
37 }
38
39 static std::string maybe_quote_string(const std::string& str) {
40 if (find_if(str.begin(), str.end(), is_not_alnum_space) == str.end())
41 return str;
42 return std::string("\"") + str + std::string("\"");
43 }
44
45 #define dout_subsys ceph_subsys_mgr
46
47 ostream& operator<<(ostream& out, const mgr_rwxa_t& p) {
48 if (p == MGR_CAP_ANY)
49 return out << "*";
50
51 if (p & MGR_CAP_R)
52 out << "r";
53 if (p & MGR_CAP_W)
54 out << "w";
55 if (p & MGR_CAP_X)
56 out << "x";
57 return out;
58 }
59
60 ostream& operator<<(ostream& out, const MgrCapGrantConstraint& c) {
61 switch (c.match_type) {
62 case MgrCapGrantConstraint::MATCH_TYPE_EQUAL:
63 out << "=";
64 break;
65 case MgrCapGrantConstraint::MATCH_TYPE_PREFIX:
66 out << " prefix ";
67 break;
68 case MgrCapGrantConstraint::MATCH_TYPE_REGEX:
69 out << " regex ";
70 break;
71 default:
72 break;
73 }
74 out << maybe_quote_string(c.value);
75 return out;
76 }
77
78 ostream& operator<<(ostream& out, const MgrCapGrant& m) {
79 if (!m.profile.empty()) {
80 out << "profile " << maybe_quote_string(m.profile);
81 } else {
82 out << "allow";
83 if (!m.service.empty()) {
84 out << " service " << maybe_quote_string(m.service);
85 } else if (!m.module.empty()) {
86 out << " module " << maybe_quote_string(m.module);
87 } else if (!m.command.empty()) {
88 out << " command " << maybe_quote_string(m.command);
89 }
90 }
91
92 if (!m.arguments.empty()) {
93 out << (!m.profile.empty() ? "" : " with");
94 for (auto& [key, constraint] : m.arguments) {
95 out << " " << maybe_quote_string(key) << constraint;
96 }
97 }
98
99 if (m.allow != 0) {
100 out << " " << m.allow;
101 }
102
103 if (m.network.size()) {
104 out << " network " << m.network;
105 }
106 return out;
107 }
108
109 // <magic>
110 // fusion lets us easily populate structs via the qi parser.
111
112 typedef std::map<std::string, MgrCapGrantConstraint> kvmap;
113
114 BOOST_FUSION_ADAPT_STRUCT(MgrCapGrant,
115 (std::string, service)
116 (std::string, module)
117 (std::string, profile)
118 (std::string, command)
119 (kvmap, arguments)
120 (mgr_rwxa_t, allow)
121 (std::string, network))
122
123 BOOST_FUSION_ADAPT_STRUCT(MgrCapGrantConstraint,
124 (MgrCapGrantConstraint::MatchType, match_type)
125 (std::string, value))
126
127 // </magic>
128
129 void MgrCapGrant::parse_network() {
130 network_valid = ::parse_network(network.c_str(), &network_parsed,
131 &network_prefix);
132 }
133
134 void MgrCapGrant::expand_profile(std::ostream *err) const {
135 // only generate this list once
136 if (!profile_grants.empty()) {
137 return;
138 }
139
140 if (profile == "read-only") {
141 // grants READ-ONLY caps MGR-wide
142 profile_grants.push_back({{}, {}, {}, {}, {}, mgr_rwxa_t{MGR_CAP_R}});
143 return;
144 }
145
146 if (profile == "read-write") {
147 // grants READ-WRITE caps MGR-wide
148 profile_grants.push_back({{}, {}, {}, {}, {},
149 mgr_rwxa_t{MGR_CAP_R | MGR_CAP_W}});
150 return;
151 }
152
153 if (profile == "crash") {
154 profile_grants.push_back({{}, {}, {}, "crash post", {}, {}});
155 return;
156 }
157
158 if (profile == "osd") {
159 // this is a documented profile (so we need to accept it as valid), but it
160 // currently doesn't do anything
161 return;
162 }
163
164 if (profile == "mds") {
165 // this is a documented profile (so we need to accept it as valid), but it
166 // currently doesn't do anything
167 return;
168 }
169
170 if (profile == "rbd" || profile == "rbd-read-only") {
171 Arguments filtered_arguments;
172 for (auto& [key, constraint] : arguments) {
173 if (key == "pool" || key == "namespace") {
174 filtered_arguments[key] = std::move(constraint);
175 } else {
176 if (err != nullptr) {
177 *err << "profile '" << profile << "' does not recognize key '" << key
178 << "'";
179 }
180 return;
181 }
182 }
183
184 mgr_rwxa_t perms = mgr_rwxa_t{MGR_CAP_R};
185 if (profile == "rbd") {
186 perms = mgr_rwxa_t{MGR_CAP_R | MGR_CAP_W};
187 }
188
189 // whitelist all 'rbd_support' commands (restricted by optional
190 // pool/namespace constraints)
191 profile_grants.push_back({{}, "rbd_support", {}, {},
192 std::move(filtered_arguments), perms});
193 return;
194 }
195
196 if (err != nullptr) {
197 *err << "unrecognized profile '" << profile << "'";
198 }
199 }
200
201 bool MgrCapGrant::validate_arguments(
202 const std::map<std::string, std::string>& args) const {
203 for (auto& [key, constraint] : arguments) {
204 auto q = args.find(key);
205
206 // argument must be present if a constraint exists
207 if (q == args.end()) {
208 return false;
209 }
210
211 switch (constraint.match_type) {
212 case MgrCapGrantConstraint::MATCH_TYPE_EQUAL:
213 if (constraint.value != q->second)
214 return false;
215 break;
216 case MgrCapGrantConstraint::MATCH_TYPE_PREFIX:
217 if (q->second.find(constraint.value) != 0)
218 return false;
219 break;
220 case MgrCapGrantConstraint::MATCH_TYPE_REGEX:
221 try {
222 std::regex pattern(constraint.value, std::regex::extended);
223 if (!std::regex_match(q->second, pattern)) {
224 return false;
225 }
226 } catch(const std::regex_error&) {
227 return false;
228 }
229 break;
230 default:
231 return false;
232 }
233 }
234
235 return true;
236 }
237
238 mgr_rwxa_t MgrCapGrant::get_allowed(
239 CephContext *cct, EntityName name, const std::string& s,
240 const std::string& m, const std::string& c,
241 const std::map<std::string, std::string>& args) const {
242 if (!profile.empty()) {
243 expand_profile(nullptr);
244 mgr_rwxa_t a;
245 for (auto& grant : profile_grants) {
246 a = a | grant.get_allowed(cct, name, s, m, c, args);
247 }
248 return a;
249 }
250
251 if (!service.empty()) {
252 if (service != s) {
253 return mgr_rwxa_t{};
254 }
255 return allow;
256 }
257
258 if (!module.empty()) {
259 if (module != m) {
260 return mgr_rwxa_t{};
261 }
262
263 // don't test module arguments when validating a specific command
264 if (c.empty() && !validate_arguments(args)) {
265 return mgr_rwxa_t{};
266 }
267 return allow;
268 }
269
270 if (!command.empty()) {
271 if (command != c) {
272 return mgr_rwxa_t{};
273 }
274 if (!validate_arguments(args)) {
275 return mgr_rwxa_t{};
276 }
277 return mgr_rwxa_t{MGR_CAP_ANY};
278 }
279
280 return allow;
281 }
282
283 ostream& operator<<(ostream&out, const MgrCap& m) {
284 bool first = true;
285 for (auto& grant : m.grants) {
286 if (!first) {
287 out << ", ";
288 }
289 first = false;
290
291 out << grant;
292 }
293 return out;
294 }
295
296 bool MgrCap::is_allow_all() const {
297 for (auto& grant : grants) {
298 if (grant.is_allow_all()) {
299 return true;
300 }
301 }
302 return false;
303 }
304
305 void MgrCap::set_allow_all() {
306 grants.clear();
307 grants.push_back({{}, {}, {}, {}, {}, mgr_rwxa_t{MGR_CAP_ANY}});
308 text = "allow *";
309 }
310
311 bool MgrCap::is_capable(
312 CephContext *cct,
313 EntityName name,
314 const std::string& service,
315 const std::string& module,
316 const std::string& command,
317 const std::map<std::string, std::string>& command_args,
318 bool op_may_read, bool op_may_write, bool op_may_exec,
319 const entity_addr_t& addr) const {
320 if (cct) {
321 ldout(cct, 20) << "is_capable service=" << service << " "
322 << "module=" << module << " "
323 << "command=" << command
324 << (op_may_read ? " read":"")
325 << (op_may_write ? " write":"")
326 << (op_may_exec ? " exec":"")
327 << " addr " << addr
328 << " on cap " << *this
329 << dendl;
330 }
331
332 mgr_rwxa_t allow;
333 for (auto& grant : grants) {
334 if (cct)
335 ldout(cct, 20) << " allow so far " << allow << ", doing grant " << grant
336 << dendl;
337
338 if (grant.network.size() &&
339 (!grant.network_valid ||
340 !network_contains(grant.network_parsed,
341 grant.network_prefix,
342 addr))) {
343 continue;
344 }
345
346 if (grant.is_allow_all()) {
347 if (cct) {
348 ldout(cct, 20) << " allow all" << dendl;
349 }
350 return true;
351 }
352
353 // check enumerated caps
354 allow = allow | grant.get_allowed(cct, name, service, module, command,
355 command_args);
356 if ((!op_may_read || (allow & MGR_CAP_R)) &&
357 (!op_may_write || (allow & MGR_CAP_W)) &&
358 (!op_may_exec || (allow & MGR_CAP_X))) {
359 if (cct) {
360 ldout(cct, 20) << " match" << dendl;
361 }
362 return true;
363 }
364 }
365 return false;
366 }
367
368 void MgrCap::encode(bufferlist& bl) const {
369 // remain backwards compatible w/ MgrCap
370 ENCODE_START(4, 4, bl);
371 encode(text, bl);
372 ENCODE_FINISH(bl);
373 }
374
375 void MgrCap::decode(bufferlist::const_iterator& bl) {
376 // remain backwards compatible w/ MgrCap
377 std::string s;
378 DECODE_START(4, bl);
379 decode(s, bl);
380 DECODE_FINISH(bl);
381 parse(s, NULL);
382 }
383
384 void MgrCap::dump(Formatter *f) const {
385 f->dump_string("text", text);
386 }
387
388 void MgrCap::generate_test_instances(list<MgrCap*>& ls) {
389 ls.push_back(new MgrCap);
390 ls.push_back(new MgrCap);
391 ls.back()->parse("allow *");
392 ls.push_back(new MgrCap);
393 ls.back()->parse("allow rwx");
394 ls.push_back(new MgrCap);
395 ls.back()->parse("allow service foo x");
396 ls.push_back(new MgrCap);
397 ls.back()->parse("allow command bar x");
398 ls.push_back(new MgrCap);
399 ls.back()->parse("allow service foo r, allow command bar x");
400 ls.push_back(new MgrCap);
401 ls.back()->parse("allow command bar with k1=v1 x");
402 ls.push_back(new MgrCap);
403 ls.back()->parse("allow command bar with k1=v1 k2=v2 x");
404 ls.push_back(new MgrCap);
405 ls.back()->parse("allow module bar with k1=v1 k2=v2 x");
406 ls.push_back(new MgrCap);
407 ls.back()->parse("profile rbd pool=rbd");
408 }
409
410 // grammar
411 namespace qi = boost::spirit::qi;
412 namespace ascii = boost::spirit::ascii;
413 namespace phoenix = boost::phoenix;
414
415 template <typename Iterator>
416 struct MgrCapParser : qi::grammar<Iterator, MgrCap()> {
417 MgrCapParser() : MgrCapParser::base_type(mgrcap) {
418 using qi::char_;
419 using qi::int_;
420 using qi::ulong_long;
421 using qi::lexeme;
422 using qi::alnum;
423 using qi::_val;
424 using qi::_1;
425 using qi::_2;
426 using qi::_3;
427 using qi::eps;
428 using qi::lit;
429
430 quoted_string %=
431 lexeme['"' >> +(char_ - '"') >> '"'] |
432 lexeme['\'' >> +(char_ - '\'') >> '\''];
433 unquoted_word %= +char_("a-zA-Z0-9_./-");
434 str %= quoted_string | unquoted_word;
435 network_str %= +char_("/.:a-fA-F0-9][");
436
437 spaces = +(lit(' ') | lit('\n') | lit('\t'));
438
439 // key <=|prefix|regex> value[ ...]
440 str_match = -spaces >> lit('=') >> -spaces >>
441 qi::attr(MgrCapGrantConstraint::MATCH_TYPE_EQUAL) >> str;
442 str_prefix = spaces >> lit("prefix") >> spaces >>
443 qi::attr(MgrCapGrantConstraint::MATCH_TYPE_PREFIX) >> str;
444 str_regex = spaces >> lit("regex") >> spaces >>
445 qi::attr(MgrCapGrantConstraint::MATCH_TYPE_REGEX) >> str;
446 kv_pair = str >> (str_match | str_prefix | str_regex);
447 kv_map %= kv_pair >> *(spaces >> kv_pair);
448
449 // command := command[=]cmd [k1=v1 k2=v2 ...]
450 command_match = -spaces >> lit("allow") >> spaces >> lit("command") >> (lit('=') | spaces)
451 >> qi::attr(std::string())
452 >> qi::attr(std::string())
453 >> qi::attr(std::string())
454 >> str
455 >> -(spaces >> lit("with") >> spaces >> kv_map)
456 >> qi::attr(0)
457 >> -(spaces >> lit("network") >> spaces >> network_str);
458
459 // service foo rwxa
460 service_match %= -spaces >> lit("allow") >> spaces >> lit("service") >> (lit('=') | spaces)
461 >> str
462 >> qi::attr(std::string())
463 >> qi::attr(std::string())
464 >> qi::attr(std::string())
465 >> qi::attr(map<std::string, MgrCapGrantConstraint>())
466 >> spaces >> rwxa
467 >> -(spaces >> lit("network") >> spaces >> network_str);
468
469 // module foo rwxa
470 module_match %= -spaces >> lit("allow") >> spaces >> lit("module") >> (lit('=') | spaces)
471 >> qi::attr(std::string())
472 >> str
473 >> qi::attr(std::string())
474 >> qi::attr(std::string())
475 >> -(spaces >> lit("with") >> spaces >> kv_map)
476 >> spaces >> rwxa
477 >> -(spaces >> lit("network") >> spaces >> network_str);
478
479 // profile foo
480 profile_match %= -spaces >> -(lit("allow") >> spaces)
481 >> lit("profile") >> (lit('=') | spaces)
482 >> qi::attr(std::string())
483 >> qi::attr(std::string())
484 >> str
485 >> qi::attr(std::string())
486 >> -(spaces >> kv_map)
487 >> qi::attr(0)
488 >> -(spaces >> lit("network") >> spaces >> network_str);
489
490 // rwxa
491 rwxa_match %= -spaces >> lit("allow") >> spaces
492 >> qi::attr(std::string())
493 >> qi::attr(std::string())
494 >> qi::attr(std::string())
495 >> qi::attr(std::string())
496 >> qi::attr(std::map<std::string,MgrCapGrantConstraint>())
497 >> rwxa
498 >> -(spaces >> lit("network") >> spaces >> network_str);
499
500 // rwxa := * | [r][w][x]
501 rwxa =
502 (lit("*")[_val = MGR_CAP_ANY]) |
503 (lit("all")[_val = MGR_CAP_ANY]) |
504 ( eps[_val = 0] >>
505 ( lit('r')[_val |= MGR_CAP_R] ||
506 lit('w')[_val |= MGR_CAP_W] ||
507 lit('x')[_val |= MGR_CAP_X]
508 )
509 );
510
511 // grant := allow ...
512 grant = -spaces >> (rwxa_match | profile_match | service_match |
513 module_match | command_match) >> -spaces;
514
515 // mgrcap := grant [grant ...]
516 grants %= (grant % (*lit(' ') >> (lit(';') | lit(',')) >> *lit(' ')));
517 mgrcap = grants [_val = phoenix::construct<MgrCap>(_1)];
518 }
519
520 qi::rule<Iterator> spaces;
521 qi::rule<Iterator, unsigned()> rwxa;
522 qi::rule<Iterator, std::string()> quoted_string;
523 qi::rule<Iterator, std::string()> unquoted_word;
524 qi::rule<Iterator, std::string()> str, network_str;
525
526 qi::rule<Iterator, MgrCapGrantConstraint()> str_match, str_prefix, str_regex;
527 qi::rule<Iterator, std::pair<std::string, MgrCapGrantConstraint>()> kv_pair;
528 qi::rule<Iterator, std::map<std::string, MgrCapGrantConstraint>()> kv_map;
529
530 qi::rule<Iterator, MgrCapGrant()> rwxa_match;
531 qi::rule<Iterator, MgrCapGrant()> command_match;
532 qi::rule<Iterator, MgrCapGrant()> service_match;
533 qi::rule<Iterator, MgrCapGrant()> module_match;
534 qi::rule<Iterator, MgrCapGrant()> profile_match;
535 qi::rule<Iterator, MgrCapGrant()> grant;
536 qi::rule<Iterator, std::vector<MgrCapGrant>()> grants;
537 qi::rule<Iterator, MgrCap()> mgrcap;
538 };
539
540 bool MgrCap::parse(const std::string& str, ostream *err) {
541 auto iter = str.begin();
542 auto end = str.end();
543
544 MgrCapParser<std::string::const_iterator> exp;
545 bool r = qi::parse(iter, end, exp, *this);
546 if (r && iter == end) {
547 text = str;
548
549 std::stringstream profile_err;
550 for (auto& g : grants) {
551 g.parse_network();
552
553 if (!g.profile.empty()) {
554 g.expand_profile(&profile_err);
555 }
556 }
557
558 if (!profile_err.str().empty()) {
559 if (err != nullptr) {
560 *err << "mgr capability parse failed during profile evaluation: "
561 << profile_err.str();
562 }
563 return false;
564 }
565 return true;
566 }
567
568 // Make sure no grants are kept after parsing failed!
569 grants.clear();
570
571 if (err) {
572 if (iter != end)
573 *err << "mgr capability parse failed, stopped at '"
574 << std::string(iter, end) << "' of '" << str << "'";
575 else
576 *err << "mgr capability parse failed, stopped at end of '" << str << "'";
577 }
578
579 return false;
580 }