]> git.proxmox.com Git - ceph.git/blob - ceph/src/mds/MDSAuthCaps.cc
update ceph source to reef 18.2.1
[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/phoenix/operator.hpp>
21 #include <boost/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 ldout(g_ceph_context, 10) << __func__ << " inode(path /" << inode_path
231 << " owner " << inode_uid << ":" << inode_gid
232 << " mode 0" << std::oct << inode_mode << std::dec
233 << ") by caller " << caller_uid << ":" << caller_gid
234 // << "[" << caller_gid_list << "]";
235 << " mask " << mask
236 << " new " << new_uid << ":" << new_gid
237 << " cap: " << *this << dendl;
238
239 for (const auto& grant : grants) {
240 if (grant.network.size() &&
241 (!grant.network_valid ||
242 !network_contains(grant.network_parsed,
243 grant.network_prefix,
244 addr))) {
245 continue;
246 }
247
248 if (grant.match.match(inode_path, caller_uid, caller_gid, caller_gid_list) &&
249 grant.spec.allows(mask & (MAY_READ|MAY_EXECUTE), mask & MAY_WRITE)) {
250 if (grant.match.root_squash && ((caller_uid == 0) || (caller_gid == 0)) &&
251 (mask & MAY_WRITE)) {
252 continue;
253 }
254 // we have a match; narrow down GIDs to those specifically allowed here
255 vector<uint64_t> gids;
256 if (std::find(grant.match.gids.begin(), grant.match.gids.end(), caller_gid) !=
257 grant.match.gids.end()) {
258 gids.push_back(caller_gid);
259 }
260 if (caller_gid_list) {
261 std::set_intersection(grant.match.gids.begin(), grant.match.gids.end(),
262 caller_gid_list->begin(), caller_gid_list->end(),
263 std::back_inserter(gids));
264 std::sort(gids.begin(), gids.end());
265 }
266
267
268 // Spec is non-allowing if caller asked for set pool but spec forbids it
269 if (mask & MAY_SET_VXATTR) {
270 if (!grant.spec.allow_set_vxattr()) {
271 continue;
272 }
273 }
274
275 if (mask & MAY_SNAPSHOT) {
276 if (!grant.spec.allow_snapshot()) {
277 continue;
278 }
279 }
280
281 if (mask & MAY_FULL) {
282 if (!grant.spec.allow_full()) {
283 continue;
284 }
285 }
286
287 // check unix permissions?
288 if (grant.match.uid == MDSCapMatch::MDS_AUTH_UID_ANY) {
289 return true;
290 }
291
292 // chown/chgrp
293 if (mask & MAY_CHOWN) {
294 if (new_uid != caller_uid || // you can't chown to someone else
295 inode_uid != caller_uid) { // you can't chown from someone else
296 continue;
297 }
298 }
299 if (mask & MAY_CHGRP) {
300 // you can only chgrp *to* one of your groups... if you own the file.
301 if (inode_uid != caller_uid ||
302 std::find(gids.begin(), gids.end(), new_gid) ==
303 gids.end()) {
304 continue;
305 }
306 }
307
308 if (inode_uid == caller_uid) {
309 if ((!(mask & MAY_READ) || (inode_mode & S_IRUSR)) &&
310 (!(mask & MAY_WRITE) || (inode_mode & S_IWUSR)) &&
311 (!(mask & MAY_EXECUTE) || (inode_mode & S_IXUSR))) {
312 return true;
313 }
314 } else if (std::find(gids.begin(), gids.end(),
315 inode_gid) != gids.end()) {
316 if ((!(mask & MAY_READ) || (inode_mode & S_IRGRP)) &&
317 (!(mask & MAY_WRITE) || (inode_mode & S_IWGRP)) &&
318 (!(mask & MAY_EXECUTE) || (inode_mode & S_IXGRP))) {
319 return true;
320 }
321 } else {
322 if ((!(mask & MAY_READ) || (inode_mode & S_IROTH)) &&
323 (!(mask & MAY_WRITE) || (inode_mode & S_IWOTH)) &&
324 (!(mask & MAY_EXECUTE) || (inode_mode & S_IXOTH))) {
325 return true;
326 }
327 }
328 }
329 }
330
331 return false;
332 }
333
334 void MDSAuthCaps::set_allow_all()
335 {
336 grants.clear();
337 grants.push_back(MDSCapGrant(MDSCapSpec(MDSCapSpec::ALL), MDSCapMatch(),
338 {}));
339 }
340
341 bool MDSAuthCaps::parse(std::string_view str, ostream *err)
342 {
343 // Special case for legacy caps
344 if (str == "allow") {
345 grants.clear();
346 grants.push_back(MDSCapGrant(MDSCapSpec(MDSCapSpec::RWPS), MDSCapMatch(),
347 {}));
348 return true;
349 }
350
351 auto iter = str.begin();
352 auto end = str.end();
353 MDSCapParser<decltype(iter)> g;
354
355 bool r = qi::phrase_parse(iter, end, g, ascii::space, *this);
356 if (r && iter == end) {
357 for (auto& grant : grants) {
358 std::sort(grant.match.gids.begin(), grant.match.gids.end());
359 grant.parse_network();
360 }
361 return true;
362 } else {
363 // Make sure no grants are kept after parsing failed!
364 grants.clear();
365
366 if (err)
367 *err << "mds capability parse failed, stopped at '"
368 << std::string(iter, end)
369 << "' of '" << str << "'";
370 return false;
371 }
372 }
373
374
375 bool MDSAuthCaps::allow_all() const
376 {
377 for (const auto& grant : grants) {
378 if (grant.match.is_match_all() && grant.spec.allow_all()) {
379 return true;
380 }
381 }
382
383 return false;
384 }
385
386
387 ostream &operator<<(ostream &out, const MDSCapMatch &match)
388 {
389 if (!match.fs_name.empty()) {
390 out << " fsname=" << match.fs_name;
391 }
392 if (match.path.length()) {
393 out << " path=\"/" << match.path << "\"";
394 }
395 if (match.root_squash) {
396 out << " root_squash";
397 }
398 if (match.uid != MDSCapMatch::MDS_AUTH_UID_ANY) {
399 out << " uid=" << match.uid;
400 if (!match.gids.empty()) {
401 out << " gids=";
402 bool first = true;
403 for (const auto& gid : match.gids) {
404 if (!first)
405 out << ',';
406 out << gid;
407 first = false;
408 }
409 }
410 }
411
412 return out;
413 }
414
415
416 ostream &operator<<(ostream &out, const MDSCapSpec &spec)
417 {
418 if (spec.allow_all()) {
419 out << "*";
420 } else {
421 if (spec.allow_read()) {
422 out << "r";
423 }
424 if (spec.allow_write()) {
425 out << "w";
426 }
427 if (spec.allow_full()) {
428 out << "f";
429 }
430 if (spec.allow_set_vxattr()) {
431 out << "p";
432 }
433 if (spec.allow_snapshot()) {
434 out << "s";
435 }
436 }
437
438 return out;
439 }
440
441
442 ostream &operator<<(ostream &out, const MDSCapGrant &grant)
443 {
444 out << "allow ";
445 out << grant.spec;
446 out << grant.match;
447 if (grant.network.size()) {
448 out << " network " << grant.network;
449 }
450 return out;
451 }
452
453
454 ostream &operator<<(ostream &out, const MDSAuthCaps &cap)
455 {
456 out << "MDSAuthCaps[";
457 for (size_t i = 0; i < cap.grants.size(); ++i) {
458 out << cap.grants[i];
459 if (i < cap.grants.size() - 1) {
460 out << ", ";
461 }
462 }
463 out << "]";
464
465 return out;
466 }
467