From: Matt Benjamin Date: Sat, 14 Nov 2015 19:51:13 +0000 (-0500) Subject: rgw: LDAP pass-through authentication X-Git-Tag: v10.1.0~124^2~6 X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=38fd3f190a45f944b745fb2c1a9e5e9623017202;p=ceph.git rgw: LDAP pass-through authentication 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 --- diff --git a/CMakeLists.txt b/CMakeLists.txt index 62ffdd69b6b48..2a079e3415f0b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 index 0000000000000..e9b956daff759 --- /dev/null +++ b/cmake/modules/FindOpenLdap.cmake @@ -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 +) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 5e5aaa23392e9..d4de01205b10d 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -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}) diff --git a/src/common/config_opts.h b/src/common/config_opts.h index 7ea78819e71e8..bf24f9a61b92d 100644 --- a/src/common/config_opts.h +++ b/src/common/config_opts.h @@ -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://") +/* 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 diff --git a/src/rgw/Makefile.am b/src/rgw/Makefile.am index 86ea5e0580d7f..fbc335171a404 100644 --- a/src/rgw/Makefile.am +++ b/src/rgw/Makefile.am @@ -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 \ diff --git a/src/rgw/librgw.cc b/src/rgw/librgw.cc index ada07af3653ab..7e6a5fd81e3e6 100644 --- a/src/rgw/librgw.cc +++ b/src/rgw/librgw.cc @@ -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); diff --git a/src/rgw/rgw_file.h b/src/rgw/rgw_file.h index fcb87c96da5bf..d7bda12f1d2d4 100644 --- a/src/rgw/rgw_file.h +++ b/src/rgw/rgw_file.h @@ -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 index 0000000000000..ac420e3ee8ab8 --- /dev/null +++ b/src/rgw/rgw_ldap.cc @@ -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 index 0000000000000..62cff3cbde702 --- /dev/null +++ b/src/rgw/rgw_ldap.h @@ -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 +#include +#include +#include +#include + +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(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 */ diff --git a/src/rgw/rgw_lib.h b/src/rgw/rgw_lib.h index bd59f6072e520..85a4c31642909 100644 --- a/src/rgw/rgw_lib.h +++ b/src/rgw/rgw_lib.h @@ -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& args); int stop(); diff --git a/src/rgw/rgw_op.cc b/src/rgw/rgw_op.cc index 4e9a2deb1e087..91dcea67f1b8b 100644 --- a/src/rgw/rgw_op.cc +++ b/src/rgw/rgw_op.cc @@ -27,9 +27,10 @@ #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; diff --git a/src/rgw/rgw_process.h b/src/rgw/rgw_process.h index 0cb74150584a6..faf942c6d9ddf 100644 --- a/src/rgw/rgw_process.h +++ b/src/rgw/rgw_process.h @@ -11,6 +11,8 @@ #include "rgw_op.h" #include "rgw_rest.h" +#include "include/assert.h" + #include "common/WorkQueue.h" #include "common/Throttle.h" diff --git a/src/rgw/rgw_rest_config.cc b/src/rgw/rgw_rest_config.cc index 321125624f854..2c0f5e46e9c25 100644 --- a/src/rgw/rgw_rest_config.cc +++ b/src/rgw/rgw_rest_config.cc @@ -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 diff --git a/src/rgw/rgw_rest_log.cc b/src/rgw/rgw_rest_log.cc index 3ff085470ade1..c6e4408a469a4 100644 --- a/src/rgw/rgw_rest_log.cc +++ b/src/rgw/rgw_rest_log.cc @@ -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 diff --git a/src/rgw/rgw_rest_metadata.cc b/src/rgw/rgw_rest_metadata.cc index 1794fbd7b3fd4..fb2663e6e7b1d 100644 --- a/src/rgw/rgw_rest_metadata.cc +++ b/src/rgw/rgw_rest_metadata.cc @@ -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 diff --git a/src/rgw/rgw_rest_opstate.cc b/src/rgw/rgw_rest_opstate.cc index 2b215a7117db5..02bdf17065a43 100644 --- a/src/rgw/rgw_rest_opstate.cc +++ b/src/rgw/rgw_rest_opstate.cc @@ -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 diff --git a/src/rgw/rgw_rest_realm.cc b/src/rgw/rgw_rest_realm.cc index 176802b1d3905..488c0cc682859 100644 --- a/src/rgw/rgw_rest_realm.cc +++ b/src/rgw/rgw_rest_realm.cc @@ -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 diff --git a/src/rgw/rgw_rest_replica_log.cc b/src/rgw/rgw_rest_replica_log.cc index 23a75fe30bfce..56cc9ee4e6f2a 100644 --- a/src/rgw/rgw_rest_replica_log.cc +++ b/src/rgw/rgw_rest_replica_log.cc @@ -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) diff --git a/src/rgw/rgw_rest_s3.cc b/src/rgw/rgw_rest_s3.cc index 5d68c5268d694..abf84b2e86ee9 100644 --- a/src/rgw/rgw_rest_s3.cc +++ b/src/rgw/rgw_rest_s3.cc @@ -27,10 +27,17 @@ #include // 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 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); diff --git a/src/rgw/rgw_rest_s3.h b/src/rgw/rgw_rest_s3.h index 7c70ec1481a9b..0719518296967 100644 --- a/src/rgw/rgw_rest_s3.h +++ b/src/rgw/rgw_rest_s3.h @@ -6,12 +6,15 @@ #define CEPH_RGW_REST_S3_H #define TIME_BUF_SIZE 128 +#include + #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 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 { diff --git a/src/rgw/rgw_rest_user.cc b/src/rgw/rgw_rest_user.cc index e8528e32c873b..587e6d00db202 100644 --- a/src/rgw/rgw_rest_user.cc +++ b/src/rgw/rgw_rest_user.cc @@ -8,6 +8,7 @@ #include "rgw_rest_user.h" #include "include/str_list.h" +#include "include/assert.h" #define dout_subsys ceph_subsys_rgw diff --git a/src/test/CMakeLists.txt b/src/test/CMakeLists.txt index a1c0ed2a11684..56bf00f8facf1 100644 --- a/src/test/CMakeLists.txt +++ b/src/test/CMakeLists.txt @@ -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 + $ + ) +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 diff --git a/src/test/Makefile-client.am b/src/test/Makefile-client.am index 14a8323b5da49..b4cb16b580c3b 100644 --- a/src/test/Makefile-client.am +++ b/src/test/Makefile-client.am @@ -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 index 0000000000000..103b3b071c639 --- /dev/null +++ b/src/test/test_rgw_ldap.cc @@ -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 +#include +#include +#include +#include +#include + +#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 args; + + argv_to_vec(argc, const_cast(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(); +}