1 // -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
2 // vim: ts=8 sw=2 smarttab
4 * Ceph - scalable distributed file system
6 * Copyright (C) 2013 Inktank
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.
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>
25 #include "include/stringify.h"
26 #include "include/ipaddr.h"
27 #include "common/debug.h"
28 #include "common/Formatter.h"
33 #include "include/ceph_assert.h"
35 static inline bool is_not_alnum_space(char c
) {
36 return !(isalpha(c
) || isdigit(c
) || (c
== '-') || (c
== '_'));
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())
42 return std::string("\"") + str
+ std::string("\"");
45 #define dout_subsys ceph_subsys_mgr
47 ostream
& operator<<(ostream
& out
, const mgr_rwxa_t
& p
) {
60 ostream
& operator<<(ostream
& out
, const MgrCapGrantConstraint
& c
) {
61 switch (c
.match_type
) {
62 case MgrCapGrantConstraint::MATCH_TYPE_EQUAL
:
65 case MgrCapGrantConstraint::MATCH_TYPE_PREFIX
:
68 case MgrCapGrantConstraint::MATCH_TYPE_REGEX
:
74 out
<< maybe_quote_string(c
.value
);
78 ostream
& operator<<(ostream
& out
, const MgrCapGrant
& m
) {
79 if (!m
.profile
.empty()) {
80 out
<< "profile " << maybe_quote_string(m
.profile
);
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
);
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
;
100 out
<< " " << m
.allow
;
103 if (m
.network
.size()) {
104 out
<< " network " << m
.network
;
110 // fusion lets us easily populate structs via the qi parser.
112 typedef std::map
<std::string
, MgrCapGrantConstraint
> kvmap
;
114 BOOST_FUSION_ADAPT_STRUCT(MgrCapGrant
,
115 (std::string
, service
)
116 (std::string
, module
)
117 (std::string
, profile
)
118 (std::string
, command
)
121 (std::string
, network
))
123 BOOST_FUSION_ADAPT_STRUCT(MgrCapGrantConstraint
,
124 (MgrCapGrantConstraint::MatchType
, match_type
)
125 (std::string
, value
))
129 void MgrCapGrant::parse_network() {
130 network_valid
= ::parse_network(network
.c_str(), &network_parsed
,
134 void MgrCapGrant::expand_profile(std::ostream
*err
) const {
135 // only generate this list once
136 if (!profile_grants
.empty()) {
140 if (profile
== "read-only") {
141 // grants READ-ONLY caps MGR-wide
142 profile_grants
.push_back({{}, {}, {}, {}, {}, mgr_rwxa_t
{MGR_CAP_R
}});
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
}});
153 if (profile
== "crash") {
154 profile_grants
.push_back({{}, {}, {}, "crash post", {}, {}});
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
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
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
);
176 if (err
!= nullptr) {
177 *err
<< "profile '" << profile
<< "' does not recognize key '" << key
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
};
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
});
196 if (err
!= nullptr) {
197 *err
<< "unrecognized profile '" << profile
<< "'";
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
);
206 // argument must be present if a constraint exists
207 if (q
== args
.end()) {
211 switch (constraint
.match_type
) {
212 case MgrCapGrantConstraint::MATCH_TYPE_EQUAL
:
213 if (constraint
.value
!= q
->second
)
216 case MgrCapGrantConstraint::MATCH_TYPE_PREFIX
:
217 if (q
->second
.find(constraint
.value
) != 0)
220 case MgrCapGrantConstraint::MATCH_TYPE_REGEX
:
222 std::regex
pattern(constraint
.value
, std::regex::extended
);
223 if (!std::regex_match(q
->second
, pattern
)) {
226 } catch(const std::regex_error
&) {
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);
245 for (auto& grant
: profile_grants
) {
246 a
= a
| grant
.get_allowed(cct
, name
, s
, m
, c
, args
);
251 if (!service
.empty()) {
258 if (!module
.empty()) {
263 // don't test module arguments when validating a specific command
264 if (c
.empty() && !validate_arguments(args
)) {
270 if (!command
.empty()) {
274 if (!validate_arguments(args
)) {
277 return mgr_rwxa_t
{MGR_CAP_ANY
};
283 ostream
& operator<<(ostream
&out
, const MgrCap
& m
) {
285 for (auto& grant
: m
.grants
) {
296 bool MgrCap::is_allow_all() const {
297 for (auto& grant
: grants
) {
298 if (grant
.is_allow_all()) {
305 void MgrCap::set_allow_all() {
307 grants
.push_back({{}, {}, {}, {}, {}, mgr_rwxa_t
{MGR_CAP_ANY
}});
311 bool MgrCap::is_capable(
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 {
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":"")
328 << " on cap " << *this
333 for (auto& grant
: grants
) {
335 ldout(cct
, 20) << " allow so far " << allow
<< ", doing grant " << grant
338 if (grant
.network
.size() &&
339 (!grant
.network_valid
||
340 !network_contains(grant
.network_parsed
,
341 grant
.network_prefix
,
346 if (grant
.is_allow_all()) {
348 ldout(cct
, 20) << " allow all" << dendl
;
353 // check enumerated caps
354 allow
= allow
| grant
.get_allowed(cct
, name
, service
, module
, command
,
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
))) {
360 ldout(cct
, 20) << " match" << dendl
;
368 void MgrCap::encode(bufferlist
& bl
) const {
369 // remain backwards compatible w/ MgrCap
370 ENCODE_START(4, 4, bl
);
375 void MgrCap::decode(bufferlist::const_iterator
& bl
) {
376 // remain backwards compatible w/ MgrCap
384 void MgrCap::dump(Formatter
*f
) const {
385 f
->dump_string("text", text
);
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");
411 namespace qi
= boost::spirit::qi
;
412 namespace ascii
= boost::spirit::ascii
;
413 namespace phoenix
= boost::phoenix
;
415 template <typename Iterator
>
416 struct MgrCapParser
: qi::grammar
<Iterator
, MgrCap()> {
417 MgrCapParser() : MgrCapParser::base_type(mgrcap
) {
420 using qi::ulong_long
;
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][");
437 spaces
= +(lit(' ') | lit('\n') | lit('\t'));
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
);
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())
455 >> -(spaces
>> lit("with") >> spaces
>> kv_map
)
457 >> -(spaces
>> lit("network") >> spaces
>> network_str
);
460 service_match
%= -spaces
>> lit("allow") >> spaces
>> lit("service") >> (lit('=') | spaces
)
462 >> qi::attr(std::string())
463 >> qi::attr(std::string())
464 >> qi::attr(std::string())
465 >> qi::attr(map
<std::string
, MgrCapGrantConstraint
>())
467 >> -(spaces
>> lit("network") >> spaces
>> network_str
);
470 module_match
%= -spaces
>> lit("allow") >> spaces
>> lit("module") >> (lit('=') | spaces
)
471 >> qi::attr(std::string())
473 >> qi::attr(std::string())
474 >> qi::attr(std::string())
475 >> -(spaces
>> lit("with") >> spaces
>> kv_map
)
477 >> -(spaces
>> lit("network") >> spaces
>> network_str
);
480 profile_match
%= -spaces
>> -(lit("allow") >> spaces
)
481 >> lit("profile") >> (lit('=') | spaces
)
482 >> qi::attr(std::string())
483 >> qi::attr(std::string())
485 >> qi::attr(std::string())
486 >> -(spaces
>> kv_map
)
488 >> -(spaces
>> lit("network") >> spaces
>> network_str
);
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
>())
498 >> -(spaces
>> lit("network") >> spaces
>> network_str
);
500 // rwxa := * | [r][w][x]
502 (lit("*")[_val
= MGR_CAP_ANY
]) |
503 (lit("all")[_val
= MGR_CAP_ANY
]) |
505 ( lit('r')[_val
|= MGR_CAP_R
] ||
506 lit('w')[_val
|= MGR_CAP_W
] ||
507 lit('x')[_val
|= MGR_CAP_X
]
511 // grant := allow ...
512 grant
= -spaces
>> (rwxa_match
| profile_match
| service_match
|
513 module_match
| command_match
) >> -spaces
;
515 // mgrcap := grant [grant ...]
516 grants
%= (grant
% (*lit(' ') >> (lit(';') | lit(',')) >> *lit(' ')));
517 mgrcap
= grants
[_val
= phoenix::construct
<MgrCap
>(_1
)];
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
;
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
;
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
;
540 bool MgrCap::parse(const std::string
& str
, ostream
*err
) {
541 auto iter
= str
.begin();
542 auto end
= str
.end();
544 MgrCapParser
<std::string::const_iterator
> exp
;
545 bool r
= qi::parse(iter
, end
, exp
, *this);
546 if (r
&& iter
== end
) {
549 std::stringstream profile_err
;
550 for (auto& g
: grants
) {
553 if (!g
.profile
.empty()) {
554 g
.expand_profile(&profile_err
);
558 if (!profile_err
.str().empty()) {
559 if (err
!= nullptr) {
560 *err
<< "mgr capability parse failed during profile evaluation: "
561 << profile_err
.str();
568 // Make sure no grants are kept after parsing failed!
573 *err
<< "mgr capability parse failed, stopped at '"
574 << std::string(iter
, end
) << "' of '" << str
<< "'";
576 *err
<< "mgr capability parse failed, stopped at end of '" << str
<< "'";