]>
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-2009 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 "common/config.h" | |
16 | #include "CephxKeyServer.h" | |
17 | #include "common/dout.h" | |
18 | #include <sstream> | |
19 | ||
20 | #define dout_subsys ceph_subsys_auth | |
21 | #undef dout_prefix | |
22 | #define dout_prefix *_dout << "cephx keyserverdata: " | |
23 | ||
f67539c2 TL |
24 | using std::ostringstream; |
25 | using std::string; | |
26 | using std::stringstream; | |
27 | ||
28 | using ceph::bufferptr; | |
29 | using ceph::bufferlist; | |
30 | using ceph::Formatter; | |
31 | ||
7c673cae | 32 | bool KeyServerData::get_service_secret(CephContext *cct, uint32_t service_id, |
c5c27e9a TL |
33 | CryptoKey& secret, uint64_t& secret_id, |
34 | double& ttl) const | |
7c673cae | 35 | { |
f67539c2 | 36 | auto iter = rotating_secrets.find(service_id); |
7c673cae FG |
37 | if (iter == rotating_secrets.end()) { |
38 | ldout(cct, 10) << "get_service_secret service " << ceph_entity_type_name(service_id) << " not found " << dendl; | |
39 | return false; | |
40 | } | |
41 | ||
42 | const RotatingSecrets& secrets = iter->second; | |
43 | ||
44 | // second to oldest, unless it's expired | |
f67539c2 | 45 | auto riter = secrets.secrets.begin(); |
7c673cae FG |
46 | if (secrets.secrets.size() > 1) |
47 | ++riter; | |
48 | ||
c5c27e9a TL |
49 | utime_t now = ceph_clock_now(); |
50 | if (riter->second.expiration < now) | |
7c673cae FG |
51 | ++riter; // "current" key has expired, use "next" key instead |
52 | ||
53 | secret_id = riter->first; | |
c5c27e9a | 54 | secret = riter->second.key; |
7c673cae | 55 | |
c5c27e9a TL |
56 | // ttl may have just been increased by the user |
57 | // cap it by expiration of "next" key to prevent handing out a ticket | |
58 | // with a bogus, possibly way into the future, validity | |
59 | ttl = service_id == CEPH_ENTITY_TYPE_AUTH ? | |
60 | cct->_conf->auth_mon_ticket_ttl : cct->_conf->auth_service_ticket_ttl; | |
20effc67 | 61 | ttl = std::min(ttl, static_cast<double>( |
c5c27e9a TL |
62 | secrets.secrets.rbegin()->second.expiration - now)); |
63 | ||
64 | ldout(cct, 30) << __func__ << " service " | |
65 | << ceph_entity_type_name(service_id) << " secret_id " | |
66 | << secret_id << " " << riter->second << " ttl " << ttl | |
67 | << dendl; | |
7c673cae FG |
68 | return true; |
69 | } | |
70 | ||
71 | bool KeyServerData::get_service_secret(CephContext *cct, uint32_t service_id, | |
72 | uint64_t secret_id, CryptoKey& secret) const | |
73 | { | |
f67539c2 | 74 | auto iter = rotating_secrets.find(service_id); |
a4b75251 TL |
75 | if (iter == rotating_secrets.end()) { |
76 | ldout(cct, 10) << __func__ << " no rotating_secrets for service " << service_id | |
77 | << " " << ceph_entity_type_name(service_id) << dendl; | |
7c673cae | 78 | return false; |
a4b75251 | 79 | } |
7c673cae FG |
80 | |
81 | const RotatingSecrets& secrets = iter->second; | |
f67539c2 | 82 | auto riter = secrets.secrets.find(secret_id); |
7c673cae FG |
83 | |
84 | if (riter == secrets.secrets.end()) { | |
85 | ldout(cct, 10) << "get_service_secret service " << ceph_entity_type_name(service_id) | |
86 | << " secret " << secret_id << " not found" << dendl; | |
87 | ldout(cct, 30) << " I have:" << dendl; | |
f67539c2 TL |
88 | for (auto iter = secrets.secrets.begin(); |
89 | iter != secrets.secrets.end(); | |
90 | ++iter) | |
7c673cae FG |
91 | ldout(cct, 30) << " id " << iter->first << " " << iter->second << dendl; |
92 | return false; | |
93 | } | |
94 | ||
95 | secret = riter->second.key; | |
96 | ||
97 | return true; | |
98 | } | |
99 | bool KeyServerData::get_auth(const EntityName& name, EntityAuth& auth) const { | |
f67539c2 | 100 | auto iter = secrets.find(name); |
7c673cae FG |
101 | if (iter != secrets.end()) { |
102 | auth = iter->second; | |
103 | return true; | |
104 | } | |
105 | return extra_secrets->get_auth(name, auth); | |
106 | } | |
107 | ||
108 | bool KeyServerData::get_secret(const EntityName& name, CryptoKey& secret) const { | |
f67539c2 | 109 | auto iter = secrets.find(name); |
7c673cae FG |
110 | if (iter != secrets.end()) { |
111 | secret = iter->second.key; | |
112 | return true; | |
113 | } | |
114 | return extra_secrets->get_secret(name, secret); | |
115 | } | |
116 | ||
117 | bool KeyServerData::get_caps(CephContext *cct, const EntityName& name, | |
118 | const string& type, AuthCapsInfo& caps_info) const | |
119 | { | |
120 | caps_info.allow_all = false; | |
121 | ||
122 | ldout(cct, 10) << "get_caps: name=" << name.to_str() << dendl; | |
f67539c2 | 123 | auto iter = secrets.find(name); |
7c673cae | 124 | if (iter != secrets.end()) { |
39ae355f | 125 | ldout(cct, 10) << "get_caps: num of caps=" << iter->second.caps.size() << dendl; |
f67539c2 | 126 | auto capsiter = iter->second.caps.find(type); |
7c673cae FG |
127 | if (capsiter != iter->second.caps.end()) { |
128 | caps_info.caps = capsiter->second; | |
129 | } | |
130 | return true; | |
131 | } | |
132 | ||
133 | return extra_secrets->get_caps(name, type, caps_info); | |
134 | } | |
135 | ||
136 | ||
137 | #undef dout_prefix | |
138 | #define dout_prefix *_dout << "cephx keyserver: " | |
139 | ||
140 | ||
141 | KeyServer::KeyServer(CephContext *cct_, KeyRing *extra_secrets) | |
142 | : cct(cct_), | |
143 | data(extra_secrets), | |
11fdf7f2 | 144 | lock{ceph::make_mutex("KeyServer::lock")} |
7c673cae FG |
145 | { |
146 | } | |
147 | ||
148 | int KeyServer::start_server() | |
149 | { | |
11fdf7f2 | 150 | std::scoped_lock l{lock}; |
7c673cae FG |
151 | _dump_rotating_secrets(); |
152 | return 0; | |
153 | } | |
154 | ||
a4b75251 | 155 | void KeyServer::dump() |
7c673cae | 156 | { |
a4b75251 | 157 | _dump_rotating_secrets(); |
7c673cae FG |
158 | } |
159 | ||
160 | void KeyServer::_dump_rotating_secrets() | |
161 | { | |
162 | ldout(cct, 30) << "_dump_rotating_secrets" << dendl; | |
f67539c2 | 163 | for (auto iter = data.rotating_secrets.begin(); |
7c673cae FG |
164 | iter != data.rotating_secrets.end(); |
165 | ++iter) { | |
166 | RotatingSecrets& key = iter->second; | |
f67539c2 | 167 | for (auto mapiter = key.secrets.begin(); |
7c673cae FG |
168 | mapiter != key.secrets.end(); |
169 | ++mapiter) | |
170 | ldout(cct, 30) << "service " << ceph_entity_type_name(iter->first) | |
171 | << " id " << mapiter->first | |
172 | << " key " << mapiter->second << dendl; | |
173 | } | |
174 | } | |
175 | ||
a4b75251 | 176 | int KeyServer::_rotate_secret(uint32_t service_id, KeyServerData &pending_data) |
7c673cae | 177 | { |
a4b75251 | 178 | RotatingSecrets& r = pending_data.rotating_secrets[service_id]; |
7c673cae FG |
179 | int added = 0; |
180 | utime_t now = ceph_clock_now(); | |
181 | double ttl = service_id == CEPH_ENTITY_TYPE_AUTH ? cct->_conf->auth_mon_ticket_ttl : cct->_conf->auth_service_ticket_ttl; | |
182 | ||
183 | while (r.need_new_secrets(now)) { | |
184 | ExpiringCryptoKey ek; | |
185 | generate_secret(ek.key); | |
186 | if (r.empty()) { | |
187 | ek.expiration = now; | |
188 | } else { | |
189 | utime_t next_ttl = now; | |
190 | next_ttl += ttl; | |
11fdf7f2 | 191 | ek.expiration = std::max(next_ttl, r.next().expiration); |
7c673cae FG |
192 | } |
193 | ek.expiration += ttl; | |
194 | uint64_t secret_id = r.add(ek); | |
195 | ldout(cct, 10) << "_rotate_secret adding " << ceph_entity_type_name(service_id) << dendl; | |
196 | ldout(cct, 30) << "_rotate_secret adding " << ceph_entity_type_name(service_id) | |
197 | << " id " << secret_id << " " << ek | |
198 | << dendl; | |
199 | added++; | |
200 | } | |
201 | return added; | |
202 | } | |
203 | ||
204 | bool KeyServer::get_secret(const EntityName& name, CryptoKey& secret) const | |
205 | { | |
11fdf7f2 | 206 | std::scoped_lock l{lock}; |
7c673cae FG |
207 | return data.get_secret(name, secret); |
208 | } | |
209 | ||
210 | bool KeyServer::get_auth(const EntityName& name, EntityAuth& auth) const | |
211 | { | |
11fdf7f2 | 212 | std::scoped_lock l{lock}; |
7c673cae FG |
213 | return data.get_auth(name, auth); |
214 | } | |
215 | ||
216 | bool KeyServer::get_caps(const EntityName& name, const string& type, | |
217 | AuthCapsInfo& caps_info) const | |
218 | { | |
11fdf7f2 | 219 | std::scoped_lock l{lock}; |
7c673cae FG |
220 | |
221 | return data.get_caps(cct, name, type, caps_info); | |
222 | } | |
223 | ||
c5c27e9a TL |
224 | bool KeyServer::get_service_secret(uint32_t service_id, CryptoKey& secret, |
225 | uint64_t& secret_id, double& ttl) const | |
7c673cae | 226 | { |
11fdf7f2 | 227 | std::scoped_lock l{lock}; |
7c673cae | 228 | |
c5c27e9a | 229 | return data.get_service_secret(cct, service_id, secret, secret_id, ttl); |
7c673cae FG |
230 | } |
231 | ||
232 | bool KeyServer::get_service_secret(uint32_t service_id, | |
233 | uint64_t secret_id, CryptoKey& secret) const | |
234 | { | |
11fdf7f2 | 235 | std::scoped_lock l{lock}; |
7c673cae FG |
236 | |
237 | return data.get_service_secret(cct, service_id, secret_id, secret); | |
238 | } | |
239 | ||
39ae355f TL |
240 | void KeyServer::note_used_pending_key(const EntityName& name, const CryptoKey& key) |
241 | { | |
242 | std::scoped_lock l(lock); | |
243 | used_pending_keys[name] = key; | |
244 | } | |
245 | ||
246 | void KeyServer::clear_used_pending_keys() | |
247 | { | |
248 | std::scoped_lock l(lock); | |
249 | used_pending_keys.clear(); | |
250 | } | |
251 | ||
252 | std::map<EntityName,CryptoKey> KeyServer::get_used_pending_keys() | |
253 | { | |
254 | std::map<EntityName,CryptoKey> ret; | |
255 | std::scoped_lock l(lock); | |
256 | ret.swap(used_pending_keys); | |
257 | return ret; | |
258 | } | |
259 | ||
7c673cae FG |
260 | bool KeyServer::generate_secret(CryptoKey& secret) |
261 | { | |
262 | bufferptr bp; | |
263 | CryptoHandler *crypto = cct->get_crypto_handler(CEPH_CRYPTO_AES); | |
264 | if (!crypto) | |
265 | return false; | |
266 | ||
11fdf7f2 | 267 | if (crypto->create(cct->random(), bp) < 0) |
7c673cae FG |
268 | return false; |
269 | ||
270 | secret.set_secret(CEPH_CRYPTO_AES, bp, ceph_clock_now()); | |
271 | ||
272 | return true; | |
273 | } | |
274 | ||
275 | bool KeyServer::generate_secret(EntityName& name, CryptoKey& secret) | |
276 | { | |
277 | if (!generate_secret(secret)) | |
278 | return false; | |
279 | ||
11fdf7f2 | 280 | std::scoped_lock l{lock}; |
7c673cae FG |
281 | |
282 | EntityAuth auth; | |
283 | auth.key = secret; | |
284 | ||
285 | data.add_auth(name, auth); | |
286 | ||
287 | return true; | |
288 | } | |
289 | ||
290 | bool KeyServer::contains(const EntityName& name) const | |
291 | { | |
11fdf7f2 | 292 | std::scoped_lock l{lock}; |
7c673cae FG |
293 | |
294 | return data.contains(name); | |
295 | } | |
296 | ||
297 | int KeyServer::encode_secrets(Formatter *f, stringstream *ds) const | |
298 | { | |
11fdf7f2 | 299 | std::scoped_lock l{lock}; |
f67539c2 | 300 | auto mapiter = data.secrets_begin(); |
7c673cae FG |
301 | |
302 | if (mapiter == data.secrets_end()) | |
303 | return -ENOENT; | |
304 | ||
305 | if (f) | |
306 | f->open_array_section("auth_dump"); | |
307 | ||
308 | while (mapiter != data.secrets_end()) { | |
309 | const EntityName& name = mapiter->first; | |
310 | if (ds) { | |
311 | *ds << name.to_str() << std::endl; | |
312 | *ds << "\tkey: " << mapiter->second.key << std::endl; | |
7c673cae FG |
313 | } |
314 | if (f) { | |
315 | f->open_object_section("auth_entities"); | |
316 | f->dump_string("entity", name.to_str()); | |
317 | f->dump_stream("key") << mapiter->second.key; | |
7c673cae FG |
318 | f->open_object_section("caps"); |
319 | } | |
320 | ||
f67539c2 | 321 | auto capsiter = mapiter->second.caps.begin(); |
7c673cae FG |
322 | for (; capsiter != mapiter->second.caps.end(); ++capsiter) { |
323 | // FIXME: need a const_iterator for bufferlist, but it doesn't exist yet. | |
324 | bufferlist *bl = const_cast<bufferlist*>(&capsiter->second); | |
11fdf7f2 | 325 | auto dataiter = bl->cbegin(); |
7c673cae | 326 | string caps; |
11fdf7f2 TL |
327 | using ceph::decode; |
328 | decode(caps, dataiter); | |
7c673cae FG |
329 | if (ds) |
330 | *ds << "\tcaps: [" << capsiter->first << "] " << caps << std::endl; | |
331 | if (f) | |
332 | f->dump_string(capsiter->first.c_str(), caps); | |
333 | } | |
334 | if (f) { | |
335 | f->close_section(); // caps | |
336 | f->close_section(); // auth_entities | |
337 | } | |
338 | ||
339 | ++mapiter; | |
340 | } | |
341 | ||
342 | if (f) | |
343 | f->close_section(); // auth_dump | |
344 | return 0; | |
345 | } | |
346 | ||
347 | void KeyServer::encode_formatted(string label, Formatter *f, bufferlist &bl) | |
348 | { | |
11fdf7f2 | 349 | ceph_assert(f != NULL); |
7c673cae FG |
350 | f->open_object_section(label.c_str()); |
351 | encode_secrets(f, NULL); | |
352 | f->close_section(); | |
353 | f->flush(bl); | |
354 | } | |
355 | ||
356 | void KeyServer::encode_plaintext(bufferlist &bl) | |
357 | { | |
358 | stringstream os; | |
359 | encode_secrets(NULL, &os); | |
360 | bl.append(os.str()); | |
361 | } | |
362 | ||
a4b75251 | 363 | bool KeyServer::prepare_rotating_update(bufferlist& rotating_bl) |
7c673cae | 364 | { |
11fdf7f2 | 365 | std::scoped_lock l{lock}; |
a4b75251 TL |
366 | ldout(cct, 20) << __func__ << " before: data.rotating_ver=" << data.rotating_ver |
367 | << dendl; | |
7c673cae | 368 | |
a4b75251 TL |
369 | KeyServerData pending_data(nullptr); |
370 | pending_data.rotating_ver = data.rotating_ver + 1; | |
371 | pending_data.rotating_secrets = data.rotating_secrets; | |
7c673cae | 372 | |
a4b75251 TL |
373 | int added = 0; |
374 | added += _rotate_secret(CEPH_ENTITY_TYPE_AUTH, pending_data); | |
375 | added += _rotate_secret(CEPH_ENTITY_TYPE_MON, pending_data); | |
376 | added += _rotate_secret(CEPH_ENTITY_TYPE_OSD, pending_data); | |
377 | added += _rotate_secret(CEPH_ENTITY_TYPE_MDS, pending_data); | |
378 | added += _rotate_secret(CEPH_ENTITY_TYPE_MGR, pending_data); | |
379 | if (!added) { | |
7c673cae | 380 | return false; |
a4b75251 | 381 | } |
7c673cae | 382 | |
a4b75251 TL |
383 | ldout(cct, 20) << __func__ << " after: pending_data.rotating_ver=" |
384 | << pending_data.rotating_ver | |
385 | << dendl; | |
386 | pending_data.encode_rotating(rotating_bl); | |
7c673cae FG |
387 | return true; |
388 | } | |
389 | ||
390 | bool KeyServer::get_rotating_encrypted(const EntityName& name, | |
391 | bufferlist& enc_bl) const | |
392 | { | |
11fdf7f2 | 393 | std::scoped_lock l{lock}; |
7c673cae | 394 | |
f67539c2 | 395 | auto mapiter = data.find_name(name); |
7c673cae FG |
396 | if (mapiter == data.secrets_end()) |
397 | return false; | |
398 | ||
399 | const CryptoKey& specific_key = mapiter->second.key; | |
400 | ||
f67539c2 | 401 | auto rotate_iter = data.rotating_secrets.find(name.get_type()); |
7c673cae FG |
402 | if (rotate_iter == data.rotating_secrets.end()) |
403 | return false; | |
404 | ||
405 | RotatingSecrets secrets = rotate_iter->second; | |
406 | ||
407 | std::string error; | |
408 | if (encode_encrypt(cct, secrets, specific_key, enc_bl, error)) | |
409 | return false; | |
410 | ||
411 | return true; | |
412 | } | |
413 | ||
414 | bool KeyServer::_get_service_caps(const EntityName& name, uint32_t service_id, | |
415 | AuthCapsInfo& caps_info) const | |
416 | { | |
417 | string s = ceph_entity_type_name(service_id); | |
418 | ||
419 | return data.get_caps(cct, name, s, caps_info); | |
420 | } | |
421 | ||
422 | bool KeyServer::get_service_caps(const EntityName& name, uint32_t service_id, | |
423 | AuthCapsInfo& caps_info) const | |
424 | { | |
11fdf7f2 | 425 | std::scoped_lock l{lock}; |
7c673cae FG |
426 | return _get_service_caps(name, service_id, caps_info); |
427 | } | |
428 | ||
429 | ||
11fdf7f2 TL |
430 | int KeyServer::_build_session_auth_info(uint32_t service_id, |
431 | const AuthTicket& parent_ticket, | |
c5c27e9a TL |
432 | CephXSessionAuthInfo& info, |
433 | double ttl) | |
7c673cae FG |
434 | { |
435 | info.service_id = service_id; | |
11fdf7f2 | 436 | info.ticket = parent_ticket; |
c5c27e9a TL |
437 | info.ticket.init_timestamps(ceph_clock_now(), ttl); |
438 | info.validity.set_from_double(ttl); | |
7c673cae FG |
439 | |
440 | generate_secret(info.session_key); | |
441 | ||
442 | // mon keys are stored externally. and the caps are blank anyway. | |
443 | if (service_id != CEPH_ENTITY_TYPE_MON) { | |
444 | string s = ceph_entity_type_name(service_id); | |
445 | if (!data.get_caps(cct, info.ticket.name, s, info.ticket.caps)) { | |
446 | return -EINVAL; | |
447 | } | |
448 | } | |
449 | return 0; | |
450 | } | |
451 | ||
11fdf7f2 TL |
452 | int KeyServer::build_session_auth_info(uint32_t service_id, |
453 | const AuthTicket& parent_ticket, | |
7c673cae FG |
454 | CephXSessionAuthInfo& info) |
455 | { | |
c5c27e9a TL |
456 | double ttl; |
457 | if (!get_service_secret(service_id, info.service_secret, info.secret_id, | |
458 | ttl)) { | |
9f95a23c | 459 | return -EACCES; |
7c673cae FG |
460 | } |
461 | ||
11fdf7f2 | 462 | std::scoped_lock l{lock}; |
c5c27e9a | 463 | return _build_session_auth_info(service_id, parent_ticket, info, ttl); |
7c673cae FG |
464 | } |
465 | ||
11fdf7f2 TL |
466 | int KeyServer::build_session_auth_info(uint32_t service_id, |
467 | const AuthTicket& parent_ticket, | |
c5c27e9a TL |
468 | const CryptoKey& service_secret, |
469 | uint64_t secret_id, | |
470 | CephXSessionAuthInfo& info) | |
7c673cae FG |
471 | { |
472 | info.service_secret = service_secret; | |
473 | info.secret_id = secret_id; | |
474 | ||
11fdf7f2 | 475 | std::scoped_lock l{lock}; |
c5c27e9a TL |
476 | return _build_session_auth_info(service_id, parent_ticket, info, |
477 | cct->_conf->auth_service_ticket_ttl); | |
7c673cae FG |
478 | } |
479 |