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