]> git.apps.os.sepia.ceph.com Git - ceph-ci.git/commitdiff
cls/otp: implement totp check logic
authorYehuda Sadeh <yehuda@redhat.com>
Wed, 15 Nov 2017 01:47:30 +0000 (17:47 -0800)
committerYehuda Sadeh <yehuda@redhat.com>
Mon, 9 Apr 2018 14:01:02 +0000 (07:01 -0700)
Currently checking for bogus results, still need to integrate with
totp library.

Signed-off-by: Yehuda Sadeh <yehuda@redhat.com>
src/cls/otp/cls_otp.cc
src/cls/otp/cls_otp_ops.h
src/cls/otp/cls_otp_types.h

index b44b9e92cc55aff331572df3ecf1ffcdd9412d57..188345d56d9fc0f77779a18db81aac9cf5102537 100644 (file)
 #include <map>
 #include <list>
 
+#include <boost/range/adaptor/reversed.hpp>
+
 #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<string> 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<otp_check_t> 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);
index 77feb456de8b65b161f95338e2246ccdff836994..86ea9f4126f65fcc4047ac8cf42011f192f50ac7 100644 (file)
@@ -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<string> ids;
index 14b9f699eb53fba6682050d322abe7cce5a034f3..e4b62aca535c8c80a4f70e15336b69f118596024 100644 (file)
@@ -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<ceph::real_time> 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<string, otp_info_t> 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