]> git.proxmox.com Git - ceph.git/blob - ceph/src/auth/cephx/CephxServiceHandler.cc
a7c67757c5825136bfc4fe0abf3c0e2cc214a7eb
[ceph.git] / ceph / src / auth / cephx / CephxServiceHandler.cc
1 // -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
2 // vim: ts=8 sw=2 smarttab
3 /*
4 * Ceph - scalable distributed file system
5 *
6 * Copyright (C) 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
16 #include "CephxServiceHandler.h"
17 #include "CephxProtocol.h"
18 #include "CephxKeyServer.h"
19 #include <errno.h>
20 #include <sstream>
21
22 #include "include/random.h"
23 #include "common/config.h"
24 #include "common/debug.h"
25
26 #define dout_subsys ceph_subsys_auth
27 #undef dout_prefix
28 #define dout_prefix *_dout << "cephx server " << entity_name << ": "
29
30 using std::dec;
31 using std::hex;
32 using std::vector;
33
34 using ceph::bufferlist;
35 using ceph::decode;
36 using ceph::encode;
37
38 int CephxServiceHandler::do_start_session(
39 bool is_new_global_id,
40 bufferlist *result_bl,
41 AuthCapsInfo *caps)
42 {
43 global_id_status = is_new_global_id ? global_id_status_t::NEW_PENDING :
44 global_id_status_t::RECLAIM_PENDING;
45
46 uint64_t min = 1; // always non-zero
47 uint64_t max = std::numeric_limits<uint64_t>::max();
48 server_challenge = ceph::util::generate_random_number<uint64_t>(min, max);
49 ldout(cct, 10) << "start_session server_challenge "
50 << hex << server_challenge << dec << dendl;
51
52 CephXServerChallenge ch;
53 ch.server_challenge = server_challenge;
54 encode(ch, *result_bl);
55 return 0;
56 }
57
58 int CephxServiceHandler::verify_old_ticket(
59 const CephXAuthenticate& req,
60 CephXServiceTicketInfo& old_ticket_info,
61 bool& should_enc_ticket)
62 {
63 ldout(cct, 20) << " checking old_ticket: secret_id="
64 << req.old_ticket.secret_id
65 << " len=" << req.old_ticket.blob.length()
66 << ", old_ticket_may_be_omitted="
67 << req.old_ticket_may_be_omitted << dendl;
68 ceph_assert(global_id_status != global_id_status_t::NONE);
69 if (global_id_status == global_id_status_t::NEW_PENDING) {
70 // old ticket is not needed
71 if (req.old_ticket.blob.length()) {
72 ldout(cct, 0) << " superfluous ticket presented" << dendl;
73 return -EINVAL;
74 }
75 if (req.old_ticket_may_be_omitted) {
76 ldout(cct, 10) << " new global_id " << global_id
77 << " (unexposed legacy client)" << dendl;
78 global_id_status = global_id_status_t::NEW_NOT_EXPOSED;
79 } else {
80 ldout(cct, 10) << " new global_id " << global_id << dendl;
81 global_id_status = global_id_status_t::NEW_OK;
82 }
83 return 0;
84 }
85
86 if (!req.old_ticket.blob.length()) {
87 // old ticket is needed but not presented
88 if (cct->_conf->auth_allow_insecure_global_id_reclaim &&
89 req.old_ticket_may_be_omitted) {
90 ldout(cct, 10) << " allowing reclaim of global_id " << global_id
91 << " with no ticket presented (legacy client, auth_allow_insecure_global_id_reclaim=true)"
92 << dendl;
93 global_id_status = global_id_status_t::RECLAIM_INSECURE;
94 return 0;
95 }
96 ldout(cct, 0) << " attempt to reclaim global_id " << global_id
97 << " without presenting ticket" << dendl;
98 return -EACCES;
99 }
100
101 if (!cephx_decode_ticket(cct, key_server, CEPH_ENTITY_TYPE_AUTH,
102 req.old_ticket, old_ticket_info)) {
103 if (cct->_conf->auth_allow_insecure_global_id_reclaim &&
104 req.old_ticket_may_be_omitted) {
105 ldout(cct, 10) << " allowing reclaim of global_id " << global_id
106 << " using bad ticket (legacy client, auth_allow_insecure_global_id_reclaim=true)"
107 << dendl;
108 global_id_status = global_id_status_t::RECLAIM_INSECURE;
109 return 0;
110 }
111 ldout(cct, 0) << " attempt to reclaim global_id " << global_id
112 << " using bad ticket" << dendl;
113 return -EACCES;
114 }
115 ldout(cct, 20) << " decoded old_ticket: global_id="
116 << old_ticket_info.ticket.global_id << dendl;
117 if (global_id != old_ticket_info.ticket.global_id) {
118 if (cct->_conf->auth_allow_insecure_global_id_reclaim &&
119 req.old_ticket_may_be_omitted) {
120 ldout(cct, 10) << " allowing reclaim of global_id " << global_id
121 << " using mismatching ticket (legacy client, auth_allow_insecure_global_id_reclaim=true)"
122 << dendl;
123 global_id_status = global_id_status_t::RECLAIM_INSECURE;
124 return 0;
125 }
126 ldout(cct, 0) << " attempt to reclaim global_id " << global_id
127 << " using mismatching ticket" << dendl;
128 return -EACCES;
129 }
130 ldout(cct, 10) << " allowing reclaim of global_id " << global_id
131 << " (valid ticket presented, will encrypt new ticket)"
132 << dendl;
133 global_id_status = global_id_status_t::RECLAIM_OK;
134 should_enc_ticket = true;
135 return 0;
136 }
137
138 int CephxServiceHandler::handle_request(
139 bufferlist::const_iterator& indata,
140 size_t connection_secret_required_len,
141 bufferlist *result_bl,
142 AuthCapsInfo *caps,
143 CryptoKey *psession_key,
144 std::string *pconnection_secret)
145 {
146 int ret = 0;
147
148 struct CephXRequestHeader cephx_header;
149 try {
150 decode(cephx_header, indata);
151 } catch (ceph::buffer::error& e) {
152 ldout(cct, 0) << __func__ << " failed to decode CephXRequestHeader: "
153 << e.what() << dendl;
154 return -EPERM;
155 }
156
157 switch (cephx_header.request_type) {
158 case CEPHX_GET_AUTH_SESSION_KEY:
159 {
160 ldout(cct, 10) << "handle_request get_auth_session_key for "
161 << entity_name << dendl;
162
163 CephXAuthenticate req;
164 try {
165 decode(req, indata);
166 } catch (ceph::buffer::error& e) {
167 ldout(cct, 0) << __func__ << " failed to decode CephXAuthenticate: "
168 << e.what() << dendl;
169 ret = -EPERM;
170 break;
171 }
172
173 CryptoKey secret;
174 if (!key_server->get_secret(entity_name, secret)) {
175 ldout(cct, 0) << "couldn't find entity name: " << entity_name << dendl;
176 ret = -EACCES;
177 break;
178 }
179
180 if (!server_challenge) {
181 ret = -EACCES;
182 break;
183 }
184
185 uint64_t expected_key;
186 std::string error;
187 cephx_calc_client_server_challenge(cct, secret, server_challenge,
188 req.client_challenge, &expected_key, error);
189 if (!error.empty()) {
190 ldout(cct, 0) << " cephx_calc_client_server_challenge error: " << error << dendl;
191 ret = -EACCES;
192 break;
193 }
194
195 ldout(cct, 20) << " checking key: req.key=" << hex << req.key
196 << " expected_key=" << expected_key << dec << dendl;
197 if (req.key != expected_key) {
198 ldout(cct, 0) << " unexpected key: req.key=" << hex << req.key
199 << " expected_key=" << expected_key << dec << dendl;
200 ret = -EACCES;
201 break;
202 }
203
204 CryptoKey session_key;
205 CephXSessionAuthInfo info;
206 bool should_enc_ticket = false;
207
208 EntityAuth eauth;
209 if (! key_server->get_auth(entity_name, eauth)) {
210 ret = -EACCES;
211 break;
212 }
213
214 CephXServiceTicketInfo old_ticket_info;
215 ret = verify_old_ticket(req, old_ticket_info, should_enc_ticket);
216 if (ret) {
217 ldout(cct, 0) << " could not verify old ticket" << dendl;
218 break;
219 }
220
221 double ttl;
222 if (!key_server->get_service_secret(CEPH_ENTITY_TYPE_AUTH,
223 info.service_secret, info.secret_id,
224 ttl)) {
225 ldout(cct, 0) << " could not get service secret for auth subsystem" << dendl;
226 ret = -EIO;
227 break;
228 }
229
230 info.service_id = CEPH_ENTITY_TYPE_AUTH;
231 info.ticket.name = entity_name;
232 info.ticket.global_id = global_id;
233 info.ticket.init_timestamps(ceph_clock_now(), ttl);
234 info.validity.set_from_double(ttl);
235
236 key_server->generate_secret(session_key);
237
238 info.session_key = session_key;
239 if (psession_key) {
240 *psession_key = session_key;
241 }
242
243 vector<CephXSessionAuthInfo> info_vec;
244 info_vec.push_back(info);
245
246 build_cephx_response_header(cephx_header.request_type, 0, *result_bl);
247 if (!cephx_build_service_ticket_reply(
248 cct, eauth.key, info_vec, should_enc_ticket,
249 old_ticket_info.session_key, *result_bl)) {
250 ret = -EIO;
251 break;
252 }
253
254 if (!key_server->get_service_caps(entity_name, CEPH_ENTITY_TYPE_MON,
255 *caps)) {
256 ldout(cct, 0) << " could not get mon caps for " << entity_name << dendl;
257 ret = -EACCES;
258 break;
259 } else {
260 char *caps_str = caps->caps.c_str();
261 if (!caps_str || !caps_str[0]) {
262 ldout(cct,0) << "mon caps null for " << entity_name << dendl;
263 ret = -EACCES;
264 break;
265 }
266
267 if (req.other_keys) {
268 // nautilus+ client
269 // generate a connection_secret
270 bufferlist cbl;
271 if (pconnection_secret) {
272 pconnection_secret->resize(connection_secret_required_len);
273 if (connection_secret_required_len) {
274 cct->random()->get_bytes(pconnection_secret->data(),
275 connection_secret_required_len);
276 }
277 std::string err;
278 if (encode_encrypt(cct, *pconnection_secret, session_key, cbl,
279 err)) {
280 lderr(cct) << __func__ << " failed to encrypt connection secret, "
281 << err << dendl;
282 ret = -EACCES;
283 break;
284 }
285 }
286 encode(cbl, *result_bl);
287 // provide requested service tickets at the same time
288 vector<CephXSessionAuthInfo> info_vec;
289 for (uint32_t service_id = 1; service_id <= req.other_keys;
290 service_id <<= 1) {
291 // skip CEPH_ENTITY_TYPE_AUTH: auth ticket is already encoded
292 // (possibly encrypted with the old session key)
293 if ((req.other_keys & service_id) &&
294 service_id != CEPH_ENTITY_TYPE_AUTH) {
295 ldout(cct, 10) << " adding key for service "
296 << ceph_entity_type_name(service_id) << dendl;
297 CephXSessionAuthInfo svc_info;
298 key_server->build_session_auth_info(
299 service_id,
300 info.ticket,
301 svc_info);
302 info_vec.push_back(svc_info);
303 }
304 }
305 bufferlist extra;
306 if (!info_vec.empty()) {
307 CryptoKey no_key;
308 cephx_build_service_ticket_reply(
309 cct, session_key, info_vec, false, no_key, extra);
310 }
311 encode(extra, *result_bl);
312 }
313
314 // caller should try to finish authentication
315 ret = 1;
316 }
317 }
318 break;
319
320 case CEPHX_GET_PRINCIPAL_SESSION_KEY:
321 {
322 ldout(cct, 10) << "handle_request get_principal_session_key" << dendl;
323
324 bufferlist tmp_bl;
325 CephXServiceTicketInfo auth_ticket_info;
326 // note: no challenge here.
327 if (!cephx_verify_authorizer(
328 cct, *key_server, indata, 0, auth_ticket_info, nullptr,
329 nullptr,
330 &tmp_bl)) {
331 ret = -EACCES;
332 break;
333 }
334
335 CephXServiceTicketRequest ticket_req;
336 try {
337 decode(ticket_req, indata);
338 } catch (ceph::buffer::error& e) {
339 ldout(cct, 0) << __func__
340 << " failed to decode CephXServiceTicketRequest: "
341 << e.what() << dendl;
342 ret = -EPERM;
343 break;
344 }
345 ldout(cct, 10) << " ticket_req.keys = " << ticket_req.keys << dendl;
346
347 ret = 0;
348 vector<CephXSessionAuthInfo> info_vec;
349 int found_services = 0;
350 int service_err = 0;
351 for (uint32_t service_id = 1; service_id <= ticket_req.keys;
352 service_id <<= 1) {
353 // skip CEPH_ENTITY_TYPE_AUTH: auth ticket must be obtained with
354 // CEPHX_GET_AUTH_SESSION_KEY
355 if ((ticket_req.keys & service_id) &&
356 service_id != CEPH_ENTITY_TYPE_AUTH) {
357 ldout(cct, 10) << " adding key for service "
358 << ceph_entity_type_name(service_id) << dendl;
359 CephXSessionAuthInfo info;
360 int r = key_server->build_session_auth_info(
361 service_id,
362 auth_ticket_info.ticket, // parent ticket (client's auth ticket)
363 info);
364 // tolerate missing MGR rotating key for the purposes of upgrades.
365 if (r < 0) {
366 ldout(cct, 10) << " missing key for service "
367 << ceph_entity_type_name(service_id) << dendl;
368 service_err = r;
369 continue;
370 }
371 info_vec.push_back(info);
372 ++found_services;
373 }
374 }
375 if (!found_services && service_err) {
376 ldout(cct, 10) << __func__ << " did not find any service keys" << dendl;
377 ret = service_err;
378 }
379 CryptoKey no_key;
380 build_cephx_response_header(cephx_header.request_type, ret, *result_bl);
381 cephx_build_service_ticket_reply(cct, auth_ticket_info.session_key,
382 info_vec, false, no_key, *result_bl);
383 }
384 break;
385
386 case CEPHX_GET_ROTATING_KEY:
387 {
388 ldout(cct, 10) << "handle_request getting rotating secret for "
389 << entity_name << dendl;
390 build_cephx_response_header(cephx_header.request_type, 0, *result_bl);
391 if (!key_server->get_rotating_encrypted(entity_name, *result_bl)) {
392 ret = -EACCES;
393 break;
394 }
395 }
396 break;
397
398 default:
399 ldout(cct, 10) << "handle_request unknown op " << cephx_header.request_type << dendl;
400 return -EINVAL;
401 }
402 return ret;
403 }
404
405 void CephxServiceHandler::build_cephx_response_header(int request_type, int status, bufferlist& bl)
406 {
407 struct CephXResponseHeader header;
408 header.request_type = request_type;
409 header.status = status;
410 encode(header, bl);
411 }