summaryrefslogtreecommitdiffstats
path: root/src/cls/otp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 18:45:59 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 18:45:59 +0000
commit19fcec84d8d7d21e796c7624e521b60d28ee21ed (patch)
tree42d26aa27d1e3f7c0b8bd3fd14e7d7082f5008dc /src/cls/otp
parentInitial commit. (diff)
downloadceph-upstream.tar.xz
ceph-upstream.zip
Adding upstream version 16.2.11+ds.upstream/16.2.11+dsupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--src/cls/otp/cls_otp.cc578
-rw-r--r--src/cls/otp/cls_otp_client.cc191
-rw-r--r--src/cls/otp/cls_otp_client.h60
-rw-r--r--src/cls/otp/cls_otp_ops.h169
-rw-r--r--src/cls/otp/cls_otp_types.cc71
-rw-r--r--src/cls/otp/cls_otp_types.h135
6 files changed, 1204 insertions, 0 deletions
diff --git a/src/cls/otp/cls_otp.cc b/src/cls/otp/cls_otp.cc
new file mode 100644
index 000000000..9143f6241
--- /dev/null
+++ b/src/cls/otp/cls_otp.cc
@@ -0,0 +1,578 @@
+// -*- 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 std::list;
+using std::string;
+using std::set;
+
+using ceph::bufferlist;
+using ceph::encode;
+using ceph::real_clock;
+
+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)
+{
+ auto 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 ceph::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 (ceph::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 ceph::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 ceph::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 ceph::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 ceph::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 ceph::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 ceph::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 000000000..0ba55571f
--- /dev/null
+++ b/src/cls/otp/cls_otp_client.cc
@@ -0,0 +1,191 @@
+// -*- 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 std::list;
+using std::string;
+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 (ceph::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 (ceph::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 (ceph::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 000000000..a921e4e38
--- /dev/null
+++ b/src/cls/otp/cls_otp_client.h
@@ -0,0 +1,60 @@
+// -*- 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 std::list<otp_info_t>& entries);
+ static void remove(librados::ObjectWriteOperation *op, const std::string& id);
+ static int get(librados::ObjectReadOperation *op,
+ librados::IoCtx& ioctx, const std::string& oid,
+ const std::string& id, otp_info_t *result);
+ static int get_all(librados::ObjectReadOperation *op,
+ librados::IoCtx& ioctx, const std::string& oid,
+ std::list<otp_info_t> *result);
+// these overloads which call io_ctx.operate() or io_ctx.exec() should not be called in the rgw.
+// rgw_rados_operate() should be called after the overloads w/o calls to io_ctx.operate()/exec()
+#ifndef CLS_CLIENT_HIDE_IOCTX
+ static int get(librados::ObjectReadOperation *op,
+ librados::IoCtx& ioctx, const std::string& oid,
+ const std::list<std::string> *ids, bool get_all, std::list<otp_info_t> *result);
+ static int check(CephContext *cct, librados::IoCtx& ioctx, const std::string& oid,
+ const std::string& id, const std::string& val, otp_check_t *result);
+ static int get_current_time(librados::IoCtx& ioctx, const std::string& oid,
+ ceph::real_time *result);
+#endif
+ };
+
+ class TOTPConfig {
+ otp_info_t config;
+ public:
+ TOTPConfig(const std::string& id, const std::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 000000000..970bd3a78
--- /dev/null
+++ b/src/cls/otp/cls_otp_ops.h
@@ -0,0 +1,169 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#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
+{
+ std::list<rados::cls::otp::otp_info_t> entries;
+
+ void encode(ceph::buffer::list &bl) const {
+ ENCODE_START(1, 1, bl);
+ encode(entries, bl);
+ ENCODE_FINISH(bl);
+ }
+ void decode(ceph::buffer::list::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
+{
+ std::string id;
+ std::string val;
+ std::string token;
+
+ void encode(ceph::buffer::list &bl) const {
+ ENCODE_START(1, 1, bl);
+ encode(id, bl);
+ encode(val, bl);
+ encode(token, bl);
+ ENCODE_FINISH(bl);
+ }
+ void decode(ceph::buffer::list::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
+{
+ std::string token;
+
+ void encode(ceph::buffer::list &bl) const {
+ ENCODE_START(1, 1, bl);
+ encode(token, bl);
+ ENCODE_FINISH(bl);
+ }
+ void decode(ceph::buffer::list::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(ceph::buffer::list &bl) const {
+ ENCODE_START(1, 1, bl);
+ encode(result, bl);
+ ENCODE_FINISH(bl);
+ }
+ void decode(ceph::buffer::list::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
+{
+ std::list<std::string> ids;
+
+ void encode(ceph::buffer::list &bl) const {
+ ENCODE_START(1, 1, bl);
+ encode(ids, bl);
+ ENCODE_FINISH(bl);
+ }
+ void decode(ceph::buffer::list::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};
+ std::list<std::string> ids;
+
+ void encode(ceph::buffer::list &bl) const {
+ ENCODE_START(1, 1, bl);
+ encode(get_all, bl);
+ encode(ids, bl);
+ ENCODE_FINISH(bl);
+ }
+ void decode(ceph::buffer::list::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
+{
+ std::list<rados::cls::otp::otp_info_t> found_entries;
+
+ void encode(ceph::buffer::list &bl) const {
+ ENCODE_START(1, 1, bl);
+ encode(found_entries, bl);
+ ENCODE_FINISH(bl);
+ }
+ void decode(ceph::buffer::list::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(ceph::buffer::list &bl) const {
+ ENCODE_START(1, 1, bl);
+ ENCODE_FINISH(bl);
+ }
+ void decode(ceph::buffer::list::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(ceph::buffer::list &bl) const {
+ ENCODE_START(1, 1, bl);
+ encode(time, bl);
+ ENCODE_FINISH(bl);
+ }
+ void decode(ceph::buffer::list::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 000000000..1f95749ed
--- /dev/null
+++ b/src/cls/otp/cls_otp_types.cc
@@ -0,0 +1,71 @@
+// -*- 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 std::string;
+
+using ceph::Formatter;
+
+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 000000000..6e431b330
--- /dev/null
+++ b/src/cls/otp/cls_otp_types.h
@@ -0,0 +1,135 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#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};
+ std::string id;
+ std::string seed;
+ SeedType seed_type{OTP_SEED_UNKNOWN};
+ ceph::buffer::list 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(ceph::buffer::list &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(ceph::buffer::list::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(ceph::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 {
+ std::string token;
+ ceph::real_time timestamp;
+ OTPCheckResult result{OTP_CHECK_UNKNOWN};
+
+ void encode(ceph::buffer::list &bl) const {
+ ENCODE_START(1, 1, bl);
+ encode(token, bl);
+ encode(timestamp, bl);
+ encode((char)result, bl);
+ ENCODE_FINISH(bl);
+ }
+ void decode(ceph::buffer::list::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 {
+ std::map<std::string, otp_info_t> entries;
+
+ otp_repo_t() {}
+
+ void encode(ceph::buffer::list &bl) const {
+ ENCODE_START(1, 1, bl);
+ encode(entries, bl);
+ ENCODE_FINISH(bl);
+ }
+ void decode(ceph::buffer::list::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