]>
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) 2004-2006 Sage Weil <sage@newdream.net> | |
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 <sstream> | |
16 | ||
17 | #include "mon/AuthMonitor.h" | |
18 | #include "mon/Monitor.h" | |
19 | #include "mon/MonitorDBStore.h" | |
31f18b77 FG |
20 | #include "mon/ConfigKeyService.h" |
21 | #include "mon/OSDMonitor.h" | |
7c673cae FG |
22 | |
23 | #include "messages/MMonCommand.h" | |
24 | #include "messages/MAuth.h" | |
25 | #include "messages/MAuthReply.h" | |
26 | #include "messages/MMonGlobalID.h" | |
27 | #include "msg/Messenger.h" | |
28 | ||
29 | #include "auth/AuthServiceHandler.h" | |
30 | #include "auth/KeyRing.h" | |
31f18b77 | 31 | #include "include/stringify.h" |
7c673cae FG |
32 | #include "include/assert.h" |
33 | ||
34 | #define dout_subsys ceph_subsys_mon | |
35 | #undef dout_prefix | |
36 | #define dout_prefix _prefix(_dout, mon, get_last_committed()) | |
37 | static ostream& _prefix(std::ostream *_dout, Monitor *mon, version_t v) { | |
38 | return *_dout << "mon." << mon->name << "@" << mon->rank | |
39 | << "(" << mon->get_state_name() | |
40 | << ").auth v" << v << " "; | |
41 | } | |
42 | ||
43 | ostream& operator<<(ostream &out, const AuthMonitor &pm) | |
44 | { | |
45 | return out << "auth"; | |
46 | } | |
47 | ||
48 | bool AuthMonitor::check_rotate() | |
49 | { | |
50 | KeyServerData::Incremental rot_inc; | |
51 | rot_inc.op = KeyServerData::AUTH_INC_SET_ROTATING; | |
52 | if (!mon->key_server.updated_rotating(rot_inc.rotating_bl, last_rotating_ver)) | |
53 | return false; | |
54 | dout(10) << __func__ << " updated rotating" << dendl; | |
55 | push_cephx_inc(rot_inc); | |
56 | return true; | |
57 | } | |
58 | ||
59 | /* | |
60 | Tick function to update the map based on performance every N seconds | |
61 | */ | |
62 | ||
63 | void AuthMonitor::tick() | |
64 | { | |
65 | if (!is_active()) return; | |
66 | ||
67 | dout(10) << *this << dendl; | |
68 | ||
69 | if (!mon->is_leader()) return; | |
70 | ||
71 | if (check_rotate()) | |
72 | propose_pending(); | |
73 | } | |
74 | ||
75 | void AuthMonitor::on_active() | |
76 | { | |
77 | dout(10) << "AuthMonitor::on_active()" << dendl; | |
78 | ||
79 | if (!mon->is_leader()) | |
80 | return; | |
81 | mon->key_server.start_server(); | |
82 | } | |
83 | ||
84 | void AuthMonitor::create_initial() | |
85 | { | |
86 | dout(10) << "create_initial -- creating initial map" << dendl; | |
87 | ||
88 | // initialize rotating keys | |
89 | last_rotating_ver = 0; | |
90 | check_rotate(); | |
91 | assert(pending_auth.size() == 1); | |
92 | ||
93 | if (mon->is_keyring_required()) { | |
94 | KeyRing keyring; | |
95 | bufferlist bl; | |
96 | int ret = mon->store->get("mkfs", "keyring", bl); | |
97 | // fail hard only if there's an error we're not expecting to see | |
98 | assert((ret == 0) || (ret == -ENOENT)); | |
99 | ||
100 | // try importing only if there's a key | |
101 | if (ret == 0) { | |
102 | KeyRing keyring; | |
103 | bufferlist::iterator p = bl.begin(); | |
104 | ||
105 | ::decode(keyring, p); | |
106 | import_keyring(keyring); | |
107 | } | |
108 | } | |
109 | ||
110 | max_global_id = MIN_GLOBAL_ID; | |
111 | ||
112 | Incremental inc; | |
113 | inc.inc_type = GLOBAL_ID; | |
114 | inc.max_global_id = max_global_id; | |
115 | pending_auth.push_back(inc); | |
116 | ||
117 | format_version = 2; | |
118 | } | |
119 | ||
120 | void AuthMonitor::update_from_paxos(bool *need_bootstrap) | |
121 | { | |
122 | dout(10) << __func__ << dendl; | |
123 | version_t version = get_last_committed(); | |
124 | version_t keys_ver = mon->key_server.get_ver(); | |
125 | if (version == keys_ver) | |
126 | return; | |
127 | assert(version > keys_ver); | |
128 | ||
129 | version_t latest_full = get_version_latest_full(); | |
130 | ||
131 | dout(10) << __func__ << " version " << version << " keys ver " << keys_ver | |
132 | << " latest " << latest_full << dendl; | |
133 | ||
134 | if ((latest_full > 0) && (latest_full > keys_ver)) { | |
135 | bufferlist latest_bl; | |
136 | int err = get_version_full(latest_full, latest_bl); | |
137 | assert(err == 0); | |
138 | assert(latest_bl.length() != 0); | |
139 | dout(7) << __func__ << " loading summary e " << latest_full << dendl; | |
140 | dout(7) << __func__ << " latest length " << latest_bl.length() << dendl; | |
141 | bufferlist::iterator p = latest_bl.begin(); | |
142 | __u8 struct_v; | |
143 | ::decode(struct_v, p); | |
144 | ::decode(max_global_id, p); | |
145 | ::decode(mon->key_server, p); | |
146 | mon->key_server.set_ver(latest_full); | |
147 | keys_ver = latest_full; | |
148 | } | |
149 | ||
150 | dout(10) << __func__ << " key server version " << mon->key_server.get_ver() << dendl; | |
151 | ||
152 | // walk through incrementals | |
153 | while (version > keys_ver) { | |
154 | bufferlist bl; | |
155 | int ret = get_version(keys_ver+1, bl); | |
156 | assert(ret == 0); | |
157 | assert(bl.length()); | |
158 | ||
159 | // reset if we are moving to initial state. we will normally have | |
160 | // keys in here temporarily for bootstrapping that we need to | |
161 | // clear out. | |
162 | if (keys_ver == 0) | |
163 | mon->key_server.clear_secrets(); | |
164 | ||
165 | dout(20) << __func__ << " walking through version " << (keys_ver+1) | |
166 | << " len " << bl.length() << dendl; | |
167 | ||
168 | bufferlist::iterator p = bl.begin(); | |
169 | __u8 v; | |
170 | ::decode(v, p); | |
171 | while (!p.end()) { | |
172 | Incremental inc; | |
173 | ::decode(inc, p); | |
174 | switch (inc.inc_type) { | |
175 | case GLOBAL_ID: | |
176 | max_global_id = inc.max_global_id; | |
177 | break; | |
178 | ||
179 | case AUTH_DATA: | |
180 | { | |
181 | KeyServerData::Incremental auth_inc; | |
182 | bufferlist::iterator iter = inc.auth_data.begin(); | |
183 | ::decode(auth_inc, iter); | |
184 | mon->key_server.apply_data_incremental(auth_inc); | |
185 | break; | |
186 | } | |
187 | } | |
188 | } | |
189 | ||
190 | keys_ver++; | |
191 | mon->key_server.set_ver(keys_ver); | |
192 | ||
193 | if (keys_ver == 1 && mon->is_keyring_required()) { | |
194 | auto t(std::make_shared<MonitorDBStore::Transaction>()); | |
195 | t->erase("mkfs", "keyring"); | |
196 | mon->store->apply_transaction(t); | |
197 | } | |
198 | } | |
199 | ||
200 | if (last_allocated_id == 0) | |
201 | last_allocated_id = max_global_id; | |
202 | ||
203 | dout(10) << "update_from_paxos() last_allocated_id=" << last_allocated_id | |
204 | << " max_global_id=" << max_global_id | |
205 | << " format_version " << format_version | |
206 | << dendl; | |
207 | } | |
208 | ||
209 | void AuthMonitor::increase_max_global_id() | |
210 | { | |
211 | assert(mon->is_leader()); | |
212 | ||
213 | max_global_id += g_conf->mon_globalid_prealloc; | |
214 | dout(10) << "increasing max_global_id to " << max_global_id << dendl; | |
215 | Incremental inc; | |
216 | inc.inc_type = GLOBAL_ID; | |
217 | inc.max_global_id = max_global_id; | |
218 | pending_auth.push_back(inc); | |
219 | } | |
220 | ||
221 | bool AuthMonitor::should_propose(double& delay) | |
222 | { | |
223 | return (!pending_auth.empty()); | |
224 | } | |
225 | ||
226 | void AuthMonitor::create_pending() | |
227 | { | |
228 | pending_auth.clear(); | |
229 | dout(10) << "create_pending v " << (get_last_committed() + 1) << dendl; | |
230 | } | |
231 | ||
232 | void AuthMonitor::encode_pending(MonitorDBStore::TransactionRef t) | |
233 | { | |
234 | dout(10) << __func__ << " v " << (get_last_committed() + 1) << dendl; | |
235 | ||
236 | bufferlist bl; | |
237 | ||
238 | __u8 v = 1; | |
239 | ::encode(v, bl); | |
240 | vector<Incremental>::iterator p; | |
241 | for (p = pending_auth.begin(); p != pending_auth.end(); ++p) | |
242 | p->encode(bl, mon->get_quorum_con_features()); | |
243 | ||
244 | version_t version = get_last_committed() + 1; | |
245 | put_version(t, version, bl); | |
246 | put_last_committed(t, version); | |
247 | } | |
248 | ||
249 | void AuthMonitor::encode_full(MonitorDBStore::TransactionRef t) | |
250 | { | |
251 | version_t version = mon->key_server.get_ver(); | |
252 | // do not stash full version 0 as it will never be removed nor read | |
253 | if (version == 0) | |
254 | return; | |
255 | ||
256 | dout(10) << __func__ << " auth v " << version << dendl; | |
257 | assert(get_last_committed() == version); | |
258 | ||
259 | bufferlist full_bl; | |
260 | Mutex::Locker l(mon->key_server.get_lock()); | |
261 | dout(20) << __func__ << " key server has " | |
262 | << (mon->key_server.has_secrets() ? "" : "no ") | |
263 | << "secrets!" << dendl; | |
264 | __u8 v = 1; | |
265 | ::encode(v, full_bl); | |
266 | ::encode(max_global_id, full_bl); | |
267 | ::encode(mon->key_server, full_bl); | |
268 | ||
269 | put_version_full(t, version, full_bl); | |
270 | put_version_latest_full(t, version); | |
271 | } | |
272 | ||
273 | version_t AuthMonitor::get_trim_to() | |
274 | { | |
275 | unsigned max = g_conf->paxos_max_join_drift * 2; | |
276 | version_t version = get_last_committed(); | |
277 | if (mon->is_leader() && (version > max)) | |
278 | return version - max; | |
279 | return 0; | |
280 | } | |
281 | ||
282 | bool AuthMonitor::preprocess_query(MonOpRequestRef op) | |
283 | { | |
284 | PaxosServiceMessage *m = static_cast<PaxosServiceMessage*>(op->get_req()); | |
285 | dout(10) << "preprocess_query " << *m << " from " << m->get_orig_source_inst() << dendl; | |
286 | switch (m->get_type()) { | |
287 | case MSG_MON_COMMAND: | |
288 | return preprocess_command(op); | |
289 | ||
290 | case CEPH_MSG_AUTH: | |
291 | return prep_auth(op, false); | |
292 | ||
293 | case MSG_MON_GLOBAL_ID: | |
294 | return false; | |
295 | ||
296 | default: | |
297 | ceph_abort(); | |
298 | return true; | |
299 | } | |
300 | } | |
301 | ||
302 | bool AuthMonitor::prepare_update(MonOpRequestRef op) | |
303 | { | |
304 | PaxosServiceMessage *m = static_cast<PaxosServiceMessage*>(op->get_req()); | |
305 | dout(10) << "prepare_update " << *m << " from " << m->get_orig_source_inst() << dendl; | |
306 | switch (m->get_type()) { | |
307 | case MSG_MON_COMMAND: | |
308 | return prepare_command(op); | |
309 | case MSG_MON_GLOBAL_ID: | |
310 | return prepare_global_id(op); | |
311 | case CEPH_MSG_AUTH: | |
312 | return prep_auth(op, true); | |
313 | default: | |
314 | ceph_abort(); | |
315 | return false; | |
316 | } | |
317 | } | |
318 | ||
319 | uint64_t AuthMonitor::assign_global_id(MonOpRequestRef op, bool should_increase_max) | |
320 | { | |
321 | MAuth *m = static_cast<MAuth*>(op->get_req()); | |
322 | int total_mon = mon->monmap->size(); | |
323 | dout(10) << "AuthMonitor::assign_global_id m=" << *m << " mon=" << mon->rank << "/" << total_mon | |
324 | << " last_allocated=" << last_allocated_id << " max_global_id=" << max_global_id << dendl; | |
325 | ||
326 | uint64_t next_global_id = last_allocated_id + 1; | |
327 | int remainder = next_global_id % total_mon; | |
328 | if (remainder) | |
329 | remainder = total_mon - remainder; | |
330 | next_global_id += remainder + mon->rank; | |
331 | dout(10) << "next_global_id should be " << next_global_id << dendl; | |
332 | ||
333 | // if we can't bump the max, bail out now on an out-of-bounds gid | |
334 | if (next_global_id > max_global_id && | |
335 | (!mon->is_leader() || !should_increase_max)) { | |
336 | return 0; | |
337 | } | |
338 | ||
339 | // can we return a gid? | |
340 | bool return_next = (next_global_id <= max_global_id); | |
341 | ||
342 | // bump the max? | |
343 | while (mon->is_leader() && | |
344 | (max_global_id < g_conf->mon_globalid_prealloc || | |
345 | next_global_id >= max_global_id - g_conf->mon_globalid_prealloc / 2)) { | |
346 | increase_max_global_id(); | |
347 | } | |
348 | ||
349 | if (return_next) { | |
350 | last_allocated_id = next_global_id; | |
351 | return next_global_id; | |
352 | } else { | |
353 | return 0; | |
354 | } | |
355 | } | |
356 | ||
357 | ||
358 | bool AuthMonitor::prep_auth(MonOpRequestRef op, bool paxos_writable) | |
359 | { | |
360 | MAuth *m = static_cast<MAuth*>(op->get_req()); | |
361 | dout(10) << "prep_auth() blob_size=" << m->get_auth_payload().length() << dendl; | |
362 | ||
363 | MonSession *s = op->get_session(); | |
364 | if (!s) { | |
365 | dout(10) << "no session, dropping" << dendl; | |
366 | return true; | |
367 | } | |
368 | ||
369 | int ret = 0; | |
370 | AuthCapsInfo caps_info; | |
371 | MAuthReply *reply; | |
372 | bufferlist response_bl; | |
373 | bufferlist::iterator indata = m->auth_payload.begin(); | |
374 | __u32 proto = m->protocol; | |
375 | bool start = false; | |
376 | EntityName entity_name; | |
377 | ||
378 | // set up handler? | |
379 | if (m->protocol == 0 && !s->auth_handler) { | |
380 | set<__u32> supported; | |
381 | ||
382 | try { | |
383 | __u8 struct_v = 1; | |
384 | ::decode(struct_v, indata); | |
385 | ::decode(supported, indata); | |
386 | ::decode(entity_name, indata); | |
387 | ::decode(s->global_id, indata); | |
388 | } catch (const buffer::error &e) { | |
389 | dout(10) << "failed to decode initial auth message" << dendl; | |
390 | ret = -EINVAL; | |
391 | goto reply; | |
392 | } | |
393 | ||
394 | // do we require cephx signatures? | |
395 | ||
396 | if (!m->get_connection()->has_feature(CEPH_FEATURE_MSG_AUTH)) { | |
397 | if (entity_name.get_type() == CEPH_ENTITY_TYPE_MON || | |
398 | entity_name.get_type() == CEPH_ENTITY_TYPE_OSD || | |
399 | entity_name.get_type() == CEPH_ENTITY_TYPE_MDS || | |
400 | entity_name.get_type() == CEPH_ENTITY_TYPE_MGR) { | |
401 | if (g_conf->cephx_cluster_require_signatures || | |
402 | g_conf->cephx_require_signatures) { | |
403 | dout(1) << m->get_source_inst() | |
404 | << " supports cephx but not signatures and" | |
405 | << " 'cephx [cluster] require signatures = true';" | |
406 | << " disallowing cephx" << dendl; | |
407 | supported.erase(CEPH_AUTH_CEPHX); | |
408 | } | |
409 | } else { | |
410 | if (g_conf->cephx_service_require_signatures || | |
411 | g_conf->cephx_require_signatures) { | |
412 | dout(1) << m->get_source_inst() | |
413 | << " supports cephx but not signatures and" | |
414 | << " 'cephx [service] require signatures = true';" | |
415 | << " disallowing cephx" << dendl; | |
416 | supported.erase(CEPH_AUTH_CEPHX); | |
417 | } | |
418 | } | |
419 | } | |
420 | ||
421 | int type; | |
422 | if (entity_name.get_type() == CEPH_ENTITY_TYPE_MON || | |
423 | entity_name.get_type() == CEPH_ENTITY_TYPE_OSD || | |
424 | entity_name.get_type() == CEPH_ENTITY_TYPE_MDS || | |
425 | entity_name.get_type() == CEPH_ENTITY_TYPE_MGR) | |
426 | type = mon->auth_cluster_required.pick(supported); | |
427 | else | |
428 | type = mon->auth_service_required.pick(supported); | |
429 | ||
430 | s->auth_handler = get_auth_service_handler(type, g_ceph_context, &mon->key_server); | |
431 | if (!s->auth_handler) { | |
432 | dout(1) << "client did not provide supported auth type" << dendl; | |
433 | ret = -ENOTSUP; | |
434 | goto reply; | |
435 | } | |
436 | start = true; | |
437 | } else if (!s->auth_handler) { | |
438 | dout(10) << "protocol specified but no s->auth_handler" << dendl; | |
439 | ret = -EINVAL; | |
440 | goto reply; | |
441 | } | |
442 | ||
443 | /* assign a new global_id? we assume this should only happen on the first | |
444 | request. If a client tries to send it later, it'll screw up its auth | |
445 | session */ | |
446 | if (!s->global_id) { | |
447 | s->global_id = assign_global_id(op, paxos_writable); | |
448 | if (!s->global_id) { | |
449 | ||
450 | delete s->auth_handler; | |
451 | s->auth_handler = NULL; | |
452 | ||
453 | if (mon->is_leader() && paxos_writable) { | |
454 | dout(10) << "increasing global id, waitlisting message" << dendl; | |
455 | wait_for_active(op, new C_RetryMessage(this, op)); | |
456 | goto done; | |
457 | } | |
458 | ||
459 | if (!mon->is_leader()) { | |
460 | dout(10) << "not the leader, requesting more ids from leader" << dendl; | |
461 | int leader = mon->get_leader(); | |
462 | MMonGlobalID *req = new MMonGlobalID(); | |
463 | req->old_max_id = max_global_id; | |
464 | mon->messenger->send_message(req, mon->monmap->get_inst(leader)); | |
465 | wait_for_finished_proposal(op, new C_RetryMessage(this, op)); | |
466 | return true; | |
467 | } | |
468 | ||
469 | assert(!paxos_writable); | |
470 | return false; | |
471 | } | |
472 | } | |
473 | ||
474 | try { | |
475 | uint64_t auid = 0; | |
476 | if (start) { | |
477 | // new session | |
478 | ||
479 | // always send the latest monmap. | |
480 | if (m->monmap_epoch < mon->monmap->get_epoch()) | |
481 | mon->send_latest_monmap(m->get_connection().get()); | |
482 | ||
483 | proto = s->auth_handler->start_session(entity_name, indata, response_bl, caps_info); | |
484 | ret = 0; | |
485 | if (caps_info.allow_all) | |
486 | s->caps.set_allow_all(); | |
487 | } else { | |
488 | // request | |
489 | ret = s->auth_handler->handle_request(indata, response_bl, s->global_id, caps_info, &auid); | |
490 | } | |
491 | if (ret == -EIO) { | |
492 | wait_for_active(op, new C_RetryMessage(this,op)); | |
493 | goto done; | |
494 | } | |
495 | if (caps_info.caps.length()) { | |
496 | bufferlist::iterator p = caps_info.caps.begin(); | |
497 | string str; | |
498 | try { | |
499 | ::decode(str, p); | |
500 | } catch (const buffer::error &err) { | |
501 | derr << "corrupt cap data for " << entity_name << " in auth db" << dendl; | |
502 | str.clear(); | |
503 | } | |
504 | s->caps.parse(str, NULL); | |
505 | s->auid = auid; | |
506 | } | |
507 | } catch (const buffer::error &err) { | |
508 | ret = -EINVAL; | |
509 | dout(0) << "caught error when trying to handle auth request, probably malformed request" << dendl; | |
510 | } | |
511 | ||
512 | reply: | |
513 | reply = new MAuthReply(proto, &response_bl, ret, s->global_id); | |
514 | mon->send_reply(op, reply); | |
515 | done: | |
516 | return true; | |
517 | } | |
518 | ||
519 | bool AuthMonitor::preprocess_command(MonOpRequestRef op) | |
520 | { | |
521 | MMonCommand *m = static_cast<MMonCommand*>(op->get_req()); | |
522 | int r = -1; | |
523 | bufferlist rdata; | |
524 | stringstream ss, ds; | |
525 | ||
526 | map<string, cmd_vartype> cmdmap; | |
527 | if (!cmdmap_from_json(m->cmd, &cmdmap, ss)) { | |
528 | // ss has reason for failure | |
529 | string rs = ss.str(); | |
530 | mon->reply_command(op, -EINVAL, rs, rdata, get_last_committed()); | |
531 | return true; | |
532 | } | |
533 | ||
534 | string prefix; | |
535 | cmd_getval(g_ceph_context, cmdmap, "prefix", prefix); | |
536 | if (prefix == "auth add" || | |
537 | prefix == "auth del" || | |
538 | prefix == "auth rm" || | |
539 | prefix == "auth get-or-create" || | |
540 | prefix == "auth get-or-create-key" || | |
541 | prefix == "auth import" || | |
542 | prefix == "auth caps") { | |
543 | return false; | |
544 | } | |
545 | ||
546 | MonSession *session = m->get_session(); | |
547 | if (!session) { | |
548 | mon->reply_command(op, -EACCES, "access denied", rdata, get_last_committed()); | |
549 | return true; | |
550 | } | |
551 | ||
552 | // entity might not be supplied, but if it is, it should be valid | |
553 | string entity_name; | |
554 | cmd_getval(g_ceph_context, cmdmap, "entity", entity_name); | |
555 | EntityName entity; | |
556 | if (!entity_name.empty() && !entity.from_str(entity_name)) { | |
557 | ss << "invalid entity_auth " << entity_name; | |
558 | mon->reply_command(op, -EINVAL, ss.str(), get_last_committed()); | |
559 | return true; | |
560 | } | |
561 | ||
562 | string format; | |
563 | cmd_getval(g_ceph_context, cmdmap, "format", format, string("plain")); | |
564 | boost::scoped_ptr<Formatter> f(Formatter::create(format)); | |
565 | ||
566 | if (prefix == "auth export") { | |
567 | KeyRing keyring; | |
568 | export_keyring(keyring); | |
569 | if (!entity_name.empty()) { | |
570 | EntityAuth eauth; | |
571 | if (keyring.get_auth(entity, eauth)) { | |
572 | KeyRing kr; | |
573 | kr.add(entity, eauth); | |
574 | if (f) | |
575 | kr.encode_formatted("auth", f.get(), rdata); | |
576 | else | |
577 | kr.encode_plaintext(rdata); | |
578 | ss << "export " << eauth; | |
579 | r = 0; | |
580 | } else { | |
581 | ss << "no key for " << eauth; | |
582 | r = -ENOENT; | |
583 | } | |
584 | } else { | |
585 | if (f) | |
586 | keyring.encode_formatted("auth", f.get(), rdata); | |
587 | else | |
588 | keyring.encode_plaintext(rdata); | |
589 | ||
590 | ss << "exported master keyring"; | |
591 | r = 0; | |
592 | } | |
593 | } else if (prefix == "auth get" && !entity_name.empty()) { | |
594 | KeyRing keyring; | |
595 | EntityAuth entity_auth; | |
596 | if(!mon->key_server.get_auth(entity, entity_auth)) { | |
597 | ss << "failed to find " << entity_name << " in keyring"; | |
598 | r = -ENOENT; | |
599 | } else { | |
600 | keyring.add(entity, entity_auth); | |
601 | if (f) | |
602 | keyring.encode_formatted("auth", f.get(), rdata); | |
603 | else | |
604 | keyring.encode_plaintext(rdata); | |
605 | ss << "exported keyring for " << entity_name; | |
606 | r = 0; | |
607 | } | |
608 | } else if (prefix == "auth print-key" || | |
609 | prefix == "auth print_key" || | |
610 | prefix == "auth get-key") { | |
611 | EntityAuth auth; | |
612 | if (!mon->key_server.get_auth(entity, auth)) { | |
613 | ss << "don't have " << entity; | |
614 | r = -ENOENT; | |
615 | goto done; | |
616 | } | |
617 | if (f) { | |
618 | auth.key.encode_formatted("auth", f.get(), rdata); | |
619 | } else { | |
620 | auth.key.encode_plaintext(rdata); | |
621 | } | |
622 | r = 0; | |
c07f9fc5 FG |
623 | } else if (prefix == "auth list" || |
624 | prefix == "auth ls") { | |
7c673cae FG |
625 | if (f) { |
626 | mon->key_server.encode_formatted("auth", f.get(), rdata); | |
627 | } else { | |
628 | mon->key_server.encode_plaintext(rdata); | |
629 | if (rdata.length() > 0) | |
630 | ss << "installed auth entries:" << std::endl; | |
631 | else | |
632 | ss << "no installed auth entries!" << std::endl; | |
633 | } | |
634 | r = 0; | |
635 | goto done; | |
636 | } else { | |
637 | ss << "invalid command"; | |
638 | r = -EINVAL; | |
639 | } | |
640 | ||
641 | done: | |
642 | rdata.append(ds); | |
643 | string rs; | |
644 | getline(ss, rs, '\0'); | |
645 | mon->reply_command(op, r, rs, rdata, get_last_committed()); | |
646 | return true; | |
647 | } | |
648 | ||
649 | void AuthMonitor::export_keyring(KeyRing& keyring) | |
650 | { | |
651 | mon->key_server.export_keyring(keyring); | |
652 | } | |
653 | ||
654 | int AuthMonitor::import_keyring(KeyRing& keyring) | |
655 | { | |
656 | for (map<EntityName, EntityAuth>::iterator p = keyring.get_keys().begin(); | |
657 | p != keyring.get_keys().end(); | |
658 | ++p) { | |
659 | if (p->second.caps.empty()) { | |
660 | dout(0) << "import: no caps supplied" << dendl; | |
661 | return -EINVAL; | |
662 | } | |
31f18b77 FG |
663 | int err = add_entity(p->first, p->second); |
664 | assert(err == 0); | |
665 | } | |
666 | return 0; | |
667 | } | |
668 | ||
669 | int AuthMonitor::remove_entity(const EntityName &entity) | |
670 | { | |
671 | dout(10) << __func__ << " " << entity << dendl; | |
672 | if (!mon->key_server.contains(entity)) | |
673 | return -ENOENT; | |
674 | ||
675 | KeyServerData::Incremental auth_inc; | |
676 | auth_inc.name = entity; | |
677 | auth_inc.op = KeyServerData::AUTH_INC_DEL; | |
678 | push_cephx_inc(auth_inc); | |
679 | ||
680 | return 0; | |
681 | } | |
682 | ||
683 | bool AuthMonitor::entity_is_pending(EntityName& entity) | |
684 | { | |
685 | // are we about to have it? | |
686 | for (auto& p : pending_auth) { | |
687 | if (p.inc_type == AUTH_DATA) { | |
688 | KeyServerData::Incremental inc; | |
689 | bufferlist::iterator q = p.auth_data.begin(); | |
690 | ::decode(inc, q); | |
691 | if (inc.op == KeyServerData::AUTH_INC_ADD && | |
692 | inc.name == entity) { | |
693 | return true; | |
694 | } | |
695 | } | |
696 | } | |
697 | return false; | |
698 | } | |
699 | ||
700 | int AuthMonitor::exists_and_matches_entity( | |
701 | const auth_entity_t& entity, | |
702 | bool has_secret, | |
703 | stringstream& ss) | |
704 | { | |
705 | return exists_and_matches_entity(entity.name, entity.auth, | |
706 | entity.auth.caps, has_secret, ss); | |
707 | } | |
708 | ||
709 | int AuthMonitor::exists_and_matches_entity( | |
710 | const EntityName& name, | |
711 | const EntityAuth& auth, | |
712 | const map<string,bufferlist>& caps, | |
713 | bool has_secret, | |
714 | stringstream& ss) | |
715 | { | |
716 | ||
717 | dout(20) << __func__ << " entity " << name << " auth " << auth | |
718 | << " caps " << caps << " has_secret " << has_secret << dendl; | |
719 | ||
720 | EntityAuth existing_auth; | |
721 | // does entry already exist? | |
722 | if (mon->key_server.get_auth(name, existing_auth)) { | |
723 | // key match? | |
724 | if (has_secret) { | |
725 | if (existing_auth.key.get_secret().cmp(auth.key.get_secret())) { | |
726 | ss << "entity " << name << " exists but key does not match"; | |
727 | return -EEXIST; | |
728 | } | |
729 | } | |
730 | ||
731 | // caps match? | |
732 | if (caps.size() != existing_auth.caps.size()) { | |
733 | ss << "entity " << name << " exists but caps do not match"; | |
734 | return -EINVAL; | |
735 | } | |
736 | for (auto& it : caps) { | |
737 | if (existing_auth.caps.count(it.first) == 0 || | |
738 | !existing_auth.caps[it.first].contents_equal(it.second)) { | |
739 | ss << "entity " << name << " exists but cap " | |
740 | << it.first << " does not match"; | |
741 | return -EINVAL; | |
742 | } | |
743 | } | |
744 | ||
745 | // they match, no-op | |
746 | return 0; | |
747 | } | |
748 | return -ENOENT; | |
749 | } | |
750 | ||
751 | int AuthMonitor::add_entity( | |
752 | const EntityName& name, | |
753 | const EntityAuth& auth) | |
754 | { | |
755 | ||
756 | // okay, add it. | |
757 | KeyServerData::Incremental auth_inc; | |
758 | auth_inc.op = KeyServerData::AUTH_INC_ADD; | |
759 | auth_inc.name = name; | |
760 | auth_inc.auth = auth; | |
761 | ||
762 | dout(10) << " importing " << auth_inc.name << dendl; | |
763 | dout(30) << " " << auth_inc.auth << dendl; | |
764 | push_cephx_inc(auth_inc); | |
765 | return 0; | |
766 | } | |
767 | ||
768 | int AuthMonitor::validate_osd_destroy( | |
769 | int32_t id, | |
770 | const uuid_d& uuid, | |
771 | EntityName& cephx_entity, | |
772 | EntityName& lockbox_entity, | |
773 | stringstream& ss) | |
774 | { | |
775 | assert(paxos->is_plugged()); | |
776 | ||
777 | dout(10) << __func__ << " id " << id << " uuid " << uuid << dendl; | |
778 | ||
779 | string cephx_str = "osd." + stringify(id); | |
780 | string lockbox_str = "client.osd-lockbox." + stringify(uuid); | |
781 | ||
782 | if (!cephx_entity.from_str(cephx_str)) { | |
783 | dout(10) << __func__ << " invalid cephx entity '" | |
784 | << cephx_str << "'" << dendl; | |
785 | ss << "invalid cephx key entity '" << cephx_str << "'"; | |
786 | return -EINVAL; | |
787 | } | |
788 | ||
789 | if (!lockbox_entity.from_str(lockbox_str)) { | |
790 | dout(10) << __func__ << " invalid lockbox entity '" | |
791 | << lockbox_str << "'" << dendl; | |
792 | ss << "invalid lockbox key entity '" << lockbox_str << "'"; | |
793 | return -EINVAL; | |
794 | } | |
795 | ||
796 | if (!mon->key_server.contains(cephx_entity) && | |
797 | !mon->key_server.contains(lockbox_entity)) { | |
798 | return -ENOENT; | |
799 | } | |
800 | ||
801 | return 0; | |
802 | } | |
803 | ||
804 | int AuthMonitor::do_osd_destroy( | |
805 | const EntityName& cephx_entity, | |
806 | const EntityName& lockbox_entity) | |
807 | { | |
808 | assert(paxos->is_plugged()); | |
809 | ||
810 | dout(10) << __func__ << " cephx " << cephx_entity | |
811 | << " lockbox " << lockbox_entity << dendl; | |
812 | ||
813 | bool removed = false; | |
814 | ||
815 | int err = remove_entity(cephx_entity); | |
816 | if (err == -ENOENT) { | |
817 | dout(10) << __func__ << " " << cephx_entity << " does not exist" << dendl; | |
818 | } else { | |
819 | removed = true; | |
820 | } | |
821 | ||
822 | err = remove_entity(lockbox_entity); | |
823 | if (err == -ENOENT) { | |
824 | dout(10) << __func__ << " " << lockbox_entity << " does not exist" << dendl; | |
825 | } else { | |
826 | removed = true; | |
827 | } | |
828 | ||
829 | if (!removed) { | |
830 | dout(10) << __func__ << " entities do not exist -- no-op." << dendl; | |
831 | return 0; | |
832 | } | |
833 | ||
834 | // given we have paxos plugged, this will not result in a proposal | |
835 | // being triggered, but it will still be needed so that we get our | |
836 | // pending state encoded into the paxos' pending transaction. | |
837 | propose_pending(); | |
838 | return 0; | |
839 | } | |
840 | ||
841 | bufferlist _encode_cap(const string& cap) | |
842 | { | |
843 | bufferlist bl; | |
844 | ::encode(cap, bl); | |
845 | return bl; | |
846 | } | |
847 | ||
848 | int _create_auth( | |
849 | EntityAuth& auth, | |
850 | const string& key, | |
851 | const map<string,bufferlist>& caps) | |
852 | { | |
853 | if (key.empty()) | |
854 | return -EINVAL; | |
855 | try { | |
856 | auth.key.decode_base64(key); | |
857 | } catch (buffer::error& e) { | |
858 | return -EINVAL; | |
7c673cae | 859 | } |
31f18b77 FG |
860 | auth.caps = caps; |
861 | return 0; | |
862 | } | |
863 | ||
864 | int AuthMonitor::validate_osd_new( | |
865 | int32_t id, | |
866 | const uuid_d& uuid, | |
867 | const string& cephx_secret, | |
868 | const string& lockbox_secret, | |
869 | auth_entity_t& cephx_entity, | |
870 | auth_entity_t& lockbox_entity, | |
871 | stringstream& ss) | |
872 | { | |
873 | ||
874 | dout(10) << __func__ << " osd." << id << " uuid " << uuid << dendl; | |
875 | ||
876 | map<string,bufferlist> cephx_caps = { | |
877 | { "osd", _encode_cap("allow *") }, | |
878 | { "mon", _encode_cap("allow profile osd") }, | |
879 | { "mgr", _encode_cap("allow profile osd") } | |
880 | }; | |
881 | map<string,bufferlist> lockbox_caps = { | |
882 | { "mon", _encode_cap("allow command \"config-key get\" " | |
883 | "with key=\"dm-crypt/osd/" + | |
884 | stringify(uuid) + | |
885 | "/luks\"") } | |
886 | }; | |
887 | ||
888 | bool has_lockbox = !lockbox_secret.empty(); | |
889 | ||
890 | string cephx_name = "osd." + stringify(id); | |
891 | string lockbox_name = "client.osd-lockbox." + stringify(uuid); | |
892 | ||
893 | if (!cephx_entity.name.from_str(cephx_name)) { | |
894 | dout(10) << __func__ << " invalid cephx entity '" | |
895 | << cephx_name << "'" << dendl; | |
896 | ss << "invalid cephx key entity '" << cephx_name << "'"; | |
897 | return -EINVAL; | |
898 | } | |
899 | ||
900 | if (has_lockbox) { | |
901 | if (!lockbox_entity.name.from_str(lockbox_name)) { | |
902 | dout(10) << __func__ << " invalid cephx lockbox entity '" | |
903 | << lockbox_name << "'" << dendl; | |
904 | ss << "invalid cephx lockbox entity '" << lockbox_name << "'"; | |
905 | return -EINVAL; | |
906 | } | |
907 | } | |
908 | ||
909 | if (entity_is_pending(cephx_entity.name) || | |
910 | (has_lockbox && entity_is_pending(lockbox_entity.name))) { | |
911 | // If we have pending entities for either the cephx secret or the | |
912 | // lockbox secret, then our safest bet is to retry the command at | |
913 | // a later time. These entities may be pending because an `osd new` | |
914 | // command has been run (which is unlikely, due to the nature of | |
915 | // the operation, which will force a paxos proposal), or (more likely) | |
916 | // because a competing client created those entities before we handled | |
917 | // the `osd new` command. Regardless, let's wait and see. | |
918 | return -EAGAIN; | |
919 | } | |
920 | ||
921 | if (!is_valid_cephx_key(cephx_secret)) { | |
922 | ss << "invalid cephx secret."; | |
923 | return -EINVAL; | |
924 | } | |
925 | ||
926 | if (has_lockbox && !is_valid_cephx_key(lockbox_secret)) { | |
927 | ss << "invalid cephx lockbox secret."; | |
928 | return -EINVAL; | |
929 | } | |
930 | ||
931 | int err = _create_auth(cephx_entity.auth, cephx_secret, cephx_caps); | |
932 | assert(0 == err); | |
933 | ||
934 | bool cephx_is_idempotent = false, lockbox_is_idempotent = false; | |
935 | err = exists_and_matches_entity(cephx_entity, true, ss); | |
936 | ||
937 | if (err != -ENOENT) { | |
938 | if (err < 0) { | |
939 | return err; | |
940 | } | |
941 | assert(0 == err); | |
942 | cephx_is_idempotent = true; | |
943 | } | |
944 | ||
945 | if (has_lockbox) { | |
946 | err = _create_auth(lockbox_entity.auth, lockbox_secret, lockbox_caps); | |
947 | assert(err == 0); | |
948 | err = exists_and_matches_entity(lockbox_entity, true, ss); | |
949 | if (err != -ENOENT) { | |
950 | if (err < 0) { | |
951 | return err; | |
952 | } | |
953 | assert(0 == err); | |
954 | lockbox_is_idempotent = true; | |
955 | } | |
956 | } | |
957 | ||
958 | if (cephx_is_idempotent && (!has_lockbox || lockbox_is_idempotent)) { | |
959 | return EEXIST; | |
960 | } | |
961 | ||
962 | return 0; | |
963 | } | |
964 | ||
965 | int AuthMonitor::do_osd_new( | |
966 | const auth_entity_t& cephx_entity, | |
967 | const auth_entity_t& lockbox_entity, | |
968 | bool has_lockbox) | |
969 | { | |
970 | assert(paxos->is_plugged()); | |
971 | ||
972 | dout(10) << __func__ << " cephx " << cephx_entity.name | |
973 | << " lockbox "; | |
974 | if (has_lockbox) { | |
975 | *_dout << lockbox_entity.name; | |
976 | } else { | |
977 | *_dout << "n/a"; | |
978 | } | |
979 | *_dout << dendl; | |
980 | ||
981 | // we must have validated before reaching this point. | |
982 | // if keys exist, then this means they also match; otherwise we would | |
983 | // have failed before calling this function. | |
984 | bool cephx_exists = mon->key_server.contains(cephx_entity.name); | |
985 | ||
986 | if (!cephx_exists) { | |
987 | int err = add_entity(cephx_entity.name, cephx_entity.auth); | |
988 | assert(0 == err); | |
989 | } | |
990 | ||
991 | if (has_lockbox && | |
992 | !mon->key_server.contains(lockbox_entity.name)) { | |
993 | int err = add_entity(lockbox_entity.name, lockbox_entity.auth); | |
994 | assert(0 == err); | |
995 | } | |
996 | ||
997 | // given we have paxos plugged, this will not result in a proposal | |
998 | // being triggered, but it will still be needed so that we get our | |
999 | // pending state encoded into the paxos' pending transaction. | |
1000 | propose_pending(); | |
7c673cae FG |
1001 | return 0; |
1002 | } | |
1003 | ||
1004 | bool AuthMonitor::prepare_command(MonOpRequestRef op) | |
1005 | { | |
1006 | MMonCommand *m = static_cast<MMonCommand*>(op->get_req()); | |
1007 | stringstream ss, ds; | |
1008 | bufferlist rdata; | |
1009 | string rs; | |
1010 | int err = -EINVAL; | |
1011 | ||
1012 | map<string, cmd_vartype> cmdmap; | |
1013 | if (!cmdmap_from_json(m->cmd, &cmdmap, ss)) { | |
1014 | // ss has reason for failure | |
1015 | string rs = ss.str(); | |
1016 | mon->reply_command(op, -EINVAL, rs, rdata, get_last_committed()); | |
1017 | return true; | |
1018 | } | |
1019 | ||
1020 | string prefix; | |
1021 | vector<string>caps_vec; | |
1022 | string entity_name; | |
1023 | EntityName entity; | |
1024 | ||
1025 | cmd_getval(g_ceph_context, cmdmap, "prefix", prefix); | |
1026 | ||
1027 | string format; | |
1028 | cmd_getval(g_ceph_context, cmdmap, "format", format, string("plain")); | |
1029 | boost::scoped_ptr<Formatter> f(Formatter::create(format)); | |
1030 | ||
1031 | MonSession *session = m->get_session(); | |
1032 | if (!session) { | |
1033 | mon->reply_command(op, -EACCES, "access denied", rdata, get_last_committed()); | |
1034 | return true; | |
1035 | } | |
1036 | ||
1037 | cmd_getval(g_ceph_context, cmdmap, "caps", caps_vec); | |
1038 | if ((caps_vec.size() % 2) != 0) { | |
1039 | ss << "bad capabilities request; odd number of arguments"; | |
1040 | err = -EINVAL; | |
1041 | goto done; | |
1042 | } | |
1043 | ||
1044 | cmd_getval(g_ceph_context, cmdmap, "entity", entity_name); | |
1045 | if (!entity_name.empty() && !entity.from_str(entity_name)) { | |
1046 | ss << "bad entity name"; | |
1047 | err = -EINVAL; | |
1048 | goto done; | |
1049 | } | |
1050 | ||
1051 | if (prefix == "auth import") { | |
1052 | bufferlist bl = m->get_data(); | |
1053 | if (bl.length() == 0) { | |
1054 | ss << "auth import: no data supplied"; | |
1055 | getline(ss, rs); | |
1056 | mon->reply_command(op, -EINVAL, rs, get_last_committed()); | |
1057 | return true; | |
1058 | } | |
1059 | bufferlist::iterator iter = bl.begin(); | |
1060 | KeyRing keyring; | |
1061 | try { | |
1062 | ::decode(keyring, iter); | |
1063 | } catch (const buffer::error &ex) { | |
1064 | ss << "error decoding keyring" << " " << ex.what(); | |
1065 | err = -EINVAL; | |
1066 | goto done; | |
1067 | } | |
1068 | err = import_keyring(keyring); | |
1069 | if (err < 0) { | |
1070 | ss << "auth import: no caps supplied"; | |
1071 | getline(ss, rs); | |
1072 | mon->reply_command(op, -EINVAL, rs, get_last_committed()); | |
1073 | return true; | |
1074 | } | |
1075 | ss << "imported keyring"; | |
1076 | getline(ss, rs); | |
1077 | err = 0; | |
1078 | wait_for_finished_proposal(op, new Monitor::C_Command(mon, op, 0, rs, | |
1079 | get_last_committed() + 1)); | |
1080 | return true; | |
1081 | } else if (prefix == "auth add" && !entity_name.empty()) { | |
1082 | /* expected behavior: | |
1083 | * - if command reproduces current state, return 0. | |
1084 | * - if command adds brand new entity, handle it. | |
1085 | * - if command adds new state to existing entity, return error. | |
1086 | */ | |
1087 | KeyServerData::Incremental auth_inc; | |
1088 | auth_inc.name = entity; | |
1089 | bufferlist bl = m->get_data(); | |
1090 | bool has_keyring = (bl.length() > 0); | |
1091 | map<string,bufferlist> new_caps; | |
1092 | ||
1093 | KeyRing new_keyring; | |
1094 | if (has_keyring) { | |
1095 | bufferlist::iterator iter = bl.begin(); | |
1096 | try { | |
1097 | ::decode(new_keyring, iter); | |
1098 | } catch (const buffer::error &ex) { | |
1099 | ss << "error decoding keyring"; | |
1100 | err = -EINVAL; | |
1101 | goto done; | |
1102 | } | |
1103 | } | |
1104 | ||
1105 | // are we about to have it? | |
31f18b77 FG |
1106 | if (entity_is_pending(entity)) { |
1107 | wait_for_finished_proposal(op, | |
1108 | new Monitor::C_Command(mon, op, 0, rs, get_last_committed() + 1)); | |
1109 | return true; | |
7c673cae FG |
1110 | } |
1111 | ||
1112 | // build new caps from provided arguments (if available) | |
1113 | for (vector<string>::iterator it = caps_vec.begin(); | |
1114 | it != caps_vec.end() && (it + 1) != caps_vec.end(); | |
1115 | it += 2) { | |
1116 | string sys = *it; | |
1117 | bufferlist cap; | |
1118 | ::encode(*(it+1), cap); | |
1119 | new_caps[sys] = cap; | |
1120 | } | |
1121 | ||
1122 | // pull info out of provided keyring | |
1123 | EntityAuth new_inc; | |
1124 | if (has_keyring) { | |
1125 | if (!new_keyring.get_auth(auth_inc.name, new_inc)) { | |
1126 | ss << "key for " << auth_inc.name | |
1127 | << " not found in provided keyring"; | |
1128 | err = -EINVAL; | |
1129 | goto done; | |
1130 | } | |
1131 | if (!new_caps.empty() && !new_inc.caps.empty()) { | |
1132 | ss << "caps cannot be specified both in keyring and in command"; | |
1133 | err = -EINVAL; | |
1134 | goto done; | |
1135 | } | |
1136 | if (new_caps.empty()) { | |
1137 | new_caps = new_inc.caps; | |
1138 | } | |
1139 | } | |
1140 | ||
31f18b77 FG |
1141 | err = exists_and_matches_entity(auth_inc.name, new_inc, |
1142 | new_caps, has_keyring, ss); | |
1143 | // if entity/key/caps do not exist in the keyring, just fall through | |
1144 | // and add the entity; otherwise, make sure everything matches (in | |
1145 | // which case it's a no-op), because if not we must fail. | |
1146 | if (err != -ENOENT) { | |
1147 | if (err < 0) { | |
1148 | goto done; | |
7c673cae | 1149 | } |
31f18b77 FG |
1150 | // no-op. |
1151 | assert(err == 0); | |
7c673cae FG |
1152 | goto done; |
1153 | } | |
31f18b77 | 1154 | err = 0; |
7c673cae FG |
1155 | |
1156 | // okay, add it. | |
31f18b77 | 1157 | if (!has_keyring) { |
7c673cae | 1158 | dout(10) << "AuthMonitor::prepare_command generating random key for " |
31f18b77 FG |
1159 | << auth_inc.name << dendl; |
1160 | new_inc.key.create(g_ceph_context, CEPH_CRYPTO_AES); | |
7c673cae | 1161 | } |
31f18b77 | 1162 | new_inc.caps = new_caps; |
7c673cae | 1163 | |
31f18b77 FG |
1164 | err = add_entity(auth_inc.name, new_inc); |
1165 | assert(err == 0); | |
7c673cae FG |
1166 | |
1167 | ss << "added key for " << auth_inc.name; | |
1168 | getline(ss, rs); | |
1169 | wait_for_finished_proposal(op, new Monitor::C_Command(mon, op, 0, rs, | |
1170 | get_last_committed() + 1)); | |
1171 | return true; | |
1172 | } else if ((prefix == "auth get-or-create-key" || | |
1173 | prefix == "auth get-or-create") && | |
1174 | !entity_name.empty()) { | |
1175 | // auth get-or-create <name> [mon osdcapa osd osdcapb ...] | |
1176 | ||
1177 | if (!valid_caps(caps_vec, &ss)) { | |
1178 | err = -EINVAL; | |
1179 | goto done; | |
1180 | } | |
1181 | ||
1182 | // Parse the list of caps into a map | |
1183 | std::map<std::string, bufferlist> wanted_caps; | |
1184 | for (vector<string>::const_iterator it = caps_vec.begin(); | |
1185 | it != caps_vec.end() && (it + 1) != caps_vec.end(); | |
1186 | it += 2) { | |
1187 | const std::string &sys = *it; | |
1188 | bufferlist cap; | |
1189 | ::encode(*(it+1), cap); | |
1190 | wanted_caps[sys] = cap; | |
1191 | } | |
1192 | ||
1193 | // do we have it? | |
1194 | EntityAuth entity_auth; | |
1195 | if (mon->key_server.get_auth(entity, entity_auth)) { | |
1196 | for (const auto &sys_cap : wanted_caps) { | |
1197 | if (entity_auth.caps.count(sys_cap.first) == 0 || | |
1198 | !entity_auth.caps[sys_cap.first].contents_equal(sys_cap.second)) { | |
1199 | ss << "key for " << entity << " exists but cap " << sys_cap.first | |
1200 | << " does not match"; | |
1201 | err = -EINVAL; | |
1202 | goto done; | |
1203 | } | |
1204 | } | |
1205 | ||
1206 | if (prefix == "auth get-or-create-key") { | |
1207 | if (f) { | |
1208 | entity_auth.key.encode_formatted("auth", f.get(), rdata); | |
1209 | } else { | |
1210 | ds << entity_auth.key; | |
1211 | } | |
1212 | } else { | |
1213 | KeyRing kr; | |
1214 | kr.add(entity, entity_auth.key); | |
1215 | if (f) { | |
1216 | kr.set_caps(entity, entity_auth.caps); | |
1217 | kr.encode_formatted("auth", f.get(), rdata); | |
1218 | } else { | |
1219 | kr.encode_plaintext(rdata); | |
1220 | } | |
1221 | } | |
1222 | err = 0; | |
1223 | goto done; | |
1224 | } | |
1225 | ||
1226 | // ...or are we about to? | |
1227 | for (vector<Incremental>::iterator p = pending_auth.begin(); | |
1228 | p != pending_auth.end(); | |
1229 | ++p) { | |
1230 | if (p->inc_type == AUTH_DATA) { | |
1231 | KeyServerData::Incremental auth_inc; | |
1232 | bufferlist::iterator q = p->auth_data.begin(); | |
1233 | ::decode(auth_inc, q); | |
1234 | if (auth_inc.op == KeyServerData::AUTH_INC_ADD && | |
1235 | auth_inc.name == entity) { | |
1236 | wait_for_finished_proposal(op, new Monitor::C_Command(mon, op, 0, rs, | |
1237 | get_last_committed() + 1)); | |
1238 | return true; | |
1239 | } | |
1240 | } | |
1241 | } | |
1242 | ||
1243 | // create it | |
1244 | KeyServerData::Incremental auth_inc; | |
1245 | auth_inc.op = KeyServerData::AUTH_INC_ADD; | |
1246 | auth_inc.name = entity; | |
1247 | auth_inc.auth.key.create(g_ceph_context, CEPH_CRYPTO_AES); | |
1248 | auth_inc.auth.caps = wanted_caps; | |
1249 | ||
1250 | push_cephx_inc(auth_inc); | |
1251 | ||
1252 | if (prefix == "auth get-or-create-key") { | |
1253 | if (f) { | |
1254 | auth_inc.auth.key.encode_formatted("auth", f.get(), rdata); | |
1255 | } else { | |
1256 | ds << auth_inc.auth.key; | |
1257 | } | |
1258 | } else { | |
1259 | KeyRing kr; | |
1260 | kr.add(entity, auth_inc.auth.key); | |
1261 | if (f) { | |
1262 | kr.set_caps(entity, wanted_caps); | |
1263 | kr.encode_formatted("auth", f.get(), rdata); | |
1264 | } else { | |
1265 | kr.encode_plaintext(rdata); | |
1266 | } | |
1267 | } | |
1268 | ||
1269 | rdata.append(ds); | |
1270 | getline(ss, rs); | |
1271 | wait_for_finished_proposal(op, new Monitor::C_Command(mon, op, 0, rs, rdata, | |
1272 | get_last_committed() + 1)); | |
1273 | return true; | |
1274 | } else if (prefix == "auth caps" && !entity_name.empty()) { | |
1275 | KeyServerData::Incremental auth_inc; | |
1276 | auth_inc.name = entity; | |
1277 | if (!mon->key_server.get_auth(auth_inc.name, auth_inc.auth)) { | |
1278 | ss << "couldn't find entry " << auth_inc.name; | |
1279 | err = -ENOENT; | |
1280 | goto done; | |
1281 | } | |
1282 | ||
1283 | if (!valid_caps(caps_vec, &ss)) { | |
1284 | err = -EINVAL; | |
1285 | goto done; | |
1286 | } | |
1287 | ||
1288 | map<string,bufferlist> newcaps; | |
1289 | for (vector<string>::iterator it = caps_vec.begin(); | |
1290 | it != caps_vec.end(); it += 2) | |
1291 | ::encode(*(it+1), newcaps[*it]); | |
1292 | ||
1293 | auth_inc.op = KeyServerData::AUTH_INC_ADD; | |
1294 | auth_inc.auth.caps = newcaps; | |
1295 | push_cephx_inc(auth_inc); | |
1296 | ||
1297 | ss << "updated caps for " << auth_inc.name; | |
1298 | getline(ss, rs); | |
1299 | wait_for_finished_proposal(op, new Monitor::C_Command(mon, op, 0, rs, | |
1300 | get_last_committed() + 1)); | |
1301 | return true; | |
1302 | } else if ((prefix == "auth del" || prefix == "auth rm") && | |
1303 | !entity_name.empty()) { | |
1304 | KeyServerData::Incremental auth_inc; | |
1305 | auth_inc.name = entity; | |
1306 | if (!mon->key_server.contains(auth_inc.name)) { | |
1307 | ss << "entity " << entity << " does not exist"; | |
1308 | err = 0; | |
1309 | goto done; | |
1310 | } | |
1311 | auth_inc.op = KeyServerData::AUTH_INC_DEL; | |
1312 | push_cephx_inc(auth_inc); | |
1313 | ||
1314 | ss << "updated"; | |
1315 | getline(ss, rs); | |
1316 | wait_for_finished_proposal(op, new Monitor::C_Command(mon, op, 0, rs, | |
1317 | get_last_committed() + 1)); | |
1318 | return true; | |
1319 | } | |
7c673cae FG |
1320 | done: |
1321 | rdata.append(ds); | |
1322 | getline(ss, rs, '\0'); | |
1323 | mon->reply_command(op, err, rs, rdata, get_last_committed()); | |
1324 | return false; | |
1325 | } | |
1326 | ||
1327 | bool AuthMonitor::prepare_global_id(MonOpRequestRef op) | |
1328 | { | |
1329 | dout(10) << "AuthMonitor::prepare_global_id" << dendl; | |
1330 | increase_max_global_id(); | |
1331 | ||
1332 | return true; | |
1333 | } | |
1334 | ||
1335 | void AuthMonitor::upgrade_format() | |
1336 | { | |
1337 | unsigned int current = 2; | |
1338 | if (!mon->get_quorum_mon_features().contains_all( | |
1339 | ceph::features::mon::FEATURE_LUMINOUS)) { | |
1340 | current = 1; | |
1341 | } | |
1342 | if (format_version >= current) { | |
1343 | dout(20) << __func__ << " format " << format_version << " is current" << dendl; | |
1344 | return; | |
1345 | } | |
1346 | ||
1347 | bool changed = false; | |
1348 | if (format_version == 0) { | |
1349 | dout(1) << __func__ << " upgrading from format 0 to 1" << dendl; | |
1350 | map<EntityName, EntityAuth>::iterator p; | |
1351 | for (p = mon->key_server.secrets_begin(); | |
1352 | p != mon->key_server.secrets_end(); | |
1353 | ++p) { | |
1354 | // grab mon caps, if any | |
1355 | string mon_caps; | |
1356 | if (p->second.caps.count("mon") == 0) | |
1357 | continue; | |
1358 | try { | |
1359 | bufferlist::iterator it = p->second.caps["mon"].begin(); | |
1360 | ::decode(mon_caps, it); | |
1361 | } | |
1362 | catch (buffer::error) { | |
1363 | dout(10) << __func__ << " unable to parse mon cap for " | |
1364 | << p->first << dendl; | |
1365 | continue; | |
1366 | } | |
1367 | ||
1368 | string n = p->first.to_str(); | |
1369 | string new_caps; | |
1370 | ||
1371 | // set daemon profiles | |
1372 | if ((p->first.is_osd() || p->first.is_mds()) && | |
1373 | mon_caps == "allow rwx") { | |
1374 | new_caps = string("allow profile ") + string(p->first.get_type_name()); | |
1375 | } | |
1376 | ||
1377 | // update bootstrap keys | |
1378 | if (n == "client.bootstrap-osd") { | |
1379 | new_caps = "allow profile bootstrap-osd"; | |
1380 | } | |
1381 | if (n == "client.bootstrap-mds") { | |
1382 | new_caps = "allow profile bootstrap-mds"; | |
1383 | } | |
1384 | ||
1385 | if (new_caps.length() > 0) { | |
1386 | dout(5) << __func__ << " updating " << p->first << " mon cap from " | |
1387 | << mon_caps << " to " << new_caps << dendl; | |
1388 | ||
1389 | bufferlist bl; | |
1390 | ::encode(new_caps, bl); | |
1391 | ||
1392 | KeyServerData::Incremental auth_inc; | |
1393 | auth_inc.name = p->first; | |
1394 | auth_inc.auth = p->second; | |
1395 | auth_inc.auth.caps["mon"] = bl; | |
1396 | auth_inc.op = KeyServerData::AUTH_INC_ADD; | |
1397 | push_cephx_inc(auth_inc); | |
1398 | changed = true; | |
1399 | } | |
1400 | } | |
1401 | } | |
1402 | ||
1403 | if (format_version == 1) { | |
1404 | dout(1) << __func__ << " upgrading from format 1 to 2" << dendl; | |
1405 | map<EntityName, EntityAuth>::iterator p; | |
1406 | for (p = mon->key_server.secrets_begin(); | |
1407 | p != mon->key_server.secrets_end(); | |
1408 | ++p) { | |
1409 | string n = p->first.to_str(); | |
1410 | ||
1411 | string newcap; | |
1412 | if (n == "client.admin") { | |
1413 | // admin gets it all | |
1414 | newcap = "allow *"; | |
1415 | } else if (n.find("osd.") == 0 || | |
1416 | n.find("mds.") == 0 || | |
1417 | n.find("mon.") == 0) { | |
1418 | // daemons follow their profile | |
1419 | string type = n.substr(0, 3); | |
1420 | newcap = "allow profile " + type; | |
1421 | } else if (p->second.caps.count("mon")) { | |
1422 | // if there are any mon caps, give them 'r' mgr caps | |
1423 | newcap = "allow r"; | |
1424 | } | |
1425 | ||
1426 | if (newcap.length() > 0) { | |
1427 | dout(5) << " giving " << n << " mgr '" << newcap << "'" << dendl; | |
1428 | bufferlist bl; | |
1429 | ::encode(newcap, bl); | |
1430 | ||
1431 | KeyServerData::Incremental auth_inc; | |
1432 | auth_inc.name = p->first; | |
1433 | auth_inc.auth = p->second; | |
1434 | auth_inc.auth.caps["mgr"] = bl; | |
1435 | auth_inc.op = KeyServerData::AUTH_INC_ADD; | |
1436 | push_cephx_inc(auth_inc); | |
1437 | } | |
1438 | ||
1439 | if (n.find("mgr.") == 0 && | |
1440 | p->second.caps.count("mon")) { | |
1441 | // the kraken ceph-mgr@.service set the mon cap to 'allow *'. | |
1442 | auto blp = p->second.caps["mon"].begin(); | |
1443 | string oldcaps; | |
1444 | ::decode(oldcaps, blp); | |
1445 | if (oldcaps == "allow *") { | |
1446 | dout(5) << " fixing " << n << " mon cap to 'allow profile mgr'" | |
1447 | << dendl; | |
1448 | bufferlist bl; | |
1449 | ::encode("allow profile mgr", bl); | |
1450 | KeyServerData::Incremental auth_inc; | |
1451 | auth_inc.name = p->first; | |
1452 | auth_inc.auth = p->second; | |
1453 | auth_inc.auth.caps["mon"] = bl; | |
1454 | auth_inc.op = KeyServerData::AUTH_INC_ADD; | |
1455 | push_cephx_inc(auth_inc); | |
1456 | } | |
1457 | } | |
1458 | } | |
1459 | ||
1460 | // add bootstrap key | |
1461 | { | |
1462 | KeyServerData::Incremental auth_inc; | |
1463 | bool r = auth_inc.name.from_str("client.bootstrap-mgr"); | |
1464 | assert(r); | |
1465 | ::encode("allow profile bootstrap-mgr", auth_inc.auth.caps["mon"]); | |
1466 | auth_inc.op = KeyServerData::AUTH_INC_ADD; | |
c07f9fc5 FG |
1467 | // generate key |
1468 | auth_inc.auth.key.create(g_ceph_context, CEPH_CRYPTO_AES); | |
7c673cae FG |
1469 | push_cephx_inc(auth_inc); |
1470 | } | |
1471 | changed = true; | |
1472 | } | |
1473 | ||
1474 | if (changed) { | |
1475 | // note new format | |
1476 | dout(10) << __func__ << " proposing update from format " << format_version | |
1477 | << " -> " << current << dendl; | |
1478 | format_version = current; | |
1479 | propose_pending(); | |
1480 | } | |
1481 | } | |
1482 | ||
1483 | void AuthMonitor::dump_info(Formatter *f) | |
1484 | { | |
1485 | /*** WARNING: do not include any privileged information here! ***/ | |
1486 | f->open_object_section("auth"); | |
1487 | f->dump_unsigned("first_committed", get_first_committed()); | |
1488 | f->dump_unsigned("last_committed", get_last_committed()); | |
1489 | f->dump_unsigned("num_secrets", mon->key_server.get_num_secrets()); | |
1490 | f->close_section(); | |
1491 | } |