]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
rgw: LDAP pass-through authentication
authorMatt Benjamin <mbenjamin@redhat.com>
Sat, 14 Nov 2015 19:51:13 +0000 (14:51 -0500)
committerMatt Benjamin <mbenjamin@redhat.com>
Wed, 9 Mar 2016 18:31:49 +0000 (13:31 -0500)
Implement a new external authenticator based on LDAP and the
new external token format.

External LDAP auth now works, at least with openldap/X.500 style
naming and ldaps:// (SSL).

The latter is AD-friendly, but since AD uses dnattr=cn (IIRC)
everywhere, AD will need testing.

Signed-off-by: Matt Benjamin <mbenjamin@redhat.com>
24 files changed:
CMakeLists.txt
cmake/modules/FindOpenLdap.cmake [new file with mode: 0644]
src/CMakeLists.txt
src/common/config_opts.h
src/rgw/Makefile.am
src/rgw/librgw.cc
src/rgw/rgw_file.h
src/rgw/rgw_ldap.cc [new file with mode: 0644]
src/rgw/rgw_ldap.h [new file with mode: 0644]
src/rgw/rgw_lib.h
src/rgw/rgw_op.cc
src/rgw/rgw_process.h
src/rgw/rgw_rest_config.cc
src/rgw/rgw_rest_log.cc
src/rgw/rgw_rest_metadata.cc
src/rgw/rgw_rest_opstate.cc
src/rgw/rgw_rest_realm.cc
src/rgw/rgw_rest_replica_log.cc
src/rgw/rgw_rest_s3.cc
src/rgw/rgw_rest_s3.h
src/rgw/rgw_rest_user.cc
src/test/CMakeLists.txt
src/test/Makefile-client.am
src/test/test_rgw_ldap.cc [new file with mode: 0644]

index 62ffdd69b6b486c54df16c56f30f78064c37ac38..2a079e3415f0b1ea6e13ae0f7c682022c40d48c1 100644 (file)
@@ -136,6 +136,13 @@ set(HAVE_LIBAIO ${AIO_FOUND})
 message(STATUS "${AIO_LIBS}")
 endif(${WITH_AIO})
 
+option(WITH_OPENLDAP "OPENLDAP is here" ON)
+if(${WITH_OPENLDAP})
+find_package(OpenLdap REQUIRED)
+set(HAVE_OPENLDAP ${OPENLDAP_FOUND})
+message(STATUS "${OPENLDAP_LIBS}")
+endif(${WITH_OPENLDAP})
+
 option(WITH_FUSE "Fuse is here" ON)
 if(${WITH_FUSE})
 find_package(fuse)
diff --git a/cmake/modules/FindOpenLdap.cmake b/cmake/modules/FindOpenLdap.cmake
new file mode 100644 (file)
index 0000000..e9b956d
--- /dev/null
@@ -0,0 +1,37 @@
+# - Find OpenLDAP C Libraries
+#
+# OPENLDAP_PREFIX - where to find ldap.h and libraries
+# OPENLDAP_FOUND - True if found.
+
+set(OPENLDAP_INCLUDE_DIR "${OPENLDAP_PREFIX}/include")
+set(OPENLDAP_LIB_DIR "${OPENLDAP_PREFIX}/lib")
+
+find_path(OPENLDAP_INCLUDE_DIR ldap.h NO_DEFAULT_PATH PATHS
+  /usr/include
+  /opt/local/include
+  /usr/local/include
+  )
+
+find_library(LIBLDAP NAMES ldap)
+find_library(LIBLBER NAMES lber)
+
+if (OPENLDAP_INCLUDE_DIR AND LIBLDAP AND LIBLBER)
+  set(OPENLDAP_FOUND TRUE)
+else (OPENLDAP_INCLUDE_DIR AND LIBLDAP AND LIBLBER)
+  set(OPENLDAP_FOUND FALSE)
+endif (OPENLDAP_INCLUDE_DIR AND LIBLDAP AND LIBLBER)
+
+if (OPENLDAP_FOUND)
+  message(STATUS "Found ldap: ${OPENLDAP_INCLUDE_DIR}")
+else ()
+  message(STATUS "Failed to find ldap.h")
+  if (OPENLDAP_FIND_REQUIRED)
+    message(FATAL_ERROR "Missing required ldap.h")
+  endif ()
+endif ()
+
+set(OPENLDAP_LIBS ${LIBLDAP} ${LIBLBER})
+
+mark_as_advanced(
+  OPENLDAP_INCLUDE_DIR OPENLDAP_LIB_DIR OPENLDAP_LIBRARIES
+)
index 5e5aaa23392e9204fac4dd8ff564fbb53adc0110..d4de01205b10d2acb110d8e61a29b37293d5196f 100644 (file)
@@ -1139,6 +1139,7 @@ if(${WITH_RADOSGW})
     rgw/rgw_http_client.cc
     rgw/rgw_json_enc.cc
     rgw/rgw_keystone.cc
+    rgw/rgw_ldap.cc
     rgw/rgw_loadgen.cc
     rgw/rgw_log.cc
     rgw/rgw_metadata.cc
@@ -1255,7 +1256,8 @@ if(${WITH_RADOSGW})
     cls_rgw_client cls_lock_client cls_refcount_client
     cls_log_client cls_statelog_client cls_timeindex_client
     cls_version_client cls_replica_log_client cls_user_client
-    curl expat global fcgi resolv ssl crypto ${BLKID_LIBRARIES})
+    curl expat global fcgi resolv ssl crypto ${BLKID_LIBRARIES} ${OPENLDAP_LIBS}
+    ${ALLOC_LIBS})
   install(TARGETS radosgw DESTINATION bin)
 
   add_executable(radosgw-admin ${radosgw_admin_srcs})
index 7ea78819e71e84034cbd3821c5beeba7a8212df4..bf24f9a61b92d60d2bab4238564d5a3adbb2643a 100644 (file)
@@ -1221,6 +1221,21 @@ OPTION(rgw_keystone_revocation_interval, OPT_INT, 15 * 60)  // seconds between t
 OPTION(rgw_keystone_verify_ssl, OPT_BOOL, true) // should we try to verify keystone's ssl
 OPTION(rgw_s3_auth_use_rados, OPT_BOOL, true)  // should we try to use the internal credentials for s3?
 OPTION(rgw_s3_auth_use_keystone, OPT_BOOL, false)  // should we try to use keystone for s3?
+
+/* OpenLDAP-style LDAP parameter strings */
+/* rgw_ldap_uri  space-separated list of LDAP servers in URI format */
+OPTION(rgw_ldap_uri, OPT_STR, "ldaps://<ldap.your.domain>")
+/* rgw_ldap_binddn  LDAP entry RGW will bind with (user match) */
+OPTION(rgw_ldap_binddn, OPT_STR, "uid=admin,cn=users,dc=example,dc=com")
+/* rgw_ldap_searchdn  LDAP search base (basedn) */
+OPTION(rgw_ldap_searchdn, OPT_STR, "cn=users,cn=accounts,dc=example,dc=com")
+/* rgw_ldap_memberattr  LDAP attribute containing RGW user names */
+OPTION(rgw_ldap_memberattr, OPT_STR, "uid")
+/* rgw_ldap_secret  file containing credentials for rgw_ldap_binddn */
+OPTION(rgw_ldap_secret, OPT_STR, "/etc/openldap/secret")
+/* rgw_s3_auth_use_ldap  use LDAP for RGW auth? */
+OPTION(rgw_s3_auth_use_ldap, OPT_BOOL, false)
+
 OPTION(rgw_admin_entry, OPT_STR, "admin")  // entry point for which a url is considered an admin request
 OPTION(rgw_enforce_swift_acls, OPT_BOOL, true)
 OPTION(rgw_swift_token_expiration, OPT_INT, 24 * 3600) // time in seconds for swift token expiration
index 86ea5e0580d7f62159a8631f8701eb7470204ed4..fbc335171a4043dc670ab4e1e9fc4e6d47b03109 100644 (file)
@@ -44,6 +44,7 @@ librgw_la_SOURCES = \
        rgw/rgw_http_client.cc \
        rgw/rgw_json_enc.cc \
        rgw/rgw_keystone.cc \
+       rgw/rgw_ldap.cc \
        rgw/rgw_loadgen.cc \
        rgw/rgw_log.cc \
        rgw/rgw_metadata.cc \
index ada07af3653abb891ece04f644238fe6c7493015..7e6a5fd81e3e6f702a94156722a31dbeda4faf3b 100644 (file)
@@ -464,6 +464,17 @@ namespace rgw {
     if (r)
       return -EIO;
 
+    const string& ldap_uri = store->ctx()->_conf->rgw_ldap_uri;
+    const string& ldap_binddn = store->ctx()->_conf->rgw_ldap_binddn;
+    const string& ldap_searchdn = store->ctx()->_conf->rgw_ldap_searchdn;
+    const string& ldap_memberattr =
+      store->ctx()->_conf->rgw_ldap_memberattr;
+
+    ldh = new rgw::LDAPHelper(ldap_uri, ldap_binddn, ldap_searchdn,
+                             ldap_memberattr);
+    ldh->init();
+    ldh->bind();
+
     rgw_user_init(store);
     rgw_bucket_init(store->meta_mgr);
     rgw_log_usage_init(g_ceph_context, store);
index fcb87c96da5bf9fc3054f9f503b394c511a93543..d7bda12f1d2d4c9e44a9b23c1fadf34647c6ec38 100644 (file)
@@ -31,6 +31,9 @@
 #include "rgw_common.h"
 #include "rgw_user.h"
 #include "rgw_lib.h"
+#include "rgw_ldap.h"
+#include "rgw_token.h"
+
 
 /* XXX
  * ASSERT_H somehow not defined after all the above (which bring
@@ -669,9 +672,24 @@ namespace rgw {
          return -EINVAL;
        if (user.suspended)
          return -ERR_USER_SUSPENDED;
+      } else {
+       /* try external authenticators (ldap for now) */
+       rgw::LDAPHelper* ldh = rgwlib.get_ldh();
+       RGWToken token{from_base64(key.id)};
+       if (ldh->auth(token.id, token.key) == 0) {
+         /* try to store user if it doesn't already exist */
+         if (rgw_get_user_info_by_uid(store, token.id, user) < 0) {
+           int ret = rgw_store_user_info(store, user, NULL, NULL, 0,
+                                         true);
+           if (ret < 0) {
+             dout(10) << "NOTICE: failed to store new user's info: ret=" << ret
+                      << dendl;
+           }
+         }
+       } /* auth success */
       }
       return ret;
-    }
+    } /* authorize */
 
     /* find or create an RGWFileHandle */
     LookupFHResult lookup_fh(RGWFileHandle* parent, const char *name,
diff --git a/src/rgw/rgw_ldap.cc b/src/rgw/rgw_ldap.cc
new file mode 100644 (file)
index 0000000..ac420e3
--- /dev/null
@@ -0,0 +1,4 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "rgw_ldap.h"
diff --git a/src/rgw/rgw_ldap.h b/src/rgw/rgw_ldap.h
new file mode 100644 (file)
index 0000000..62cff3c
--- /dev/null
@@ -0,0 +1,86 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#ifndef RGW_LDAP_H
+#define RGW_LDAP_H
+
+#define LDAP_DEPRECATED 1
+#include "ldap.h"
+
+#include <stdint.h>
+#include <tuple>
+#include <vector>
+#include <string>
+#include <iostream>
+
+namespace rgw {
+
+  class LDAPHelper
+  {
+    std::string uri;
+    std::string binddn;
+    std::string searchdn;
+    std::string memberattr;
+    LDAP *ldap, *tldap;
+
+  public:
+    LDAPHelper(std::string _uri, std::string _binddn, std::string _searchdn,
+             std::string _memberattr)
+      : uri(std::move(_uri)), binddn(std::move(_binddn)), searchdn(_searchdn),
+       memberattr(_memberattr), ldap(nullptr) {
+      // nothing
+    }
+
+    int init() {
+      int ret;
+      ret = ldap_initialize(&ldap, uri.c_str());
+      return ret;
+    }
+
+    int bind() {
+      return ldap_simple_bind_s(ldap, nullptr, nullptr);
+    }
+
+    int simple_bind(const char *dn, const std::string& pwd) {
+      int ret = ldap_initialize(&tldap, uri.c_str());
+      ret = ldap_simple_bind_s(tldap, dn, pwd.c_str());
+      if (ret == LDAP_SUCCESS) {
+       ldap_unbind(tldap);
+       return 0;
+      }
+      return -1;
+    }
+
+    int auth(const std::string uid, const std::string pwd) {
+      int ret;
+      std::string filter;
+      filter = "(";
+      filter += memberattr;
+      filter += "=";
+      filter += uid;
+      filter += ")";
+      char *attrs[] = { const_cast<char*>(memberattr.c_str()), nullptr };
+      LDAPMessage *answer, *entry;
+      ret = ldap_search_s(ldap, searchdn.c_str(), LDAP_SCOPE_SUBTREE,
+                         filter.c_str(), attrs, 0, &answer);
+      if (ret == LDAP_SUCCESS) {
+       entry = ldap_first_entry(ldap, answer);
+       char *dn = ldap_get_dn(ldap, entry);
+       //std::cout << dn << std::endl;
+       ret = simple_bind(dn, pwd);
+       ldap_memfree(dn);
+       ldap_msgfree(answer);
+      }
+      return ret;
+    }
+
+    ~LDAPHelper() {
+      if (ldap)
+       ldap_unbind(ldap);
+    }
+
+  };
+
+} /* namespace rgw */
+
+#endif /* RGW_LDAP_H */
index bd59f6072e520e8c6bc495d794fc9e29515678c1..85a4c31642909bad613cbbf57923789c3d24c90f 100644 (file)
@@ -12,6 +12,8 @@
 #include "rgw_frontend.h"
 #include "rgw_process.h"
 #include "rgw_rest_s3.h" // RGW_Auth_S3
+#include "rgw_ldap.h"
+#include "include/assert.h"
 
 class OpsLogSocket;
 
@@ -24,6 +26,7 @@ namespace rgw {
     RGWFrontendConfig* fec;
     RGWLibFrontend* fe;
     OpsLogSocket* olog;
+    rgw::LDAPHelper* ldh;
     RGWREST rest; // XXX needed for RGWProcessEnv
     RGWProcessEnv env;
     RGWRados* store;
@@ -37,6 +40,8 @@ namespace rgw {
 
     RGWLibFrontend* get_fe() { return fe; }
 
+    rgw::LDAPHelper* get_ldh() { return ldh; }
+
     int init();
     int init(vector<const char *>& args);
     int stop();
index 4e9a2deb1e087a0f2bc6f219c5fb694534a28771..91dcea67f1b8b12954164a88e0df9d274affb48e 100644 (file)
 #include "rgw_cors_s3.h"
 #include "rgw_rest_conn.h"
 #include "rgw_rest_s3.h"
-
 #include "rgw_client_io.h"
 
+#include "include/assert.h"
+
 #define dout_subsys ceph_subsys_rgw
 
 using namespace std;
index 0cb74150584a6b6f0f3a266b99413b71e3e4c159..faf942c6d9ddf45255d673e1fb2cb32655a1c7a6 100644 (file)
@@ -11,6 +11,8 @@
 #include "rgw_op.h"
 #include "rgw_rest.h"
 
+#include "include/assert.h"
+
 #include "common/WorkQueue.h"
 #include "common/Throttle.h"
 
index 321125624f854a24df169775330eedf1750fe3b3..2c0f5e46e9c258c08162acb51d0de519546d6be8 100644 (file)
@@ -20,6 +20,7 @@
 #include "rgw_rest_config.h"
 #include "rgw_client_io.h"
 #include "common/errno.h"
+#include "include/assert.h"
 
 #define dout_subsys ceph_subsys_rgw
 
index 3ff085470ade121e9bbc2758addd41e5a379c2e9..c6e4408a469a47189465a4cefda889e65d995fe4 100644 (file)
@@ -19,6 +19,7 @@
 #include "rgw_rest_log.h"
 #include "rgw_client_io.h"
 #include "common/errno.h"
+#include "include/assert.h"
 
 #define LOG_CLASS_LIST_MAX_ENTRIES (1000)
 #define dout_subsys ceph_subsys_rgw
index 1794fbd7b3fd4517047f63b773320d08fd4e37b0..fb2663e6e7b1dfe42b6258a9d48487c17e13abcb 100644 (file)
@@ -20,6 +20,7 @@
 #include "rgw_client_io.h"
 #include "common/errno.h"
 #include "common/strtol.h"
+#include "include/assert.h"
 
 #define dout_subsys ceph_subsys_rgw
 
index 2b215a7117db5a12181ad624099552598c3dbdf0..02bdf17065a4318c535010e0752b62641e7b9936 100644 (file)
@@ -19,6 +19,7 @@
 #include "rgw_rest_opstate.h"
 #include "rgw_client_io.h"
 #include "common/errno.h"
+#include "include/assert.h"
 
 #define OPSTATE_LIST_MAX_ENTRIES 1000
 #define dout_subsys ceph_subsys_rgw
index 176802b1d3905560826730567bbae39a7bab7872..488c0cc682859966e7490e7c4ee1aa75bea36b87 100644 (file)
@@ -6,6 +6,8 @@
 #include "rgw_rest_s3.h"
 #include "rgw_rest_config.h"
 
+#include "include/assert.h"
+
 #define dout_subsys ceph_subsys_rgw
 
 // reject 'period push' if we would have to fetch too many intermediate periods
index 23a75fe30bfce560239350601dd26edab467e1f2..56cc9ee4e6f2a5619d6899d8a9d7fb70a47ff15f 100644 (file)
@@ -22,6 +22,7 @@
 #include "rgw_rest_replica_log.h"
 #include "rgw_client_io.h"
 #include "common/errno.h"
+#include "include/assert.h"
 
 #define dout_subsys ceph_subsys_rgw
 #define REPLICA_INPUT_MAX_LEN (512*1024)
index 5d68c5268d6940d9e7654cc5c841d991835cd456..abf84b2e86ee9ba77f643514a8f220b370ccd644 100644 (file)
 
 #include <typeinfo> // for 'typeid'
 
+#include "rgw_ldap.h"
+#include "rgw_token.h"
+#include "include/assert.h"
+
 #define dout_subsys ceph_subsys_rgw
 
+using namespace rgw;
 using namespace ceph::crypto;
 
+using std::get;
+
 void list_all_buckets_start(struct req_state *s)
 {
   s->formatter->open_array_section_in_ns("ListAllMyBucketsResult",
@@ -1426,40 +1433,65 @@ int RGWPostObj_ObjStore_S3::get_policy()
 
     op_ret = rgw_get_user_info_by_access_key(store, s3_access_key, user_info);
     if (op_ret < 0) {
-      // Try keystone authentication as well
-      int keystone_result = -EINVAL;
-      if (!store->ctx()->_conf->rgw_s3_auth_use_keystone ||
-         store->ctx()->_conf->rgw_keystone_url.empty()) {
-       return -EACCES;
-      }
-      dout(20) << "s3 keystone: trying keystone auth" << dendl;
-
-      RGW_Auth_S3_Keystone_ValidateToken keystone_validator(store->ctx());
-      keystone_result =
-       keystone_validator.validate_s3token(s3_access_key,
-                                           string(encoded_policy.c_str(),
-                                                  encoded_policy.length()),
-                                           received_signature_str);
+        // try external authenticators
+      if (store->ctx()->_conf->rgw_s3_auth_use_keystone &&
+         store->ctx()->_conf->rgw_keystone_url.empty())
+      {
+       // keystone
+       int external_auth_result = -EINVAL;
+       dout(20) << "s3 keystone: trying keystone auth" << dendl;
+
+       RGW_Auth_S3_Keystone_ValidateToken keystone_validator(store->ctx());
+       external_auth_result =
+         keystone_validator.validate_s3token(s3_access_key,
+                                             string(encoded_policy.c_str(),
+                                                   encoded_policy.length()),
+                                             received_signature_str);
+
+       if (external_auth_result < 0) {
+         ldout(s->cct, 0) << "User lookup failed!" << dendl;
+         err_msg = "Bad access key / signature";
+         return -EACCES;
+       }
 
-      if (keystone_result < 0) {
-       ldout(s->cct, 0) << "User lookup failed!" << dendl;
-       err_msg = "Bad access key / signature";
-       return -EACCES;
-      }
+       string project_id = keystone_validator.response.get_project_id();
+       rgw_user uid(project_id);
 
-      string project_id = keystone_validator.response.get_project_id();
-      user_info.user_id = project_id;
-      user_info.display_name = keystone_validator.response.get_project_name();
+       user_info.user_id = project_id;
+       user_info.display_name = keystone_validator.response.get_project_name();
 
-      rgw_user uid(project_id);
-      /* try to store user if it not already exists */
-      if (rgw_get_user_info_by_uid(store, uid, user_info) < 0) {
-        int ret = rgw_store_user_info(store, user_info, NULL, NULL, 0, true);
-        if (ret < 0) {
-          dout(10) << "NOTICE: failed to store new user's info: ret="
-                  << ret << dendl;
-        }
-       s->perm_mask = RGW_PERM_FULL_CONTROL;
+       /* try to store user if it not already exists */
+       if (rgw_get_user_info_by_uid(store, uid, user_info) < 0) {
+         int ret = rgw_store_user_info(store, user_info, NULL, NULL, 0, true);
+         if (ret < 0) {
+           dout(10) << "NOTICE: failed to store new user's info: ret="
+                    << ret << dendl;
+         }
+         s->perm_mask = RGW_PERM_FULL_CONTROL;
+       }
+      } else if (store->ctx()->_conf->rgw_s3_auth_use_ldap &&
+               store->ctx()->_conf->rgw_ldap_uri.empty()) {
+       RGWToken token{from_base64(s3_access_key)};
+       rgw::LDAPHelper *ldh = RGW_Auth_S3::get_ldap_ctx(store);
+       if (ldh->auth(token.id, token.key) != 0)
+         return -EACCES;
+
+       /* ok, succeeded, try to create shadow */
+       user_info.user_id = token.id;
+       user_info.display_name = token.id; // cn?
+
+       /* try to store user if it not already exists */
+       if (rgw_get_user_info_by_uid(store, user_info.user_id,
+                                       user_info) < 0) {
+         int ret = rgw_store_user_info(store, user_info, NULL, NULL, 0, true);
+         if (ret < 0) {
+           dout(10) << "NOTICE: failed to store new user's info: ret=" << ret
+                    << dendl;
+         }
+         s->perm_mask = RGW_PERM_FULL_CONTROL;
+       }
+      } else {
+       return -EACCES;
       }
     } else {
       map<string, RGWAccessKey> access_keys  = user_info.access_keys;
@@ -2752,6 +2784,26 @@ int RGWHandler_REST_S3::init(RGWRados *store, struct req_state *s,
   return RGWHandler_REST::init(store, s, cio);
 }
 
+/* RGW_Auth_S3 static members */
+std::mutex RGW_Auth_S3::mtx;
+rgw::LDAPHelper* RGW_Auth_S3::ldh;
+
+/* static */
+void RGW_Auth_S3::init_impl(RGWRados* store)
+{
+  const string& ldap_uri = store->ctx()->_conf->rgw_ldap_uri;
+  const string& ldap_binddn = store->ctx()->_conf->rgw_ldap_binddn;
+  const string& ldap_searchdn = store->ctx()->_conf->rgw_ldap_searchdn;
+  const string& ldap_memberattr =
+    store->ctx()->_conf->rgw_ldap_memberattr;
+
+  ldh = new rgw::LDAPHelper(ldap_uri, ldap_binddn, ldap_searchdn,
+                           ldap_memberattr);
+
+  ldh->init();
+  ldh->bind();
+}
+
 /*
  * Try to validate S3 auth against keystone s3token interface
  */
@@ -2862,8 +2914,9 @@ int RGW_Auth_S3::authorize(RGWRados *store, struct req_state *s)
 {
 
   /* neither keystone and rados enabled; warn and exit! */
-  if (!store->ctx()->_conf->rgw_s3_auth_use_rados
-      && !store->ctx()->_conf->rgw_s3_auth_use_keystone) {
+  if (!store->ctx()->_conf->rgw_s3_auth_use_rados &&
+      !store->ctx()->_conf->rgw_s3_auth_use_keystone &&
+      !store->ctx()->_conf->rgw_s3_auth_use_ldap) {
     dout(0) << "WARNING: no authorization backend enabled! Users will never authenticate." << dendl;
     return -EPERM;
   }
@@ -3478,7 +3531,7 @@ int RGW_Auth_S3::authorize_v2(RGWRados *store, struct req_state *s)
   }
 
   /* try keystone auth first */
-  int keystone_result = -ERR_INVALID_ACCESS_KEY;;
+  int external_auth_result = -ERR_INVALID_ACCESS_KEY;;
   if (store->ctx()->_conf->rgw_s3_auth_use_keystone
       && !store->ctx()->_conf->rgw_keystone_url.empty()) {
     dout(20) << "s3 keystone: trying keystone auth" << dendl;
@@ -3489,11 +3542,11 @@ int RGW_Auth_S3::authorize_v2(RGWRados *store, struct req_state *s)
     if (!rgw_create_s3_canonical_header(s->info,
                                         &s->header_time, token, qsr)) {
       dout(10) << "failed to create auth header\n" << token << dendl;
-      keystone_result = -EPERM;
+      external_auth_result = -EPERM;
     } else {
-      keystone_result = keystone_validator.validate_s3token(auth_id, token,
+      external_auth_result = keystone_validator.validate_s3token(auth_id, token,
                                                            auth_sign);
-      if (keystone_result == 0) {
+      if (external_auth_result == 0) {
        // Check for time skew first
        time_t req_sec = s->header_time.sec();
 
@@ -3531,17 +3584,45 @@ int RGW_Auth_S3::authorize_v2(RGWRados *store, struct req_state *s)
     }
   }
 
-  if (keystone_result < 0) {
-    if (!store->ctx()->_conf->rgw_s3_auth_use_rados) {
-      /* No other auth option possible. Terminate request. */
-      return keystone_result;
-    }
+  if ((external_auth_result < 0) &&
+      (store->ctx()->_conf->rgw_s3_auth_use_ldap) &&
+      (! store->ctx()->_conf->rgw_ldap_uri.empty())) {
+
+    RGW_Auth_S3::init(store);
+
+    RGWToken token{from_base64(auth_id)};
+    if (ldh->auth(token.id, token.key) != 0)
+      external_auth_result = -EACCES;
+    else {
+      /* ok, succeeded */
+      external_auth_result = 0;
+      /* create local account, if none exists */
+      s->user->user_id = token.id;
+      s->user->display_name = token.id; // cn?
+      if (rgw_get_user_info_by_uid(store, s->user->user_id,
+                                  *(s->user)) < 0) {
+       int ret = rgw_store_user_info(store, *(s->user), NULL, NULL, 0, true);
+       if (ret < 0) {
+         dout(10) << "NOTICE: failed to store new user's info: ret=" << ret
+                  << dendl;
+       }
+       s->perm_mask = RGW_PERM_FULL_CONTROL;
+      }
+    } /* success */
+  } /* ldap */
+
+  /* keystone failed (or not enabled); check if we want to use rados backend */
+  if (!store->ctx()->_conf->rgw_s3_auth_use_rados
+      && external_auth_result < 0)
+    return external_auth_result;
 
+  /* now try rados backend, but only if keystone did not succeed */
+  if (external_auth_result < 0) {
     /* get the user info */
     if (rgw_get_user_info_by_access_key(store, auth_id, *(s->user)) < 0) {
       dout(5) << "error reading user info, uid=" << auth_id
               << " can't authenticate" << dendl;
-      return keystone_result;
+      return external_auth_result;
     }
 
     /* now verify signature */
@@ -3617,7 +3698,7 @@ int RGW_Auth_S3::authorize_v2(RGWRados *store, struct req_state *s)
       }
     }
 
-  } /* if keystone_result < 0 */
+  } /* if external_auth_result < 0 */
 
   // populate the owner info
   s->owner.set_id(s->user->user_id);
index 7c70ec1481a9b167ac3535c231fbde48ddef2cec..0719518296967429fe61e5f746a1e1fad9917518 100644 (file)
@@ -6,12 +6,15 @@
 #define CEPH_RGW_REST_S3_H
 #define TIME_BUF_SIZE 128
 
+#include <mutex>
+
 #include "rgw_op.h"
 #include "rgw_http_errors.h"
 #include "rgw_acl_s3.h"
 #include "rgw_policy_s3.h"
 #include "rgw_keystone.h"
 #include "rgw_rest_conn.h"
+#include "rgw_ldap.h"
 
 #define RGW_AUTH_GRACE_MINS 15
 
@@ -392,15 +395,34 @@ public:
 };
 
 class RGW_Auth_S3 {
-public:
-  static int authorize(RGWRados *store, struct req_state *s);
-  static int authorize_aws4_auth_complete(RGWRados *store, struct req_state *s);
 private:
+  static std::mutex mtx;
+  static rgw::LDAPHelper* ldh;
+
   static int authorize_v2(RGWRados *store, struct req_state *s);
   static int authorize_v4(RGWRados *store, struct req_state *s);
   static int authorize_v4_complete(RGWRados *store, struct req_state *s,
-                                   const string& request_payload, bool unsigned_payload);
+                                 const string& request_payload,
+                                 bool unsigned_payload);
+public:
+  static int authorize(RGWRados *store, struct req_state *s);
+  static int authorize_aws4_auth_complete(RGWRados *store, struct req_state *s);
+
+  static inline void init(RGWRados* store) {
+    if (! ldh) {
+      std::lock_guard<std::mutex> lck(mtx);
+      if (! ldh) {
+       init_impl(store);
+      }
+    }
+  }
+
+  static inline rgw::LDAPHelper* get_ldap_ctx(RGWRados* store) {
+    init(store);
+    return ldh;
+  }
 
+  static void init_impl(RGWRados* store);
 };
 
 class RGWHandler_Auth_S3 : public RGWHandler_REST {
index e8528e32c873badcdc33c9e3a4b0180a4882aeb4..587e6d00db202759fd116a25c3e16e7be468c9a4 100644 (file)
@@ -8,6 +8,7 @@
 #include "rgw_rest_user.h"
 
 #include "include/str_list.h"
+#include "include/assert.h"
 
 #define dout_subsys ceph_subsys_rgw
 
index a1c0ed2a11684f87d075ed19638f3217385a991d..56bf00f8facf1bc6eea1feeb3464b31bd125e758 100644 (file)
@@ -2238,6 +2238,22 @@ if(${HAVE_LIBFUSE})
     )
 endif(${HAVE_LIBFUSE})
 
+# librgw_file_gp (just the rgw_file get-put bucket ops)
+add_executable(test_rgw_ldap
+  ../rgw/rgw_ldap.cc
+  test_rgw_ldap.cc
+  $<TARGET_OBJECTS:heap_profiler_objs>
+  )
+set_target_properties(test_rgw_ldap PROPERTIES COMPILE_FLAGS
+  ${UNITTEST_CXX_FLAGS})
+target_link_libraries(test_rgw_ldap
+  librados
+  ${OPENLDAP_LIBS}
+  ${Boost_LIBRARIES}
+  ${ALLOC_LIBS}
+  ${UNITTEST_LIBS}
+  )
+
 if(${WITH_CEPHFS})
   add_executable(test_c_headers
     test_c_headers.c
index 14a8323b5da491f27377427438d7de54b2503402..0b0de4f79c79b98438cb1b000ca08f2cb52ea8a0 100644 (file)
@@ -746,6 +746,13 @@ test_rgw_token_LDADD = $(UNITTEST_LDADD) \
        librgw.la $(PTHREAD_LIBS) $(LIBOS) $(CEPH_GLOBAL) $(EXTRALIBS)
 bin_DEBUGPROGRAMS += test_rgw_token
 
+test_rgw_ldap_SOURCES = ../rgw/rgw_ldap.cc test/test_rgw_ldap.cc
+test_rgw_ldap_CXXFLAGS = $(UNITTEST_CXXFLAGS)
+test_rgw_ldap_LDADD = $(UNITTEST_LDADD) \
+       librados.la $(PTHREAD_LIBS) $(LIBOS) $(CEPH_GLOBAL) ${OPENLDAP_LIBS}
+       $(EXTRALIBS)
+bin_DEBUGPROGRAMS += test_rgw_token
+
 endif # WITH_RADOSGW
 
 
diff --git a/src/test/test_rgw_ldap.cc b/src/test/test_rgw_ldap.cc
new file mode 100644 (file)
index 0000000..103b3b0
--- /dev/null
@@ -0,0 +1,114 @@
+// -*- 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) 2015 New Dream Network
+ *
+ * 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 <stdint.h>
+#include <tuple>
+#include <iostream>
+#include <vector>
+#include <map>
+#include <random>
+
+#include "rgw/rgw_ldap.h"
+#include "rgw/rgw_token.h"
+
+#include "gtest/gtest.h"
+#include "common/ceph_argparse.h"
+#include "common/debug.h"
+#include "global/global_init.h"
+
+#define dout_subsys ceph_subsys_rgw
+
+namespace {
+
+  struct {
+    int argc;
+    char **argv;
+  } saved_args;
+
+  bool do_hexdump = false;
+
+  string access_key("ewogICAgIlJHV19UT0tFTiI6IHsKICAgICAgICAidmVyc2lvbiI6IDEsCiAgICAgICAgInR5cGUiOiAibGRhcCIsCiAgICAgICAgImlkIjogImFkbWluIiwKICAgICAgICAia2V5IjogImxpbnV4Ym94IgogICAgfQp9Cg=="); // {admin,linuxbox}
+  string other_key("ewogICAgIlJHV19UT0tFTiI6IHsKICAgICAgICAidmVyc2lvbiI6IDEsCiAgICAgICAgInR5cGUiOiAibGRhcCIsCiAgICAgICAgImlkIjogImFkbWluIiwKICAgICAgICAia2V5IjogImJhZHBhc3MiCiAgICB9Cn0K"); // {admin,badpass}
+
+  string ldap_uri = "ldaps://f23-kdc.rgw.com";
+  string ldap_binddn = "uid=admin,cn=users,cn=accounts,dc=rgw,dc=com";
+  string ldap_searchdn = "cn=users,cn=accounts,dc=rgw,dc=com";
+  string ldap_memberattr = "uid";
+
+  rgw::LDAPHelper ldh(ldap_uri, ldap_binddn, ldap_searchdn, ldap_memberattr);
+
+} /* namespace */
+
+TEST(RGW_LDAP, INIT) {
+  int ret = ldh.init();
+  ASSERT_EQ(ret, 0);
+}
+
+TEST(RGW_LDAP, BIND) {
+  int ret = ldh.bind();
+  ASSERT_EQ(ret, 0);
+}
+
+TEST(RGW_LDAP, AUTH) {
+  using std::get;
+  using namespace rgw;
+  int ret = 0;
+  {
+    RGWToken token{from_base64(access_key)};
+    ret = ldh.auth(token.id, token.key);
+    ASSERT_EQ(ret, 0);
+  }
+  {
+    RGWToken token{from_base64(other_key)};
+    ret = ldh.auth(token.id, token.key);
+    ASSERT_NE(ret, 0);
+  }
+}
+
+TEST(RGW_LDAP, SHUTDOWN) {
+  // nothing
+}
+
+int main(int argc, char *argv[])
+{
+  string val;
+  vector<const char*> args;
+
+  argv_to_vec(argc, const_cast<const char**>(argv), args);
+  env_to_vec(args);
+
+  for (auto arg_iter = args.begin(); arg_iter != args.end();) {
+    if (ceph_argparse_witharg(args, arg_iter, &val, "--access",
+                             (char*) nullptr)) {
+      access_key = val;
+    } else if (ceph_argparse_flag(args, arg_iter, "--hexdump",
+                                           (char*) nullptr)) {
+      do_hexdump = true;
+    } else {
+      ++arg_iter;
+    }
+  }
+
+  /* dont accidentally run as anonymous */
+  if (access_key == "") {
+    std::cout << argv[0] << " no AWS credentials, exiting" << std::endl;
+    return EPERM;
+  }
+
+  saved_args.argc = argc;
+  saved_args.argv = argv;
+
+  ::testing::InitGoogleTest(&argc, argv);
+  return RUN_ALL_TESTS();
+}