]> git.apps.os.sepia.ceph.com Git - ceph-ci.git/commitdiff
rgw: mfa - initial work
authorYehuda Sadeh <yehuda@redhat.com>
Thu, 9 Nov 2017 23:31:26 +0000 (18:31 -0500)
committerYehuda Sadeh <yehuda@redhat.com>
Mon, 9 Apr 2018 14:01:02 +0000 (07:01 -0700)
Signed-off-by: Yehuda Sadeh <yehuda@redhat.com>
CMakeLists.txt
cmake/modules/Findliboath.cmake [new file with mode: 0644]
src/rgw/rgw_common.h
src/rgw/rgw_json_enc.cc
src/rgw/rgw_op.h
src/rgw/rgw_rest_s3.cc

index 34c5796a47dc931ccd4f17af16f4dc675cd48559..47309814a76bdc24ac77abaf92e49f359a4c890f 100644 (file)
@@ -395,6 +395,7 @@ if(WITH_RADOSGW)
     message(WARNING "disabling WITH_RADOSGW_BEAST_FRONTEND, which depends on WITH_BOOST_CONTEXT")
     set(WITH_RADOSGW_BEAST_FRONTEND OFF)
   endif()
+  find_package(liboath 2.4 REQUIRED)
 
 # https://curl.haxx.se/docs/install.html mentions the
 # configure flags for various ssl backends
diff --git a/cmake/modules/Findliboath.cmake b/cmake/modules/Findliboath.cmake
new file mode 100644 (file)
index 0000000..ae5446a
--- /dev/null
@@ -0,0 +1,22 @@
+# CMake module to search for liboath headers
+#
+# If it's found it sets LIBOATH_FOUND to TRUE
+# and following variables are set:
+# LIBOATH_INCLUDE_DIR
+# LIBOATH_LIBRARY
+find_path(LIBOATH_INCLUDE_DIR
+  oath.h
+  PATHS
+  /usr/include
+  /usr/local/include
+  /usr/include/liboath)
+find_library(LIBOATH_LIBRARY NAMES oath liboath PATHS
+  /usr/local/lib
+  /usr/lib)
+
+# handle the QUIETLY and REQUIRED arguments and set UUID_FOUND to TRUE if
+# all listed variables are TRUE
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(oath DEFAULT_MSG LIBOATH_LIBRARY LIBOATH_INCLUDE_DIR)
+
+mark_as_advanced(LIBOATH_LIBRARY LIBOATH_INCLUDE_DIR)
index fdba4ef14a6e3747cba1126441659a9b74d1bdc2..b1adef0ccb4cb28c2c5cc5a955b28f51ab32bacf 100644 (file)
@@ -634,6 +634,7 @@ struct RGWUserInfo
   map<int, string> temp_url_keys;
   RGWQuotaInfo user_quota;
   uint32_t type;
+  map<string, string> mfa_devices;
 
   RGWUserInfo()
     : auid(0),
@@ -653,7 +654,7 @@ struct RGWUserInfo
   }
 
   void encode(bufferlist& bl) const {
-     ENCODE_START(19, 9, bl);
+     ENCODE_START(20, 9, bl);
      encode(auid, bl);
      string access_key;
      string secret_key;
@@ -694,10 +695,11 @@ struct RGWUserInfo
      encode(user_id.tenant, bl);
      encode(admin, bl);
      encode(type, bl);
+     encode(mfa_devices, bl);
      ENCODE_FINISH(bl);
   }
   void decode(bufferlist::iterator& bl) {
-     DECODE_START_LEGACY_COMPAT_LEN_32(19, 9, 9, bl);
+     DECODE_START_LEGACY_COMPAT_LEN_32(20, 9, 9, bl);
      if (struct_v >= 2) decode(auid, bl);
      else auid = CEPH_AUTH_UID_DEFAULT;
      string access_key;
@@ -770,6 +772,9 @@ struct RGWUserInfo
     if (struct_v >= 19) {
       decode(type, bl);
     }
+    if (struct_v >= 20) {
+      ::decode(mfa_devices, bl);
+    }
     DECODE_FINISH(bl);
   }
   void dump(Formatter *f) const;
@@ -1178,6 +1183,7 @@ enum RGWBucketFlags {
   BUCKET_VERSIONED = 0x2,
   BUCKET_VERSIONS_SUSPENDED = 0x4,
   BUCKET_DATASYNC_DISABLED = 0X8,
+  BUCKET_MFA_ENABLED = 0X10,
 };
 
 enum RGWBucketIndexType {
@@ -1345,8 +1351,9 @@ struct RGWBucketInfo
   void decode_json(JSONObj *obj);
 
   bool versioned() const { return (flags & BUCKET_VERSIONED) != 0; }
-  int versioning_status() { return flags & (BUCKET_VERSIONED | BUCKET_VERSIONS_SUSPENDED); }
+  int versioning_status() { return flags & (BUCKET_VERSIONED | BUCKET_VERSIONS_SUSPENDED | BUCKET_MFA_ENABLED); }
   bool versioning_enabled() { return versioning_status() == BUCKET_VERSIONED; }
+  bool mfa_enabled() { return versioning_status() == BUCKET_MFA_ENABLED; }
   bool datasync_flag_enabled() const { return (flags & BUCKET_DATASYNC_DISABLED) == 0; }
 
   bool has_swift_versioning() const {
@@ -1881,6 +1888,8 @@ struct req_state {
   string req_id;
   string trans_id;
 
+  bool mfa_verified;
+
   req_state(CephContext* _cct, RGWEnv* e, RGWUserInfo* u);
   ~req_state();
 
index 60a1212d739b1371462cbbf1c98a238241236a4c..36bbb3531d32cfbe31b791edb8978fea4964c817 100644 (file)
@@ -474,6 +474,7 @@ void RGWUserInfo::dump(Formatter *f) const
     break;
   }
   encode_json("type", user_source_type, f);
+  encode_json("mfa_devices", mfa_devices, f);
 }
 
 
@@ -543,6 +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);
 }
 
 void RGWQuotaInfo::dump(Formatter *f) const
@@ -781,7 +783,6 @@ void RGWBucketInfo::decode_json(JSONObj *obj) {
   int rs;
   JSONDecoder::decode_json("reshard_status", rs, obj);
   reshard_status = (cls_rgw_reshard_status)rs;
-  JSONDecoder::decode_json("new_bucket_instance_id",new_bucket_instance_id, obj);
 }
 
 void rgw_obj_key::dump(Formatter *f) const
index b4ee8e499592f810b471b004c25099282c4e6574..bbf03f058f53d85f8f957a7a423fd17725c504d6 100644 (file)
@@ -766,6 +766,7 @@ public:
 };
 
 enum BucketVersionStatus {
+  VersioningStatusInvalid = -1,
   VersioningNotChanged = 0,
   VersioningEnabled = 1,
   VersioningSuspended =2,
@@ -774,6 +775,7 @@ enum BucketVersionStatus {
 class RGWSetBucketVersioning : public RGWOp {
 protected:
   int versioning_status;
+  bool mfa_status{false};
   bufferlist in_data;
 public:
   RGWSetBucketVersioning() : versioning_status(VersioningNotChanged) {}
index e2054a7412204f6dd613e4cf3112117ce286dd3c..0d256e752f91e99fc78a4f2f9730b52bd119ddd4 100644 (file)
@@ -14,6 +14,8 @@
 #include <boost/algorithm/string/replace.hpp>
 #include <boost/utility/string_view.hpp>
 
+#include <liboath/oath.h>
+
 #include "rgw_rest.h"
 #include "rgw_rest_s3.h"
 #include "rgw_rest_s3website.h"
@@ -879,37 +881,33 @@ void RGWGetBucketVersioning_ObjStore_S3::send_response()
   rgw_flush_formatter_and_reset(s, s->formatter);
 }
 
-class RGWSetBucketVersioningParser : public RGWXMLParser
-{
-  XMLObj *alloc_obj(const char *el) override {
-    return new XMLObj;
-  }
-
-public:
-  RGWSetBucketVersioningParser() {}
-  ~RGWSetBucketVersioningParser() override {}
-
-  int get_versioning_status(int *status) {
-    XMLObj *config = find_first("VersioningConfiguration");
-    if (!config)
-      return -EINVAL;
-
-    *status = VersioningNotChanged;
-
-    XMLObj *field = config->find_first("Status");
-    if (!field)
-      return 0;
+struct ver_config_status {
+  int status{VersioningSuspended};
+  int mfa_status{0};
+
+  void decode_xml(XMLObj *obj) {
+dout(0) << __FILE__ << ":" << __LINE__ << dendl;
+    string status_str;
+    string mfa_str;
+    RGWXMLDecoder::decode_xml("Status", status_str, obj);
+dout(0) << __FILE__ << ":" << __LINE__ << dendl;
+    if (status_str == "Enabled") {
+      status = VersioningEnabled;
+    } else if (status_str != "Suspended") {
+      status = VersioningStatusInvalid;
+    }
 
-    *status = VersioningSuspended;
-    string& s = field->get_data();
+dout(0) << __FILE__ << ":" << __LINE__ << " status_str=" << status_str << dendl;
 
-    if (stringcasecmp(s, "Enabled") == 0) {
-      *status = VersioningEnabled;
-    } else if (stringcasecmp(s, "Suspended") != 0) {
-      return -EINVAL;
+    RGWXMLDecoder::decode_xml("MfaDelete", mfa_str, obj);
+dout(0) << __FILE__ << ":" << __LINE__ << " mfa_str=" << mfa_str << dendl;
+    if (mfa_str == "Enabled") {
+      mfa_status = 1;
+    } else if (mfa_str == "Disabled") {
+      mfa_status = 0;
+    } else {
+      mfa_status = -EINVAL;
     }
-
-    return 0;
   }
 };
 
@@ -930,12 +928,10 @@ int RGWSetBucketVersioning_ObjStore_S3::get_params()
     return r;
   }
 
-  RGWSetBucketVersioningParser parser;
-
+  RGWXMLDecoder::XMLParser parser;
   if (!parser.init()) {
     ldout(s->cct, 0) << "ERROR: failed to initialize parser" << dendl;
-    r = -EIO;
-    return r;
+    return -EIO;
   }
 
   if (!parser.parse(data, len, 1)) {
@@ -944,13 +940,25 @@ int RGWSetBucketVersioning_ObjStore_S3::get_params()
     return r;
   }
 
+  ver_config_status status_conf;
+
+  RGWXMLDecoder::decode_xml("VersioningConfiguration", status_conf, &parser);
+
   if (!store->is_meta_master()) {
     /* only need to keep this data around if we're not meta master */
     in_data.append(data, len);
   }
 
-  r = parser.get_versioning_status(&versioning_status);
-  
+  versioning_status = status_conf.status;
+  if (versioning_status == VersioningStatusInvalid) {
+    r = -EINVAL;
+  }
+
+  if (status_conf.mfa_status >= 0) {
+    mfa_status = (bool)status_conf.mfa_status;
+  } else {
+    r = -EINVAL;
+  }
   return r;
 }
 
@@ -3216,6 +3224,42 @@ int RGWHandler_REST_S3::init_from_header(struct req_state* s,
   return 0;
 }
 
+static int verify_mfa(RGWRados *store, RGWUserInfo *user, const string& mfa_str, bool *verified)
+{
+  vector<string> params;
+  get_str_vec(mfa_str, " ", params);
+
+  if (params.size() != 2) {
+    ldout(store->ctx(), 5) << "NOTICE: invalid mfa string provided: " << mfa_str << dendl;
+    return -EINVAL;
+  }
+
+  string& serial = params[0];
+  string& otp = params[1];
+
+  auto i = user->mfa_devices.find(serial);
+  if (i == user->mfa_devices.end()) {
+    ldout(store->ctx(), 5) << "NOTICE: user does not have mfa device with serial=" << serial << dendl;
+    return -EACCES;
+  }
+
+  string& seed = i->second;
+
+  utime_t now = ceph_clock_now();
+
+  int result = oath_totp_validate3(seed.c_str(), seed.size(), now.sec(),
+                                   30 /* step size */, 0, 2 /* window */,
+                                   NULL, NULL, otp.c_str());
+  if (result == OATH_INVALID_OTP) {
+    ldout(cct, 5) << "NOTICE: totp token failed to validate" << dendl;
+    return -EACCES;
+  }
+
+  *verified = true;
+
+  return 0;
+}
+
 int RGWHandler_REST_S3::postauth_init()
 {
   struct req_init_state *t = &s->init_state;
@@ -3250,6 +3294,12 @@ int RGWHandler_REST_S3::postauth_init()
     if (ret)
       return ret;
   }
+
+  const char *mfa = s->info.env->get("HTTP_X_AMZ_MFA");
+  if (mfa) {
+    ret = verify_mfa(store, s->user, string(mfa), &s->mfa_verified);
+  }
+
   return 0;
 }