]> git.proxmox.com Git - ceph.git/blob - ceph/src/mds/MDSAuthCaps.cc
import ceph pacific 16.2.5
[ceph.git] / ceph / src / mds / MDSAuthCaps.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) 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
15 #include <string_view>
16
17 #include <errno.h>
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"
25 #include "mdstypes.h"
26 #include "include/ipaddr.h"
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;
35 using std::vector;
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 {
45 using qi::attr;
46 using qi::bool_;
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_./-");
65 network_str %= +char_("/.:a-fA-F0-9][");
66 fs_name_str %= +char_("a-zA-Z0-9_.-");
67
68 // match := [path=<path>] [uid=<uid> [gids=<gid>[,<gid>...]]
69 // TODO: allow fsname, and root_squash to be specified with uid, and gidlist
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);
74 fs_name %= -(spaces >> lit("fsname") >> lit('=') >> fs_name_str);
75 root_squash %= (spaces >> lit("root_squash") >> attr(true));
76 match = -(
77 (fs_name >> path >> root_squash)[_val = phoenix::construct<MDSCapMatch>(_2, _1, _3)] |
78 (uid >> gidlist)[_val = phoenix::construct<MDSCapMatch>(_1, _2)] |
79 (path >> uid >> gidlist)[_val = phoenix::construct<MDSCapMatch>(_1, _2, _3)] |
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)]);
87
88 // capspec = * | r[w][f][p][s]
89 capspec = spaces >> (
90 lit("*")[_val = MDSCapSpec(MDSCapSpec::ALL)]
91 |
92 lit("all")[_val = MDSCapSpec(MDSCapSpec::ALL)]
93 |
94 (lit("rwfps"))[_val = MDSCapSpec(MDSCapSpec::RWFPS)]
95 |
96 (lit("rwps"))[_val = MDSCapSpec(MDSCapSpec::RWPS)]
97 |
98 (lit("rwfp"))[_val = MDSCapSpec(MDSCapSpec::RWFP)]
99 |
100 (lit("rwfs"))[_val = MDSCapSpec(MDSCapSpec::RWFS)]
101 |
102 (lit("rwp"))[_val = MDSCapSpec(MDSCapSpec::RWP)]
103 |
104 (lit("rws"))[_val = MDSCapSpec(MDSCapSpec::RWS)]
105 |
106 (lit("rwf"))[_val = MDSCapSpec(MDSCapSpec::RWF)]
107 |
108 (lit("rw"))[_val = MDSCapSpec(MDSCapSpec::RW)]
109 |
110 (lit("r"))[_val = MDSCapSpec(MDSCapSpec::READ)]
111 );
112
113 grant = lit("allow") >> (capspec >> match >>
114 -(spaces >> lit("network") >> spaces >> network_str))
115 [_val = phoenix::construct<MDSCapGrant>(_1, _2, _3)];
116 grants %= (grant % (*lit(' ') >> (lit(';') | lit(',')) >> *lit(' ')));
117 mdscaps = grants [_val = phoenix::construct<MDSAuthCaps>(_1)];
118 }
119 qi::rule<Iterator> spaces;
120 qi::rule<Iterator, string()> quoted_path, unquoted_path, network_str;
121 qi::rule<Iterator, string()> fs_name_str, fs_name, path;
122 qi::rule<Iterator, bool()> root_squash;
123 qi::rule<Iterator, MDSCapSpec()> capspec;
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
145 bool MDSCapMatch::match(std::string_view target_path,
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;
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 }
163 }
164 }
165 if (!gid_matched)
166 return false;
167 }
168 }
169
170 if (!match_path(target_path)) {
171 return false;
172 }
173
174 return true;
175 }
176
177 bool MDSCapMatch::match_path(std::string_view target_path) const
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
193 void MDSCapGrant::parse_network()
194 {
195 network_valid = ::parse_network(network.c_str(), &network_parsed,
196 &network_prefix);
197 }
198
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 */
203 bool MDSAuthCaps::path_capable(std::string_view inode_path) const
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 */
221 bool MDSAuthCaps::is_capable(std::string_view inode_path,
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,
227 uid_t new_uid, gid_t new_gid,
228 const entity_addr_t& addr) const
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
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,
245 addr))) {
246 continue;
247 }
248
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)) {
251 if (grant.match.root_squash && ((caller_uid == 0) || (caller_gid == 0)) &&
252 (mask & MAY_WRITE)) {
253 continue;
254 }
255 // we have a match; narrow down GIDs to those specifically allowed here
256 vector<uint64_t> gids;
257 if (std::find(grant.match.gids.begin(), grant.match.gids.end(), caller_gid) !=
258 grant.match.gids.end()) {
259 gids.push_back(caller_gid);
260 }
261 if (caller_gid_list) {
262 std::set_intersection(grant.match.gids.begin(), grant.match.gids.end(),
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) {
271 if (!grant.spec.allow_set_vxattr()) {
272 continue;
273 }
274 }
275
276 if (mask & MAY_SNAPSHOT) {
277 if (!grant.spec.allow_snapshot()) {
278 continue;
279 }
280 }
281
282 if (mask & MAY_FULL) {
283 if (!grant.spec.allow_full()) {
284 continue;
285 }
286 }
287
288 // check unix permissions?
289 if (grant.match.uid == MDSCapMatch::MDS_AUTH_UID_ANY) {
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();
338 grants.push_back(MDSCapGrant(MDSCapSpec(MDSCapSpec::ALL), MDSCapMatch(),
339 {}));
340 }
341
342 bool MDSAuthCaps::parse(CephContext *c, std::string_view str, ostream *err)
343 {
344 // Special case for legacy caps
345 if (str == "allow") {
346 grants.clear();
347 grants.push_back(MDSCapGrant(MDSCapSpec(MDSCapSpec::RWPS), MDSCapMatch(),
348 {}));
349 return true;
350 }
351
352 auto iter = str.begin();
353 auto end = str.end();
354 MDSCapParser<decltype(iter)> g;
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());
361 grant.parse_network();
362 }
363 return true;
364 } else {
365 // Make sure no grants are kept after parsing failed!
366 grants.clear();
367
368 if (err)
369 *err << "mds capability parse failed, stopped at '"
370 << std::string(iter, end)
371 << "' of '" << str << "'";
372 return false;
373 }
374 }
375
376
377 bool MDSAuthCaps::allow_all() const
378 {
379 for (const auto& grant : grants) {
380 if (grant.match.is_match_all() && grant.spec.allow_all()) {
381 return true;
382 }
383 }
384
385 return false;
386 }
387
388
389 ostream &operator<<(ostream &out, const MDSCapMatch &match)
390 {
391 if (!match.fs_name.empty()) {
392 out << " fsname=" << match.fs_name;
393 }
394 if (match.path.length()) {
395 out << " path=\"/" << match.path << "\"";
396 }
397 if (match.root_squash) {
398 out << " root_squash";
399 }
400 if (match.uid != MDSCapMatch::MDS_AUTH_UID_ANY) {
401 out << " uid=" << match.uid;
402 if (!match.gids.empty()) {
403 out << " gids=";
404 bool first = true;
405 for (const auto& gid : match.gids) {
406 if (!first)
407 out << ',';
408 out << gid;
409 first = false;
410 }
411 }
412 }
413
414 return out;
415 }
416
417
418 ostream &operator<<(ostream &out, const MDSCapSpec &spec)
419 {
420 if (spec.allow_all()) {
421 out << "*";
422 } else {
423 if (spec.allow_read()) {
424 out << "r";
425 }
426 if (spec.allow_write()) {
427 out << "w";
428 }
429 if (spec.allow_full()) {
430 out << "f";
431 }
432 if (spec.allow_set_vxattr()) {
433 out << "p";
434 }
435 if (spec.allow_snapshot()) {
436 out << "s";
437 }
438 }
439
440 return out;
441 }
442
443
444 ostream &operator<<(ostream &out, const MDSCapGrant &grant)
445 {
446 out << "allow ";
447 out << grant.spec;
448 out << grant.match;
449 if (grant.network.size()) {
450 out << " network " << grant.network;
451 }
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