]>
Commit | Line | Data |
---|---|---|
7c673cae FG |
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) 2014 Red Hat | |
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 | ||
11fdf7f2 | 15 | #include <string_view> |
7c673cae FG |
16 | |
17 | #include <errno.h> | |
7c673cae FG |
18 | |
19 | #include <boost/spirit/include/qi.hpp> | |
20 | #include <boost/spirit/include/phoenix_operator.hpp> | |
21 | #include <boost/spirit/include/phoenix.hpp> | |
22 | ||
23 | #include "common/debug.h" | |
24 | #include "MDSAuthCaps.h" | |
f67539c2 | 25 | #include "mdstypes.h" |
11fdf7f2 | 26 | #include "include/ipaddr.h" |
7c673cae FG |
27 | |
28 | #define dout_subsys ceph_subsys_mds | |
29 | ||
30 | #undef dout_prefix | |
31 | #define dout_prefix *_dout << "MDSAuthCap " | |
32 | ||
33 | using std::ostream; | |
34 | using std::string; | |
f67539c2 | 35 | using std::vector; |
7c673cae FG |
36 | namespace qi = boost::spirit::qi; |
37 | namespace ascii = boost::spirit::ascii; | |
38 | namespace phoenix = boost::phoenix; | |
39 | ||
40 | template <typename Iterator> | |
41 | struct MDSCapParser : qi::grammar<Iterator, MDSAuthCaps()> | |
42 | { | |
43 | MDSCapParser() : MDSCapParser::base_type(mdscaps) | |
44 | { | |
f67539c2 TL |
45 | using qi::attr; |
46 | using qi::bool_; | |
7c673cae FG |
47 | using qi::char_; |
48 | using qi::int_; | |
49 | using qi::uint_; | |
50 | using qi::lexeme; | |
51 | using qi::alnum; | |
52 | using qi::_val; | |
53 | using qi::_1; | |
54 | using qi::_2; | |
55 | using qi::_3; | |
56 | using qi::eps; | |
57 | using qi::lit; | |
58 | ||
59 | spaces = +(lit(' ') | lit('\n') | lit('\t')); | |
60 | ||
61 | quoted_path %= | |
62 | lexeme[lit("\"") >> *(char_ - '"') >> '"'] | | |
63 | lexeme[lit("'") >> *(char_ - '\'') >> '\'']; | |
64 | unquoted_path %= +char_("a-zA-Z0-9_./-"); | |
11fdf7f2 | 65 | network_str %= +char_("/.:a-fA-F0-9]["); |
f67539c2 | 66 | fs_name_str %= +char_("a-zA-Z0-9_.-"); |
7c673cae FG |
67 | |
68 | // match := [path=<path>] [uid=<uid> [gids=<gid>[,<gid>...]] | |
f67539c2 | 69 | // TODO: allow fsname, and root_squash to be specified with uid, and gidlist |
7c673cae FG |
70 | path %= (spaces >> lit("path") >> lit('=') >> (quoted_path | unquoted_path)); |
71 | uid %= (spaces >> lit("uid") >> lit('=') >> uint_); | |
72 | uintlist %= (uint_ % lit(',')); | |
73 | gidlist %= -(spaces >> lit("gids") >> lit('=') >> uintlist); | |
f67539c2 TL |
74 | fs_name %= -(spaces >> lit("fsname") >> lit('=') >> fs_name_str); |
75 | root_squash %= (spaces >> lit("root_squash") >> attr(true)); | |
7c673cae | 76 | match = -( |
f67539c2 | 77 | (fs_name >> path >> root_squash)[_val = phoenix::construct<MDSCapMatch>(_2, _1, _3)] | |
7c673cae FG |
78 | (uid >> gidlist)[_val = phoenix::construct<MDSCapMatch>(_1, _2)] | |
79 | (path >> uid >> gidlist)[_val = phoenix::construct<MDSCapMatch>(_1, _2, _3)] | | |
f67539c2 TL |
80 | (fs_name >> path)[_val = phoenix::construct<MDSCapMatch>(_2, _1)] | |
81 | (fs_name >> root_squash)[_val = phoenix::construct<MDSCapMatch>(std::string(), _1, _2)] | | |
82 | (path >> root_squash)[_val = phoenix::construct<MDSCapMatch>(_1, std::string(), _2)] | | |
83 | (path)[_val = phoenix::construct<MDSCapMatch>(_1)] | | |
84 | (root_squash)[_val = phoenix::construct<MDSCapMatch>(std::string(), std::string(), _1)] | | |
85 | (fs_name)[_val = phoenix::construct<MDSCapMatch>(std::string(), | |
86 | _1)]); | |
7c673cae | 87 | |
b3b6e05e | 88 | // capspec = * | r[w][f][p][s] |
7c673cae | 89 | capspec = spaces >> ( |
11fdf7f2 | 90 | lit("*")[_val = MDSCapSpec(MDSCapSpec::ALL)] |
7c673cae | 91 | | |
11fdf7f2 | 92 | lit("all")[_val = MDSCapSpec(MDSCapSpec::ALL)] |
7c673cae | 93 | | |
b3b6e05e TL |
94 | (lit("rwfps"))[_val = MDSCapSpec(MDSCapSpec::RWFPS)] |
95 | | | |
11fdf7f2 | 96 | (lit("rwps"))[_val = MDSCapSpec(MDSCapSpec::RWPS)] |
7c673cae | 97 | | |
b3b6e05e TL |
98 | (lit("rwfp"))[_val = MDSCapSpec(MDSCapSpec::RWFP)] |
99 | | | |
100 | (lit("rwfs"))[_val = MDSCapSpec(MDSCapSpec::RWFS)] | |
101 | | | |
11fdf7f2 TL |
102 | (lit("rwp"))[_val = MDSCapSpec(MDSCapSpec::RWP)] |
103 | | | |
104 | (lit("rws"))[_val = MDSCapSpec(MDSCapSpec::RWS)] | |
105 | | | |
b3b6e05e TL |
106 | (lit("rwf"))[_val = MDSCapSpec(MDSCapSpec::RWF)] |
107 | | | |
11fdf7f2 TL |
108 | (lit("rw"))[_val = MDSCapSpec(MDSCapSpec::RW)] |
109 | | | |
110 | (lit("r"))[_val = MDSCapSpec(MDSCapSpec::READ)] | |
7c673cae FG |
111 | ); |
112 | ||
11fdf7f2 TL |
113 | grant = lit("allow") >> (capspec >> match >> |
114 | -(spaces >> lit("network") >> spaces >> network_str)) | |
115 | [_val = phoenix::construct<MDSCapGrant>(_1, _2, _3)]; | |
7c673cae FG |
116 | grants %= (grant % (*lit(' ') >> (lit(';') | lit(',')) >> *lit(' '))); |
117 | mdscaps = grants [_val = phoenix::construct<MDSAuthCaps>(_1)]; | |
118 | } | |
119 | qi::rule<Iterator> spaces; | |
11fdf7f2 | 120 | qi::rule<Iterator, string()> quoted_path, unquoted_path, network_str; |
f67539c2 TL |
121 | qi::rule<Iterator, string()> fs_name_str, fs_name, path; |
122 | qi::rule<Iterator, bool()> root_squash; | |
7c673cae | 123 | qi::rule<Iterator, MDSCapSpec()> capspec; |
7c673cae FG |
124 | qi::rule<Iterator, uint32_t()> uid; |
125 | qi::rule<Iterator, std::vector<uint32_t>() > uintlist; | |
126 | qi::rule<Iterator, std::vector<uint32_t>() > gidlist; | |
127 | qi::rule<Iterator, MDSCapMatch()> match; | |
128 | qi::rule<Iterator, MDSCapGrant()> grant; | |
129 | qi::rule<Iterator, std::vector<MDSCapGrant>()> grants; | |
130 | qi::rule<Iterator, MDSAuthCaps()> mdscaps; | |
131 | }; | |
132 | ||
133 | void MDSCapMatch::normalize_path() | |
134 | { | |
135 | // drop any leading / | |
136 | while (path.length() && path[0] == '/') { | |
137 | path = path.substr(1); | |
138 | } | |
139 | ||
140 | // drop dup // | |
141 | // drop . | |
142 | // drop .. | |
143 | } | |
144 | ||
11fdf7f2 | 145 | bool MDSCapMatch::match(std::string_view target_path, |
7c673cae FG |
146 | const int caller_uid, |
147 | const int caller_gid, | |
148 | const vector<uint64_t> *caller_gid_list) const | |
149 | { | |
150 | if (uid != MDS_AUTH_UID_ANY) { | |
151 | if (uid != caller_uid) | |
152 | return false; | |
b32b8144 FG |
153 | if (!gids.empty()) { |
154 | bool gid_matched = false; | |
155 | if (std::find(gids.begin(), gids.end(), caller_gid) != gids.end()) | |
156 | gid_matched = true; | |
157 | if (caller_gid_list) { | |
158 | for (auto i = caller_gid_list->begin(); i != caller_gid_list->end(); ++i) { | |
159 | if (std::find(gids.begin(), gids.end(), *i) != gids.end()) { | |
160 | gid_matched = true; | |
161 | break; | |
162 | } | |
7c673cae FG |
163 | } |
164 | } | |
b32b8144 FG |
165 | if (!gid_matched) |
166 | return false; | |
7c673cae | 167 | } |
7c673cae FG |
168 | } |
169 | ||
170 | if (!match_path(target_path)) { | |
171 | return false; | |
172 | } | |
173 | ||
174 | return true; | |
175 | } | |
176 | ||
11fdf7f2 | 177 | bool MDSCapMatch::match_path(std::string_view target_path) const |
7c673cae FG |
178 | { |
179 | if (path.length()) { | |
180 | if (target_path.find(path) != 0) | |
181 | return false; | |
182 | // if path doesn't already have a trailing /, make sure the target | |
183 | // does so that path=/foo doesn't match target_path=/food | |
184 | if (target_path.length() > path.length() && | |
185 | path[path.length()-1] != '/' && | |
186 | target_path[path.length()] != '/') | |
187 | return false; | |
188 | } | |
189 | ||
190 | return true; | |
191 | } | |
192 | ||
11fdf7f2 TL |
193 | void MDSCapGrant::parse_network() |
194 | { | |
195 | network_valid = ::parse_network(network.c_str(), &network_parsed, | |
196 | &network_prefix); | |
197 | } | |
198 | ||
7c673cae FG |
199 | /** |
200 | * Is the client *potentially* able to access this path? Actual | |
201 | * permission will depend on uids/modes in the full is_capable. | |
202 | */ | |
11fdf7f2 | 203 | bool MDSAuthCaps::path_capable(std::string_view inode_path) const |
7c673cae FG |
204 | { |
205 | for (const auto &i : grants) { | |
206 | if (i.match.match_path(inode_path)) { | |
207 | return true; | |
208 | } | |
209 | } | |
210 | ||
211 | return false; | |
212 | } | |
213 | ||
214 | /** | |
215 | * For a given filesystem path, query whether this capability carries` | |
216 | * authorization to read or write. | |
217 | * | |
218 | * This is true if any of the 'grant' clauses in the capability match the | |
219 | * requested path + op. | |
220 | */ | |
11fdf7f2 | 221 | bool MDSAuthCaps::is_capable(std::string_view inode_path, |
7c673cae FG |
222 | uid_t inode_uid, gid_t inode_gid, |
223 | unsigned inode_mode, | |
224 | uid_t caller_uid, gid_t caller_gid, | |
225 | const vector<uint64_t> *caller_gid_list, | |
226 | unsigned mask, | |
11fdf7f2 TL |
227 | uid_t new_uid, gid_t new_gid, |
228 | const entity_addr_t& addr) const | |
7c673cae FG |
229 | { |
230 | if (cct) | |
231 | ldout(cct, 10) << __func__ << " inode(path /" << inode_path | |
232 | << " owner " << inode_uid << ":" << inode_gid | |
233 | << " mode 0" << std::oct << inode_mode << std::dec | |
234 | << ") by caller " << caller_uid << ":" << caller_gid | |
235 | // << "[" << caller_gid_list << "]"; | |
236 | << " mask " << mask | |
237 | << " new " << new_uid << ":" << new_gid | |
238 | << " cap: " << *this << dendl; | |
239 | ||
9f95a23c TL |
240 | for (const auto& grant : grants) { |
241 | if (grant.network.size() && | |
242 | (!grant.network_valid || | |
243 | !network_contains(grant.network_parsed, | |
244 | grant.network_prefix, | |
11fdf7f2 TL |
245 | addr))) { |
246 | continue; | |
247 | } | |
7c673cae | 248 | |
9f95a23c TL |
249 | if (grant.match.match(inode_path, caller_uid, caller_gid, caller_gid_list) && |
250 | grant.spec.allows(mask & (MAY_READ|MAY_EXECUTE), mask & MAY_WRITE)) { | |
f67539c2 TL |
251 | if (grant.match.root_squash && ((caller_uid == 0) || (caller_gid == 0)) && |
252 | (mask & MAY_WRITE)) { | |
253 | continue; | |
254 | } | |
7c673cae FG |
255 | // we have a match; narrow down GIDs to those specifically allowed here |
256 | vector<uint64_t> gids; | |
9f95a23c TL |
257 | if (std::find(grant.match.gids.begin(), grant.match.gids.end(), caller_gid) != |
258 | grant.match.gids.end()) { | |
7c673cae FG |
259 | gids.push_back(caller_gid); |
260 | } | |
261 | if (caller_gid_list) { | |
9f95a23c | 262 | std::set_intersection(grant.match.gids.begin(), grant.match.gids.end(), |
7c673cae FG |
263 | caller_gid_list->begin(), caller_gid_list->end(), |
264 | std::back_inserter(gids)); | |
265 | std::sort(gids.begin(), gids.end()); | |
266 | } | |
267 | ||
268 | ||
269 | // Spec is non-allowing if caller asked for set pool but spec forbids it | |
270 | if (mask & MAY_SET_VXATTR) { | |
9f95a23c | 271 | if (!grant.spec.allow_set_vxattr()) { |
11fdf7f2 TL |
272 | continue; |
273 | } | |
274 | } | |
275 | ||
276 | if (mask & MAY_SNAPSHOT) { | |
9f95a23c | 277 | if (!grant.spec.allow_snapshot()) { |
7c673cae FG |
278 | continue; |
279 | } | |
280 | } | |
281 | ||
b3b6e05e TL |
282 | if (mask & MAY_FULL) { |
283 | if (!grant.spec.allow_full()) { | |
284 | continue; | |
285 | } | |
286 | } | |
287 | ||
7c673cae | 288 | // check unix permissions? |
9f95a23c | 289 | if (grant.match.uid == MDSCapMatch::MDS_AUTH_UID_ANY) { |
7c673cae FG |
290 | return true; |
291 | } | |
292 | ||
293 | // chown/chgrp | |
294 | if (mask & MAY_CHOWN) { | |
295 | if (new_uid != caller_uid || // you can't chown to someone else | |
296 | inode_uid != caller_uid) { // you can't chown from someone else | |
297 | continue; | |
298 | } | |
299 | } | |
300 | if (mask & MAY_CHGRP) { | |
301 | // you can only chgrp *to* one of your groups... if you own the file. | |
302 | if (inode_uid != caller_uid || | |
303 | std::find(gids.begin(), gids.end(), new_gid) == | |
304 | gids.end()) { | |
305 | continue; | |
306 | } | |
307 | } | |
308 | ||
309 | if (inode_uid == caller_uid) { | |
310 | if ((!(mask & MAY_READ) || (inode_mode & S_IRUSR)) && | |
311 | (!(mask & MAY_WRITE) || (inode_mode & S_IWUSR)) && | |
312 | (!(mask & MAY_EXECUTE) || (inode_mode & S_IXUSR))) { | |
313 | return true; | |
314 | } | |
315 | } else if (std::find(gids.begin(), gids.end(), | |
316 | inode_gid) != gids.end()) { | |
317 | if ((!(mask & MAY_READ) || (inode_mode & S_IRGRP)) && | |
318 | (!(mask & MAY_WRITE) || (inode_mode & S_IWGRP)) && | |
319 | (!(mask & MAY_EXECUTE) || (inode_mode & S_IXGRP))) { | |
320 | return true; | |
321 | } | |
322 | } else { | |
323 | if ((!(mask & MAY_READ) || (inode_mode & S_IROTH)) && | |
324 | (!(mask & MAY_WRITE) || (inode_mode & S_IWOTH)) && | |
325 | (!(mask & MAY_EXECUTE) || (inode_mode & S_IXOTH))) { | |
326 | return true; | |
327 | } | |
328 | } | |
329 | } | |
330 | } | |
331 | ||
332 | return false; | |
333 | } | |
334 | ||
335 | void MDSAuthCaps::set_allow_all() | |
336 | { | |
337 | grants.clear(); | |
11fdf7f2 TL |
338 | grants.push_back(MDSCapGrant(MDSCapSpec(MDSCapSpec::ALL), MDSCapMatch(), |
339 | {})); | |
7c673cae FG |
340 | } |
341 | ||
11fdf7f2 | 342 | bool MDSAuthCaps::parse(CephContext *c, std::string_view str, ostream *err) |
7c673cae FG |
343 | { |
344 | // Special case for legacy caps | |
345 | if (str == "allow") { | |
346 | grants.clear(); | |
11fdf7f2 TL |
347 | grants.push_back(MDSCapGrant(MDSCapSpec(MDSCapSpec::RWPS), MDSCapMatch(), |
348 | {})); | |
7c673cae FG |
349 | return true; |
350 | } | |
351 | ||
94b18763 FG |
352 | auto iter = str.begin(); |
353 | auto end = str.end(); | |
354 | MDSCapParser<decltype(iter)> g; | |
7c673cae FG |
355 | |
356 | bool r = qi::phrase_parse(iter, end, g, ascii::space, *this); | |
357 | cct = c; // set after parser self-assignment | |
358 | if (r && iter == end) { | |
359 | for (auto& grant : grants) { | |
360 | std::sort(grant.match.gids.begin(), grant.match.gids.end()); | |
11fdf7f2 | 361 | grant.parse_network(); |
7c673cae FG |
362 | } |
363 | return true; | |
364 | } else { | |
365 | // Make sure no grants are kept after parsing failed! | |
366 | grants.clear(); | |
367 | ||
368 | if (err) | |
11fdf7f2 TL |
369 | *err << "mds capability parse failed, stopped at '" |
370 | << std::string(iter, end) | |
371 | << "' of '" << str << "'"; | |
7c673cae FG |
372 | return false; |
373 | } | |
374 | } | |
375 | ||
376 | ||
377 | bool MDSAuthCaps::allow_all() const | |
378 | { | |
9f95a23c TL |
379 | for (const auto& grant : grants) { |
380 | if (grant.match.is_match_all() && grant.spec.allow_all()) { | |
7c673cae FG |
381 | return true; |
382 | } | |
383 | } | |
384 | ||
385 | return false; | |
386 | } | |
387 | ||
388 | ||
389 | ostream &operator<<(ostream &out, const MDSCapMatch &match) | |
390 | { | |
f67539c2 TL |
391 | if (!match.fs_name.empty()) { |
392 | out << " fsname=" << match.fs_name; | |
393 | } | |
7c673cae | 394 | if (match.path.length()) { |
f67539c2 TL |
395 | out << " path=\"/" << match.path << "\""; |
396 | } | |
397 | if (match.root_squash) { | |
398 | out << " root_squash"; | |
7c673cae FG |
399 | } |
400 | if (match.uid != MDSCapMatch::MDS_AUTH_UID_ANY) { | |
f67539c2 | 401 | out << " uid=" << match.uid; |
7c673cae FG |
402 | if (!match.gids.empty()) { |
403 | out << " gids="; | |
9f95a23c TL |
404 | bool first = true; |
405 | for (const auto& gid : match.gids) { | |
406 | if (!first) | |
7c673cae | 407 | out << ','; |
9f95a23c TL |
408 | out << gid; |
409 | first = false; | |
7c673cae FG |
410 | } |
411 | } | |
412 | } | |
413 | ||
414 | return out; | |
415 | } | |
416 | ||
417 | ||
418 | ostream &operator<<(ostream &out, const MDSCapSpec &spec) | |
419 | { | |
11fdf7f2 | 420 | if (spec.allow_all()) { |
7c673cae FG |
421 | out << "*"; |
422 | } else { | |
11fdf7f2 | 423 | if (spec.allow_read()) { |
7c673cae FG |
424 | out << "r"; |
425 | } | |
11fdf7f2 | 426 | if (spec.allow_write()) { |
7c673cae FG |
427 | out << "w"; |
428 | } | |
b3b6e05e TL |
429 | if (spec.allow_full()) { |
430 | out << "f"; | |
431 | } | |
11fdf7f2 TL |
432 | if (spec.allow_set_vxattr()) { |
433 | out << "p"; | |
434 | } | |
435 | if (spec.allow_snapshot()) { | |
436 | out << "s"; | |
437 | } | |
7c673cae FG |
438 | } |
439 | ||
440 | return out; | |
441 | } | |
442 | ||
443 | ||
444 | ostream &operator<<(ostream &out, const MDSCapGrant &grant) | |
445 | { | |
446 | out << "allow "; | |
447 | out << grant.spec; | |
f67539c2 | 448 | out << grant.match; |
11fdf7f2 TL |
449 | if (grant.network.size()) { |
450 | out << " network " << grant.network; | |
451 | } | |
7c673cae FG |
452 | return out; |
453 | } | |
454 | ||
455 | ||
456 | ostream &operator<<(ostream &out, const MDSAuthCaps &cap) | |
457 | { | |
458 | out << "MDSAuthCaps["; | |
459 | for (size_t i = 0; i < cap.grants.size(); ++i) { | |
460 | out << cap.grants[i]; | |
461 | if (i < cap.grants.size() - 1) { | |
462 | out << ", "; | |
463 | } | |
464 | } | |
465 | out << "]"; | |
466 | ||
467 | return out; | |
468 | } | |
469 |