]> git.proxmox.com Git - ceph.git/blame - ceph/src/mds/MDSAuthCaps.cc
import ceph pacific 16.2.5
[ceph.git] / ceph / src / mds / MDSAuthCaps.cc
CommitLineData
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
33using std::ostream;
34using std::string;
f67539c2 35using std::vector;
7c673cae
FG
36namespace qi = boost::spirit::qi;
37namespace ascii = boost::spirit::ascii;
38namespace phoenix = boost::phoenix;
39
40template <typename Iterator>
41struct 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
133void 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 145bool 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 177bool 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
193void 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 203bool 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 221bool 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
335void 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 342bool 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
377bool 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
389ostream &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
418ostream &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
444ostream &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
456ostream &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