]> git.proxmox.com Git - ceph.git/blob - ceph/src/cls/otp/cls_otp.cc
update sources to ceph Nautilus 14.2.1
[ceph.git] / ceph / src / cls / otp / cls_otp.cc
1 // -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
2 // vim: ts=8 sw=2 smarttab
3
4 /** \file
5 *
6 * This is an OSD class that implements methods for management
7 * and use of otp (one time password).
8 *
9 */
10
11 #include <errno.h>
12 #include <map>
13 #include <list>
14
15 #include <boost/range/adaptor/reversed.hpp>
16
17 #include <liboath/oath.h>
18
19 #include "include/types.h"
20 #include "include/utime.h"
21 #include "objclass/objclass.h"
22
23 #include "common/errno.h"
24 #include "common/Clock.h"
25
26 #include "cls/otp/cls_otp_ops.h"
27 #include "cls/otp/cls_otp_types.h"
28
29
30 using namespace rados::cls::otp;
31
32
33 CLS_VER(1,0)
34 CLS_NAME(otp)
35
36 #define ATTEMPTS_PER_WINDOW 5
37
38 static string otp_header_key = "header";
39 static string otp_key_prefix = "otp/";
40
41 struct otp_header {
42 set<string> ids;
43
44 otp_header() {}
45
46 void encode(bufferlist &bl) const {
47 ENCODE_START(1, 1, bl);
48 encode(ids, bl);
49 ENCODE_FINISH(bl);
50 }
51 void decode(bufferlist::const_iterator &bl) {
52 DECODE_START(1, bl);
53 decode(ids, bl);
54 DECODE_FINISH(bl);
55 }
56 };
57 WRITE_CLASS_ENCODER(otp_header)
58
59 struct otp_instance {
60 otp_info_t otp;
61
62 list<otp_check_t> last_checks;
63 uint64_t last_success{0}; /* otp counter/step of last successful check */
64
65 otp_instance() {}
66
67 void encode(bufferlist &bl) const {
68 ENCODE_START(1, 1, bl);
69 encode(otp, bl);
70 encode(last_checks, bl);
71 encode(last_success, bl);
72 ENCODE_FINISH(bl);
73 }
74 void decode(bufferlist::const_iterator &bl) {
75 DECODE_START(1, bl);
76 decode(otp, bl);
77 decode(last_checks, bl);
78 decode(last_success, bl);
79 DECODE_FINISH(bl);
80 }
81
82 void trim_expired(const ceph::real_time& now);
83 void check(const string& token, const string& val, bool *update);
84 bool verify(const ceph::real_time& timestamp, const string& val);
85
86 void find(const string& token, otp_check_t *result);
87 };
88 WRITE_CLASS_ENCODER(otp_instance)
89
90
91 void otp_instance::trim_expired(const ceph::real_time& now)
92 {
93 ceph::real_time window_start = now - std::chrono::seconds(otp.step_size);
94
95 while (!last_checks.empty() &&
96 last_checks.front().timestamp < window_start) {
97 last_checks.pop_front();
98 }
99 }
100
101 void otp_instance::check(const string& token, const string& val, bool *update)
102 {
103 ceph::real_time now = ceph::real_clock::now();
104 trim_expired(now);
105
106 if (last_checks.size() >= ATTEMPTS_PER_WINDOW) {
107 /* too many attempts */
108 *update = false;
109 return;
110 }
111
112 otp_check_t check;
113 check.token = token;
114 check.timestamp = now;
115 check.result = (verify(now, val) ? OTP_CHECK_SUCCESS : OTP_CHECK_FAIL);
116
117 last_checks.push_back(check);
118
119 *update = true;
120 }
121
122 bool otp_instance::verify(const ceph::real_time& timestamp, const string& val)
123 {
124 uint64_t index;
125 uint32_t secs = (uint32_t)ceph::real_clock::to_time_t(timestamp);
126 int result = oath_totp_validate2(otp.seed_bin.c_str(), otp.seed_bin.length(),
127 secs, otp.step_size, otp.time_ofs, otp.window,
128 nullptr /* otp pos */,
129 val.c_str());
130 if (result == OATH_INVALID_OTP ||
131 result < 0) {
132 CLS_LOG(20, "otp check failed, result=%d", result);
133 return false;
134 }
135
136 index = result + (secs - otp.time_ofs) / otp.step_size;
137
138 if (index <= last_success) { /* already used value */
139 CLS_LOG(20, "otp, use of old token: index=%lld last_success=%lld", (long long)index, (long long)last_success);
140 return false;
141 }
142
143 last_success = index;
144
145 return true;
146 }
147
148 void otp_instance::find(const string& token, otp_check_t *result)
149 {
150 ceph::real_time now = real_clock::now();
151 trim_expired(now);
152
153 for (auto& entry : boost::adaptors::reverse(last_checks)) {
154 if (entry.token == token) {
155 *result = entry;
156 return;
157 }
158 }
159 result->token = token;
160 result->result = OTP_CHECK_UNKNOWN;
161 result->timestamp = now;
162 }
163
164 static int get_otp_instance(cls_method_context_t hctx, const string& id, otp_instance *instance)
165 {
166 bufferlist bl;
167 string key = otp_key_prefix + id;
168
169 int r = cls_cxx_map_get_val(hctx, key, &bl);
170 if (r < 0) {
171 if (r != -ENOENT) {
172 CLS_ERR("error reading key %s: %d", key.c_str(), r);
173 }
174 return r;
175 }
176
177 try {
178 auto it = bl.cbegin();
179 decode(*instance, it);
180 } catch (const buffer::error &err) {
181 CLS_ERR("ERROR: failed to decode %s", key.c_str());
182 return -EIO;
183 }
184
185 return 0;
186 }
187
188 static int write_otp_instance(cls_method_context_t hctx, const otp_instance& instance)
189 {
190 string key = otp_key_prefix + instance.otp.id;
191
192 bufferlist bl;
193 encode(instance, bl);
194
195 int r = cls_cxx_map_set_val(hctx, key, &bl);
196 if (r < 0) {
197 CLS_ERR("ERROR: %s(): failed to store key (otp id=%s, r=%d)", __func__, instance.otp.id.c_str(), r);
198 return r;
199 }
200
201 return 0;
202 }
203
204 static int remove_otp_instance(cls_method_context_t hctx, const string& id)
205 {
206 string key = otp_key_prefix + id;
207
208 int r = cls_cxx_map_remove_key(hctx, key);
209 if (r < 0) {
210 CLS_ERR("ERROR: %s(): failed to remove key (otp id=%s, r=%d)", __func__, id.c_str(), r);
211 return r;
212 }
213
214 return 0;
215 }
216
217 static int read_header(cls_method_context_t hctx, otp_header *h)
218 {
219 bufferlist bl;
220 encode(h, bl);
221 int r = cls_cxx_map_get_val(hctx, otp_header_key, &bl);
222 if (r == -ENOENT || r == -ENODATA) {
223 *h = otp_header();
224 return 0;
225 }
226 if (r < 0) {
227 CLS_ERR("ERROR: %s(): failed to read header (r=%d)", __func__, r);
228 return r;
229 }
230
231 if (bl.length() == 0) {
232 *h = otp_header();
233 return 0;
234 }
235
236 auto iter = bl.cbegin();
237 try {
238 decode(*h, iter);
239 } catch (buffer::error& err) {
240 CLS_ERR("failed to decode otp_header");
241 return -EIO;
242 }
243
244 return 0;
245 }
246
247 static int write_header(cls_method_context_t hctx, const otp_header& h)
248 {
249 bufferlist bl;
250 encode(h, bl);
251
252 int r = cls_cxx_map_set_val(hctx, otp_header_key, &bl);
253 if (r < 0) {
254 CLS_ERR("failed to store header (r=%d)", r);
255 return r;
256 }
257
258 return 0;
259 }
260
261 static int parse_seed(const string& seed, SeedType seed_type, bufferlist *seed_bin)
262 {
263 size_t slen = seed.length();
264 char secret[seed.length()];
265 char *psecret = secret;
266 int result;
267 bool need_free = false;
268
269 seed_bin->clear();
270
271 switch (seed_type) {
272 case OTP_SEED_BASE32:
273 need_free = true; /* oath_base32_decode allocates dest buffer */
274 result = oath_base32_decode(seed.c_str(), seed.length(),
275 &psecret, &slen);
276 break;
277 default: /* just assume hex is the default */
278 result = oath_hex2bin(seed.c_str(), psecret, &slen);
279 }
280 if (result != OATH_OK) {
281 CLS_LOG(20, "failed to parse seed");
282 return -EINVAL;
283 }
284
285 seed_bin->append(psecret, slen);
286
287 if (need_free) {
288 free(psecret);
289 }
290
291 return 0;
292 }
293
294 static int otp_set_op(cls_method_context_t hctx,
295 bufferlist *in, bufferlist *out)
296 {
297 CLS_LOG(20, "%s", __func__);
298 cls_otp_set_otp_op op;
299 try {
300 auto iter = in->cbegin();
301 decode(op, iter);
302 } catch (const buffer::error &err) {
303 CLS_ERR("ERROR: %s(): failed to decode request", __func__);
304 return -EINVAL;
305 }
306
307 otp_header h;
308 int r = read_header(hctx, &h);
309 if (r < 0) {
310 return r;
311 }
312
313 for (auto entry : op.entries) {
314 otp_instance instance;
315 r = get_otp_instance(hctx, entry.id, &instance);
316 if (r < 0 &&
317 r != -ENOENT) {
318 return r;
319 }
320 instance.otp = entry;
321
322 r = parse_seed(instance.otp.seed, instance.otp.seed_type, &instance.otp.seed_bin);
323 if (r < 0) {
324 return r;
325 }
326
327 r = write_otp_instance(hctx, instance);
328 if (r < 0) {
329 return r;
330 }
331
332 h.ids.insert(entry.id);
333 }
334
335 r = write_header(hctx, h);
336 if (r < 0) {
337 return r;
338 }
339
340 return 0;
341 }
342
343 static int otp_remove_op(cls_method_context_t hctx,
344 bufferlist *in, bufferlist *out)
345 {
346 CLS_LOG(20, "%s", __func__);
347 cls_otp_remove_otp_op op;
348 try {
349 auto iter = in->cbegin();
350 decode(op, iter);
351 } catch (const buffer::error &err) {
352 CLS_ERR("ERROR: %s(): failed to decode request", __func__);
353 return -EINVAL;
354 }
355
356 otp_header h;
357 bool removed_existing = false;
358 int r = read_header(hctx, &h);
359 if (r < 0) {
360 return r;
361 }
362
363 for (auto id : op.ids) {
364 bool existed = (h.ids.find(id) != h.ids.end());
365 removed_existing = (removed_existing || existed);
366
367 if (!existed) {
368 continue;
369 }
370
371 r = remove_otp_instance(hctx, id);
372 if (r < 0) {
373 return r;
374 }
375
376 h.ids.erase(id);
377 }
378
379 if (removed_existing) {
380 r = write_header(hctx, h);
381 if (r < 0) {
382 return r;
383 }
384 }
385
386 return 0;
387 }
388
389 static int otp_get_op(cls_method_context_t hctx,
390 bufferlist *in, bufferlist *out)
391 {
392 CLS_LOG(20, "%s", __func__);
393 cls_otp_get_otp_op op;
394 try {
395 auto iter = in->cbegin();
396 decode(op, iter);
397 } catch (const buffer::error &err) {
398 CLS_ERR("ERROR: %s(): failed to decode request", __func__);
399 return -EINVAL;
400 }
401
402 cls_otp_get_otp_reply result;
403
404 otp_header h;
405 int r;
406
407 r = read_header(hctx, &h);
408 if (r < 0) {
409 return r;
410 }
411
412 if (op.get_all) {
413 op.ids.clear();
414 for (auto id : h.ids) {
415 op.ids.push_back(id);
416 }
417 }
418
419 for (auto id : op.ids) {
420 bool exists = (h.ids.find(id) != h.ids.end());
421
422 if (!exists) {
423 continue;
424 }
425
426 otp_instance instance;
427 r = get_otp_instance(hctx, id, &instance);
428 if (r < 0) {
429 return r;
430 }
431
432 result.found_entries.push_back(instance.otp);
433 }
434
435 encode(result, *out);
436
437 return 0;
438 }
439
440 static int otp_check_op(cls_method_context_t hctx,
441 bufferlist *in, bufferlist *out)
442 {
443 CLS_LOG(20, "%s", __func__);
444 cls_otp_check_otp_op op;
445 try {
446 auto iter = in->cbegin();
447 decode(op, iter);
448 } catch (const buffer::error &err) {
449 CLS_ERR("ERROR: %s(): failed to decode request", __func__);
450 return -EINVAL;
451 }
452
453 otp_header h;
454 int r;
455
456 otp_instance instance;
457
458 r = get_otp_instance(hctx, op.id, &instance);
459 if (r < 0) {
460 return r;
461 }
462
463 bool update{false};
464 instance.check(op.token, op.val, &update);
465
466 if (update) {
467 r = write_otp_instance(hctx, instance);
468 if (r < 0) {
469 return r;
470 }
471 }
472
473 return 0;
474 }
475
476 static int otp_get_result(cls_method_context_t hctx,
477 bufferlist *in, bufferlist *out)
478 {
479 CLS_LOG(20, "%s", __func__);
480 cls_otp_check_otp_op op;
481 try {
482 auto iter = in->cbegin();
483 decode(op, iter);
484 } catch (const buffer::error &err) {
485 CLS_ERR("ERROR: %s(): failed to decode request", __func__);
486 return -EINVAL;
487 }
488
489 otp_header h;
490 int r;
491
492 otp_instance instance;
493
494 r = get_otp_instance(hctx, op.id, &instance);
495 if (r < 0) {
496 return r;
497 }
498
499 cls_otp_get_result_reply reply;
500 instance.find(op.token, &reply.result);
501 encode(reply, *out);
502
503 return 0;
504 }
505
506 static int otp_get_current_time_op(cls_method_context_t hctx,
507 bufferlist *in, bufferlist *out)
508 {
509 CLS_LOG(20, "%s", __func__);
510 cls_otp_get_current_time_op op;
511 try {
512 auto iter = in->cbegin();
513 decode(op, iter);
514 } catch (const buffer::error &err) {
515 CLS_ERR("ERROR: %s(): failed to decode request", __func__);
516 return -EINVAL;
517 }
518
519 cls_otp_get_current_time_reply reply;
520 reply.time = real_clock::now();
521 encode(reply, *out);
522
523 return 0;
524 }
525
526 CLS_INIT(otp)
527 {
528 CLS_LOG(20, "Loaded otp class!");
529
530 oath_init();
531
532 cls_handle_t h_class;
533 cls_method_handle_t h_set_otp_op;
534 cls_method_handle_t h_get_otp_op;
535 cls_method_handle_t h_check_otp_op;
536 cls_method_handle_t h_get_result_op; /*
537 * need to check and get check result in two phases. The
538 * reason is that we need to update failure internally,
539 * however, there's no way to both return a failure and
540 * update, because a failure will cancel the operation,
541 * and write operations will not return a value. So
542 * we're returning a success, potentially updating the
543 * status internally, then a subsequent request can try
544 * to fetch the status. If it fails it means that failed
545 * to authenticate.
546 */
547 cls_method_handle_t h_remove_otp_op;
548 cls_method_handle_t h_get_current_time_op;
549
550 cls_register("otp", &h_class);
551 cls_register_cxx_method(h_class, "otp_set",
552 CLS_METHOD_RD | CLS_METHOD_WR,
553 otp_set_op, &h_set_otp_op);
554 cls_register_cxx_method(h_class, "otp_get",
555 CLS_METHOD_RD,
556 otp_get_op, &h_get_otp_op);
557 cls_register_cxx_method(h_class, "otp_check",
558 CLS_METHOD_RD | CLS_METHOD_WR,
559 otp_check_op, &h_check_otp_op);
560 cls_register_cxx_method(h_class, "otp_get_result",
561 CLS_METHOD_RD,
562 otp_get_result, &h_get_result_op);
563 cls_register_cxx_method(h_class, "otp_remove",
564 CLS_METHOD_RD | CLS_METHOD_WR,
565 otp_remove_op, &h_remove_otp_op);
566 cls_register_cxx_method(h_class, "get_current_time",
567 CLS_METHOD_RD,
568 otp_get_current_time_op, &h_get_current_time_op);
569
570 return;
571 }