From: Yehuda Sadeh Date: Wed, 15 Nov 2017 01:47:30 +0000 (-0800) Subject: cls/otp: implement totp check logic X-Git-Tag: v13.1.0~343^2~32 X-Git-Url: http://git.apps.os.sepia.ceph.com/?a=commitdiff_plain;h=a774884bafe7da1195947396ec0880bd8ae98baf;p=ceph-ci.git cls/otp: implement totp check logic Currently checking for bogus results, still need to integrate with totp library. Signed-off-by: Yehuda Sadeh --- diff --git a/src/cls/otp/cls_otp.cc b/src/cls/otp/cls_otp.cc index b44b9e92cc5..188345d56d9 100644 --- a/src/cls/otp/cls_otp.cc +++ b/src/cls/otp/cls_otp.cc @@ -12,11 +12,14 @@ #include #include +#include + #include "include/types.h" #include "include/utime.h" #include "objclass/objclass.h" #include "common/errno.h" +#include "common/Clock.h" #include "cls/otp/cls_otp_ops.h" #include "cls/otp/cls_otp_types.h" @@ -30,6 +33,8 @@ CLS_NAME(otp) #define ATTEMPTS_PER_WINDOW 5 +static string otp_key_prefix = "otp."; + struct otp_header { set ids; @@ -48,9 +53,115 @@ struct otp_header { }; WRITE_CLASS_ENCODER(otp_header) -static string otp_key_prefix = "otp."; +struct otp_instance { + otp_info_t otp; + + list last_checks; + uint32_t last_success{0}; /* otp counter/step of last successful check */ + + otp_instance() {} + + void encode(bufferlist &bl) const { + ENCODE_START(1, 1, bl); + ::encode(otp, bl); + ::encode(last_checks, bl); + ::encode(last_success, bl); + ENCODE_FINISH(bl); + } + void decode(bufferlist::iterator &bl) { + DECODE_START(1, bl); + ::decode(otp, bl); + ::decode(last_checks, bl); + ::decode(last_success, bl); + DECODE_FINISH(bl); + } + + void trim_expired(const ceph::real_time& now); + void check(const string& token, const string& val, bool *update); + bool verify(const ceph::real_time& timestamp, const string& val); -static int get_otp_info(cls_method_context_t hctx, const string& id, otp_info_t *otp) + void find(const string& token, otp_check_t *result); +}; +WRITE_CLASS_ENCODER(otp_instance) + + +void otp_instance::trim_expired(const ceph::real_time& now) +{ + ceph::real_time window_start = now - make_timespan(otp.step_size); + + while (!last_checks.empty() && + last_checks.front().timestamp < window_start) { + last_checks.pop_front(); + } +} + +void otp_instance::check(const string& token, const string& val, bool *update) +{ + ceph::real_time now = ceph::real_clock::now(); + trim_expired(now); + + if (last_checks.size() >= ATTEMPTS_PER_WINDOW) { + /* too many attempts */ + *update = false; + return; + } + + otp_check_t check; + check.token = token; + check.timestamp = now; + check.result = (verify(now, val) ? OTP_CHECK_SUCCESS : OTP_CHECK_FAIL); + + last_checks.push_back(check); + + *update = true; +} + +bool otp_instance::verify(const ceph::real_time& timestamp, const string& val) +{ +#warning FIXME should be based on result from library + uint32_t secs = (uint32_t)ceph::real_clock::to_time_t(timestamp); + int step_size = (otp.step_size ? otp.step_size : 1); + int step_window = otp.window * step_size; + + if (step_window > (uint32_t)secs / 2) { + step_window = secs / 2; + } + + for (int s = -step_window / 2; s <= step_window / 2; s += step_size) { + uint32_t index = (secs + s) / step_size; + if (index <= last_success) { /* already used value */ + continue; + } + +#warning FIXME check here is temporary + char buf[otp.seed.size() + 16]; + snprintf(buf, sizeof(buf), "%s:%d", otp.seed.c_str(), (int)index); + if (val == buf) { + last_success = index; + return true; + } + } + + return false; +} + +void otp_instance::find(const string& token, otp_check_t *result) +{ + ceph::real_time now = real_clock::now(); + trim_expired(now); + + for (auto entry : boost::adaptors::reverse(last_checks)) { + if (entry.token == token) { + *result = entry; + return; + } + } + result->token = token; + result->result = OTP_CHECK_UNKNOWN; + result->timestamp = now; +} + +static int get_otp_instance(cls_method_context_t hctx, const string& id, otp_instance *instance) { bufferlist bl; string key = otp_key_prefix + id; @@ -65,7 +176,7 @@ static int get_otp_info(cls_method_context_t hctx, const string& id, otp_info_t try { bufferlist::iterator it = bl.begin(); - ::decode(*otp, it); + ::decode(*instance, it); } catch (const buffer::error &err) { CLS_ERR("ERROR: failed to decode %s", key.c_str()); return -EIO; @@ -74,23 +185,23 @@ static int get_otp_info(cls_method_context_t hctx, const string& id, otp_info_t return 0; } -static int write_otp_info(cls_method_context_t hctx, const otp_info_t& otp) +static int write_otp_instance(cls_method_context_t hctx, const otp_instance& instance) { - string key = otp_key_prefix + otp.id; + string key = otp_key_prefix + instance.otp.id; bufferlist bl; - ::encode(otp, bl); + ::encode(instance, bl); int r = cls_cxx_map_set_val(hctx, key.c_str(), &bl); if (r < 0) { - CLS_ERR("ERROR: %s(): failed to store key (otp id=%s, r=%d)", __func__, otp.id.c_str(), r); + CLS_ERR("ERROR: %s(): failed to store key (otp id=%s, r=%d)", __func__, instance.otp.id.c_str(), r); return r; } return 0; } -static int remove_otp_info(cls_method_context_t hctx, const string& id) +static int remove_otp_instance(cls_method_context_t hctx, const string& id) { string key = otp_key_prefix + id; @@ -166,11 +277,10 @@ static int otp_set_op(cls_method_context_t hctx, bool existed = (h.ids.find(entry.id) != h.ids.end()); update_header = (update_header || !existed); - if (!existed) { - continue; - } + otp_instance instance; + instance.otp = entry; - r = write_otp_info(hctx, entry); + r = write_otp_instance(hctx, instance); if (r < 0) { return r; } @@ -216,7 +326,7 @@ static int otp_remove_op(cls_method_context_t hctx, continue; } - r = remove_otp_info(hctx, id); + r = remove_otp_instance(hctx, id); if (r < 0) { return r; } @@ -271,10 +381,13 @@ static int otp_get_op(cls_method_context_t hctx, continue; } - r = get_otp_info(hctx, id, &result.found_entries[id]); + otp_instance instance; + r = get_otp_instance(hctx, id, &instance); if (r < 0) { return r; } + + result.found_entries.push_back(instance.otp); } ::encode(result, *out); @@ -298,19 +411,53 @@ static int otp_check_op(cls_method_context_t hctx, otp_header h; int r; - otp_info_t otp; + otp_instance instance; - r = get_otp_info(hctx, op.id, &otp); + r = get_otp_instance(hctx, op.id, &instance); if (r < 0) { return r; } -#warning FIXME - if (op.val != otp.seed) { /* temporary */ - return -EACCES; + bool update{false}; + instance.check(op.token, op.val, &update); + + if (update) { + r = write_otp_instance(hctx, instance); + if (r < 0) { + return r; + } + } + + return 0; +} + +static int otp_get_result(cls_method_context_t hctx, + bufferlist *in, bufferlist *out) +{ + CLS_LOG(20, "%s", __func__); + cls_otp_check_otp_op op; + try { + auto iter = in->begin(); + ::decode(op, iter); + } catch (const buffer::error &err) { + CLS_ERR("ERROR: %s(): failed to decode request", __func__); + return -EINVAL; + } + + otp_header h; + int r; + + otp_instance instance; + + r = get_otp_instance(hctx, op.id, &instance); + if (r < 0) { + return r; } -#warning missing + cls_otp_get_result_reply reply; + instance.find(op.token, &reply.result); + ::encode(reply, *out); + return 0; } @@ -322,17 +469,17 @@ CLS_INIT(otp) cls_method_handle_t h_set_otp_op; cls_method_handle_t h_get_otp_op; cls_method_handle_t h_check_otp_op; - cls_method_handle_t h_get_otp_check_result_op; /* - * need to check and get check result in two phases. The - * reason is that we need to update failure internally, - * however, there's no way to both return a failure and - * update, because a failure will cancel the operation, - * and write operations will not return a value. So - * we're returning a success, potentially updating the - * status internally, then a subsequent request can try - * to fetch the status. If it fails it means that failed - * to authenticate. - */ + cls_method_handle_t h_get_result_op; /* + * need to check and get check result in two phases. The + * reason is that we need to update failure internally, + * however, there's no way to both return a failure and + * update, because a failure will cancel the operation, + * and write operations will not return a value. So + * we're returning a success, potentially updating the + * status internally, then a subsequent request can try + * to fetch the status. If it fails it means that failed + * to authenticate. + */ cls_method_handle_t h_remove_otp_op; cls_register("otp", &h_class); @@ -347,7 +494,7 @@ CLS_INIT(otp) otp_check_op, &h_check_otp_op); cls_register_cxx_method(h_class, "otp_get_result", CLS_METHOD_RD, - otp_check_op, &h_check_otp_op); + otp_get_result, &h_get_result_op); cls_register_cxx_method(h_class, "otp_remove", CLS_METHOD_RD | CLS_METHOD_WR, otp_remove_op, &h_remove_otp_op); diff --git a/src/cls/otp/cls_otp_ops.h b/src/cls/otp/cls_otp_ops.h index 77feb456de8..86ea9f4126f 100644 --- a/src/cls/otp/cls_otp_ops.h +++ b/src/cls/otp/cls_otp_ops.h @@ -28,6 +28,7 @@ struct cls_otp_check_otp_op { string id; string val; + string token; cls_otp_check_otp_op() = default; @@ -35,17 +36,57 @@ struct cls_otp_check_otp_op ENCODE_START(1, 1, bl); ::encode(id, bl); ::encode(val, bl); + ::encode(token, bl); ENCODE_FINISH(bl); } void decode(bufferlist::iterator &bl) { DECODE_START(1, bl); ::decode(id, bl); ::decode(val, bl); + ::decode(token, bl); DECODE_FINISH(bl); } }; WRITE_CLASS_ENCODER(cls_otp_check_otp_op) +struct cls_otp_get_result_op +{ + string token; + + cls_otp_get_result_op() = default; + + void encode(bufferlist &bl) const { + ENCODE_START(1, 1, bl); + ::encode(token, bl); + ENCODE_FINISH(bl); + } + void decode(bufferlist::iterator &bl) { + DECODE_START(1, bl); + ::decode(token, bl); + DECODE_FINISH(bl); + } +}; +WRITE_CLASS_ENCODER(cls_otp_get_result_op) + +struct cls_otp_get_result_reply +{ + rados::cls::otp::otp_check_t result; + + cls_otp_get_result_reply() = default; + + void encode(bufferlist &bl) const { + ENCODE_START(1, 1, bl); + ::encode(result, bl); + ENCODE_FINISH(bl); + } + void decode(bufferlist::iterator &bl) { + DECODE_START(1, bl); + ::decode(result, bl); + DECODE_FINISH(bl); + } +}; +WRITE_CLASS_ENCODER(cls_otp_get_result_reply) + struct cls_otp_remove_otp_op { list ids; diff --git a/src/cls/otp/cls_otp_types.h b/src/cls/otp/cls_otp_types.h index 14b9f699eb5..e4b62aca535 100644 --- a/src/cls/otp/cls_otp_types.h +++ b/src/cls/otp/cls_otp_types.h @@ -13,20 +13,18 @@ namespace rados { namespace otp { enum OTPType { - UNKNOWN = 0, - HOTP = 1, /* unsupported */ - TOTP = 2, + OTP_UNKNOWN = 0, + OTP_HOTP = 1, /* unsupported */ + OTP_TOTP = 2, }; struct otp_info_t { - OTPType type{TOTP}; + OTPType type{OTP_TOTP}; string id; string seed; ceph::real_time time_ofs; uint32_t step_size{30}; /* num of seconds foreach otp to test */ uint32_t window{2}; /* num of otp after/before start otp to test */ - list last_tries; - uint64_t last_success{0}; /* otp_counter of last successful check */ otp_info_t() {} @@ -40,8 +38,6 @@ namespace rados { ::encode(time_ofs, bl); ::encode(step_size, bl); ::encode(window, bl); - ::encode(last_tries, bl); - ::encode(last_success, bl); ENCODE_FINISH(bl); } void decode(bufferlist::iterator &bl) { @@ -54,13 +50,41 @@ namespace rados { ::decode(time_ofs, bl); ::decode(step_size, bl); ::decode(window, bl); - ::decode(last_tries, bl); - ::decode(last_success, bl); DECODE_FINISH(bl); } }; WRITE_CLASS_ENCODER(rados::cls::otp::otp_info_t) + enum OTPCheckResult { + OTP_CHECK_UNKNOWN = 0, + OTP_CHECK_SUCCESS = 1, + OTP_CHECK_FAIL = 2, + }; + + struct otp_check_t { + string token; + ceph::real_time timestamp; + OTPCheckResult result{OTP_CHECK_UNKNOWN}; + + void encode(bufferlist &bl) const { + ENCODE_START(1, 1, bl); + ::encode(token, bl); + ::encode(timestamp, bl); + ::encode((char)result, bl); + ENCODE_FINISH(bl); + } + void decode(bufferlist::iterator &bl) { + DECODE_START(1, bl); + ::decode(token, bl); + ::decode(timestamp, bl); + uint8_t t; + ::decode(t, bl); + result = (OTPCheckResult)t; + DECODE_FINISH(bl); + } + }; + WRITE_CLASS_ENCODER(rados::cls::otp::otp_check_t) + struct otp_repo_t { map entries; @@ -83,6 +107,7 @@ namespace rados { } WRITE_CLASS_ENCODER(rados::cls::otp::otp_info_t) +WRITE_CLASS_ENCODER(rados::cls::otp::otp_check_t) WRITE_CLASS_ENCODER(rados::cls::otp::otp_repo_t) #endif