]>
git.proxmox.com Git - ceph.git/blob - ceph/src/mon/MonCap.cc
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>
24 #include "include/stringify.h"
25 #include "common/debug.h"
26 #include "common/Formatter.h"
30 #include <boost/regex.hpp>
31 #include "include/assert.h"
33 static inline bool is_not_alnum_space(char c
)
35 return !(isalpha(c
) || isdigit(c
) || (c
== '-') || (c
== '_'));
38 static string
maybe_quote_string(const std::string
& str
)
40 if (find_if(str
.begin(), str
.end(), is_not_alnum_space
) == str
.end())
42 return string("\"") + str
+ string("\"");
48 #define dout_subsys ceph_subsys_mon
50 ostream
& operator<<(ostream
& out
, const mon_rwxa_t
& p
)
64 ostream
& operator<<(ostream
& out
, const StringConstraint
& c
)
66 switch (c
.match_type
) {
67 case StringConstraint::MATCH_TYPE_EQUAL
:
68 return out
<< "value " << c
.value
;
69 case StringConstraint::MATCH_TYPE_PREFIX
:
70 return out
<< "prefix " << c
.value
;
71 case StringConstraint::MATCH_TYPE_REGEX
:
72 return out
<< "regex " << c
.value
;
79 ostream
& operator<<(ostream
& out
, const MonCapGrant
& m
)
82 if (m
.service
.length()) {
83 out
<< " service " << maybe_quote_string(m
.service
);
85 if (m
.command
.length()) {
86 out
<< " command " << maybe_quote_string(m
.command
);
87 if (!m
.command_args
.empty()) {
89 for (map
<string
,StringConstraint
>::const_iterator p
= m
.command_args
.begin();
90 p
!= m
.command_args
.end();
92 switch (p
->second
.match_type
) {
93 case StringConstraint::MATCH_TYPE_EQUAL
:
94 out
<< " " << maybe_quote_string(p
->first
) << "="
95 << maybe_quote_string(p
->second
.value
);
97 case StringConstraint::MATCH_TYPE_PREFIX
:
98 out
<< " " << maybe_quote_string(p
->first
) << " prefix "
99 << maybe_quote_string(p
->second
.value
);
101 case StringConstraint::MATCH_TYPE_REGEX
:
102 out
<< " " << maybe_quote_string(p
->first
) << " regex "
103 << maybe_quote_string(p
->second
.value
);
111 if (m
.profile
.length()) {
112 out
<< " profile " << maybe_quote_string(m
.profile
);
115 out
<< " " << m
.allow
;
121 // fusion lets us easily populate structs via the qi parser.
123 typedef map
<string
,StringConstraint
> kvmap
;
125 BOOST_FUSION_ADAPT_STRUCT(MonCapGrant
,
126 (std::string
, service
)
127 (std::string
, profile
)
128 (std::string
, command
)
129 (kvmap
, command_args
)
132 BOOST_FUSION_ADAPT_STRUCT(StringConstraint
,
133 (StringConstraint::MatchType
, match_type
)
134 (std::string
, value
))
138 void MonCapGrant::expand_profile(int daemon_type
, const EntityName
& name
) const
140 // only generate this list once
141 if (!profile_grants
.empty())
144 if (profile
== "read-only") {
145 // grants READ-ONLY caps monitor-wide
146 // 'auth' requires MON_CAP_X even for RO, which we do not grant here.
147 profile_grants
.push_back(mon_rwxa_t(MON_CAP_R
));
151 if (profile
== "read-write") {
152 // grants READ-WRITE caps monitor-wide
153 // 'auth' requires MON_CAP_X for all operations, which we do not grant.
154 profile_grants
.push_back(mon_rwxa_t(MON_CAP_R
| MON_CAP_W
));
158 switch (daemon_type
) {
159 case CEPH_ENTITY_TYPE_MON
:
160 expand_profile_mon(name
);
162 case CEPH_ENTITY_TYPE_MGR
:
163 expand_profile_mgr(name
);
168 void MonCapGrant::expand_profile_mgr(const EntityName
& name
) const
172 void MonCapGrant::expand_profile_mon(const EntityName
& name
) const
174 if (profile
== "mon") {
175 profile_grants
.push_back(MonCapGrant("mon", MON_CAP_ALL
));
176 profile_grants
.push_back(MonCapGrant("log", MON_CAP_ALL
));
178 if (profile
== "osd") {
179 profile_grants
.push_back(MonCapGrant("osd", MON_CAP_ALL
));
180 profile_grants
.push_back(MonCapGrant("mon", MON_CAP_R
));
181 profile_grants
.push_back(MonCapGrant("pg", MON_CAP_R
| MON_CAP_W
));
182 profile_grants
.push_back(MonCapGrant("log", MON_CAP_W
));
184 if (profile
== "mds") {
185 profile_grants
.push_back(MonCapGrant("mds", MON_CAP_ALL
));
186 profile_grants
.push_back(MonCapGrant("mon", MON_CAP_R
));
187 profile_grants
.push_back(MonCapGrant("osd", MON_CAP_R
));
188 // This command grant is checked explicitly in MRemoveSnaps handling
189 profile_grants
.push_back(MonCapGrant("osd pool rmsnap"));
190 profile_grants
.push_back(MonCapGrant("osd blacklist"));
191 profile_grants
.push_back(MonCapGrant("log", MON_CAP_W
));
193 if (profile
== "mgr") {
194 profile_grants
.push_back(MonCapGrant("mgr", MON_CAP_ALL
));
195 profile_grants
.push_back(MonCapGrant("log", MON_CAP_R
| MON_CAP_W
));
196 profile_grants
.push_back(MonCapGrant("mon", MON_CAP_R
| MON_CAP_W
));
197 profile_grants
.push_back(MonCapGrant("mds", MON_CAP_R
| MON_CAP_W
));
198 profile_grants
.push_back(MonCapGrant("osd", MON_CAP_R
| MON_CAP_W
));
199 profile_grants
.push_back(MonCapGrant("auth", MON_CAP_R
| MON_CAP_X
));
200 profile_grants
.push_back(MonCapGrant("config-key", MON_CAP_R
| MON_CAP_W
));
201 StringConstraint
constraint(StringConstraint::MATCH_TYPE_PREFIX
,
202 "daemon-private/mgr/");
203 profile_grants
.push_back(MonCapGrant("config-key get", "key", constraint
));
204 profile_grants
.push_back(MonCapGrant("config-key set", "key", constraint
));
205 profile_grants
.push_back(MonCapGrant("config-key put", "key", constraint
));
206 profile_grants
.push_back(MonCapGrant("config-key exists", "key", constraint
));
207 profile_grants
.push_back(MonCapGrant("config-key delete", "key", constraint
));
209 if (profile
== "osd" || profile
== "mds" || profile
== "mon" ||
211 StringConstraint
constraint(StringConstraint::MATCH_TYPE_PREFIX
,
212 string("daemon-private/") + stringify(name
) +
214 string prefix
= string("daemon-private/") + stringify(name
) + string("/");
215 profile_grants
.push_back(MonCapGrant("config-key get", "key", constraint
));
216 profile_grants
.push_back(MonCapGrant("config-key put", "key", constraint
));
217 profile_grants
.push_back(MonCapGrant("config-key set", "key", constraint
));
218 profile_grants
.push_back(MonCapGrant("config-key exists", "key", constraint
));
219 profile_grants
.push_back(MonCapGrant("config-key delete", "key", constraint
));
221 if (profile
== "bootstrap-osd") {
222 profile_grants
.push_back(MonCapGrant("mon", MON_CAP_R
)); // read monmap
223 profile_grants
.push_back(MonCapGrant("osd", MON_CAP_R
)); // read osdmap
224 profile_grants
.push_back(MonCapGrant("mon getmap"));
225 profile_grants
.push_back(MonCapGrant("osd new"));
227 if (profile
== "bootstrap-mds") {
228 profile_grants
.push_back(MonCapGrant("mon", MON_CAP_R
)); // read monmap
229 profile_grants
.push_back(MonCapGrant("osd", MON_CAP_R
)); // read osdmap
230 profile_grants
.push_back(MonCapGrant("mon getmap"));
231 profile_grants
.push_back(MonCapGrant("auth get-or-create")); // FIXME: this can expose other mds keys
232 profile_grants
.back().command_args
["entity"] = StringConstraint(
233 StringConstraint::MATCH_TYPE_PREFIX
, "mds.");
234 profile_grants
.back().command_args
["caps_mon"] = StringConstraint(
235 StringConstraint::MATCH_TYPE_EQUAL
, "allow profile mds");
236 profile_grants
.back().command_args
["caps_osd"] = StringConstraint(
237 StringConstraint::MATCH_TYPE_EQUAL
, "allow rwx");
238 profile_grants
.back().command_args
["caps_mds"] = StringConstraint(
239 StringConstraint::MATCH_TYPE_EQUAL
, "allow");
241 if (profile
== "bootstrap-mgr") {
242 profile_grants
.push_back(MonCapGrant("mon", MON_CAP_R
)); // read monmap
243 profile_grants
.push_back(MonCapGrant("osd", MON_CAP_R
)); // read osdmap
244 profile_grants
.push_back(MonCapGrant("mon getmap"));
245 profile_grants
.push_back(MonCapGrant("auth get-or-create")); // FIXME: this can expose other mgr keys
246 profile_grants
.back().command_args
["entity"] = StringConstraint(
247 StringConstraint::MATCH_TYPE_PREFIX
, "mgr.");
248 profile_grants
.back().command_args
["caps_mon"] = StringConstraint(
249 StringConstraint::MATCH_TYPE_EQUAL
, "allow profile mgr");
251 if (profile
== "bootstrap-rgw") {
252 profile_grants
.push_back(MonCapGrant("mon", MON_CAP_R
)); // read monmap
253 profile_grants
.push_back(MonCapGrant("osd", MON_CAP_R
)); // read osdmap
254 profile_grants
.push_back(MonCapGrant("mon getmap"));
255 profile_grants
.push_back(MonCapGrant("auth get-or-create")); // FIXME: this can expose other mds keys
256 profile_grants
.back().command_args
["entity"] = StringConstraint(
257 StringConstraint::MATCH_TYPE_PREFIX
, "client.rgw.");
258 profile_grants
.back().command_args
["caps_mon"] = StringConstraint(
259 StringConstraint::MATCH_TYPE_EQUAL
, "allow rw");
260 profile_grants
.back().command_args
["caps_osd"] = StringConstraint(
261 StringConstraint::MATCH_TYPE_EQUAL
, "allow rwx");
263 if (profile
== "fs-client") {
264 profile_grants
.push_back(MonCapGrant("mon", MON_CAP_R
));
265 profile_grants
.push_back(MonCapGrant("mds", MON_CAP_R
));
266 profile_grants
.push_back(MonCapGrant("osd", MON_CAP_R
));
267 profile_grants
.push_back(MonCapGrant("pg", MON_CAP_R
));
269 if (profile
== "simple-rados-client") {
270 profile_grants
.push_back(MonCapGrant("mon", MON_CAP_R
));
271 profile_grants
.push_back(MonCapGrant("osd", MON_CAP_R
));
272 profile_grants
.push_back(MonCapGrant("pg", MON_CAP_R
));
274 if (profile
== "rbd") {
275 profile_grants
.push_back(MonCapGrant("mon", MON_CAP_R
));
276 profile_grants
.push_back(MonCapGrant("osd", MON_CAP_R
));
277 profile_grants
.push_back(MonCapGrant("pg", MON_CAP_R
));
279 // exclusive lock dead-client blacklisting (IP+nonce required)
280 profile_grants
.push_back(MonCapGrant("osd blacklist"));
281 profile_grants
.back().command_args
["blacklistop"] = StringConstraint(
282 StringConstraint::MATCH_TYPE_EQUAL
, "add");
283 profile_grants
.back().command_args
["addr"] = StringConstraint(
284 StringConstraint::MATCH_TYPE_REGEX
, "^[^/]/[0-9]*$");
287 if (profile
== "role-definer") {
288 // grants ALL caps to the auth subsystem, read-only on the
289 // monitor subsystem and nothing else.
290 profile_grants
.push_back(MonCapGrant("mon", MON_CAP_R
));
291 profile_grants
.push_back(MonCapGrant("auth", MON_CAP_ALL
));
295 mon_rwxa_t
MonCapGrant::get_allowed(CephContext
*cct
,
298 const std::string
& s
, const std::string
& c
,
299 const map
<string
,string
>& c_args
) const
301 if (profile
.length()) {
302 expand_profile(daemon_type
, name
);
304 for (list
<MonCapGrant
>::const_iterator p
= profile_grants
.begin();
305 p
!= profile_grants
.end(); ++p
)
306 a
= a
| p
->get_allowed(cct
, daemon_type
, name
, s
, c
, c_args
);
309 if (service
.length()) {
314 if (command
.length()) {
317 for (map
<string
,StringConstraint
>::const_iterator p
= command_args
.begin(); p
!= command_args
.end(); ++p
) {
318 map
<string
,string
>::const_iterator q
= c_args
.find(p
->first
);
319 // argument must be present if a constraint exists
320 if (q
== c_args
.end())
322 switch (p
->second
.match_type
) {
323 case StringConstraint::MATCH_TYPE_EQUAL
:
324 if (p
->second
.value
!= q
->second
)
327 case StringConstraint::MATCH_TYPE_PREFIX
:
328 if (q
->second
.find(p
->second
.value
) != 0)
331 case StringConstraint::MATCH_TYPE_REGEX
:
333 boost::regex
pattern(p
->second
.value
,
334 boost::regex::basic
| boost::regex::no_except
);
335 if (pattern
.empty() || !boost::regex_match(q
->second
, pattern
))
348 ostream
& operator<<(ostream
&out
, const MonCap
& m
)
350 for (vector
<MonCapGrant
>::const_iterator p
= m
.grants
.begin(); p
!= m
.grants
.end(); ++p
) {
351 if (p
!= m
.grants
.begin())
358 bool MonCap::is_allow_all() const
360 for (vector
<MonCapGrant
>::const_iterator p
= grants
.begin(); p
!= grants
.end(); ++p
)
361 if (p
->is_allow_all())
366 void MonCap::set_allow_all()
369 grants
.push_back(MonCapGrant(MON_CAP_ANY
));
373 bool MonCap::is_capable(CephContext
*cct
,
376 const string
& service
,
377 const string
& command
, const map
<string
,string
>& command_args
,
378 bool op_may_read
, bool op_may_write
, bool op_may_exec
) const
381 ldout(cct
, 20) << "is_capable service=" << service
<< " command=" << command
382 << (op_may_read
? " read":"")
383 << (op_may_write
? " write":"")
384 << (op_may_exec
? " exec":"")
385 << " on cap " << *this
387 mon_rwxa_t allow
= 0;
388 for (vector
<MonCapGrant
>::const_iterator p
= grants
.begin();
389 p
!= grants
.end(); ++p
) {
391 ldout(cct
, 20) << " allow so far " << allow
<< ", doing grant " << *p
<< dendl
;
393 if (p
->is_allow_all()) {
395 ldout(cct
, 20) << " allow all" << dendl
;
399 // check enumerated caps
400 allow
= allow
| p
->get_allowed(cct
, daemon_type
, name
, service
, command
,
402 if ((!op_may_read
|| (allow
& MON_CAP_R
)) &&
403 (!op_may_write
|| (allow
& MON_CAP_W
)) &&
404 (!op_may_exec
|| (allow
& MON_CAP_X
))) {
406 ldout(cct
, 20) << " match" << dendl
;
413 void MonCap::encode(bufferlist
& bl
) const
415 ENCODE_START(4, 4, bl
); // legacy MonCaps was 3, 3
420 void MonCap::decode(bufferlist::iterator
& bl
)
429 void MonCap::dump(Formatter
*f
) const
431 f
->dump_string("text", text
);
434 void MonCap::generate_test_instances(list
<MonCap
*>& ls
)
436 ls
.push_back(new MonCap
);
437 ls
.push_back(new MonCap
);
438 ls
.back()->parse("allow *");
439 ls
.push_back(new MonCap
);
440 ls
.back()->parse("allow rwx");
441 ls
.push_back(new MonCap
);
442 ls
.back()->parse("allow service foo x");
443 ls
.push_back(new MonCap
);
444 ls
.back()->parse("allow command bar x");
445 ls
.push_back(new MonCap
);
446 ls
.back()->parse("allow service foo r, allow command bar x");
447 ls
.push_back(new MonCap
);
448 ls
.back()->parse("allow command bar with k1=v1 x");
449 ls
.push_back(new MonCap
);
450 ls
.back()->parse("allow command bar with k1=v1 k2=v2 x");
454 namespace qi
= boost::spirit::qi
;
455 namespace ascii
= boost::spirit::ascii
;
456 namespace phoenix
= boost::phoenix
;
459 template <typename Iterator
>
460 struct MonCapParser
: qi::grammar
<Iterator
, MonCap()>
462 MonCapParser() : MonCapParser::base_type(moncap
)
466 using qi::ulong_long
;
477 lexeme
['"' >> +(char_
- '"') >> '"'] |
478 lexeme
['\'' >> +(char_
- '\'') >> '\''];
479 unquoted_word
%= +char_("a-zA-Z0-9_.-");
480 str
%= quoted_string
| unquoted_word
;
482 spaces
= +(lit(' ') | lit('\n') | lit('\t'));
484 // command := command[=]cmd [k1=v1 k2=v2 ...]
485 str_match
= '=' >> qi::attr(StringConstraint::MATCH_TYPE_EQUAL
) >> str
;
486 str_prefix
= spaces
>> lit("prefix") >> spaces
>>
487 qi::attr(StringConstraint::MATCH_TYPE_PREFIX
) >> str
;
488 str_regex
= spaces
>> lit("regex") >> spaces
>>
489 qi::attr(StringConstraint::MATCH_TYPE_REGEX
) >> str
;
490 kv_pair
= str
>> (str_match
| str_prefix
| str_regex
);
491 kv_map
%= kv_pair
>> *(spaces
>> kv_pair
);
492 command_match
= -spaces
>> lit("allow") >> spaces
>> lit("command") >> (lit('=') | spaces
)
493 >> qi::attr(string()) >> qi::attr(string())
495 >> -(spaces
>> lit("with") >> spaces
>> kv_map
)
499 service_match
%= -spaces
>> lit("allow") >> spaces
>> lit("service") >> (lit('=') | spaces
)
500 >> str
>> qi::attr(string()) >> qi::attr(string())
501 >> qi::attr(map
<string
,StringConstraint
>())
505 profile_match
%= -spaces
>> -(lit("allow") >> spaces
)
506 >> lit("profile") >> (lit('=') | spaces
)
507 >> qi::attr(string())
509 >> qi::attr(string())
510 >> qi::attr(map
<string
,StringConstraint
>())
514 rwxa_match
%= -spaces
>> lit("allow") >> spaces
515 >> qi::attr(string()) >> qi::attr(string()) >> qi::attr(string())
516 >> qi::attr(map
<string
,StringConstraint
>())
519 // rwxa := * | [r][w][x]
521 (lit("*")[_val
= MON_CAP_ANY
]) |
523 ( lit('r')[_val
|= MON_CAP_R
] ||
524 lit('w')[_val
|= MON_CAP_W
] ||
525 lit('x')[_val
|= MON_CAP_X
]
529 // grant := allow ...
530 grant
= -spaces
>> (rwxa_match
| profile_match
| service_match
| command_match
) >> -spaces
;
532 // moncap := grant [grant ...]
533 grants
%= (grant
% (*lit(' ') >> (lit(';') | lit(',')) >> *lit(' ')));
534 moncap
= grants
[_val
= phoenix::construct
<MonCap
>(_1
)];
537 qi::rule
<Iterator
> spaces
;
538 qi::rule
<Iterator
, unsigned()> rwxa
;
539 qi::rule
<Iterator
, string()> quoted_string
;
540 qi::rule
<Iterator
, string()> unquoted_word
;
541 qi::rule
<Iterator
, string()> str
;
543 qi::rule
<Iterator
, StringConstraint()> str_match
, str_prefix
, str_regex
;
544 qi::rule
<Iterator
, pair
<string
, StringConstraint
>()> kv_pair
;
545 qi::rule
<Iterator
, map
<string
, StringConstraint
>()> kv_map
;
547 qi::rule
<Iterator
, MonCapGrant()> rwxa_match
;
548 qi::rule
<Iterator
, MonCapGrant()> command_match
;
549 qi::rule
<Iterator
, MonCapGrant()> service_match
;
550 qi::rule
<Iterator
, MonCapGrant()> profile_match
;
551 qi::rule
<Iterator
, MonCapGrant()> grant
;
552 qi::rule
<Iterator
, std::vector
<MonCapGrant
>()> grants
;
553 qi::rule
<Iterator
, MonCap()> moncap
;
556 bool MonCap::parse(const string
& str
, ostream
*err
)
559 string::iterator iter
= s
.begin();
560 string::iterator end
= s
.end();
562 MonCapParser
<string::iterator
> g
;
563 bool r
= qi::parse(iter
, end
, g
, *this);
565 //bool r = qi::phrase_parse(iter, end, g, ascii::space, foo);
566 if (r
&& iter
== end
) {
571 // Make sure no grants are kept after parsing failed!
576 *err
<< "moncap parse failed, stopped at '" << std::string(iter
, end
)
577 << "' of '" << str
<< "'\n";
579 *err
<< "moncap parse failed, stopped at end of '" << str
<< "'\n";