From d89c2923e88e0a6d05dc277d3ef4e754e92e6bb0 Mon Sep 17 00:00:00 2001 From: panda-sheep <59197347+panda-sheep@users.noreply.github.com> Date: Wed, 18 Aug 2021 10:16:51 +0800 Subject: [PATCH 1/3] support ldap --- CMakeLists.txt | 9 +- cmake/FindLdap.cmake | 29 ++ cmake/nebula/ConfigNebulaCommon.cmake | 1 + cmake/nebula/GeneralCMakeOptions.cmake | 1 + cmake/nebula/NebulaCMakeMacros.cmake | 1 + cmake/nebula/ThirdPartyConfig.cmake | 3 + src/graph/service/CMakeLists.txt | 1 + src/graph/service/GraphFlags.cpp | 25 ++ src/graph/service/GraphFlags.h | 17 ++ src/graph/service/GraphService.cpp | 5 + src/graph/service/LdapAuthenticator.cpp | 352 ++++++++++++++++++++++++ src/graph/service/LdapAuthenticator.h | 102 +++++++ tests/tck/features/ldap/Ldap.feature | 103 +++++++ 13 files changed, 645 insertions(+), 4 deletions(-) create mode 100644 cmake/FindLdap.cmake create mode 100644 src/graph/service/LdapAuthenticator.cpp create mode 100644 src/graph/service/LdapAuthenticator.h create mode 100644 tests/tck/features/ldap/Ldap.feature diff --git a/CMakeLists.txt b/CMakeLists.txt index 087f3ad9f25..21b0932498c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,10 +13,11 @@ # NEBULA_OTHER_ROOT -- Specify the root directory for user build # -- Split with ":", exp: DIR:DIR # -# ENABLE_JEMALLOC -- Link jemalloc into all executables -# ENABLE_NATIVE -- Build native client -# ENABLE_TESTING -- Build unit test -# ENABLE_PACK_ONE -- Package to one or multi packages +# ENABLE_JEMALLOC -- Link jemalloc into all executables +# ENABLE_NATIVE -- Build native client +# ENABLE_TESTING -- Build unit test +# ENABLE_PACK_ONE -- Package to one or multi packages +# ENABLE_LDAP -- Link ldap for ldap authentication # CMake version check cmake_minimum_required(VERSION 3.9.0) diff --git a/cmake/FindLdap.cmake b/cmake/FindLdap.cmake new file mode 100644 index 00000000000..f4646572453 --- /dev/null +++ b/cmake/FindLdap.cmake @@ -0,0 +1,29 @@ +# - Try to find Ldap includes dirs and libraries +# +# Usage of this module as follows: +# +# find_package(Ldap) +# +# Variables used by this module, they can change the default behaviour and need +# to be set before calling find_package: +# +# Variables defined by this module: +# +# Ldap_FOUND System has Ldap, include and lib dirs found +# Ldap_INCLUDE_DIR The Ldap includes directories. +# Ldap_LIBRARY The Ldap library. + +find_path(Ldap_INCLUDE_DIR NAMES ldap.h) +find_library(Ldap_LIBRARY NAMES ldap) + +if(Ldap_INCLUDE_DIR AND Ldap_LIBRARY) + set(Ldap_FOUND TRUE) + mark_as_advanced( + Ldap_INCLUDE_DIR + Ldap_LIBRARY + ) +endif() + +if(NOT Ldap_FOUND) + message(FATAL_ERROR "Ldap doesn't exist") +endif() diff --git a/cmake/nebula/ConfigNebulaCommon.cmake b/cmake/nebula/ConfigNebulaCommon.cmake index 9c122b4e4f0..3a67e591950 100644 --- a/cmake/nebula/ConfigNebulaCommon.cmake +++ b/cmake/nebula/ConfigNebulaCommon.cmake @@ -28,6 +28,7 @@ macro(config_nebula_common) -DNEBULA_THIRDPARTY_ROOT=${NEBULA_THIRDPARTY_ROOT} -DNEBULA_OTHER_ROOT=${NEBULA_OTHER_ROOT} -DENABLE_JEMALLOC=${ENABLE_JEMALLOC} + -DENABLE_LDAP=${ENABLE_LDAP} -DENABLE_TESTING=OFF -DENABLE_CCACHE=${ENABLE_CCACHE} -DENABLE_ASAN=${ENABLE_ASAN} diff --git a/cmake/nebula/GeneralCMakeOptions.cmake b/cmake/nebula/GeneralCMakeOptions.cmake index 8fc3fd7a821..a6e20377b06 100644 --- a/cmake/nebula/GeneralCMakeOptions.cmake +++ b/cmake/nebula/GeneralCMakeOptions.cmake @@ -4,6 +4,7 @@ option(ENABLE_TESTING "Build unit tests" ON) option(ENABLE_CCACHE "Use ccache to speed up compiling" ON) option(ENABLE_WERROR "Regard warnings as errors" ON) option(ENABLE_JEMALLOC "Use jemalloc as memory allocator" ON) +option(ENABLE_LDAP "Enable ldap" OFF) option(ENABLE_ASAN "Build with AddressSanitizer" OFF) option(ENABLE_UBSAN "Build with UndefinedBehaviourSanitizer" OFF) option(ENABLE_TSAN "Build with ThreadSanitizer" OFF) diff --git a/cmake/nebula/NebulaCMakeMacros.cmake b/cmake/nebula/NebulaCMakeMacros.cmake index 6e2c9f7e4c1..baf30433944 100644 --- a/cmake/nebula/NebulaCMakeMacros.cmake +++ b/cmake/nebula/NebulaCMakeMacros.cmake @@ -87,6 +87,7 @@ macro(nebula_link_libraries target) boost_filesystem boost_program_options event + ldap double-conversion s2 ${OPENSSL_SSL_LIBRARY} diff --git a/cmake/nebula/ThirdPartyConfig.cmake b/cmake/nebula/ThirdPartyConfig.cmake index 3ebf855b60b..38a374ff9f4 100644 --- a/cmake/nebula/ThirdPartyConfig.cmake +++ b/cmake/nebula/ThirdPartyConfig.cmake @@ -89,6 +89,9 @@ find_package(Googletest REQUIRED) if(ENABLE_JEMALLOC) find_package(Jemalloc REQUIRED) endif() +if(ENABLE_LDAP) + find_package(Ldap REQUIRED) +endif() find_package(Libevent REQUIRED) find_package(Proxygen REQUIRED) find_package(Rocksdb REQUIRED) diff --git a/src/graph/service/CMakeLists.txt b/src/graph/service/CMakeLists.txt index 6df3ded0646..cefed2806e3 100644 --- a/src/graph/service/CMakeLists.txt +++ b/src/graph/service/CMakeLists.txt @@ -25,5 +25,6 @@ nebula_add_library( PermissionCheck.cpp PasswordAuthenticator.cpp CloudAuthenticator.cpp + LdapAuthenticator.cpp ) diff --git a/src/graph/service/GraphFlags.cpp b/src/graph/service/GraphFlags.cpp index 08e3164ae75..38decaa6451 100644 --- a/src/graph/service/GraphFlags.cpp +++ b/src/graph/service/GraphFlags.cpp @@ -62,3 +62,28 @@ DEFINE_bool(disable_octal_escape_char, false, "Octal escape character will be disabled" " in next version to ensure compatibility with cypher."); + +// LDAP authentication common parameters +DEFINE_string(ldap_server, "", "list of ldap server addresses, " + "separate multiple addresses with comma"); +DEFINE_int32(ldap_port, 0, "Ldap server port, if no port is specified, use the default port"); +DEFINE_string(ldap_scheme, "ldap", "Only support ldap"); +DEFINE_bool(ldap_tls, false, "Whether the connection between graphd and the LDAP server uses " + "TLS encryption"); + +// LDAP authentication simple bind mode parameters +DEFINE_string(ldap_prefix, "", "Prepend the string to the user name " + "to form the distinguished name"); +DEFINE_string(ldap_suffix, "", "Append the string to the user name " + "to form the distinguished name"); + +// LDAP authentication search bind mode parameters +DEFINE_string(ldap_basedn, "", "Root distinguished name to search the user"); +DEFINE_string(ldap_binddn, "", "User distinguished name binding to the " + "directory to perform the search"); +DEFINE_string(ldap_bindpasswd, "", "User password binding to the directory " + "to perform the search"); +DEFINE_string(ldap_searchattribute, "", "Attribute to match the user name in the search"); +DEFINE_string(ldap_searchfilter, "", "Use search filter, more flexible than searchattribut"); + + diff --git a/src/graph/service/GraphFlags.h b/src/graph/service/GraphFlags.h index f9af5d756a9..74c44930812 100644 --- a/src/graph/service/GraphFlags.h +++ b/src/graph/service/GraphFlags.h @@ -46,4 +46,21 @@ DECLARE_int64(max_allowed_connections); DECLARE_string(local_ip); +// LDAP authentication common parameters +DECLARE_string(ldap_server); +DECLARE_int32(ldap_port); +DECLARE_string(ldap_scheme); +DECLARE_bool(ldap_tls); + +// LDAP authentication simple bind mode parameters +DECLARE_string(ldap_prefix); +DECLARE_string(ldap_suffix); + +// LDAP authentication search bind mode parameters +DECLARE_string(ldap_basedn); +DECLARE_string(ldap_binddn); +DECLARE_string(ldap_bindpasswd); +DECLARE_string(ldap_searchattribute); +DECLARE_string(ldap_searchfilter); + #endif // GRAPH_GRAPHFLAGS_H_ diff --git a/src/graph/service/GraphService.cpp b/src/graph/service/GraphService.cpp index 6b217fb0c1f..4fb111307e9 100644 --- a/src/graph/service/GraphService.cpp +++ b/src/graph/service/GraphService.cpp @@ -14,6 +14,7 @@ #include "graph/service/CloudAuthenticator.h" #include "graph/service/GraphFlags.h" #include "graph/service/PasswordAuthenticator.h" +#include "graph/service/LdapAuthenticator.h" #include "graph/service/RequestContext.h" #include "graph/stats/StatsDef.h" #include "version/Version.h" @@ -163,7 +164,11 @@ bool GraphService::auth(const std::string& username, const std::string& password } else if (FLAGS_auth_type == "cloud") { auto authenticator = std::make_unique(queryEngine_->metaClient()); return authenticator->auth(username, password); + } else if (FLAGS_auth_type == "ldap") { + auto authenticator = std::make_unique(queryEngine_->metaClient()); + return authenticator->auth(username, password); } + LOG(WARNING) << "Unknown auth type: " << FLAGS_auth_type; return false; } diff --git a/src/graph/service/LdapAuthenticator.cpp b/src/graph/service/LdapAuthenticator.cpp new file mode 100644 index 00000000000..e11585cd82e --- /dev/null +++ b/src/graph/service/LdapAuthenticator.cpp @@ -0,0 +1,352 @@ +/* Copyright (c) 2021 vesoft inc. All rights reserved. + * + * This source code is licensed under Apache 2.0 License, + * attached with Common Clause Condition 1.0, found in the LICENSES directory. + */ + +#include "graph/service/LdapAuthenticator.h" +#include "graph/service/GraphFlags.h" + +namespace nebula { +namespace graph { + +LdapAuthenticator::LdapAuthenticator(const meta::MetaClient* client) { + metaClient_ = client; +} + +Status LdapAuthenticator::prepare() { + if (FLAGS_ldap_server.empty()) { + return Status::Error("LDAP authentication \"ldap_server\" parameter is not set."); + } + + if (FLAGS_ldap_scheme.compare("ldap")) { + return Status::Error("LDAP authentication ldap_scheme only support \"ldap\"."); + } + + if (FLAGS_ldap_port == 0) { + FLAGS_ldap_port = ldapPort_; + } + + // Simple bind mode + if (!FLAGS_ldap_prefix.empty() || !FLAGS_ldap_suffix.empty()) { + if (!FLAGS_ldap_basedn.empty() || + !FLAGS_ldap_binddn.empty() || + !FLAGS_ldap_bindpasswd.empty() || + !FLAGS_ldap_searchattribute.empty() || + !FLAGS_ldap_searchfilter.empty()) { + return Status::Error("LDAP authentication direct bind mode and search bind mode" + " parameters cannot be mixed."); + } + // Here search bind mode must be set FLAGS_ldap_basedn + } else if (FLAGS_ldap_basedn.empty()) { + return Status::Error("LDAP authentication requires argument \"ldap_prefix\", " + "\"ldap_suffix\", or \"ldap_basedn\" to be set."); + } + + /** + * Search bind mode can either use FLAGS_ldap_searchattribute or + * FLAGS_ldap_searchfilter, but can't use both. FLAGS_Ldap_searchattribute + * default is "uid". FLAGS_Ldap_searchfilter is more flexible search filtes + * than FLAGS_Ldap_searchattribute. + */ + if (!FLAGS_ldap_searchattribute.empty() && !FLAGS_ldap_searchfilter.empty()) { + return Status::Error("LDAP authentication cannot use ldap_searchattribute " + "together with ldap_searchfilter."); + } + + return Status::OK(); +} + +Status LdapAuthenticator::initLDAPConnection() { + int ldapVersion = LDAP_VERSION3; + // Ldap_create interface is only available in versions higher than 2.4.49 + // ldap_init is deprecated, so use ldap_initialize + + // FLAGS_ldap_server is comma separated list of hosts + auto ldap_server = folly::trimWhitespace(FLAGS_ldap_server); + if (ldap_server.empty()) { + return Status::Error("LDAP authentication ldap_server is illegal."); + } + const char *host = ldap_server.str().c_str(); + std::string uris(""); + do { + auto size = std::strcspn(host, ","); + if (uris.length() > 0) { + uris.append(" "); + } + uris.append(FLAGS_ldap_scheme); + uris.append("://"); + uris.append(host, size); + uris += folly::stringPrintf(":%d", FLAGS_ldap_port); + host += size; + if (*host == ',') { + host++; + } + while (*host == ' ') { + host++; + } + } while (*host); + + // initialize the LDAP ldap://host[:port] host[:port] + auto ret = ldap_initialize(&ldap_, uris.c_str()); + if (ret != LDAP_SUCCESS) { + return Status::Error("Init LDAP failed."); + } + + ret = ldap_set_option(ldap_, LDAP_OPT_PROTOCOL_VERSION, &ldapVersion); + if (ret != LDAP_SUCCESS) { + ldap_unbind_ext(ldap_, NULL, NULL); + return Status::Error("Set LDAP protocol version failed"); + } + + if (FLAGS_ldap_tls) { + ret = ldap_start_tls_s(ldap_, NULL, NULL); + if (ret != LDAP_SUCCESS) { + ldap_unbind_ext(ldap_, NULL, NULL); + return Status::Error("Start LDAP TLS session failed"); + } + } + return Status::OK(); +} + +std::string LdapAuthenticator::buildSearchFilter() { + std::string result = FLAGS_ldap_searchfilter; + std::string ph = "$username"; + auto len = ph.length(); + + std::string::size_type pos = 0; + while ((pos = result.find(ph)) != std::string::npos) { + result.replace(pos, len, userName_); + } + return result; +} + +std::string LdapAuthenticator::buildFilter() { + std::string filter; + if (!FLAGS_ldap_searchfilter.empty()) { + filter = buildSearchFilter(); + } else if (!FLAGS_ldap_searchattribute.empty()) { + filter = folly::stringPrintf("(%s=%s)", FLAGS_ldap_searchattribute.c_str(), + userName_.c_str()); + } else { + filter = folly::stringPrintf("(uid=%s)", userName_.c_str()); + } + return filter; +} + +StatusOr LdapAuthenticator::simpleBindAuth() { + auto fullUserName = FLAGS_ldap_prefix + userName_ + FLAGS_ldap_suffix; + + struct berval cred; + cred.bv_val = const_cast(password_.c_str()); + cred.bv_len = password_.length(); + + auto ret = ldap_sasl_bind_s(ldap_, + fullUserName.c_str(), + LDAP_SASL_SIMPLE, + &cred, + NULL, + NULL, + NULL); + + ldap_unbind_ext(ldap_, NULL, NULL); + if (ret != LDAP_SUCCESS) { + return Status::Error("LDAP login failed for user \"%s\" on server \"%s\".", + fullUserName.c_str(), FLAGS_ldap_server.c_str()); + } + return true; +} + +StatusOr LdapAuthenticator::searchBindAuth() { + LDAPMessage *searchMessage, *entry; + char* distName; + std::string dn; + + /** + * Firstly, perform the LDAP seach to find the distinguished name + * of the user who is logging in. + */ + + /** + * LDAP authentication disallows any characters that need to escape + * in user name. + */ + for (auto& c : userName_) { + if (c == '*' || + c == '(' || + c == ')' || + c == '/' || + c == '\\') { + ldap_unbind_ext(ldap_, NULL, NULL); + return Status::Error("User name contains invalid character in LDAP authentication."); + } + } + + /** + * Bind with a pre-defined user name and password to search. + * If none is specified, use the anonymous bind. + */ + auto& binddn = FLAGS_ldap_binddn; + auto& bindPassword = FLAGS_ldap_bindpasswd; + + struct berval cred1; + cred1.bv_val = const_cast(bindPassword.c_str()); + cred1.bv_len = bindPassword.length(); + + auto rc = ldap_sasl_bind_s(ldap_, + binddn.c_str(), + LDAP_SASL_SIMPLE, + &cred1, + NULL, + NULL, + NULL); + + if (rc != LDAP_SUCCESS) { + ldap_unbind_ext(ldap_, NULL, NULL); + return Status::Error("Perform initial LDAP bind for ldapbinddn \"%s\" on server " + "\"%s\" failed.", binddn.c_str(), FLAGS_ldap_server.c_str()); + } + + // Build the filter + auto filter = buildFilter(); + + char *attributes[2]; + if (FLAGS_ldap_searchattribute.empty()) { + char tempStr[] = "uid"; + attributes[0] = tempStr; + } else { + attributes[0] = const_cast(FLAGS_ldap_searchattribute.c_str()); + } + attributes[1] = nullptr; + + // Initiate an ldap search, synchronize + rc = ldap_search_ext_s(ldap_, + FLAGS_ldap_basedn.c_str(), + 0, // The search scope, use LDAP_SCOPE_BASE + filter.c_str(), + attributes, + 0, + NULL, + NULL, + NULL, + LDAP_NO_LIMIT, + &searchMessage); + + if (rc != LDAP_SUCCESS) { + ldap_unbind_ext(ldap_, NULL, NULL); + return Status::Error("Search LDAP for filter \"%s\" on server \"%s\" failed.", + filter.c_str(), FLAGS_ldap_server.c_str()); + } + + // Check the number of search result + auto retCount = ldap_count_entries(ldap_, searchMessage); + if (retCount != 1) { + ldap_unbind_ext(ldap_, NULL, NULL); + ldap_msgfree(searchMessage); + if (retCount == 0) { + return Status::Error("LDAP user \"%s\" does not exist.", userName_.c_str()); + } else { + return Status::Error("LDAP user \"%s\" is not unique.", userName_.c_str()); + } + } + + // Get distinguished name from search result + entry = ldap_first_entry(ldap_, searchMessage); + distName = ldap_get_dn(ldap_, entry); + + if (!distName) { + ldap_unbind_ext(ldap_, NULL, NULL); + ldap_msgfree(searchMessage); + return Status::Error("Get distinguished name for the first entry with filter \"%s\" " + "on server \"%s\" failed.", filter.c_str(), + FLAGS_ldap_server.c_str()); + } + + dn = distName; + ldap_memfree(distName); + ldap_msgfree(searchMessage); + + // Unbind and disconnect the first connection from the LDAP server + rc = ldap_unbind_ext_s(ldap_, NULL, NULL); + if (rc != LDAP_SUCCESS) { + return Status::Error("Unbind failed after searching for user \"%s\" on server \"%s\".", + userName_.c_str(), FLAGS_ldap_server.c_str()); + } + + /** + * Secondly, reinit LDAP connect to LADP server for the second connect. + * Using the distinguished name found on the first connection and the user's + * login password to LDAP authentication + */ + auto reInit = initLDAPConnection(); + if (!reInit.ok()) { + return reInit; + } + + struct berval cred2; + cred2.bv_val = const_cast(password_.c_str()); + cred2.bv_len = password_.length(); + + rc = ldap_sasl_bind_s(ldap_, + dn.c_str(), + LDAP_SASL_SIMPLE, + &cred2, + NULL, + NULL, + NULL); + + ldap_unbind_ext(ldap_, NULL, NULL); + if (rc != LDAP_SUCCESS) { + return Status::Error("LDAP login failed for user \"%s\" on server \"%s\".", + dn.c_str(), FLAGS_ldap_server.c_str()); + } + return true; +} + +bool LdapAuthenticator::auth(const std::string &user, + const std::string &password) { + // The shadow account on the nebula side has been created + // First, go to meta to check if the shadow account exists + if (!metaClient_->checkShadowAccountFromCache(user)) { + return false; + } + + // Second, use user + password authentication methods + if (password.empty()) { + LOG(ERROR) << "Password cannot be empty in LDAP authentication."; + return false; + } + + userName_ = user; + password_ = password; + + auto ret = prepare(); + if (!ret.ok()) { + LOG(ERROR) << ret.toString(); + return false; + } + + ret = initLDAPConnection(); + if (!ret.ok()) { + LOG(ERROR) << ret.toString(); + return false; + } + + // Search bind mode + StatusOr rets; + if (!FLAGS_ldap_basedn.empty()) { + rets = searchBindAuth(); + } else { + rets = simpleBindAuth(); + } + + if (!rets.ok()) { + LOG(ERROR) << rets.status().toString(); + return false; + } + return rets.value(); +} + +} // namespace graph +} // namespace nebula + diff --git a/src/graph/service/LdapAuthenticator.h b/src/graph/service/LdapAuthenticator.h new file mode 100644 index 00000000000..fb7482b9390 --- /dev/null +++ b/src/graph/service/LdapAuthenticator.h @@ -0,0 +1,102 @@ +/* Copyright (c) 2021 vesoft inc. All rights reserved. + * + * This source code is licensed under Apache 2.0 License, + * attached with Common Clause Condition 1.0, found in the LICENSES directory. + */ + +#ifndef GRAPH_SERVICE_LDAPAUTHENTICATOR_H_ +#define GRAPH_SERVICE_LDAPAUTHENTICATOR_H_ + +#include "common/base/Base.h" +#include "common/base/StatusOr.h" +#include "common/base/Status.h" +#include "clients/meta/MetaClient.h" +#include "graph/service/Authenticator.h" + +#include + +namespace nebula { +namespace graph { + +/** + * LDAP authentication has two modes: simple bind mode and search bind mode. + * common parameters: + * FLAGS_ldap_server, + * FLAGS_ldap_port, + * FLAGS_ldap_scheme, + * FLAGS_ldap_tls + * + * Simple bind mode uses the parameters: + * FLAGS_ldap_prefix, + * FLAGS_ldap_suffix + * dn(distinguished name) = FLAGS_ldap_prefix + userName + FLAGS_ldap_suffix + * + * Search bind mode uses the parameters: + * FLAGS_ldap_basedn, + * FLAGS_ldap_binddn, + * FLAGS_ldap_bindpasswd, + * one of FLAGS_ldap_searchattribute or FLAGS_ldap_searchfilter + * + * Disallow mixing the parameters of two modes. + */ +class LdapAuthenticator final : public Authenticator { +public: + explicit LdapAuthenticator(const meta::MetaClient* client); + + /** + * Execute LDAP authentication. + */ + bool auth(const std::string &user, const std::string &password) override; + +private: + /** + * Check if LDAP authentication parameters are set legally. + */ + Status prepare(); + + /** + * Init LDAP connect to LADP server. + * FLAGS_ldap_server may be a comma-separated list of IP addresses. + */ + Status initLDAPConnection(); + + /** + * Build the custom search filter. + * Replace all occurrences of the placeholder "$username" in FLAGS_ldap_searchfilter + * with variable userName_. + */ + std::string buildSearchFilter(); + + /** + * Build the filter + * Either search filter, or attribute filter, or default value. + */ + std::string buildFilter(); + + /** + * Execute LDAP simple bind mode authentication + */ + StatusOr simpleBindAuth(); + + /** + * Execute LDAP search bind mode authentication + */ + StatusOr searchBindAuth(); + +private: + const meta::MetaClient* metaClient_; + + LDAP* ldap_{nullptr}; + + // Default LDAP port + int ldapPort_{389}; + + std::string userName_; + std::string password_; +}; + +} // namespace graph +} // namespace nebula + +#endif // GRAPH_SERVICE_LDAPAUTHENTICATOR_H_ + diff --git a/tests/tck/features/ldap/Ldap.feature b/tests/tck/features/ldap/Ldap.feature new file mode 100644 index 00000000000..45ade260f28 --- /dev/null +++ b/tests/tck/features/ldap/Ldap.feature @@ -0,0 +1,103 @@ + # Copyright (c) 2021 vesoft inc. All rights reserved. +# +# This source code is licensed under Apache 2.0 License, +# attached with Common Clause Condition 1.0, found in the LICENSES directory. +@skip +# need ldap server And modify the graph conf +Feature: ldap authentication-SimpleBindAuth + + Background: + FLAGS_enable_authorize = true; + FLAGS_auth_type = "password"; + use username "root", password "nebula" to login, and execute: + CREATE SPACE space1(partition_num=1, replica_factor=1); + // Create shadow account + CREATE USER test2 WITH PASSWORD ""; + GRANT ROLE ADMIN ON space1 TO test2; + then signout. + + Scenario: SimpleBindAuth + modify the graph conf: + FLAGS_auth_type = "ldap"; + FLAGS_ldap_server = "127.0.0.1"; + FLAGS_ldap_port = 389; + FLAGS_ldap_scheme = "ldap"; + FLAGS_ldap_prefix = "uid="; + FLAGS_ldap_suffix = ",ou=it,dc=sys,dc=com"; + When executing query: + """ + # Ldap server contains corresponding records + use username "test2", password "passwdtest2" to login + """ + Then the login should be successful + When executing query: + """ + # Ldap server has no corresponding record + use username "admin", password "987" to login + """ + Then the login should be failed + When executing query: + """ + # Ldap server has no corresponding record + use username "test2", password "987" to login + """ + Then the login should be failed + + signout, and modify graph conf + FLAGS_auth_type = "password"; + use username "root", password "nebula" to login, and execute: + When executing query: + """ + DROP USER IF test2 + """ + Then the login should be successful + +@skip +Feature: ldap authentication-SearchBindAuth + + Background: + FLAGS_enable_authorize = true; + FLAGS_auth_type = "password"; + use username "root", password "nebula" to login, and execute: + CREATE SPACE space1(partition_num=1, replica_factor=1); + // Create shadow account + CREATE USER test2 WITH PASSWORD ""; + GRANT ROLE ADMIN ON space1 TO test2; + then signout. + + Scenario: SearchBindAuth + modify the graph conf: + FLAGS_auth_type = "ldap"; + FLAGS_ldap_server = "127.0.0.1"; + FLAGS_ldap_port = 389; + FLAGS_ldap_scheme = "ldap"; + FLAGS_ldap_prefix = ""; + FLAGS_ldap_suffix = ""; + FLAGS_ldap_basedn = "uid=test2,ou=it,dc=sys,dc=com"; + When executing query: + """ + # Ldap server contains corresponding records + use username "test2", password "passwdtest2" to login + """ + Then the login should be successful + When executing query: + """ + # Ldap server has no corresponding record + use username "admin", password "987" to login + """ + Then the login should be failed + When executing query: + """ + # Ldap server has no corresponding record + use username "test2", password "987" to login + """ + Then the login should be failed + + signout, and modify graph conf + FLAGS_auth_type = "password"; + use username "root", password "nebula" to login, and execute: + When executing query: + """ + DROP USER IF test2 + """ + Then the login should be successful From 0b394fbacdda01ea72466a30d4b4cc6773135104 Mon Sep 17 00:00:00 2001 From: panda-sheep <59197347+panda-sheep@users.noreply.github.com> Date: Thu, 19 Aug 2021 17:08:20 +0800 Subject: [PATCH 2/3] adjust the format --- src/graph/service/GraphFlags.cpp | 38 +- src/graph/service/GraphService.cpp | 2 +- src/graph/service/LdapAuthenticator.cpp | 589 ++++++++++++------------ src/graph/service/LdapAuthenticator.h | 119 +++-- tests/tck/features/ldap/Ldap.feature | 98 ++-- 5 files changed, 412 insertions(+), 434 deletions(-) diff --git a/src/graph/service/GraphFlags.cpp b/src/graph/service/GraphFlags.cpp index 38decaa6451..9769290708f 100644 --- a/src/graph/service/GraphFlags.cpp +++ b/src/graph/service/GraphFlags.cpp @@ -64,26 +64,36 @@ DEFINE_bool(disable_octal_escape_char, " in next version to ensure compatibility with cypher."); // LDAP authentication common parameters -DEFINE_string(ldap_server, "", "list of ldap server addresses, " - "separate multiple addresses with comma"); +DEFINE_string(ldap_server, + "", + "list of ldap server addresses, " + "separate multiple addresses with comma"); DEFINE_int32(ldap_port, 0, "Ldap server port, if no port is specified, use the default port"); DEFINE_string(ldap_scheme, "ldap", "Only support ldap"); -DEFINE_bool(ldap_tls, false, "Whether the connection between graphd and the LDAP server uses " - "TLS encryption"); +DEFINE_bool(ldap_tls, + false, + "Whether the connection between graphd and the LDAP server uses " + "TLS encryption"); // LDAP authentication simple bind mode parameters -DEFINE_string(ldap_prefix, "", "Prepend the string to the user name " - "to form the distinguished name"); -DEFINE_string(ldap_suffix, "", "Append the string to the user name " - "to form the distinguished name"); +DEFINE_string(ldap_prefix, + "", + "Prepend the string to the user name " + "to form the distinguished name"); +DEFINE_string(ldap_suffix, + "", + "Append the string to the user name " + "to form the distinguished name"); // LDAP authentication search bind mode parameters DEFINE_string(ldap_basedn, "", "Root distinguished name to search the user"); -DEFINE_string(ldap_binddn, "", "User distinguished name binding to the " - "directory to perform the search"); -DEFINE_string(ldap_bindpasswd, "", "User password binding to the directory " - "to perform the search"); +DEFINE_string(ldap_binddn, + "", + "User distinguished name binding to the " + "directory to perform the search"); +DEFINE_string(ldap_bindpasswd, + "", + "User password binding to the directory " + "to perform the search"); DEFINE_string(ldap_searchattribute, "", "Attribute to match the user name in the search"); DEFINE_string(ldap_searchfilter, "", "Use search filter, more flexible than searchattribut"); - - diff --git a/src/graph/service/GraphService.cpp b/src/graph/service/GraphService.cpp index 4fb111307e9..71849c18899 100644 --- a/src/graph/service/GraphService.cpp +++ b/src/graph/service/GraphService.cpp @@ -13,8 +13,8 @@ #include "common/time/TimezoneInfo.h" #include "graph/service/CloudAuthenticator.h" #include "graph/service/GraphFlags.h" -#include "graph/service/PasswordAuthenticator.h" #include "graph/service/LdapAuthenticator.h" +#include "graph/service/PasswordAuthenticator.h" #include "graph/service/RequestContext.h" #include "graph/stats/StatsDef.h" #include "version/Version.h" diff --git a/src/graph/service/LdapAuthenticator.cpp b/src/graph/service/LdapAuthenticator.cpp index e11585cd82e..744d99d09f6 100644 --- a/src/graph/service/LdapAuthenticator.cpp +++ b/src/graph/service/LdapAuthenticator.cpp @@ -5,348 +5,333 @@ */ #include "graph/service/LdapAuthenticator.h" + #include "graph/service/GraphFlags.h" namespace nebula { namespace graph { -LdapAuthenticator::LdapAuthenticator(const meta::MetaClient* client) { - metaClient_ = client; -} +LdapAuthenticator::LdapAuthenticator(const meta::MetaClient* client) { metaClient_ = client; } Status LdapAuthenticator::prepare() { - if (FLAGS_ldap_server.empty()) { - return Status::Error("LDAP authentication \"ldap_server\" parameter is not set."); - } - - if (FLAGS_ldap_scheme.compare("ldap")) { - return Status::Error("LDAP authentication ldap_scheme only support \"ldap\"."); + if (FLAGS_ldap_server.empty()) { + return Status::Error("LDAP authentication \"ldap_server\" parameter is not set."); + } + + if (FLAGS_ldap_scheme.compare("ldap")) { + return Status::Error("LDAP authentication ldap_scheme only support \"ldap\"."); + } + + if (FLAGS_ldap_port == 0) { + FLAGS_ldap_port = ldapPort_; + } + + // Simple bind mode + if (!FLAGS_ldap_prefix.empty() || !FLAGS_ldap_suffix.empty()) { + if (!FLAGS_ldap_basedn.empty() || !FLAGS_ldap_binddn.empty() || + !FLAGS_ldap_bindpasswd.empty() || !FLAGS_ldap_searchattribute.empty() || + !FLAGS_ldap_searchfilter.empty()) { + return Status::Error( + "LDAP authentication direct bind mode and search bind mode" + " parameters cannot be mixed."); } - - if (FLAGS_ldap_port == 0) { - FLAGS_ldap_port = ldapPort_; - } - - // Simple bind mode - if (!FLAGS_ldap_prefix.empty() || !FLAGS_ldap_suffix.empty()) { - if (!FLAGS_ldap_basedn.empty() || - !FLAGS_ldap_binddn.empty() || - !FLAGS_ldap_bindpasswd.empty() || - !FLAGS_ldap_searchattribute.empty() || - !FLAGS_ldap_searchfilter.empty()) { - return Status::Error("LDAP authentication direct bind mode and search bind mode" - " parameters cannot be mixed."); - } // Here search bind mode must be set FLAGS_ldap_basedn - } else if (FLAGS_ldap_basedn.empty()) { - return Status::Error("LDAP authentication requires argument \"ldap_prefix\", " - "\"ldap_suffix\", or \"ldap_basedn\" to be set."); - } - - /** - * Search bind mode can either use FLAGS_ldap_searchattribute or - * FLAGS_ldap_searchfilter, but can't use both. FLAGS_Ldap_searchattribute - * default is "uid". FLAGS_Ldap_searchfilter is more flexible search filtes - * than FLAGS_Ldap_searchattribute. - */ - if (!FLAGS_ldap_searchattribute.empty() && !FLAGS_ldap_searchfilter.empty()) { - return Status::Error("LDAP authentication cannot use ldap_searchattribute " - "together with ldap_searchfilter."); - } - - return Status::OK(); + } else if (FLAGS_ldap_basedn.empty()) { + return Status::Error( + "LDAP authentication requires argument \"ldap_prefix\", " + "\"ldap_suffix\", or \"ldap_basedn\" to be set."); + } + + /** + * Search bind mode can either use FLAGS_ldap_searchattribute or + * FLAGS_ldap_searchfilter, but can't use both. FLAGS_Ldap_searchattribute + * default is "uid". FLAGS_Ldap_searchfilter is more flexible search filtes + * than FLAGS_Ldap_searchattribute. + */ + if (!FLAGS_ldap_searchattribute.empty() && !FLAGS_ldap_searchfilter.empty()) { + return Status::Error( + "LDAP authentication cannot use ldap_searchattribute " + "together with ldap_searchfilter."); + } + + return Status::OK(); } Status LdapAuthenticator::initLDAPConnection() { - int ldapVersion = LDAP_VERSION3; - // Ldap_create interface is only available in versions higher than 2.4.49 - // ldap_init is deprecated, so use ldap_initialize - - // FLAGS_ldap_server is comma separated list of hosts - auto ldap_server = folly::trimWhitespace(FLAGS_ldap_server); - if (ldap_server.empty()) { - return Status::Error("LDAP authentication ldap_server is illegal."); + int ldapVersion = LDAP_VERSION3; + // Ldap_create interface is only available in versions higher than 2.4.49 + // ldap_init is deprecated, so use ldap_initialize + + // FLAGS_ldap_server is comma separated list of hosts + auto ldap_server = folly::trimWhitespace(FLAGS_ldap_server); + if (ldap_server.empty()) { + return Status::Error("LDAP authentication ldap_server is illegal."); + } + const char* host = ldap_server.str().c_str(); + std::string uris(""); + do { + auto size = std::strcspn(host, ","); + if (uris.length() > 0) { + uris.append(" "); } - const char *host = ldap_server.str().c_str(); - std::string uris(""); - do { - auto size = std::strcspn(host, ","); - if (uris.length() > 0) { - uris.append(" "); - } - uris.append(FLAGS_ldap_scheme); - uris.append("://"); - uris.append(host, size); - uris += folly::stringPrintf(":%d", FLAGS_ldap_port); - host += size; - if (*host == ',') { - host++; - } - while (*host == ' ') { - host++; - } - } while (*host); - - // initialize the LDAP ldap://host[:port] host[:port] - auto ret = ldap_initialize(&ldap_, uris.c_str()); - if (ret != LDAP_SUCCESS) { - return Status::Error("Init LDAP failed."); + uris.append(FLAGS_ldap_scheme); + uris.append("://"); + uris.append(host, size); + uris += folly::stringPrintf(":%d", FLAGS_ldap_port); + host += size; + if (*host == ',') { + host++; } - - ret = ldap_set_option(ldap_, LDAP_OPT_PROTOCOL_VERSION, &ldapVersion); - if (ret != LDAP_SUCCESS) { - ldap_unbind_ext(ldap_, NULL, NULL); - return Status::Error("Set LDAP protocol version failed"); + while (*host == ' ') { + host++; } + } while (*host); - if (FLAGS_ldap_tls) { - ret = ldap_start_tls_s(ldap_, NULL, NULL); - if (ret != LDAP_SUCCESS) { - ldap_unbind_ext(ldap_, NULL, NULL); - return Status::Error("Start LDAP TLS session failed"); - } + // initialize the LDAP ldap://host[:port] host[:port] + auto ret = ldap_initialize(&ldap_, uris.c_str()); + if (ret != LDAP_SUCCESS) { + return Status::Error("Init LDAP failed."); + } + + ret = ldap_set_option(ldap_, LDAP_OPT_PROTOCOL_VERSION, &ldapVersion); + if (ret != LDAP_SUCCESS) { + ldap_unbind_ext(ldap_, NULL, NULL); + return Status::Error("Set LDAP protocol version failed"); + } + + if (FLAGS_ldap_tls) { + ret = ldap_start_tls_s(ldap_, NULL, NULL); + if (ret != LDAP_SUCCESS) { + ldap_unbind_ext(ldap_, NULL, NULL); + return Status::Error("Start LDAP TLS session failed"); } - return Status::OK(); + } + return Status::OK(); } std::string LdapAuthenticator::buildSearchFilter() { - std::string result = FLAGS_ldap_searchfilter; - std::string ph = "$username"; - auto len = ph.length(); - - std::string::size_type pos = 0; - while ((pos = result.find(ph)) != std::string::npos) { - result.replace(pos, len, userName_); - } - return result; + std::string result = FLAGS_ldap_searchfilter; + std::string ph = "$username"; + auto len = ph.length(); + + std::string::size_type pos = 0; + while ((pos = result.find(ph)) != std::string::npos) { + result.replace(pos, len, userName_); + } + return result; } std::string LdapAuthenticator::buildFilter() { - std::string filter; - if (!FLAGS_ldap_searchfilter.empty()) { - filter = buildSearchFilter(); - } else if (!FLAGS_ldap_searchattribute.empty()) { - filter = folly::stringPrintf("(%s=%s)", FLAGS_ldap_searchattribute.c_str(), - userName_.c_str()); - } else { - filter = folly::stringPrintf("(uid=%s)", userName_.c_str()); - } - return filter; + std::string filter; + if (!FLAGS_ldap_searchfilter.empty()) { + filter = buildSearchFilter(); + } else if (!FLAGS_ldap_searchattribute.empty()) { + filter = folly::stringPrintf("(%s=%s)", FLAGS_ldap_searchattribute.c_str(), userName_.c_str()); + } else { + filter = folly::stringPrintf("(uid=%s)", userName_.c_str()); + } + return filter; } StatusOr LdapAuthenticator::simpleBindAuth() { - auto fullUserName = FLAGS_ldap_prefix + userName_ + FLAGS_ldap_suffix; - - struct berval cred; - cred.bv_val = const_cast(password_.c_str()); - cred.bv_len = password_.length(); - - auto ret = ldap_sasl_bind_s(ldap_, - fullUserName.c_str(), - LDAP_SASL_SIMPLE, - &cred, - NULL, - NULL, - NULL); - - ldap_unbind_ext(ldap_, NULL, NULL); - if (ret != LDAP_SUCCESS) { - return Status::Error("LDAP login failed for user \"%s\" on server \"%s\".", - fullUserName.c_str(), FLAGS_ldap_server.c_str()); - } - return true; + auto fullUserName = FLAGS_ldap_prefix + userName_ + FLAGS_ldap_suffix; + + struct berval cred; + cred.bv_val = const_cast(password_.c_str()); + cred.bv_len = password_.length(); + + auto ret = + ldap_sasl_bind_s(ldap_, fullUserName.c_str(), LDAP_SASL_SIMPLE, &cred, NULL, NULL, NULL); + + ldap_unbind_ext(ldap_, NULL, NULL); + if (ret != LDAP_SUCCESS) { + return Status::Error("LDAP login failed for user \"%s\" on server \"%s\".", + fullUserName.c_str(), + FLAGS_ldap_server.c_str()); + } + return true; } StatusOr LdapAuthenticator::searchBindAuth() { - LDAPMessage *searchMessage, *entry; - char* distName; - std::string dn; - - /** - * Firstly, perform the LDAP seach to find the distinguished name - * of the user who is logging in. - */ - - /** - * LDAP authentication disallows any characters that need to escape - * in user name. - */ - for (auto& c : userName_) { - if (c == '*' || - c == '(' || - c == ')' || - c == '/' || - c == '\\') { - ldap_unbind_ext(ldap_, NULL, NULL); - return Status::Error("User name contains invalid character in LDAP authentication."); - } + LDAPMessage *searchMessage, *entry; + char* distName; + std::string dn; + + /** + * Firstly, perform the LDAP seach to find the distinguished name + * of the user who is logging in. + */ + + /** + * LDAP authentication disallows any characters that need to escape + * in user name. + */ + for (auto& c : userName_) { + if (c == '*' || c == '(' || c == ')' || c == '/' || c == '\\') { + ldap_unbind_ext(ldap_, NULL, NULL); + return Status::Error("User name contains invalid character in LDAP authentication."); } + } - /** - * Bind with a pre-defined user name and password to search. - * If none is specified, use the anonymous bind. - */ - auto& binddn = FLAGS_ldap_binddn; - auto& bindPassword = FLAGS_ldap_bindpasswd; - - struct berval cred1; - cred1.bv_val = const_cast(bindPassword.c_str()); - cred1.bv_len = bindPassword.length(); - - auto rc = ldap_sasl_bind_s(ldap_, - binddn.c_str(), - LDAP_SASL_SIMPLE, - &cred1, - NULL, - NULL, - NULL); - - if (rc != LDAP_SUCCESS) { - ldap_unbind_ext(ldap_, NULL, NULL); - return Status::Error("Perform initial LDAP bind for ldapbinddn \"%s\" on server " - "\"%s\" failed.", binddn.c_str(), FLAGS_ldap_server.c_str()); - } + /** + * Bind with a pre-defined user name and password to search. + * If none is specified, use the anonymous bind. + */ + auto& binddn = FLAGS_ldap_binddn; + auto& bindPassword = FLAGS_ldap_bindpasswd; - // Build the filter - auto filter = buildFilter(); + struct berval cred1; + cred1.bv_val = const_cast(bindPassword.c_str()); + cred1.bv_len = bindPassword.length(); - char *attributes[2]; - if (FLAGS_ldap_searchattribute.empty()) { - char tempStr[] = "uid"; - attributes[0] = tempStr; - } else { - attributes[0] = const_cast(FLAGS_ldap_searchattribute.c_str()); - } - attributes[1] = nullptr; - - // Initiate an ldap search, synchronize - rc = ldap_search_ext_s(ldap_, - FLAGS_ldap_basedn.c_str(), - 0, // The search scope, use LDAP_SCOPE_BASE - filter.c_str(), - attributes, - 0, - NULL, - NULL, - NULL, - LDAP_NO_LIMIT, - &searchMessage); - - if (rc != LDAP_SUCCESS) { - ldap_unbind_ext(ldap_, NULL, NULL); - return Status::Error("Search LDAP for filter \"%s\" on server \"%s\" failed.", - filter.c_str(), FLAGS_ldap_server.c_str()); - } - - // Check the number of search result - auto retCount = ldap_count_entries(ldap_, searchMessage); - if (retCount != 1) { - ldap_unbind_ext(ldap_, NULL, NULL); - ldap_msgfree(searchMessage); - if (retCount == 0) { - return Status::Error("LDAP user \"%s\" does not exist.", userName_.c_str()); - } else { - return Status::Error("LDAP user \"%s\" is not unique.", userName_.c_str()); - } - } - - // Get distinguished name from search result - entry = ldap_first_entry(ldap_, searchMessage); - distName = ldap_get_dn(ldap_, entry); + auto rc = ldap_sasl_bind_s(ldap_, binddn.c_str(), LDAP_SASL_SIMPLE, &cred1, NULL, NULL, NULL); - if (!distName) { - ldap_unbind_ext(ldap_, NULL, NULL); - ldap_msgfree(searchMessage); - return Status::Error("Get distinguished name for the first entry with filter \"%s\" " - "on server \"%s\" failed.", filter.c_str(), - FLAGS_ldap_server.c_str()); - } - - dn = distName; - ldap_memfree(distName); + if (rc != LDAP_SUCCESS) { + ldap_unbind_ext(ldap_, NULL, NULL); + return Status::Error( + "Perform initial LDAP bind for ldapbinddn \"%s\" on server " + "\"%s\" failed.", + binddn.c_str(), + FLAGS_ldap_server.c_str()); + } + + // Build the filter + auto filter = buildFilter(); + + char* attributes[2]; + if (FLAGS_ldap_searchattribute.empty()) { + char tempStr[] = "uid"; + attributes[0] = tempStr; + } else { + attributes[0] = const_cast(FLAGS_ldap_searchattribute.c_str()); + } + attributes[1] = nullptr; + + // Initiate an ldap search, synchronize + rc = ldap_search_ext_s(ldap_, + FLAGS_ldap_basedn.c_str(), + 0, // The search scope, use LDAP_SCOPE_BASE + filter.c_str(), + attributes, + 0, + NULL, + NULL, + NULL, + LDAP_NO_LIMIT, + &searchMessage); + + if (rc != LDAP_SUCCESS) { + ldap_unbind_ext(ldap_, NULL, NULL); + return Status::Error("Search LDAP for filter \"%s\" on server \"%s\" failed.", + filter.c_str(), + FLAGS_ldap_server.c_str()); + } + + // Check the number of search result + auto retCount = ldap_count_entries(ldap_, searchMessage); + if (retCount != 1) { + ldap_unbind_ext(ldap_, NULL, NULL); ldap_msgfree(searchMessage); - - // Unbind and disconnect the first connection from the LDAP server - rc = ldap_unbind_ext_s(ldap_, NULL, NULL); - if (rc != LDAP_SUCCESS) { - return Status::Error("Unbind failed after searching for user \"%s\" on server \"%s\".", - userName_.c_str(), FLAGS_ldap_server.c_str()); - } - - /** - * Secondly, reinit LDAP connect to LADP server for the second connect. - * Using the distinguished name found on the first connection and the user's - * login password to LDAP authentication - */ - auto reInit = initLDAPConnection(); - if (!reInit.ok()) { - return reInit; + if (retCount == 0) { + return Status::Error("LDAP user \"%s\" does not exist.", userName_.c_str()); + } else { + return Status::Error("LDAP user \"%s\" is not unique.", userName_.c_str()); } + } - struct berval cred2; - cred2.bv_val = const_cast(password_.c_str()); - cred2.bv_len = password_.length(); - - rc = ldap_sasl_bind_s(ldap_, - dn.c_str(), - LDAP_SASL_SIMPLE, - &cred2, - NULL, - NULL, - NULL); + // Get distinguished name from search result + entry = ldap_first_entry(ldap_, searchMessage); + distName = ldap_get_dn(ldap_, entry); + if (!distName) { ldap_unbind_ext(ldap_, NULL, NULL); - if (rc != LDAP_SUCCESS) { - return Status::Error("LDAP login failed for user \"%s\" on server \"%s\".", - dn.c_str(), FLAGS_ldap_server.c_str()); - } - return true; + ldap_msgfree(searchMessage); + return Status::Error( + "Get distinguished name for the first entry with filter \"%s\" " + "on server \"%s\" failed.", + filter.c_str(), + FLAGS_ldap_server.c_str()); + } + + dn = distName; + ldap_memfree(distName); + ldap_msgfree(searchMessage); + + // Unbind and disconnect the first connection from the LDAP server + rc = ldap_unbind_ext_s(ldap_, NULL, NULL); + if (rc != LDAP_SUCCESS) { + return Status::Error("Unbind failed after searching for user \"%s\" on server \"%s\".", + userName_.c_str(), + FLAGS_ldap_server.c_str()); + } + + /** + * Secondly, reinit LDAP connect to LADP server for the second connect. + * Using the distinguished name found on the first connection and the user's + * login password to LDAP authentication + */ + auto reInit = initLDAPConnection(); + if (!reInit.ok()) { + return reInit; + } + + struct berval cred2; + cred2.bv_val = const_cast(password_.c_str()); + cred2.bv_len = password_.length(); + + rc = ldap_sasl_bind_s(ldap_, dn.c_str(), LDAP_SASL_SIMPLE, &cred2, NULL, NULL, NULL); + + ldap_unbind_ext(ldap_, NULL, NULL); + if (rc != LDAP_SUCCESS) { + return Status::Error("LDAP login failed for user \"%s\" on server \"%s\".", + dn.c_str(), + FLAGS_ldap_server.c_str()); + } + return true; } -bool LdapAuthenticator::auth(const std::string &user, - const std::string &password) { - // The shadow account on the nebula side has been created - // First, go to meta to check if the shadow account exists - if (!metaClient_->checkShadowAccountFromCache(user)) { - return false; - } - - // Second, use user + password authentication methods - if (password.empty()) { - LOG(ERROR) << "Password cannot be empty in LDAP authentication."; - return false; - } - - userName_ = user; - password_ = password; - - auto ret = prepare(); - if (!ret.ok()) { - LOG(ERROR) << ret.toString(); - return false; - } - - ret = initLDAPConnection(); - if (!ret.ok()) { - LOG(ERROR) << ret.toString(); - return false; - } - - // Search bind mode - StatusOr rets; - if (!FLAGS_ldap_basedn.empty()) { - rets = searchBindAuth(); - } else { - rets = simpleBindAuth(); - } - - if (!rets.ok()) { - LOG(ERROR) << rets.status().toString(); - return false; - } - return rets.value(); +bool LdapAuthenticator::auth(const std::string& user, const std::string& password) { + // The shadow account on the nebula side has been created + // First, go to meta to check if the shadow account exists + if (!metaClient_->checkShadowAccountFromCache(user)) { + return false; + } + + // Second, use user + password authentication methods + if (password.empty()) { + LOG(ERROR) << "Password cannot be empty in LDAP authentication."; + return false; + } + + userName_ = user; + password_ = password; + + auto ret = prepare(); + if (!ret.ok()) { + LOG(ERROR) << ret.toString(); + return false; + } + + ret = initLDAPConnection(); + if (!ret.ok()) { + LOG(ERROR) << ret.toString(); + return false; + } + + // Search bind mode + StatusOr rets; + if (!FLAGS_ldap_basedn.empty()) { + rets = searchBindAuth(); + } else { + rets = simpleBindAuth(); + } + + if (!rets.ok()) { + LOG(ERROR) << rets.status().toString(); + return false; + } + return rets.value(); } -} // namespace graph -} // namespace nebula - +} // namespace graph +} // namespace nebula diff --git a/src/graph/service/LdapAuthenticator.h b/src/graph/service/LdapAuthenticator.h index fb7482b9390..cb05e01f44c 100644 --- a/src/graph/service/LdapAuthenticator.h +++ b/src/graph/service/LdapAuthenticator.h @@ -7,14 +7,14 @@ #ifndef GRAPH_SERVICE_LDAPAUTHENTICATOR_H_ #define GRAPH_SERVICE_LDAPAUTHENTICATOR_H_ +#include + +#include "clients/meta/MetaClient.h" #include "common/base/Base.h" -#include "common/base/StatusOr.h" #include "common/base/Status.h" -#include "clients/meta/MetaClient.h" +#include "common/base/StatusOr.h" #include "graph/service/Authenticator.h" -#include - namespace nebula { namespace graph { @@ -40,63 +40,62 @@ namespace graph { * Disallow mixing the parameters of two modes. */ class LdapAuthenticator final : public Authenticator { -public: - explicit LdapAuthenticator(const meta::MetaClient* client); - - /** - * Execute LDAP authentication. - */ - bool auth(const std::string &user, const std::string &password) override; - -private: - /** - * Check if LDAP authentication parameters are set legally. - */ - Status prepare(); - - /** - * Init LDAP connect to LADP server. - * FLAGS_ldap_server may be a comma-separated list of IP addresses. - */ - Status initLDAPConnection(); - - /** - * Build the custom search filter. - * Replace all occurrences of the placeholder "$username" in FLAGS_ldap_searchfilter - * with variable userName_. - */ - std::string buildSearchFilter(); - - /** - * Build the filter - * Either search filter, or attribute filter, or default value. - */ - std::string buildFilter(); - - /** - * Execute LDAP simple bind mode authentication - */ - StatusOr simpleBindAuth(); - - /** - * Execute LDAP search bind mode authentication - */ - StatusOr searchBindAuth(); - -private: - const meta::MetaClient* metaClient_; - - LDAP* ldap_{nullptr}; - - // Default LDAP port - int ldapPort_{389}; - - std::string userName_; - std::string password_; + public: + explicit LdapAuthenticator(const meta::MetaClient* client); + + /** + * Execute LDAP authentication. + */ + bool auth(const std::string& user, const std::string& password) override; + + private: + /** + * Check if LDAP authentication parameters are set legally. + */ + Status prepare(); + + /** + * Init LDAP connect to LADP server. + * FLAGS_ldap_server may be a comma-separated list of IP addresses. + */ + Status initLDAPConnection(); + + /** + * Build the custom search filter. + * Replace all occurrences of the placeholder "$username" in FLAGS_ldap_searchfilter + * with variable userName_. + */ + std::string buildSearchFilter(); + + /** + * Build the filter + * Either search filter, or attribute filter, or default value. + */ + std::string buildFilter(); + + /** + * Execute LDAP simple bind mode authentication + */ + StatusOr simpleBindAuth(); + + /** + * Execute LDAP search bind mode authentication + */ + StatusOr searchBindAuth(); + + private: + const meta::MetaClient* metaClient_; + + LDAP* ldap_{nullptr}; + + // Default LDAP port + int ldapPort_{389}; + + std::string userName_; + std::string password_; }; -} // namespace graph -} // namespace nebula +} // namespace graph +} // namespace nebula #endif // GRAPH_SERVICE_LDAPAUTHENTICATOR_H_ - diff --git a/tests/tck/features/ldap/Ldap.feature b/tests/tck/features/ldap/Ldap.feature index 45ade260f28..2f551c21b0d 100644 --- a/tests/tck/features/ldap/Ldap.feature +++ b/tests/tck/features/ldap/Ldap.feature @@ -1,4 +1,4 @@ - # Copyright (c) 2021 vesoft inc. All rights reserved. +# Copyright (c) 2021 vesoft inc. All rights reserved. # # This source code is licensed under Apache 2.0 License, # attached with Common Clause Condition 1.0, found in the LICENSES directory. @@ -7,95 +7,79 @@ Feature: ldap authentication-SimpleBindAuth Background: - FLAGS_enable_authorize = true; - FLAGS_auth_type = "password"; - use username "root", password "nebula" to login, and execute: - CREATE SPACE space1(partition_num=1, replica_factor=1); - // Create shadow account - CREATE USER test2 WITH PASSWORD ""; - GRANT ROLE ADMIN ON space1 TO test2; - then signout. - + # FLAGS_enable_authorize = true; + # FLAGS_auth_type = "password"; + # use username "root", password "nebula" to login, and execute: + # CREATE SPACE space1(partition_num=1, replica_factor=1); + # Create shadow account + # CREATE USER test2 WITH PASSWORD ""; + # GRANT ROLE ADMIN ON space1 TO test2; + # then signout. Scenario: SimpleBindAuth - modify the graph conf: - FLAGS_auth_type = "ldap"; - FLAGS_ldap_server = "127.0.0.1"; - FLAGS_ldap_port = 389; - FLAGS_ldap_scheme = "ldap"; - FLAGS_ldap_prefix = "uid="; - FLAGS_ldap_suffix = ",ou=it,dc=sys,dc=com"; + # modify the graph conf: + # FLAGS_auth_type = "ldap"; + # FLAGS_ldap_server = "127.0.0.1"; + # FLAGS_ldap_port = 389; + # FLAGS_ldap_scheme = "ldap"; + # FLAGS_ldap_prefix = "uid="; + # FLAGS_ldap_suffix = ",ou=it,dc=sys,dc=com"; When executing query: """ # Ldap server contains corresponding records - use username "test2", password "passwdtest2" to login + # use username "test2", password "passwdtest2" to login """ Then the login should be successful When executing query: """ # Ldap server has no corresponding record - use username "admin", password "987" to login + # use username "admin", password "987" to login """ Then the login should be failed When executing query: """ # Ldap server has no corresponding record - use username "test2", password "987" to login + # use username "test2", password "987" to login """ Then the login should be failed - signout, and modify graph conf - FLAGS_auth_type = "password"; - use username "root", password "nebula" to login, and execute: - When executing query: - """ - DROP USER IF test2 - """ - Then the login should be successful - -@skip -Feature: ldap authentication-SearchBindAuth - - Background: - FLAGS_enable_authorize = true; - FLAGS_auth_type = "password"; - use username "root", password "nebula" to login, and execute: - CREATE SPACE space1(partition_num=1, replica_factor=1); - // Create shadow account - CREATE USER test2 WITH PASSWORD ""; - GRANT ROLE ADMIN ON space1 TO test2; - then signout. - + # signout, and modify graph conf + # FLAGS_auth_type = "password"; + # use username "root", password "nebula" to login, and execute: + # When executing query: + # """ + # DROP USER IF test2 + # """ + # Then the login should be successful Scenario: SearchBindAuth - modify the graph conf: - FLAGS_auth_type = "ldap"; - FLAGS_ldap_server = "127.0.0.1"; - FLAGS_ldap_port = 389; - FLAGS_ldap_scheme = "ldap"; - FLAGS_ldap_prefix = ""; - FLAGS_ldap_suffix = ""; - FLAGS_ldap_basedn = "uid=test2,ou=it,dc=sys,dc=com"; + # modify the graph conf: + # FLAGS_auth_type = "ldap"; + # FLAGS_ldap_server = "127.0.0.1"; + # FLAGS_ldap_port = 389; + # FLAGS_ldap_scheme = "ldap"; + # FLAGS_ldap_prefix = ""; + # FLAGS_ldap_suffix = ""; + # FLAGS_ldap_basedn = "uid=test2,ou=it,dc=sys,dc=com"; When executing query: """ # Ldap server contains corresponding records - use username "test2", password "passwdtest2" to login + # use username "test2", password "passwdtest2" to login """ Then the login should be successful When executing query: """ # Ldap server has no corresponding record - use username "admin", password "987" to login + # use username "admin", password "987" to login """ Then the login should be failed When executing query: """ # Ldap server has no corresponding record - use username "test2", password "987" to login + # use username "test2", password "987" to login """ Then the login should be failed - - signout, and modify graph conf - FLAGS_auth_type = "password"; - use username "root", password "nebula" to login, and execute: + # signout, and modify graph conf + # FLAGS_auth_type = "password"; + # use username "root", password "nebula" to login, and execute: When executing query: """ DROP USER IF test2 From b62a0867a15161e1069e26654d6c5e5882830d72 Mon Sep 17 00:00:00 2001 From: panda-sheep <59197347+panda-sheep@users.noreply.github.com> Date: Wed, 25 Aug 2021 16:17:41 +0800 Subject: [PATCH 3/3] support ci pass --- .github/workflows/pull_request.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 6f41fa0fd0f..f2ba8ac2c75 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -45,14 +45,14 @@ jobs: - ubuntu2004 compiler: - gcc-9.2 - - clang-9 + - clang-10 exclude: - os: centos7 - compiler: clang-9 + compiler: clang-10 container: image: vesoft/nebula-dev:${{ matrix.os }} env: - TOOLSET_DIR: /opt/vesoft/toolset/clang/9.0.0 + TOOLSET_DIR: /opt/vesoft/toolset/clang/10.0.0 CCACHE_DIR: /tmp/ccache/nebula/${{ matrix.os }}-${{ matrix.compiler }} CCACHE_MAXSIZE: 8G volumes: