diff options
Diffstat (limited to 'src/cls/otp')
-rw-r--r-- | src/cls/otp/cls_otp.cc | 571 | ||||
-rw-r--r-- | src/cls/otp/cls_otp_client.cc | 190 | ||||
-rw-r--r-- | src/cls/otp/cls_otp_client.h | 56 | ||||
-rw-r--r-- | src/cls/otp/cls_otp_ops.h | 166 | ||||
-rw-r--r-- | src/cls/otp/cls_otp_types.cc | 67 | ||||
-rw-r--r-- | src/cls/otp/cls_otp_types.h | 132 |
6 files changed, 1182 insertions, 0 deletions
diff --git a/src/cls/otp/cls_otp.cc b/src/cls/otp/cls_otp.cc new file mode 100644 index 00000000..355e14da --- /dev/null +++ b/src/cls/otp/cls_otp.cc @@ -0,0 +1,571 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +/** \file + * + * This is an OSD class that implements methods for management + * and use of otp (one time password). + * + */ + +#include <errno.h> +#include <map> +#include <list> + +#include <boost/range/adaptor/reversed.hpp> + +#include <liboath/oath.h> + +#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" + + +using namespace rados::cls::otp; + + +CLS_VER(1,0) +CLS_NAME(otp) + +#define ATTEMPTS_PER_WINDOW 5 + +static string otp_header_key = "header"; +static string otp_key_prefix = "otp/"; + +struct otp_header { + set<string> ids; + + otp_header() {} + + void encode(bufferlist &bl) const { + ENCODE_START(1, 1, bl); + encode(ids, bl); + ENCODE_FINISH(bl); + } + void decode(bufferlist::const_iterator &bl) { + DECODE_START(1, bl); + decode(ids, bl); + DECODE_FINISH(bl); + } +}; +WRITE_CLASS_ENCODER(otp_header) + +struct otp_instance { + otp_info_t otp; + + list<otp_check_t> last_checks; + uint64_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::const_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); + + 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 - std::chrono::seconds(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) +{ + uint64_t index; + uint32_t secs = (uint32_t)ceph::real_clock::to_time_t(timestamp); + int result = oath_totp_validate2(otp.seed_bin.c_str(), otp.seed_bin.length(), + secs, otp.step_size, otp.time_ofs, otp.window, + nullptr /* otp pos */, + val.c_str()); + if (result == OATH_INVALID_OTP || + result < 0) { + CLS_LOG(20, "otp check failed, result=%d", result); + return false; + } + + index = result + (secs - otp.time_ofs) / otp.step_size; + + if (index <= last_success) { /* already used value */ + CLS_LOG(20, "otp, use of old token: index=%lld last_success=%lld", (long long)index, (long long)last_success); + return false; + } + + last_success = index; + + return true; +} + +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; + + int r = cls_cxx_map_get_val(hctx, key, &bl); + if (r < 0) { + if (r != -ENOENT) { + CLS_ERR("error reading key %s: %d", key.c_str(), r); + } + return r; + } + + try { + auto it = bl.cbegin(); + decode(*instance, it); + } catch (const buffer::error &err) { + CLS_ERR("ERROR: failed to decode %s", key.c_str()); + return -EIO; + } + + return 0; +} + +static int write_otp_instance(cls_method_context_t hctx, const otp_instance& instance) +{ + string key = otp_key_prefix + instance.otp.id; + + bufferlist bl; + encode(instance, bl); + + int r = cls_cxx_map_set_val(hctx, key, &bl); + if (r < 0) { + 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_instance(cls_method_context_t hctx, const string& id) +{ + string key = otp_key_prefix + id; + + int r = cls_cxx_map_remove_key(hctx, key); + if (r < 0) { + CLS_ERR("ERROR: %s(): failed to remove key (otp id=%s, r=%d)", __func__, id.c_str(), r); + return r; + } + + return 0; +} + +static int read_header(cls_method_context_t hctx, otp_header *h) +{ + bufferlist bl; + encode(h, bl); + int r = cls_cxx_map_get_val(hctx, otp_header_key, &bl); + if (r == -ENOENT || r == -ENODATA) { + *h = otp_header(); + return 0; + } + if (r < 0) { + CLS_ERR("ERROR: %s(): failed to read header (r=%d)", __func__, r); + return r; + } + + if (bl.length() == 0) { + *h = otp_header(); + return 0; + } + + auto iter = bl.cbegin(); + try { + decode(*h, iter); + } catch (buffer::error& err) { + CLS_ERR("failed to decode otp_header"); + return -EIO; + } + + return 0; +} + +static int write_header(cls_method_context_t hctx, const otp_header& h) +{ + bufferlist bl; + encode(h, bl); + + int r = cls_cxx_map_set_val(hctx, otp_header_key, &bl); + if (r < 0) { + CLS_ERR("failed to store header (r=%d)", r); + return r; + } + + return 0; +} + +static int parse_seed(const string& seed, SeedType seed_type, bufferlist *seed_bin) +{ + size_t slen = seed.length(); + char secret[seed.length()]; + char *psecret = secret; + int result; + bool need_free = false; + + seed_bin->clear(); + + switch (seed_type) { + case OTP_SEED_BASE32: + need_free = true; /* oath_base32_decode allocates dest buffer */ + result = oath_base32_decode(seed.c_str(), seed.length(), + &psecret, &slen); + break; + default: /* just assume hex is the default */ + result = oath_hex2bin(seed.c_str(), psecret, &slen); + } + if (result != OATH_OK) { + CLS_LOG(20, "failed to parse seed"); + return -EINVAL; + } + + seed_bin->append(psecret, slen); + + if (need_free) { + free(psecret); + } + + return 0; +} + +static int otp_set_op(cls_method_context_t hctx, + bufferlist *in, bufferlist *out) +{ + CLS_LOG(20, "%s", __func__); + cls_otp_set_otp_op op; + try { + auto iter = in->cbegin(); + decode(op, iter); + } catch (const buffer::error &err) { + CLS_ERR("ERROR: %s(): failed to decode request", __func__); + return -EINVAL; + } + + otp_header h; + int r = read_header(hctx, &h); + if (r < 0) { + return r; + } + + for (auto entry : op.entries) { + otp_instance instance; + r = get_otp_instance(hctx, entry.id, &instance); + if (r < 0 && + r != -ENOENT) { + return r; + } + instance.otp = entry; + + r = parse_seed(instance.otp.seed, instance.otp.seed_type, &instance.otp.seed_bin); + if (r < 0) { + return r; + } + + r = write_otp_instance(hctx, instance); + if (r < 0) { + return r; + } + + h.ids.insert(entry.id); + } + + r = write_header(hctx, h); + if (r < 0) { + return r; + } + + return 0; +} + +static int otp_remove_op(cls_method_context_t hctx, + bufferlist *in, bufferlist *out) +{ + CLS_LOG(20, "%s", __func__); + cls_otp_remove_otp_op op; + try { + auto iter = in->cbegin(); + decode(op, iter); + } catch (const buffer::error &err) { + CLS_ERR("ERROR: %s(): failed to decode request", __func__); + return -EINVAL; + } + + otp_header h; + bool removed_existing = false; + int r = read_header(hctx, &h); + if (r < 0) { + return r; + } + + for (auto id : op.ids) { + bool existed = (h.ids.find(id) != h.ids.end()); + removed_existing = (removed_existing || existed); + + if (!existed) { + continue; + } + + r = remove_otp_instance(hctx, id); + if (r < 0) { + return r; + } + + h.ids.erase(id); + } + + if (removed_existing) { + r = write_header(hctx, h); + if (r < 0) { + return r; + } + } + + return 0; +} + +static int otp_get_op(cls_method_context_t hctx, + bufferlist *in, bufferlist *out) +{ + CLS_LOG(20, "%s", __func__); + cls_otp_get_otp_op op; + try { + auto iter = in->cbegin(); + decode(op, iter); + } catch (const buffer::error &err) { + CLS_ERR("ERROR: %s(): failed to decode request", __func__); + return -EINVAL; + } + + cls_otp_get_otp_reply result; + + otp_header h; + int r; + + r = read_header(hctx, &h); + if (r < 0) { + return r; + } + + if (op.get_all) { + op.ids.clear(); + for (auto id : h.ids) { + op.ids.push_back(id); + } + } + + for (auto id : op.ids) { + bool exists = (h.ids.find(id) != h.ids.end()); + + if (!exists) { + continue; + } + + 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); + + return 0; +} + +static int otp_check_op(cls_method_context_t hctx, + bufferlist *in, bufferlist *out) +{ + CLS_LOG(20, "%s", __func__); + cls_otp_check_otp_op op; + try { + auto iter = in->cbegin(); + 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; + } + + 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->cbegin(); + 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; + } + + cls_otp_get_result_reply reply; + instance.find(op.token, &reply.result); + encode(reply, *out); + + return 0; +} + +static int otp_get_current_time_op(cls_method_context_t hctx, + bufferlist *in, bufferlist *out) +{ + CLS_LOG(20, "%s", __func__); + cls_otp_get_current_time_op op; + try { + auto iter = in->cbegin(); + decode(op, iter); + } catch (const buffer::error &err) { + CLS_ERR("ERROR: %s(): failed to decode request", __func__); + return -EINVAL; + } + + cls_otp_get_current_time_reply reply; + reply.time = real_clock::now(); + encode(reply, *out); + + return 0; +} + +CLS_INIT(otp) +{ + CLS_LOG(20, "Loaded otp class!"); + + oath_init(); + + cls_handle_t h_class; + 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_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_method_handle_t h_get_current_time_op; + + cls_register("otp", &h_class); + cls_register_cxx_method(h_class, "otp_set", + CLS_METHOD_RD | CLS_METHOD_WR, + otp_set_op, &h_set_otp_op); + cls_register_cxx_method(h_class, "otp_get", + CLS_METHOD_RD, + otp_get_op, &h_get_otp_op); + cls_register_cxx_method(h_class, "otp_check", + CLS_METHOD_RD | CLS_METHOD_WR, + otp_check_op, &h_check_otp_op); + cls_register_cxx_method(h_class, "otp_get_result", + CLS_METHOD_RD, + 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); + cls_register_cxx_method(h_class, "get_current_time", + CLS_METHOD_RD, + otp_get_current_time_op, &h_get_current_time_op); + + return; +} diff --git a/src/cls/otp/cls_otp_client.cc b/src/cls/otp/cls_otp_client.cc new file mode 100644 index 00000000..dc1efab4 --- /dev/null +++ b/src/cls/otp/cls_otp_client.cc @@ -0,0 +1,190 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab +/* + * Ceph - scalable distributed file system + * + * Copyright (C) 2004-2006 Sage Weil <sage@newdream.net> + * + * This is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1, as published by the Free Software + * Foundation. See file COPYING. + * + */ + +#include "include/types.h" +#include "msg/msg_types.h" +#include "include/rados/librados.hpp" +#include "include/utime.h" + +using namespace librados; + +#include "cls/otp/cls_otp_ops.h" +#include "cls/otp/cls_otp_client.h" + +#include "common/random_string.h" /* for gen_rand_alphanumeric */ + +namespace rados { + namespace cls { + namespace otp { + + void OTP::create(librados::ObjectWriteOperation *rados_op, + const otp_info_t& config) { + cls_otp_set_otp_op op; + op.entries.push_back(config); + bufferlist in; + encode(op, in); + rados_op->exec("otp", "otp_set", in); + } + + void OTP::set(librados::ObjectWriteOperation *rados_op, + const list<otp_info_t>& entries) { + cls_otp_set_otp_op op; + op.entries = entries; + bufferlist in; + encode(op, in); + rados_op->exec("otp", "otp_set", in); + } + + void OTP::remove(librados::ObjectWriteOperation *rados_op, + const string& id) { + cls_otp_remove_otp_op op; + op.ids.push_back(id); + bufferlist in; + encode(op, in); + rados_op->exec("otp", "otp_remove", in); + } + + int OTP::check(CephContext *cct, librados::IoCtx& ioctx, const string& oid, + const string& id, const string& val, otp_check_t *result) { + cls_otp_check_otp_op op; + op.id = id; + op.val = val; +#define TOKEN_LEN 16 + op.token = gen_rand_alphanumeric(cct, TOKEN_LEN); + + bufferlist in; + bufferlist out; + encode(op, in); + int r = ioctx.exec(oid, "otp", "otp_check", in, out); + if (r < 0) { + return r; + } + + cls_otp_get_result_op op2; + op2.token = op.token; + bufferlist in2; + bufferlist out2; + encode(op2, in2); + r = ioctx.exec(oid, "otp", "otp_get_result", in, out); + if (r < 0) { + return r; + } + + auto iter = out.cbegin(); + cls_otp_get_result_reply ret; + try { + decode(ret, iter); + } catch (buffer::error& err) { + return -EBADMSG; + } + + *result = ret.result; + + return 0; + } + + int OTP::get(librados::ObjectReadOperation *rop, + librados::IoCtx& ioctx, const string& oid, + const list<string> *ids, bool get_all, list<otp_info_t> *result) { + librados::ObjectReadOperation _rop; + if (!rop) { + rop = &_rop; + } + cls_otp_get_otp_op op; + if (ids) { + op.ids = *ids; + } + op.get_all = get_all; + bufferlist in; + bufferlist out; + int op_ret; + encode(op, in); + rop->exec("otp", "otp_get", in, &out, &op_ret); + int r = ioctx.operate(oid, rop, nullptr); + if (r < 0) { + return r; + } + if (op_ret < 0) { + return op_ret; + } + + cls_otp_get_otp_reply ret; + auto iter = out.cbegin(); + try { + decode(ret, iter); + } catch (buffer::error& err) { + return -EBADMSG; + } + + *result = ret.found_entries;; + + return 0; + } + + int OTP::get(librados::ObjectReadOperation *op, + librados::IoCtx& ioctx, const string& oid, + const string& id, otp_info_t *result) { + list<string> ids{ id }; + list<otp_info_t> ret; + + int r = get(op, ioctx, oid, &ids, false, &ret); + if (r < 0) { + return r; + } + if (ret.empty()) { + return -ENOENT; + } + *result = ret.front(); + + return 0; + } + + int OTP::get_all(librados::ObjectReadOperation *op, librados::IoCtx& ioctx, const string& oid, + list<otp_info_t> *result) { + return get(op, ioctx, oid, nullptr, true, result); + } + + int OTP::get_current_time(librados::IoCtx& ioctx, const string& oid, + ceph::real_time *result) { + cls_otp_get_current_time_op op; + bufferlist in; + bufferlist out; + int op_ret; + encode(op, in); + ObjectReadOperation rop; + rop.exec("otp", "get_current_time", in, &out, &op_ret); + int r = ioctx.operate(oid, &rop, nullptr); + if (r < 0) { + return r; + } + if (op_ret < 0) { + return op_ret; + } + + cls_otp_get_current_time_reply ret; + auto iter = out.cbegin(); + try { + decode(ret, iter); + } catch (buffer::error& err) { + return -EBADMSG; + } + + *result = ret.time; + + return 0; + } + } // namespace otp + } // namespace cls +} // namespace rados + diff --git a/src/cls/otp/cls_otp_client.h b/src/cls/otp/cls_otp_client.h new file mode 100644 index 00000000..f19c9459 --- /dev/null +++ b/src/cls/otp/cls_otp_client.h @@ -0,0 +1,56 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#ifndef CEPH_CLS_OTP_CLIENT_H +#define CEPH_CLS_OTP_CLIENT_H + +#include "include/rados/librados_fwd.hpp" +#include "cls/otp/cls_otp_types.h" + +namespace rados { + namespace cls { + namespace otp { + + class OTP { + public: + static void create(librados::ObjectWriteOperation *op, const otp_info_t& config); + static void set(librados::ObjectWriteOperation *op, const list<otp_info_t>& entries); + static void remove(librados::ObjectWriteOperation *op, const string& id); + static int get(librados::ObjectReadOperation *op, + librados::IoCtx& ioctx, const string& oid, + const list<string> *ids, bool get_all, list<otp_info_t> *result); + static int get(librados::ObjectReadOperation *op, + librados::IoCtx& ioctx, const string& oid, + const string& id, otp_info_t *result); + static int get_all(librados::ObjectReadOperation *op, + librados::IoCtx& ioctx, const string& oid, + list<otp_info_t> *result); + static int check(CephContext *cct, librados::IoCtx& ioctx, const string& oid, + const string& id, const string& val, otp_check_t *result); + static int get_current_time(librados::IoCtx& ioctx, const string& oid, + ceph::real_time *result); + }; + + class TOTPConfig { + otp_info_t config; + public: + TOTPConfig(const string& id, const string& seed) { + config.type = OTP_TOTP; + config.id = id; + config.seed = seed; + } + void set_step_size(int step_size) { + config.step_size = step_size; + } + void set_window(int window) { + config.window = window; + } + void get_config(otp_info_t *conf) { + *conf = config; + } + }; + } // namespace otp + } // namespace cls +} // namespace rados + +#endif diff --git a/src/cls/otp/cls_otp_ops.h b/src/cls/otp/cls_otp_ops.h new file mode 100644 index 00000000..51cec3eb --- /dev/null +++ b/src/cls/otp/cls_otp_ops.h @@ -0,0 +1,166 @@ +#ifndef CEPH_CLS_OTP_OPS_H +#define CEPH_CLS_OTP_OPS_H + +#include "include/types.h" +#include "include/utime.h" +#include "cls/otp/cls_otp_types.h" + +struct cls_otp_set_otp_op +{ + list<rados::cls::otp::otp_info_t> entries; + + void encode(bufferlist &bl) const { + ENCODE_START(1, 1, bl); + encode(entries, bl); + ENCODE_FINISH(bl); + } + void decode(bufferlist::const_iterator &bl) { + DECODE_START(1, bl); + decode(entries, bl); + DECODE_FINISH(bl); + } +}; +WRITE_CLASS_ENCODER(cls_otp_set_otp_op) + +struct cls_otp_check_otp_op +{ + string id; + string val; + string token; + + void encode(bufferlist &bl) const { + ENCODE_START(1, 1, bl); + encode(id, bl); + encode(val, bl); + encode(token, bl); + ENCODE_FINISH(bl); + } + void decode(bufferlist::const_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; + + void encode(bufferlist &bl) const { + ENCODE_START(1, 1, bl); + encode(token, bl); + ENCODE_FINISH(bl); + } + void decode(bufferlist::const_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; + + void encode(bufferlist &bl) const { + ENCODE_START(1, 1, bl); + encode(result, bl); + ENCODE_FINISH(bl); + } + void decode(bufferlist::const_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<string> ids; + + void encode(bufferlist &bl) const { + ENCODE_START(1, 1, bl); + encode(ids, bl); + ENCODE_FINISH(bl); + } + void decode(bufferlist::const_iterator &bl) { + DECODE_START(1, bl); + decode(ids, bl); + DECODE_FINISH(bl); + } +}; +WRITE_CLASS_ENCODER(cls_otp_remove_otp_op) + +struct cls_otp_get_otp_op +{ + bool get_all{false}; + list<string> ids; + + void encode(bufferlist &bl) const { + ENCODE_START(1, 1, bl); + encode(get_all, bl); + encode(ids, bl); + ENCODE_FINISH(bl); + } + void decode(bufferlist::const_iterator &bl) { + DECODE_START(1, bl); + decode(get_all, bl); + decode(ids, bl); + DECODE_FINISH(bl); + } +}; +WRITE_CLASS_ENCODER(cls_otp_get_otp_op) + +struct cls_otp_get_otp_reply +{ + list<rados::cls::otp::otp_info_t> found_entries; + + void encode(bufferlist &bl) const { + ENCODE_START(1, 1, bl); + encode(found_entries, bl); + ENCODE_FINISH(bl); + } + void decode(bufferlist::const_iterator &bl) { + DECODE_START(1, bl); + decode(found_entries, bl); + DECODE_FINISH(bl); + } +}; +WRITE_CLASS_ENCODER(cls_otp_get_otp_reply) + +struct cls_otp_get_current_time_op +{ + void encode(bufferlist &bl) const { + ENCODE_START(1, 1, bl); + ENCODE_FINISH(bl); + } + void decode(bufferlist::const_iterator &bl) { + DECODE_START(1, bl); + DECODE_FINISH(bl); + } +}; +WRITE_CLASS_ENCODER(cls_otp_get_current_time_op) + +struct cls_otp_get_current_time_reply +{ + ceph::real_time time; + + void encode(bufferlist &bl) const { + ENCODE_START(1, 1, bl); + encode(time, bl); + ENCODE_FINISH(bl); + } + void decode(bufferlist::const_iterator &bl) { + DECODE_START(1, bl); + decode(time, bl); + DECODE_FINISH(bl); + } +}; +WRITE_CLASS_ENCODER(cls_otp_get_current_time_reply) + +#endif diff --git a/src/cls/otp/cls_otp_types.cc b/src/cls/otp/cls_otp_types.cc new file mode 100644 index 00000000..b8a6e012 --- /dev/null +++ b/src/cls/otp/cls_otp_types.cc @@ -0,0 +1,67 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab +/* + * Ceph - scalable distributed file system + * + * Copyright (C) 2004-2006 Sage Weil <sage@newdream.net> + * + * This is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1, as published by the Free Software + * Foundation. See file COPYING. + * + */ + +#include "objclass/objclass.h" +#include "common/Formatter.h" +#include "common/Clock.h" +#include "common/ceph_json.h" + +#include "include/utime.h" + +#include "cls/otp/cls_otp_types.h" + +using namespace rados::cls::otp; + +void otp_info_t::dump(Formatter *f) const +{ + encode_json("type", (int)type, f); + encode_json("id", id, f); + encode_json("seed", seed, f); + string st; + switch (seed_type) { + case rados::cls::otp::OTP_SEED_HEX: + st = "hex"; + break; + case rados::cls::otp::OTP_SEED_BASE32: + st = "base32"; + break; + default: + st = "unknown"; + } + encode_json("seed_type", st, f); + encode_json("time_ofs", time_ofs, f); + encode_json("step_size", step_size, f); + encode_json("window", window, f); +} + +void otp_info_t::decode_json(JSONObj *obj) +{ + int t{-1}; + JSONDecoder::decode_json("type", t, obj); + type = (OTPType)t; + JSONDecoder::decode_json("id", id, obj); + JSONDecoder::decode_json("seed", seed, obj); + string st; + JSONDecoder::decode_json("seed_type", st, obj); + if (st == "hex") { + seed_type = OTP_SEED_HEX; + } else if (st == "base32") { + seed_type = OTP_SEED_BASE32; + } else { + seed_type = OTP_SEED_UNKNOWN; + } + JSONDecoder::decode_json("time_ofs", time_ofs, obj); + JSONDecoder::decode_json("step_size", step_size, obj); + JSONDecoder::decode_json("window", window, obj); +} diff --git a/src/cls/otp/cls_otp_types.h b/src/cls/otp/cls_otp_types.h new file mode 100644 index 00000000..b542b5cb --- /dev/null +++ b/src/cls/otp/cls_otp_types.h @@ -0,0 +1,132 @@ +#ifndef CEPH_CLS_OTP_TYPES_H +#define CEPH_CLS_OTP_TYPES_H + +#include "include/encoding.h" +#include "include/types.h" + + +#define CLS_OTP_MAX_REPO_SIZE 100 + +class JSONObj; + +namespace rados { + namespace cls { + namespace otp { + + enum OTPType { + OTP_UNKNOWN = 0, + OTP_HOTP = 1, /* unsupported */ + OTP_TOTP = 2, + }; + + enum SeedType { + OTP_SEED_UNKNOWN = 0, + OTP_SEED_HEX = 1, + OTP_SEED_BASE32 = 2, + }; + + struct otp_info_t { + OTPType type{OTP_TOTP}; + string id; + string seed; + SeedType seed_type{OTP_SEED_UNKNOWN}; + bufferlist seed_bin; /* parsed seed, built automatically by otp_set_op, + * not being json encoded/decoded on purpose + */ + int32_t time_ofs{0}; + 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 */ + + otp_info_t() {} + + void encode(bufferlist &bl) const { + ENCODE_START(1, 1, bl); + encode((uint8_t)type, bl); + /* if we ever implement anything other than TOTP + * then we'll need to branch here */ + encode(id, bl); + encode(seed, bl); + encode((uint8_t)seed_type, bl); + encode(seed_bin, bl); + encode(time_ofs, bl); + encode(step_size, bl); + encode(window, bl); + ENCODE_FINISH(bl); + } + void decode(bufferlist::const_iterator &bl) { + DECODE_START(1, bl); + uint8_t t; + decode(t, bl); + type = (OTPType)t; + decode(id, bl); + decode(seed, bl); + uint8_t st; + decode(st, bl); + seed_type = (SeedType)st; + decode(seed_bin, bl); + decode(time_ofs, bl); + decode(step_size, bl); + decode(window, bl); + DECODE_FINISH(bl); + } + void dump(Formatter *f) const; + void decode_json(JSONObj *obj); + }; + 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::const_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<string, otp_info_t> entries; + + otp_repo_t() {} + + void encode(bufferlist &bl) const { + ENCODE_START(1, 1, bl); + encode(entries, bl); + ENCODE_FINISH(bl); + } + void decode(bufferlist::const_iterator &bl) { + DECODE_START(1, bl); + decode(entries, bl); + DECODE_FINISH(bl); + } + }; + WRITE_CLASS_ENCODER(rados::cls::otp::otp_repo_t) + } + } +} + +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 |