]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
cls/otp: initial work
authorYehuda Sadeh <yehuda@redhat.com>
Tue, 14 Nov 2017 23:57:07 +0000 (15:57 -0800)
committerYehuda Sadeh <yehuda@redhat.com>
Mon, 9 Apr 2018 14:01:02 +0000 (07:01 -0700)
Signed-off-by: Yehuda Sadeh <yehuda@redhat.com>
src/cls/CMakeLists.txt
src/cls/otp/cls_otp.cc [new file with mode: 0644]
src/cls/otp/cls_otp_ops.h [new file with mode: 0644]
src/cls/otp/cls_otp_types.h [new file with mode: 0644]
src/rgw/rgw_common.h
src/rgw/rgw_json_enc.cc
src/rgw/rgw_rest_s3.cc

index 556769264ad5197a81e315d7cc1b7edec06a83f2..c9cf3862a0d63e547afba4ac48c0f0e0c8d341a1 100644 (file)
@@ -69,6 +69,23 @@ add_library(cls_lock_client STATIC ${cls_lock_client_srcs})
 
 list(APPEND cls_embedded_srcs ${cls_lock_srcs} ${cls_lock_client_srcs})
 
+# cls_otp
+set(cls_otp_srcs otp/cls_otp.cc)
+add_library(cls_otp SHARED ${cls_otp_srcs})
+set_target_properties(cls_otp PROPERTIES
+  VERSION "1.0.0"
+  SOVERSION "1"
+  INSTALL_RPATH "")
+install(TARGETS cls_otp DESTINATION ${cls_dir})
+
+# set(cls_otp_client_srcs
+#   otp/cls_otp_client.cc
+#   otp/cls_otp_types.cc
+#   otp/cls_otp_ops.cc)
+# add_library(cls_otp_client STATIC ${cls_otp_client_srcs})
+# 
+# list(APPEND cls_embedded_srcs ${cls_otp_srcs} ${cls_otp_client_srcs})
+
 # cls_refcount
 set(cls_refcount_srcs
   refcount/cls_refcount.cc
diff --git a/src/cls/otp/cls_otp.cc b/src/cls/otp/cls_otp.cc
new file mode 100644 (file)
index 0000000..b44b9e9
--- /dev/null
@@ -0,0 +1,356 @@
+// -*- 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 "include/types.h"
+#include "include/utime.h"
+#include "objclass/objclass.h"
+
+#include "common/errno.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
+
+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::iterator &bl) {
+    DECODE_START(1, bl);
+    ::decode(ids, bl);
+    DECODE_FINISH(bl);
+  }
+};
+WRITE_CLASS_ENCODER(otp_header)
+
+static string otp_key_prefix = "otp.";
+
+static int get_otp_info(cls_method_context_t hctx, const string& id, otp_info_t *otp)
+{
+  bufferlist bl;
+  string key = otp_key_prefix + id;
+  int r = cls_cxx_map_get_val(hctx, key.c_str(), &bl);
+  if (r < 0) {
+    if (r != -ENOENT) {
+      CLS_ERR("error reading key %s: %d", key.c_str(), r);
+    }
+    return r;
+  }
+
+  try {
+    bufferlist::iterator it = bl.begin();
+    ::decode(*otp, it);
+  } catch (const buffer::error &err) {
+    CLS_ERR("ERROR: failed to decode %s", key.c_str());
+    return -EIO;
+  }
+
+  return 0;
+}
+
+static int write_otp_info(cls_method_context_t hctx, const otp_info_t& otp)
+{
+  string key = otp_key_prefix + otp.id;
+
+  bufferlist bl;
+  ::encode(otp, 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);
+    return r;
+  }
+
+  return 0;
+}
+
+static int remove_otp_info(cls_method_context_t hctx, const string& id)
+{
+  string key = otp_key_prefix + id;
+
+  int r = cls_cxx_map_remove_key(hctx, key.c_str());
+  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_read_header(hctx, &bl);
+  if (r == -ENOENT || r == -ENODATA) {
+    *h = otp_header();
+    return 0;
+  }
+  if (r < 0) {
+    CLS_ERR("ERROR: %s(): failed to read map header (r=%d)", __func__, r);
+    return r;
+  }
+
+  auto iter = bl.begin();
+  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_write_header(hctx, &bl);
+  if (r < 0) {
+    CLS_ERR("failed to store map header (r=%d)", r);
+    return r;
+  }
+
+  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->begin();
+    ::decode(op, iter);
+  } catch (const buffer::error &err) {
+    CLS_ERR("ERROR: %s(): failed to decode request", __func__);
+    return -EINVAL;
+  }
+
+  otp_header h;
+  bool update_header = false;
+  int r = read_header(hctx, &h);
+  if (r < 0) {
+    return r;
+  }
+
+  for (auto entry : op.entries) {
+    bool existed = (h.ids.find(entry.id) != h.ids.end());
+    update_header = (update_header || !existed);
+
+    if (!existed) {
+      continue;
+    }
+
+    r = write_otp_info(hctx, entry);
+    if (r < 0) {
+      return r;
+    }
+
+    h.ids.insert(entry.id);
+  }
+
+  if (update_header) {
+    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->begin();
+    ::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_info(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->begin();
+    ::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;
+    }
+
+    r = get_otp_info(hctx, id, &result.found_entries[id]);
+    if (r < 0) {
+      return r;
+    }
+  }
+
+  ::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->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_info_t otp;
+
+  r = get_otp_info(hctx, op.id, &otp);
+  if (r < 0) {
+    return r;
+  }
+
+#warning FIXME
+  if (op.val != otp.seed) { /* temporary */
+    return -EACCES;
+  }
+
+#warning missing
+  return 0;
+}
+
+CLS_INIT(otp)
+{
+  CLS_LOG(20, "Loaded otp class!");
+
+  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_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_remove_otp_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_check_op, &h_check_otp_op);
+  cls_register_cxx_method(h_class, "otp_remove",
+                          CLS_METHOD_RD | CLS_METHOD_WR,
+                          otp_remove_op, &h_remove_otp_op);
+
+  return;
+}
diff --git a/src/cls/otp/cls_otp_ops.h b/src/cls/otp/cls_otp_ops.h
new file mode 100644 (file)
index 0000000..77feb45
--- /dev/null
@@ -0,0 +1,109 @@
+#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;
+
+  cls_otp_set_otp_op() = default;
+
+  void encode(bufferlist &bl) const {
+    ENCODE_START(1, 1, bl);
+    ::encode(entries, bl);
+    ENCODE_FINISH(bl);
+  }
+  void decode(bufferlist::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;
+
+  cls_otp_check_otp_op() = default;
+
+  void encode(bufferlist &bl) const {
+    ENCODE_START(1, 1, bl);
+    ::encode(id, bl);
+    ::encode(val, bl);
+    ENCODE_FINISH(bl);
+  }
+  void decode(bufferlist::iterator &bl) {
+    DECODE_START(1, bl);
+    ::decode(id, bl);
+    ::decode(val, bl);
+    DECODE_FINISH(bl);
+  }
+};
+WRITE_CLASS_ENCODER(cls_otp_check_otp_op)
+
+struct cls_otp_remove_otp_op
+{
+  list<string> ids;
+
+  cls_otp_remove_otp_op() = default;
+
+  void encode(bufferlist &bl) const {
+    ENCODE_START(1, 1, bl);
+    ::encode(ids, bl);
+    ENCODE_FINISH(bl);
+  }
+  void decode(bufferlist::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;
+
+  cls_otp_get_otp_op() = default;
+
+  void encode(bufferlist &bl) const {
+    ENCODE_START(1, 1, bl);
+    ::encode(get_all, bl);
+    ::encode(ids, bl);
+    ENCODE_FINISH(bl);
+  }
+  void decode(bufferlist::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;
+
+  cls_otp_get_otp_reply() = default;
+
+  void encode(bufferlist &bl) const {
+    ENCODE_START(1, 1, bl);
+    ::encode(found_entries, bl);
+    ENCODE_FINISH(bl);
+  }
+  void decode(bufferlist::iterator &bl) {
+    DECODE_START(1, bl);
+    ::decode(found_entries, bl);
+    DECODE_FINISH(bl);
+  }
+};
+WRITE_CLASS_ENCODER(cls_otp_get_otp_reply)
+
+#endif
diff --git a/src/cls/otp/cls_otp_types.h b/src/cls/otp/cls_otp_types.h
new file mode 100644 (file)
index 0000000..14b9f69
--- /dev/null
@@ -0,0 +1,88 @@
+#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
+
+
+namespace rados {
+  namespace cls {
+    namespace otp {
+
+      enum OTPType {
+        UNKNOWN = 0,
+        HOTP = 1,  /* unsupported */
+        TOTP = 2,
+      };
+
+      struct otp_info_t {
+        OTPType type{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<ceph::real_time> last_tries;
+        uint64_t last_success{0}; /* otp_counter of last successful check */
+
+        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(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) {
+          DECODE_START(1, bl);
+          uint8_t t;
+          ::decode(t, bl);
+          type = (OTPType)t;
+          ::decode(id, bl);
+          ::decode(seed, bl);
+          ::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)
+
+      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::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_repo_t)
+
+#endif
index b1adef0ccb4cb28c2c5cc5a955b28f51ab32bacf..fae0e698eb16874ceb6d88f0cb9c578671af7497 100644 (file)
@@ -634,7 +634,7 @@ struct RGWUserInfo
   map<int, string> temp_url_keys;
   RGWQuotaInfo user_quota;
   uint32_t type;
-  map<string, string> mfa_devices;
+  set<string> mfa_ids;
 
   RGWUserInfo()
     : auid(0),
@@ -695,7 +695,7 @@ struct RGWUserInfo
      encode(user_id.tenant, bl);
      encode(admin, bl);
      encode(type, bl);
-     encode(mfa_devices, bl);
+     encode(mfa_ids, bl);
      ENCODE_FINISH(bl);
   }
   void decode(bufferlist::iterator& bl) {
@@ -773,7 +773,7 @@ struct RGWUserInfo
       decode(type, bl);
     }
     if (struct_v >= 20) {
-      ::decode(mfa_devices, bl);
+      decode(mfa_ids, bl);
     }
     DECODE_FINISH(bl);
   }
index 36bbb3531d32cfbe31b791edb8978fea4964c817..af0bf3ba9367b7b8455feffc1d0a898e7e0fc3f2 100644 (file)
@@ -474,7 +474,7 @@ void RGWUserInfo::dump(Formatter *f) const
     break;
   }
   encode_json("type", user_source_type, f);
-  encode_json("mfa_devices", mfa_devices, f);
+  encode_json("mfa_ids", mfa_ids, f);
 }
 
 
@@ -544,7 +544,7 @@ void RGWUserInfo::decode_json(JSONObj *obj)
   } else if (user_source_type == "none") {
     type = TYPE_NONE;
   }
-  JSONDecoder::decode_json("mfa_devices", mfa_devices, obj);
+  JSONDecoder::decode_json("mfa_ids", mfa_ids, obj);
 }
 
 void RGWQuotaInfo::dump(Formatter *f) const
index 0d256e752f91e99fc78a4f2f9730b52bd119ddd4..c692cd6c9614fb461384de4da1d691efeb318a8d 100644 (file)
@@ -3237,12 +3237,13 @@ static int verify_mfa(RGWRados *store, RGWUserInfo *user, const string& mfa_str,
   string& serial = params[0];
   string& otp = params[1];
 
-  auto i = user->mfa_devices.find(serial);
-  if (i == user->mfa_devices.end()) {
+  auto i = user->mfa_ids.find(serial);
+  if (i == user->mfa_ids.end()) {
     ldout(store->ctx(), 5) << "NOTICE: user does not have mfa device with serial=" << serial << dendl;
     return -EACCES;
   }
 
+#if 0
   string& seed = i->second;
 
   utime_t now = ceph_clock_now();
@@ -3254,6 +3255,7 @@ static int verify_mfa(RGWRados *store, RGWUserInfo *user, const string& mfa_str,
     ldout(cct, 5) << "NOTICE: totp token failed to validate" << dendl;
     return -EACCES;
   }
+#endif
 
   *verified = true;