diff options
author | molotkov-and <molotkov-and@ydb.tech> | 2023-08-30 12:26:33 +0300 |
---|---|---|
committer | molotkov-and <molotkov-and@ydb.tech> | 2023-08-30 13:10:29 +0300 |
commit | 518a643cc2da2d6313a4bbe2169f9dcaced332f3 (patch) | |
tree | 425ebf377cca2457724e179e0332568e8a4263d2 | |
parent | 7ccc4cc77189900b84644f90037c7490e05ee881 (diff) | |
download | ydb-518a643cc2da2d6313a4bbe2169f9dcaced332f3.tar.gz |
KIKIMR-18218: Authenticate users on Ldap server
31 files changed, 622 insertions, 45 deletions
diff --git a/ydb/core/driver_lib/run/kikimr_services_initializers.cpp b/ydb/core/driver_lib/run/kikimr_services_initializers.cpp index 172775dac7..161733d738 100644 --- a/ydb/core/driver_lib/run/kikimr_services_initializers.cpp +++ b/ydb/core/driver_lib/run/kikimr_services_initializers.cpp @@ -110,6 +110,7 @@ #include <ydb/core/scheme/scheme_type_registry.h> #include <ydb/core/security/ticket_parser.h> +#include <ydb/core/security/ldap_auth_provider.h> #include <ydb/core/sys_view/processor/processor.h> #include <ydb/core/sys_view/service/sysview_service.h> @@ -1589,6 +1590,13 @@ TSecurityServicesInitializer::TSecurityServicesInitializer(const TKikimrRunConfi void TSecurityServicesInitializer::InitializeServices(NActors::TActorSystemSetup* setup, const NKikimr::TAppData* appData) { + const auto& authConfig = appData->AuthConfig; + if (!IsServiceInitialized(setup, MakeLdapAuthProviderID()) && authConfig.HasLdapAuthentication()) { + IActor* ldapAuthProvider = CreateLdapAuthProvider(authConfig.GetLdapAuthentication()); + if (ldapAuthProvider) { + setup->LocalServices.push_back(std::make_pair<TActorId, TActorSetupCmd>(MakeLdapAuthProviderID(), TActorSetupCmd(ldapAuthProvider, TMailboxType::HTSwap, appData->UserPoolId))); + } + } if (!IsServiceInitialized(setup, MakeTicketParserID())) { IActor* ticketParser = nullptr; if (Factories && Factories->CreateTicketParser) { diff --git a/ydb/core/grpc_services/CMakeLists.darwin-x86_64.txt b/ydb/core/grpc_services/CMakeLists.darwin-x86_64.txt index cc90ccc9d3..cf2f1375a2 100644 --- a/ydb/core/grpc_services/CMakeLists.darwin-x86_64.txt +++ b/ydb/core/grpc_services/CMakeLists.darwin-x86_64.txt @@ -50,6 +50,7 @@ target_link_libraries(ydb-core-grpc_services PUBLIC tx-long_tx_service-public core-tx-ev_write ydb-core-ydb_convert + ydb-core-security ydb-library-aclib ydb-library-binary_json ydb-library-dynumber diff --git a/ydb/core/grpc_services/CMakeLists.linux-aarch64.txt b/ydb/core/grpc_services/CMakeLists.linux-aarch64.txt index 7f6a2f1461..12e7ce60b7 100644 --- a/ydb/core/grpc_services/CMakeLists.linux-aarch64.txt +++ b/ydb/core/grpc_services/CMakeLists.linux-aarch64.txt @@ -51,6 +51,7 @@ target_link_libraries(ydb-core-grpc_services PUBLIC tx-long_tx_service-public core-tx-ev_write ydb-core-ydb_convert + ydb-core-security ydb-library-aclib ydb-library-binary_json ydb-library-dynumber diff --git a/ydb/core/grpc_services/CMakeLists.linux-x86_64.txt b/ydb/core/grpc_services/CMakeLists.linux-x86_64.txt index 7f6a2f1461..12e7ce60b7 100644 --- a/ydb/core/grpc_services/CMakeLists.linux-x86_64.txt +++ b/ydb/core/grpc_services/CMakeLists.linux-x86_64.txt @@ -51,6 +51,7 @@ target_link_libraries(ydb-core-grpc_services PUBLIC tx-long_tx_service-public core-tx-ev_write ydb-core-ydb_convert + ydb-core-security ydb-library-aclib ydb-library-binary_json ydb-library-dynumber diff --git a/ydb/core/grpc_services/CMakeLists.windows-x86_64.txt b/ydb/core/grpc_services/CMakeLists.windows-x86_64.txt index cc90ccc9d3..cf2f1375a2 100644 --- a/ydb/core/grpc_services/CMakeLists.windows-x86_64.txt +++ b/ydb/core/grpc_services/CMakeLists.windows-x86_64.txt @@ -50,6 +50,7 @@ target_link_libraries(ydb-core-grpc_services PUBLIC tx-long_tx_service-public core-tx-ev_write ydb-core-ydb_convert + ydb-core-security ydb-library-aclib ydb-library-binary_json ydb-library-dynumber diff --git a/ydb/core/grpc_services/rpc_login.cpp b/ydb/core/grpc_services/rpc_login.cpp index ba2cb11d98..9c3173ae31 100644 --- a/ydb/core/grpc_services/rpc_login.cpp +++ b/ydb/core/grpc_services/rpc_login.cpp @@ -7,6 +7,9 @@ #include <ydb/core/tx/schemeshard/schemeshard.h> #include <ydb/core/tx/scheme_cache/scheme_cache.h> +#include <ydb/core/security/ldap_auth_provider.h> +#include <ydb/core/security/login_shared_func.h> + namespace NKikimr { namespace NGRpcService { @@ -15,6 +18,10 @@ using namespace NSchemeShard; using TEvLoginRequest = TGRpcRequestWrapperNoAuth<TRpcServices::EvLogin, Ydb::Auth::LoginRequest, Ydb::Auth::LoginResponse>; class TLoginRPC : public TRpcRequestActor<TLoginRPC, TEvLoginRequest, true> { +private: + TAuthCredentials Credentials; + TString PathToDatabase; + public: using TRpcRequestActor::TRpcRequestActor; @@ -30,16 +37,12 @@ public: } void Bootstrap() { + const Ydb::Auth::LoginRequest* protoRequest = GetProtoRequest(); + Credentials = PrepareCredentials(protoRequest->user(), protoRequest->password(), AppData()->AuthConfig); TString domainName = "/" + AppData()->DomainsInfo->Domains.begin()->second->Name; - TString path = AppData()->AuthConfig.GetDomainLoginOnly() ? domainName : DatabaseName; - auto request = MakeHolder<NSchemeCache::TSchemeCacheNavigate>(); - request->DatabaseName = path; - auto& entry = request->ResultSet.emplace_back(); - entry.Operation = NSchemeCache::TSchemeCacheNavigate::OpPath; - entry.Path = ::NKikimr::SplitPath(path); - entry.RedirectRequired = false; - Send(MakeSchemeCacheID(), new TEvTxProxySchemeCache::TEvNavigateKeySet(request.Release())); - + PathToDatabase = AppData()->AuthConfig.GetDomainLoginOnly() ? domainName : DatabaseName; + auto sendParameters = GetSendParameters(Credentials, PathToDatabase); + Send(sendParameters.Recipient, sendParameters.Event.Release()); Become(&TThis::StateWork, Timeout, new TEvents::TEvWakeup()); } @@ -56,9 +59,7 @@ public: IActor* pipe = NTabletPipe::CreateClient(SelfId(), domainInfo->ExtractSchemeShard(), GetPipeClientConfig()); PipeClient = RegisterWithSameMailbox(pipe); THolder<TEvSchemeShard::TEvLogin> request = MakeHolder<TEvSchemeShard::TEvLogin>(); - const Ydb::Auth::LoginRequest* protoRequest = GetProtoRequest(); - request.Get()->Record.SetUser(protoRequest->user()); - request.Get()->Record.SetPassword(protoRequest->password()); + request.Get()->Record = CreateLoginRequest(Credentials, AppData()->AuthConfig); NTabletPipe::SendData(SelfId(), PipeClient, request.Release()); return; } @@ -67,6 +68,23 @@ public: ReplyAndPassAway(); } + void Handle(TEvLdapAuthProvider::TEvAuthenticateResponse::TPtr& ev) { + TEvLdapAuthProvider::TEvAuthenticateResponse* response = ev->Get(); + if (response->Status == TEvLdapAuthProvider::EStatus::SUCCESS) { + Send(MakeSchemeCacheID(), new TEvTxProxySchemeCache::TEvNavigateKeySet(CreateNavigateKeySetRequest(PathToDatabase).Release())); + } else { + TResponse loginResponse; + Ydb::Operations::Operation& operation = *loginResponse.mutable_operation(); + Ydb::Issue::IssueMessage* issue = operation.add_issues(); + issue->set_message(response->Error.Message); + Status = ConvertLdapStatus(response->Status); + issue->set_issue_code(Status); + operation.set_ready(true); + operation.set_status(Status); + Reply(loginResponse); + } + } + void HandleResult(TEvSchemeShard::TEvLoginResult::TPtr& ev) { Status = Ydb::StatusIds::SUCCESS; Result = ev->Release(); @@ -91,6 +109,7 @@ public: hFunc(TEvTabletPipe::TEvClientConnected, HandleConnect); hFunc(TEvTxProxySchemeCache::TEvNavigateKeySetResult, HandleNavigate); hFunc(TEvSchemeShard::TEvLoginResult, HandleResult); + hFunc(TEvLdapAuthProvider::TEvAuthenticateResponse, Handle); cFunc(TEvents::TSystem::Wakeup, HandleTimeout); } } @@ -119,6 +138,20 @@ public: operation.set_status(Status); return Reply(response); } + +private: + static Ydb::StatusIds::StatusCode ConvertLdapStatus(const TEvLdapAuthProvider::EStatus& status) { + switch (status) { + case NKikimr::TEvLdapAuthProvider::EStatus::SUCCESS: + return Ydb::StatusIds::SUCCESS; + case NKikimr::TEvLdapAuthProvider::EStatus::UNAUTHORIZED: + return Ydb::StatusIds::UNAUTHORIZED; + case NKikimr::TEvLdapAuthProvider::EStatus::UNAVAILABLE: + return Ydb::StatusIds::UNAVAILABLE; + case NKikimr::TEvLdapAuthProvider::EStatus::BAD_REQUEST: + return Ydb::StatusIds::BAD_REQUEST; + } + } }; void TGRpcRequestProxyHandleMethods::Handle(TEvLoginRequest::TPtr& ev, const TActorContext& ctx) { diff --git a/ydb/core/grpc_services/ya.make b/ydb/core/grpc_services/ya.make index fe28ae3736..3746c1295a 100644 --- a/ydb/core/grpc_services/ya.make +++ b/ydb/core/grpc_services/ya.make @@ -112,6 +112,7 @@ PEERDIR( ydb/core/tx/long_tx_service/public ydb/core/tx/ev_write ydb/core/ydb_convert + ydb/core/security ydb/library/aclib ydb/library/binary_json ydb/library/dynumber diff --git a/ydb/core/protos/flat_tx_scheme.proto b/ydb/core/protos/flat_tx_scheme.proto index bb4bcde645..eab789772c 100644 --- a/ydb/core/protos/flat_tx_scheme.proto +++ b/ydb/core/protos/flat_tx_scheme.proto @@ -141,6 +141,7 @@ message TEvUpdateConfigResult { message TEvLogin { optional string User = 1; optional string Password = 2; + optional string ExternalAuth = 3; } message TEvLoginResult { diff --git a/ydb/core/security/CMakeLists.darwin-x86_64.txt b/ydb/core/security/CMakeLists.darwin-x86_64.txt index 6cb42ad6df..d31e217a46 100644 --- a/ydb/core/security/CMakeLists.darwin-x86_64.txt +++ b/ydb/core/security/CMakeLists.darwin-x86_64.txt @@ -28,6 +28,7 @@ target_link_libraries(ydb-core-security PUBLIC ) target_sources(ydb-core-security PRIVATE ${CMAKE_SOURCE_DIR}/ydb/core/security/login_page.cpp + ${CMAKE_SOURCE_DIR}/ydb/core/security/login_shared_func.cpp ${CMAKE_SOURCE_DIR}/ydb/core/security/ticket_parser.cpp ${CMAKE_SOURCE_DIR}/ydb/core/security/ldap_auth_provider.cpp ${CMAKE_SOURCE_DIR}/ydb/core/security/ldap_auth_provider_linux.cpp diff --git a/ydb/core/security/CMakeLists.linux-aarch64.txt b/ydb/core/security/CMakeLists.linux-aarch64.txt index d5e42c799c..0ecd192061 100644 --- a/ydb/core/security/CMakeLists.linux-aarch64.txt +++ b/ydb/core/security/CMakeLists.linux-aarch64.txt @@ -29,6 +29,7 @@ target_link_libraries(ydb-core-security PUBLIC ) target_sources(ydb-core-security PRIVATE ${CMAKE_SOURCE_DIR}/ydb/core/security/login_page.cpp + ${CMAKE_SOURCE_DIR}/ydb/core/security/login_shared_func.cpp ${CMAKE_SOURCE_DIR}/ydb/core/security/ticket_parser.cpp ${CMAKE_SOURCE_DIR}/ydb/core/security/ldap_auth_provider.cpp ${CMAKE_SOURCE_DIR}/ydb/core/security/ldap_auth_provider_linux.cpp diff --git a/ydb/core/security/CMakeLists.linux-x86_64.txt b/ydb/core/security/CMakeLists.linux-x86_64.txt index d5e42c799c..0ecd192061 100644 --- a/ydb/core/security/CMakeLists.linux-x86_64.txt +++ b/ydb/core/security/CMakeLists.linux-x86_64.txt @@ -29,6 +29,7 @@ target_link_libraries(ydb-core-security PUBLIC ) target_sources(ydb-core-security PRIVATE ${CMAKE_SOURCE_DIR}/ydb/core/security/login_page.cpp + ${CMAKE_SOURCE_DIR}/ydb/core/security/login_shared_func.cpp ${CMAKE_SOURCE_DIR}/ydb/core/security/ticket_parser.cpp ${CMAKE_SOURCE_DIR}/ydb/core/security/ldap_auth_provider.cpp ${CMAKE_SOURCE_DIR}/ydb/core/security/ldap_auth_provider_linux.cpp diff --git a/ydb/core/security/CMakeLists.windows-x86_64.txt b/ydb/core/security/CMakeLists.windows-x86_64.txt index 0ce2d4a14c..515495b740 100644 --- a/ydb/core/security/CMakeLists.windows-x86_64.txt +++ b/ydb/core/security/CMakeLists.windows-x86_64.txt @@ -32,6 +32,7 @@ target_link_options(ydb-core-security INTERFACE ) target_sources(ydb-core-security PRIVATE ${CMAKE_SOURCE_DIR}/ydb/core/security/login_page.cpp + ${CMAKE_SOURCE_DIR}/ydb/core/security/login_shared_func.cpp ${CMAKE_SOURCE_DIR}/ydb/core/security/ticket_parser.cpp ${CMAKE_SOURCE_DIR}/ydb/core/security/ldap_auth_provider.cpp ${CMAKE_SOURCE_DIR}/ydb/core/security/ldap_auth_provider_win.cpp diff --git a/ydb/core/security/ldap_auth_provider.cpp b/ydb/core/security/ldap_auth_provider.cpp index 6379bc89ef..67f3ce7641 100644 --- a/ydb/core/security/ldap_auth_provider.cpp +++ b/ydb/core/security/ldap_auth_provider.cpp @@ -2,7 +2,6 @@ #include <library/cpp/actors/core/log.h> #include <ydb/core/base/ticket_parser.h> #include "ticket_parser_log.h" -#include <util/generic/string.h> #include "ldap_auth_provider.h" // This temporary solution @@ -39,6 +38,13 @@ class TLdapAuthProvider : public NActors::TActorBootstrapped<TLdapAuthProvider> char* Attribute = nullptr; }; + struct TAuthenticateUserRequest : TBasicRequest { + LDAP** Ld = nullptr; + LDAPMessage* Entry = nullptr; + TString Login; + TString Password; + }; + struct TBasicResponse { TEvLdapAuthProvider::EStatus Status = TEvLdapAuthProvider::EStatus::SUCCESS; TEvLdapAuthProvider::TError Error; @@ -50,6 +56,8 @@ class TLdapAuthProvider : public NActors::TActorBootstrapped<TLdapAuthProvider> LDAPMessage* SearchMessage = nullptr; }; + struct TAuthenticateUserResponse : TBasicResponse {}; + public: TLdapAuthProvider(const NKikimrProto::TLdapAuthentication& settings) : Settings(settings) @@ -62,11 +70,49 @@ public: void StateWork(TAutoPtr<NActors::IEventHandle>& ev) { switch (ev->GetTypeRewrite()) { hFunc(TEvLdapAuthProvider::TEvEnrichGroupsRequest, Handle); + hFunc(TEvLdapAuthProvider::TEvAuthenticateRequest, Handle); CFunc(TEvents::TSystem::PoisonPill, Die); } } private: + void Handle(TEvLdapAuthProvider::TEvAuthenticateRequest::TPtr& ev) { + TEvLdapAuthProvider::TEvAuthenticateRequest* request = ev->Get(); + LDAP* ld = nullptr; + auto initializeResponse = InitializeLDAPConnection(&ld); + if (initializeResponse.Error) { + Send(ev->Sender, new TEvLdapAuthProvider::TEvAuthenticateResponse(initializeResponse.Status, initializeResponse.Error)); + return; + } + + int result = NKikimrLdap::Bind(ld, Settings.GetBindDn(), Settings.GetBindPassword()); + if (!NKikimrLdap::IsSuccess(result)) { + TEvLdapAuthProvider::TError error { + .Message = "Could not perform initial LDAP bind for dn " + Settings.GetBindDn() + " on server " + Settings.GetHost() + "\n" + + NKikimrLdap::ErrorToString(result) + }; + // The Unbind operation is not the antithesis of the Bind operation as the name implies. + // Close the LDAP connection, free the resources contained in the LDAP structure + NKikimrLdap::Unbind(ld); + Send(ev->Sender, new TEvLdapAuthProvider::TEvAuthenticateResponse(NKikimrLdap::ErrorToStatus(result), error)); + return; + } + + TSearchUserResponse searchUserResponse = SearchUserRecord({.Ld = ld, + .User = request->Login, + .RequestedAttributes = NKikimrLdap::noAttributes}); + if (searchUserResponse.Status != TEvLdapAuthProvider::EStatus::SUCCESS) { + NKikimrLdap::Unbind(ld); + Send(ev->Sender, new TEvLdapAuthProvider::TEvAuthenticateResponse(searchUserResponse.Status, searchUserResponse.Error)); + return; + } + auto entry = NKikimrLdap::FirstEntry(ld, searchUserResponse.SearchMessage); + TAuthenticateUserResponse authResponse = AuthenticateUser({.Ld = &ld, .Entry = entry, .Login = request->Login, .Password = request->Password}); + NKikimrLdap::MsgFree(entry); + NKikimrLdap::Unbind(ld); + Send(ev->Sender, new TEvLdapAuthProvider::TEvAuthenticateResponse(authResponse.Status, authResponse.Error)); + } + void Handle(TEvLdapAuthProvider::TEvEnrichGroupsRequest::TPtr& ev) { TEvLdapAuthProvider::TEvEnrichGroupsRequest* request = ev->Get(); LDAP* ld = nullptr; @@ -83,6 +129,8 @@ private: + NKikimrLdap::ErrorToString(result), .Retryable = NKikimrLdap::IsRetryableError(result) }; + // The Unbind operation is not the antithesis of the Bind operation as the name implies. + // Close the LDAP connection, free the resources contained in the LDAP structure NKikimrLdap::Unbind(ld); Send(ev->Sender, new TEvLdapAuthProvider::TEvEnrichGroupsResponse(request->Key, NKikimrLdap::ErrorToStatus(result), error)); return; @@ -123,10 +171,9 @@ private: *ld = NKikimrLdap::Init(host, port); if (*ld == nullptr) { - - return {{TEvLdapAuthProvider::EStatus::UNAVAILABLE, - {.Message = "Could not initialize LDAP connection for host: " + host + ", port: " + ToString(port) + ". " + NKikimrLdap::LdapError(*ld), - .Retryable = false}}}; + return {{TEvLdapAuthProvider::EStatus::UNAVAILABLE, + {.Message = "Could not initialize LDAP connection for host: " + host + ", port: " + ToString(port) + ". " + NKikimrLdap::LdapError(*ld), + .Retryable = false}}}; } int result = NKikimrLdap::SetProtocolVersion(*ld); @@ -139,6 +186,25 @@ private: return {}; } + TAuthenticateUserResponse AuthenticateUser(const TAuthenticateUserRequest& request) { + char* dn = NKikimrLdap::GetDn(*request.Ld, request.Entry); + if (dn == nullptr) { + return {{TEvLdapAuthProvider::EStatus::UNAUTHORIZED, + {.Message = "Could not get dn for the first entry matching " + GetFilter(request.Login) + " on server " + Settings.GetHost() + "\n" + + NKikimrLdap::LdapError(*request.Ld), + .Retryable = false}}}; + } + TEvLdapAuthProvider::TError error; + int result = NKikimrLdap::Bind(*request.Ld, dn, request.Password); + if (!NKikimrLdap::IsSuccess(result)) { + error.Message = "LDAP login failed for user " + TString(dn) + " on server " + Settings.GetHost() + "\n" + + NKikimrLdap::ErrorToString((result)); + error.Retryable = NKikimrLdap::IsRetryableError(result); + } + NKikimrLdap::MemFree(dn); + return {{NKikimrLdap::ErrorToStatus(result), error}}; + } + TSearchUserResponse SearchUserRecord(const TSearchUserRequest& request) { LDAPMessage* searchMessage = nullptr; const TString searchFilter = GetFilter(request.User); diff --git a/ydb/core/security/ldap_auth_provider.h b/ydb/core/security/ldap_auth_provider.h index 323905fd7b..e241e4fee3 100644 --- a/ydb/core/security/ldap_auth_provider.h +++ b/ydb/core/security/ldap_auth_provider.h @@ -9,9 +9,11 @@ struct TEvLdapAuthProvider { enum EEv { // requests EvEnrichGroupsRequest = EventSpaceBegin(TKikimrEvents::ES_LDAP_AUTH_PROVIDER), + EvAuthenticateRequest, // replies EvEnrichGroupsResponse = EventSpaceBegin(TKikimrEvents::ES_LDAP_AUTH_PROVIDER) + 512, + EvAuthenticateResponse, EvEnd }; @@ -35,6 +37,16 @@ struct TEvLdapAuthProvider { {} }; + struct TEvAuthenticateRequest : TEventLocal<TEvAuthenticateRequest, EvAuthenticateRequest> { + TString Login; + TString Password; + + TEvAuthenticateRequest(const TString& login, const TString& password) + : Login(login) + , Password(password) + {} + }; + using TError = TEvTicketParser::TError; template <typename TResponse, ui32 TEvent> @@ -64,8 +76,19 @@ struct TEvLdapAuthProvider { , Key(key) {} }; + + struct TEvAuthenticateResponse : TEvResponse<TEvAuthenticateResponse, EvAuthenticateResponse> { + TEvAuthenticateResponse(const EStatus& status, const TError& error) + : TEvResponse<TEvAuthenticateResponse, EvAuthenticateResponse>(status, error) + {} + }; }; +inline NActors::TActorId MakeLdapAuthProviderID() { + static const char name[12] = "ldapauthpdr"; + return NActors::TActorId(0, TStringBuf(name, 12)); +} + IActor* CreateLdapAuthProvider(const NKikimrProto::TLdapAuthentication& settings); } diff --git a/ydb/core/security/ldap_auth_provider_linux.cpp b/ydb/core/security/ldap_auth_provider_linux.cpp index 25fa794170..7a6f565f74 100644 --- a/ydb/core/security/ldap_auth_provider_linux.cpp +++ b/ydb/core/security/ldap_auth_provider_linux.cpp @@ -11,6 +11,14 @@ namespace NKikimrLdap { +namespace { + +char ldapNoAttribute[] = LDAP_NO_ATTRS; + +} + +char* noAttributes[] = {ldapNoAttribute, nullptr}; + int Bind(LDAP* ld, const TString& dn, const TString& password) { return ldap_simple_bind_s(ld, dn.c_str(), password.c_str()); } @@ -126,5 +134,9 @@ bool IsRetryableError(int error) { return false; } +char* GetDn(LDAP* ld, LDAPMessage* entry) { + return ldap_get_dn(ld, entry); +} + } diff --git a/ydb/core/security/ldap_auth_provider_win.cpp b/ydb/core/security/ldap_auth_provider_win.cpp index a4b0c95f4c..9715e2ef69 100644 --- a/ydb/core/security/ldap_auth_provider_win.cpp +++ b/ydb/core/security/ldap_auth_provider_win.cpp @@ -11,6 +11,14 @@ namespace NKikimrLdap { +namespace { + +char ldapNoAttribute[] = LDAP_NO_ATTRS; + +} + +char* noAttributes[] = {ldapNoAttribute, nullptr}; + int Bind(LDAP* ld, const TString& dn, const TString& password) { char* bindDn = const_cast<char*>(dn.c_str()); char* bindPassword = const_cast<char*>(password.c_str()); @@ -130,5 +138,9 @@ bool IsRetryableError(int error) { return false; } +char* GetDn(LDAP* ld, LDAPMessage* entry) { + return ldap_get_dn(ld, entry); +} + } diff --git a/ydb/core/security/ldap_compat.h b/ydb/core/security/ldap_compat.h index 07deeeee09..e8e6c54ff8 100644 --- a/ydb/core/security/ldap_compat.h +++ b/ydb/core/security/ldap_compat.h @@ -1,8 +1,14 @@ #pragma once #include <util/generic/string.h> +#ifndef LDAP_NO_ATTRS +#define LDAP_NO_ATTRS "1.1" +#endif + namespace NKikimrLdap { +extern char* noAttributes[]; + enum class EScope : int { BASE, ONE_LEVEL, @@ -21,7 +27,6 @@ int Search(LDAP* ld, LDAPMessage** res); TString LdapError(LDAP* ld); TString ErrorToString(int err); - LDAPMessage* FirstEntry(LDAP* ld, LDAPMessage* chain); char* FirstAttribute(LDAP* ld, LDAPMessage* entry, BerElement** berout); void MemFree(char* p); @@ -29,13 +34,11 @@ void BerFree(BerElement* ber, int freebuf); void MsgFree(LDAPMessage* lm); int CountEntries(LDAP *ld, LDAPMessage *chain); std::vector<TString> GetAllValuesOfAttribute(LDAP* ld, LDAPMessage* entry, char* target); - int SetProtocolVersion(LDAP* ld); ui32 GetPort(); int GetScope(const EScope& scope); bool IsSuccess(int result); - - NKikimr::TEvLdapAuthProvider::EStatus ErrorToStatus(int err); bool IsRetryableError(int error); +char* GetDn(LDAP* ld, LDAPMessage* entry); } diff --git a/ydb/core/security/login_page.cpp b/ydb/core/security/login_page.cpp index ad5290e3a6..d4fa195dd9 100644 --- a/ydb/core/security/login_page.cpp +++ b/ydb/core/security/login_page.cpp @@ -1,4 +1,5 @@ #include "login_page.h" +#include "login_shared_func.h" #include <library/cpp/json/json_value.h> #include <library/cpp/json/json_reader.h> @@ -7,6 +8,7 @@ #include <ydb/core/base/tablet_pipe.h> #include <ydb/core/tx/scheme_cache/scheme_cache.h> #include <ydb/core/tx/schemeshard/schemeshard.h> +#include <ydb/core/security/ldap_auth_provider.h> #include <ydb/library/login/login.h> #include <ydb/library/security/util.h> @@ -44,6 +46,7 @@ public: hFunc(TEvTabletPipe::TEvClientConnected, HandleConnect); hFunc(TEvTxProxySchemeCache::TEvNavigateKeySetResult, HandleNavigate); hFunc(TEvSchemeShard::TEvLoginResult, HandleResult); + hFunc(TEvLdapAuthProvider::TEvAuthenticateResponse, Handle); cFunc(TEvents::TSystem::Wakeup, HandleTimeout); } } @@ -96,27 +99,25 @@ public: Database = "/" + domain.Name; } + TString login; + TString password; NJson::TJsonValue* jsonUser; if (postData.GetValuePointer("user", &jsonUser)) { - User = jsonUser->GetStringRobust(); + login = jsonUser->GetStringRobust(); } else { return ReplyErrorAndPassAway("400 Bad Request", "User must be specified"); } NJson::TJsonValue* jsonPassword; if (postData.GetValuePointer("password", &jsonPassword)) { - Password = jsonPassword->GetStringRobust(); + password = jsonPassword->GetStringRobust(); } else { return ReplyErrorAndPassAway("400 Bad Request", "Password must be specified"); } - auto request = MakeHolder<NSchemeCache::TSchemeCacheNavigate>(); - request->DatabaseName = Database; - auto& entry = request->ResultSet.emplace_back(); - entry.Operation = NSchemeCache::TSchemeCacheNavigate::OpPath; - entry.Path = ::NKikimr::SplitPath(Database); - entry.RedirectRequired = false; - Send(MakeSchemeCacheID(), new TEvTxProxySchemeCache::TEvNavigateKeySet(request.Release())); + AuthCredentials = PrepareCredentials(login, password, AppData()->AuthConfig); + auto sendParameters = GetSendParameters(AuthCredentials, Database); + Send(sendParameters.Recipient, sendParameters.Event.Release()); Become(&TThis::StateWork, Timeout, new TEvents::TEvWakeup()); } @@ -136,8 +137,7 @@ public: IActor* pipe = NTabletPipe::CreateClient(SelfId(), schemeShardTabletId, GetPipeClientConfig()); TActorId pipeClient = RegisterWithSameMailbox(pipe); THolder<TEvSchemeShard::TEvLogin> request = MakeHolder<TEvSchemeShard::TEvLogin>(); - request.Get()->Record.SetUser(User); - request.Get()->Record.SetPassword(Password); + request.Get()->Record = CreateLoginRequest(AuthCredentials, AppData()->AuthConfig); NTabletPipe::SendData(SelfId(), pipeClient, request.Release()); return; } else { @@ -149,6 +149,15 @@ public: } } + void Handle(TEvLdapAuthProvider::TEvAuthenticateResponse::TPtr& ev) { + TEvLdapAuthProvider::TEvAuthenticateResponse* response = ev->Get(); + if (response->Status == TEvLdapAuthProvider::EStatus::SUCCESS) { + Send(MakeSchemeCacheID(), new TEvTxProxySchemeCache::TEvNavigateKeySet(CreateNavigateKeySetRequest(Database).Release())); + } else { + ReplyErrorAndPassAway("403 Forbidden", response->Error.Message); + } + } + void HandleResult(TEvSchemeShard::TEvLoginResult::TPtr& ev) { if (ev->Get()->Record.GetError()) { ReplyErrorAndPassAway("403 Forbidden", ev->Get()->Record.GetError()); @@ -223,8 +232,9 @@ protected: NThreading::TPromise<THttpResponsePtr> Result; TDuration Timeout = TDuration::Seconds(60); TString Database; - TString User; - TString Password; + +private: + TAuthCredentials AuthCredentials; }; class TLoginMonPage: public IMonPage { diff --git a/ydb/core/security/login_shared_func.cpp b/ydb/core/security/login_shared_func.cpp new file mode 100644 index 0000000000..b3a68997f9 --- /dev/null +++ b/ydb/core/security/login_shared_func.cpp @@ -0,0 +1,60 @@ +#include <util/generic/string.h> +#include <ydb/core/tx/scheme_cache/scheme_cache.h> +#include <ydb/core/base/path.h> +#include "login_shared_func.h" +#include "ldap_auth_provider.h" + +namespace NKikimr { + +THolder<NSchemeCache::TSchemeCacheNavigate> CreateNavigateKeySetRequest(const TString& pathToDatabase) { + auto request = MakeHolder<NSchemeCache::TSchemeCacheNavigate>(); + request->DatabaseName = pathToDatabase; + auto& entry = request->ResultSet.emplace_back(); + entry.Operation = NSchemeCache::TSchemeCacheNavigate::OpPath; + entry.Path = ::NKikimr::SplitPath(pathToDatabase); + entry.RedirectRequired = false; + return request; +} + +TAuthCredentials PrepareCredentials(const TString& login, const TString& password, const NKikimrProto::TAuthConfig& config) { + if (config.HasLdapAuthentication() && !config.GetLdapAuthenticationDomain().empty()) { + size_t n = login.find("@" + config.GetLdapAuthenticationDomain()); + if (n != TString::npos) { + return {.AuthType = TAuthCredentials::EAuthType::Ldap, .Login = login.substr(0, n), .Password = password}; + } + } + return {.AuthType = TAuthCredentials::EAuthType::Internal, .Login = login, .Password = password}; +} + +NKikimrScheme::TEvLogin CreateLoginRequest(const TAuthCredentials& credentials, const NKikimrProto::TAuthConfig& config) { + NKikimrScheme::TEvLogin record; + record.SetUser(credentials.Login); + record.SetPassword(credentials.Password); + switch (credentials.AuthType) { + case TAuthCredentials::EAuthType::Ldap: { + record.SetExternalAuth(config.GetLdapAuthenticationDomain()); + break; + } + default: {} + } + return record; +} + +TSendParameters GetSendParameters(const TAuthCredentials& credentials, const TString& pathToDatabase) { + switch (credentials.AuthType) { + case TAuthCredentials::EAuthType::Internal: { + return { + .Recipient = MakeSchemeCacheID(), + .Event = MakeHolder<TEvTxProxySchemeCache::TEvNavigateKeySet>(CreateNavigateKeySetRequest(pathToDatabase).Release()) + }; + } + case TAuthCredentials::EAuthType::Ldap: { + return { + .Recipient = MakeLdapAuthProviderID(), + .Event = MakeHolder<TEvLdapAuthProvider::TEvAuthenticateRequest>(credentials.Login, credentials.Password) + }; + } + } +} + +} // namespace NKikimr diff --git a/ydb/core/security/login_shared_func.h b/ydb/core/security/login_shared_func.h new file mode 100644 index 0000000000..2e4f96ee2a --- /dev/null +++ b/ydb/core/security/login_shared_func.h @@ -0,0 +1,30 @@ +#pragma once + +#include <util/generic/string.h> +#include <util/generic/ptr.h> +#include <ydb/core/tx/scheme_cache/scheme_cache.h> + +namespace NKikimr { + +struct TAuthCredentials { + enum class EAuthType { + Internal, + Ldap + }; + + EAuthType AuthType = EAuthType::Internal; + TString Login; + TString Password; +}; + +struct TSendParameters { + TActorId Recipient; + THolder<IEventBase> Event; +}; + +THolder<NSchemeCache::TSchemeCacheNavigate> CreateNavigateKeySetRequest(const TString& pathToDatabase); +TAuthCredentials PrepareCredentials(const TString& login, const TString& password, const NKikimrProto::TAuthConfig& config); +NKikimrScheme::TEvLogin CreateLoginRequest(const TAuthCredentials& credentials, const NKikimrProto::TAuthConfig& config); +TSendParameters GetSendParameters(const TAuthCredentials& credentials, const TString& pathToDatabase); + +} // namespace NKikimr diff --git a/ydb/core/security/ticket_parser_impl.h b/ydb/core/security/ticket_parser_impl.h index aede519ffe..40b4ce802c 100644 --- a/ydb/core/security/ticket_parser_impl.h +++ b/ydb/core/security/ticket_parser_impl.h @@ -148,7 +148,6 @@ class TTicketParserImpl : public TActorBootstrapped<TDerived> { TPriorityQueue<TTokenRefreshRecord> RefreshQueue; std::unordered_map<TString, NLogin::TLoginProvider> LoginProviders; bool UseLoginProvider = false; - TActorId LdapAuthProvider; TInstant GetExpireTime(TInstant now) const { return now + ExpireTime; @@ -345,8 +344,8 @@ class TTicketParserImpl : public TActorBootstrapped<TDerived> { template <typename TTokenRecord> void SendRequestToLdap(const TString& key, TTokenRecord& record, const TString& user) { - if (LdapAuthProvider) { - Send(LdapAuthProvider, new TEvLdapAuthProvider::TEvEnrichGroupsRequest(key, user)); + if (Config.HasLdapAuthentication()) { + Send(MakeLdapAuthProviderID(), new TEvLdapAuthProvider::TEvEnrichGroupsRequest(key, user)); } else { SetError(key, record, {.Message = "LdapAuthProvider is not initialized", .Retryable = false}); } @@ -1415,9 +1414,6 @@ protected: if (Config.GetUseLoginProvider()) { UseLoginProvider = true; - if (Config.HasLdapAuthentication()) { - LdapAuthProvider = Register(CreateLdapAuthProvider(Config.GetLdapAuthentication()), TMailboxType::Simple, AppData()->IOPoolId); - } } } @@ -1440,9 +1436,6 @@ protected: if (ServiceAccountService) { Send(ServiceAccountService, new TEvents::TEvPoisonPill); } - if (LdapAuthProvider) { - Send(LdapAuthProvider, new TEvents::TEvPoisonPill); - } TBase::PassAway(); } diff --git a/ydb/core/security/ticket_parser_ut.cpp b/ydb/core/security/ticket_parser_ut.cpp index 589e202f87..4524a0bb44 100644 --- a/ydb/core/security/ticket_parser_ut.cpp +++ b/ydb/core/security/ticket_parser_ut.cpp @@ -107,7 +107,6 @@ TAutoPtr<IEventHandle> LdapAuthenticate(TLdapKikimrServer& server, const TString } Y_UNIT_TEST_SUITE(TTicketParserTest) { - Y_UNIT_TEST(LoginGood) { using namespace Tests; TPortManager tp; diff --git a/ydb/core/security/ya.make b/ydb/core/security/ya.make index 809d46c2fc..239abee78c 100644 --- a/ydb/core/security/ya.make +++ b/ydb/core/security/ya.make @@ -3,6 +3,7 @@ LIBRARY() SRCS( login_page.cpp login_page.h + login_shared_func.cpp secure_request.h ticket_parser_impl.h ticket_parser.cpp diff --git a/ydb/core/testlib/test_client.cpp b/ydb/core/testlib/test_client.cpp index cbbd8dabf1..c53a12b2c2 100644 --- a/ydb/core/testlib/test_client.cpp +++ b/ydb/core/testlib/test_client.cpp @@ -43,6 +43,7 @@ #include <ydb/core/cms/console/immediate_controls_configurator.h> #include <ydb/core/formats/clickhouse_block.h> #include <ydb/core/security/ticket_parser.h> +#include <ydb/core/security/ldap_auth_provider.h> #include <ydb/core/base/user_registry.h> #include <ydb/core/health_check/health_check.h> #include <ydb/core/kafka_proxy/kafka_listener.h> @@ -785,6 +786,11 @@ namespace Tests { void TServer::SetupProxies(ui32 nodeIdx) { Runtime->SetTxAllocatorTabletIds({ChangeStateStorage(TxAllocator, Settings->Domain)}); { + if (Settings->AuthConfig.HasLdapAuthentication()) { + IActor* ldapAuthProvider = NKikimr::CreateLdapAuthProvider(Settings->AuthConfig.GetLdapAuthentication()); + TActorId ldapAuthProviderId = Runtime->Register(ldapAuthProvider, nodeIdx); + Runtime->RegisterService(MakeLdapAuthProviderID(), ldapAuthProviderId, nodeIdx); + } IActor* ticketParser = Settings->CreateTicketParser(Settings->AuthConfig); TActorId ticketParserId = Runtime->Register(ticketParser, nodeIdx); Runtime->RegisterService(MakeTicketParserID(), ticketParserId, nodeIdx); diff --git a/ydb/core/tx/schemeshard/schemeshard__login.cpp b/ydb/core/tx/schemeshard/schemeshard__login.cpp index ad8058e380..4915b5d1c8 100644 --- a/ydb/core/tx/schemeshard/schemeshard__login.cpp +++ b/ydb/core/tx/schemeshard/schemeshard__login.cpp @@ -21,7 +21,8 @@ struct TSchemeShard::TTxLogin : TSchemeShard::TRwTxBase { NLogin::TLoginProvider::TLoginUserRequest GetLoginRequest() const { return { .User = Request->Get()->Record.GetUser(), - .Password = Request->Get()->Record.GetPassword() + .Password = Request->Get()->Record.GetPassword(), + .ExternalAuth = (Request->Get()->Record.HasExternalAuth() ? std::make_optional(Request->Get()->Record.GetExternalAuth()) : std::nullopt) }; } diff --git a/ydb/services/ydb/ut/CMakeLists.darwin-x86_64.txt b/ydb/services/ydb/ut/CMakeLists.darwin-x86_64.txt index 6bbaa47ebd..42d951e5d3 100644 --- a/ydb/services/ydb/ut/CMakeLists.darwin-x86_64.txt +++ b/ydb/services/ydb/ut/CMakeLists.darwin-x86_64.txt @@ -31,10 +31,12 @@ target_link_libraries(ydb-services-ydb-ut PUBLIC ydb-core-testlib yql-minikql-dom yql-minikql-jsonpath + testlib-service_mocks-ldap_mock public-lib-experimental public-lib-json_value public-lib-yson_value public-lib-ut_helpers + clicommands cpp-client-draft cpp-client-ydb_coordination cpp-client-ydb_export @@ -67,6 +69,7 @@ target_sources(ydb-services-ydb-ut PRIVATE ${CMAKE_SOURCE_DIR}/ydb/services/ydb/ydb_monitoring_ut.cpp ${CMAKE_SOURCE_DIR}/ydb/services/ydb/cert_gen.cpp ${CMAKE_SOURCE_DIR}/ydb/services/ydb/ydb_query_ut.cpp + ${CMAKE_SOURCE_DIR}/ydb/services/ydb/ydb_ldap_login_ut.cpp ) set_property( TARGET diff --git a/ydb/services/ydb/ut/CMakeLists.linux-aarch64.txt b/ydb/services/ydb/ut/CMakeLists.linux-aarch64.txt index 099eb30f66..c99764e581 100644 --- a/ydb/services/ydb/ut/CMakeLists.linux-aarch64.txt +++ b/ydb/services/ydb/ut/CMakeLists.linux-aarch64.txt @@ -31,10 +31,12 @@ target_link_libraries(ydb-services-ydb-ut PUBLIC ydb-core-testlib yql-minikql-dom yql-minikql-jsonpath + testlib-service_mocks-ldap_mock public-lib-experimental public-lib-json_value public-lib-yson_value public-lib-ut_helpers + clicommands cpp-client-draft cpp-client-ydb_coordination cpp-client-ydb_export @@ -70,6 +72,7 @@ target_sources(ydb-services-ydb-ut PRIVATE ${CMAKE_SOURCE_DIR}/ydb/services/ydb/ydb_monitoring_ut.cpp ${CMAKE_SOURCE_DIR}/ydb/services/ydb/cert_gen.cpp ${CMAKE_SOURCE_DIR}/ydb/services/ydb/ydb_query_ut.cpp + ${CMAKE_SOURCE_DIR}/ydb/services/ydb/ydb_ldap_login_ut.cpp ) set_property( TARGET diff --git a/ydb/services/ydb/ut/CMakeLists.linux-x86_64.txt b/ydb/services/ydb/ut/CMakeLists.linux-x86_64.txt index b3e3fbb5ed..ca7ad45afb 100644 --- a/ydb/services/ydb/ut/CMakeLists.linux-x86_64.txt +++ b/ydb/services/ydb/ut/CMakeLists.linux-x86_64.txt @@ -32,10 +32,12 @@ target_link_libraries(ydb-services-ydb-ut PUBLIC ydb-core-testlib yql-minikql-dom yql-minikql-jsonpath + testlib-service_mocks-ldap_mock public-lib-experimental public-lib-json_value public-lib-yson_value public-lib-ut_helpers + clicommands cpp-client-draft cpp-client-ydb_coordination cpp-client-ydb_export @@ -71,6 +73,7 @@ target_sources(ydb-services-ydb-ut PRIVATE ${CMAKE_SOURCE_DIR}/ydb/services/ydb/ydb_monitoring_ut.cpp ${CMAKE_SOURCE_DIR}/ydb/services/ydb/cert_gen.cpp ${CMAKE_SOURCE_DIR}/ydb/services/ydb/ydb_query_ut.cpp + ${CMAKE_SOURCE_DIR}/ydb/services/ydb/ydb_ldap_login_ut.cpp ) set_property( TARGET diff --git a/ydb/services/ydb/ut/CMakeLists.windows-x86_64.txt b/ydb/services/ydb/ut/CMakeLists.windows-x86_64.txt index 93de926226..88837dc167 100644 --- a/ydb/services/ydb/ut/CMakeLists.windows-x86_64.txt +++ b/ydb/services/ydb/ut/CMakeLists.windows-x86_64.txt @@ -31,10 +31,12 @@ target_link_libraries(ydb-services-ydb-ut PUBLIC ydb-core-testlib yql-minikql-dom yql-minikql-jsonpath + testlib-service_mocks-ldap_mock public-lib-experimental public-lib-json_value public-lib-yson_value public-lib-ut_helpers + clicommands cpp-client-draft cpp-client-ydb_coordination cpp-client-ydb_export @@ -60,6 +62,7 @@ target_sources(ydb-services-ydb-ut PRIVATE ${CMAKE_SOURCE_DIR}/ydb/services/ydb/ydb_monitoring_ut.cpp ${CMAKE_SOURCE_DIR}/ydb/services/ydb/cert_gen.cpp ${CMAKE_SOURCE_DIR}/ydb/services/ydb/ydb_query_ut.cpp + ${CMAKE_SOURCE_DIR}/ydb/services/ydb/ydb_ldap_login_ut.cpp ) set_property( TARGET diff --git a/ydb/services/ydb/ut/ya.make b/ydb/services/ydb/ut/ya.make index 40e44afbb9..41f68a7052 100644 --- a/ydb/services/ydb/ut/ya.make +++ b/ydb/services/ydb/ut/ya.make @@ -29,6 +29,7 @@ SRCS( ydb_monitoring_ut.cpp cert_gen.cpp ydb_query_ut.cpp + ydb_ldap_login_ut.cpp ) PEERDIR( @@ -43,10 +44,12 @@ PEERDIR( ydb/core/testlib ydb/library/yql/minikql/dom ydb/library/yql/minikql/jsonpath + ydb/library/testlib/service_mocks/ldap_mock ydb/public/lib/experimental ydb/public/lib/json_value ydb/public/lib/yson_value ydb/public/lib/ut_helpers + ydb/public/lib/ydb_cli/commands ydb/public/sdk/cpp/client/draft ydb/public/sdk/cpp/client/ydb_coordination ydb/public/sdk/cpp/client/ydb_export diff --git a/ydb/services/ydb/ydb_ldap_login_ut.cpp b/ydb/services/ydb/ydb_ldap_login_ut.cpp new file mode 100644 index 0000000000..91f45f21a0 --- /dev/null +++ b/ydb/services/ydb/ydb_ldap_login_ut.cpp @@ -0,0 +1,295 @@ +#include <library/cpp/testing/unittest/tests_data.h> +#include <library/cpp/testing/unittest/registar.h> + +#include <ydb/public/sdk/cpp/client/ydb_types/credentials/credentials.h> +#include <ydb/public/lib/ydb_cli/commands/ydb_sdk_core_access.h> + +#include <ydb/core/testlib/test_client.h> +#include <ydb/library/testlib/service_mocks/ldap_mock/ldap_simple_server.h> + +#include "ydb_common_ut.h" + +namespace NKikimr { + +using namespace Tests; +using namespace NYdb; + +namespace { +void InitLdapSettings(NKikimrProto::TLdapAuthentication* ldapSettings, ui16 ldapPort) { + ldapSettings->SetHost("localhost"); + ldapSettings->SetPort(ldapPort); + ldapSettings->SetBaseDn("dc=search,dc=yandex,dc=net"); + ldapSettings->SetBindDn("cn=robouser,dc=search,dc=yandex,dc=net"); + ldapSettings->SetBindPassword("robouserPassword"); + ldapSettings->SetSearchFilter("uid=$username"); +} + +void InitLdapSettingsWithInvalidRobotUserLogin(NKikimrProto::TLdapAuthentication* ldapSettings, ui16 ldapPort) { + InitLdapSettings(ldapSettings, ldapPort); + ldapSettings->SetBindDn("cn=invalidRobouser,dc=search,dc=yandex,dc=net"); +} + +void InitLdapSettingsWithInvalidRobotUserPassword(NKikimrProto::TLdapAuthentication* ldapSettings, ui16 ldapPort) { + InitLdapSettings(ldapSettings, ldapPort); + ldapSettings->SetBindPassword("invalidPassword"); +} + +void InitLdapSettingsWithInvalidFilter(NKikimrProto::TLdapAuthentication* ldapSettings, ui16 ldapPort) { + InitLdapSettings(ldapSettings, ldapPort); + ldapSettings->SetSearchFilter("&(uid=$username)()"); +} + +void InitLdapSettingsWithUnavaliableHost(NKikimrProto::TLdapAuthentication* ldapSettings, ui16 ldapPort) { + InitLdapSettings(ldapSettings, ldapPort); + ldapSettings->SetHost("unavaliablehost"); +} + +class TLoginClientConnection { +public: + TLoginClientConnection(std::function<void(NKikimrProto::TLdapAuthentication*, ui16)> initLdapSettings) + : Server(InitAuthSettings(initLdapSettings)) + , Connection(GetDriverConfig(Server.GetPort())) + , Client(Connection) + {} + + ui16 GetLdapPort() const { + return LdapPort; + } + + void Stop() { + Connection.Stop(true); + } + + std::shared_ptr<NYdb::ICoreFacility> GetCoreFacility() { + return Client.GetCoreFacility(); + } + +private: + NKikimrConfig::TAppConfig InitAuthSettings(std::function<void(NKikimrProto::TLdapAuthentication*, ui16)> initLdapSettings) { + TPortManager tp; + LdapPort = tp.GetPort(389); + + NKikimrConfig::TAppConfig appConfig; + auto authConfig = appConfig.MutableAuthConfig(); + + authConfig->SetUseBlackBox(false); + authConfig->SetUseLoginProvider(true); + + initLdapSettings(authConfig->MutableLdapAuthentication(), LdapPort); + return appConfig; + } + + TDriverConfig GetDriverConfig(ui16 grpcPort) { + TDriverConfig config; + config.SetEndpoint("localhost:" + ToString(grpcPort)); + return config; + } + +private: + ui16 LdapPort; + TBasicKikimrWithGrpcAndRootSchema<TKikimrTestSettings> Server; + NYdb::TDriver Connection; + NConsoleClient::TDummyClient Client; +}; + +} // namespace + + +// These tests check to create token for ldap authenticated users +Y_UNIT_TEST_SUITE(TGRpcLdapAuthentication) { + Y_UNIT_TEST(LdapAuthWithValidCredentials) { + TString login = "ldapuser"; + TString password = "ldapUserPassword"; + + LdapMock::TLdapMockResponses responses; + responses.BindResponses.push_back({{{.Login = "cn=robouser,dc=search,dc=yandex,dc=net", .Password = "robouserPassword"}}, {.Status = LdapMock::EStatus::SUCCESS}}); + responses.BindResponses.push_back({{{.Login = "uid=" + login + ",dc=search,dc=yandex,dc=net", .Password = password}}, {.Status = LdapMock::EStatus::SUCCESS}}); + + LdapMock::TSearchRequestInfo fetchUserSearchRequestInfo { + { + .BaseDn = "dc=search,dc=yandex,dc=net", + .Scope = 2, + .DerefAliases = 0, + .Filter = {.Type = LdapMock::EFilterType::LDAP_FILTER_EQUALITY, .Attribute = "uid", .Value = login}, + .Attributes = {"1.1"} + } + }; + + std::vector<LdapMock::TSearchEntry> fetchUserSearchResponseEntries { + { + .Dn = "uid=" + login + ",dc=search,dc=yandex,dc=net" + } + }; + + LdapMock::TSearchResponseInfo fetchUserSearchResponseInfo { + .ResponseEntries = fetchUserSearchResponseEntries, + .ResponseDone = {.Status = LdapMock::EStatus::SUCCESS} + }; + responses.SearchResponses.push_back({fetchUserSearchRequestInfo, fetchUserSearchResponseInfo}); + + + TLoginClientConnection loginConnection(InitLdapSettings); + LdapMock::TLdapSimpleServer ldapServer(loginConnection.GetLdapPort(), responses); + + auto factory = CreateLoginCredentialsProviderFactory({.User = login + "@ldap", .Password = password}); + auto loginProvider = factory->CreateProvider(loginConnection.GetCoreFacility()); + TString token; + UNIT_ASSERT_NO_EXCEPTION(token = loginProvider->GetAuthInfo()); + UNIT_ASSERT(!token.empty()); + + loginConnection.Stop(); + ldapServer.Stop(); + } + + Y_UNIT_TEST(LdapAuthWithInvalidRobouserLogin) { + TString login = "ldapuser"; + TString password = "ldapUserPassword"; + + LdapMock::TLdapMockResponses responses; + responses.BindResponses.push_back({{{.Login = "cn=invalidRobouser,dc=search,dc=yandex,dc=net", .Password = "robouserPassword"}}, {.Status = LdapMock::EStatus::INVALID_CREDENTIALS}}); + + TLoginClientConnection loginConnection(InitLdapSettingsWithInvalidRobotUserLogin); + LdapMock::TLdapSimpleServer ldapServer(loginConnection.GetLdapPort(), responses); + + auto factory = CreateLoginCredentialsProviderFactory({.User = login + "@ldap", .Password = password}); + auto loginProvider = factory->CreateProvider(loginConnection.GetCoreFacility()); + TString expectedErrorMessage = "Could not perform initial LDAP bind for dn cn=invalidRobouser,dc=search,dc=yandex,dc=net on server localhost\nInvalid credentials"; + UNIT_ASSERT_EXCEPTION_CONTAINS(loginProvider->GetAuthInfo(), yexception, expectedErrorMessage); + + loginConnection.Stop(); + ldapServer.Stop(); + } + + Y_UNIT_TEST(LdapAuthWithInvalidRobouserPassword) { + TString login = "ldapuser"; + TString password = "ldapUserPassword"; + + LdapMock::TLdapMockResponses responses; + responses.BindResponses.push_back({{{.Login = "cn=robouser,dc=search,dc=yandex,dc=net", .Password = "invalidPassword"}}, {.Status = LdapMock::EStatus::INVALID_CREDENTIALS}}); + + TLoginClientConnection loginConnection(InitLdapSettingsWithInvalidRobotUserPassword); + LdapMock::TLdapSimpleServer ldapServer(loginConnection.GetLdapPort(), responses); + + auto factory = CreateLoginCredentialsProviderFactory({.User = login + "@ldap", .Password = password}); + auto loginProvider = factory->CreateProvider(loginConnection.GetCoreFacility()); + TString expectedErrorMessage = "Could not perform initial LDAP bind for dn cn=robouser,dc=search,dc=yandex,dc=net on server localhost\nInvalid credentials"; + UNIT_ASSERT_EXCEPTION_CONTAINS(loginProvider->GetAuthInfo(), yexception, expectedErrorMessage); + + loginConnection.Stop(); + ldapServer.Stop(); + } + + Y_UNIT_TEST(LdapAuthWithInvalidSearchFilter) { + TString login = "ldapuser"; + TString password = "ldapUserPassword"; + + LdapMock::TLdapMockResponses responses; + responses.BindResponses.push_back({{{.Login = "cn=robouser,dc=search,dc=yandex,dc=net", .Password = "robouserPassword"}}, {.Status = LdapMock::EStatus::SUCCESS}}); + + TLoginClientConnection loginConnection(InitLdapSettingsWithInvalidFilter); + LdapMock::TLdapSimpleServer ldapServer(loginConnection.GetLdapPort(), responses); + + auto factory = CreateLoginCredentialsProviderFactory({.User = login + "@ldap", .Password = password}); + auto loginProvider = factory->CreateProvider(loginConnection.GetCoreFacility()); + TString expectedErrorMessage = "Could not search for filter &(uid=" + login + ")() on server localhost\nBad search filter"; + UNIT_ASSERT_EXCEPTION_CONTAINS(loginProvider->GetAuthInfo(), yexception, expectedErrorMessage); + + loginConnection.Stop(); + ldapServer.Stop(); + } + + Y_UNIT_TEST(LdapAuthServerIsUnavaliable) { + TString login = "ldapuser"; + TString password = "ldapUserPassword"; + + TLoginClientConnection loginConnection(InitLdapSettingsWithUnavaliableHost); + LdapMock::TLdapMockResponses responses; + LdapMock::TLdapSimpleServer ldapServer(loginConnection.GetLdapPort(), responses); + + auto factory = CreateLoginCredentialsProviderFactory({.User = login + "@ldap", .Password = password}); + auto loginProvider = factory->CreateProvider(loginConnection.GetCoreFacility()); + TString expectedErrorMessage = "Could not perform initial LDAP bind for dn cn=robouser,dc=search,dc=yandex,dc=net on server unavaliablehost\nCan't contact LDAP server"; + UNIT_ASSERT_EXCEPTION_CONTAINS(loginProvider->GetAuthInfo(), yexception, expectedErrorMessage); + + loginConnection.Stop(); + ldapServer.Stop(); + } + + Y_UNIT_TEST(LdapAuthWithInvalidLogin) { + TString nonExistenUser = "nonexistenldapuser"; + TString password = "ldapUserPassword"; + + LdapMock::TLdapMockResponses responses; + responses.BindResponses.push_back({{{.Login = "cn=robouser,dc=search,dc=yandex,dc=net", .Password = "robouserPassword"}}, {.Status = LdapMock::EStatus::SUCCESS}}); + + LdapMock::TSearchRequestInfo fetchUserSearchRequestInfo { + { + .BaseDn = "dc=search,dc=yandex,dc=net", + .Scope = 2, + .DerefAliases = 0, + .Filter = {.Type = LdapMock::EFilterType::LDAP_FILTER_EQUALITY, .Attribute = "uid", .Value = nonExistenUser}, + .Attributes = {"1.1"} + } + }; + + LdapMock::TSearchResponseInfo fetchUserSearchResponseInfo { + .ResponseEntries = {}, // User does not exist. Return empty entries list + .ResponseDone = {.Status = LdapMock::EStatus::SUCCESS} + }; + responses.SearchResponses.push_back({fetchUserSearchRequestInfo, fetchUserSearchResponseInfo}); + + TLoginClientConnection loginConnection(InitLdapSettings); + LdapMock::TLdapSimpleServer ldapServer(loginConnection.GetLdapPort(), responses); + + auto factory = CreateLoginCredentialsProviderFactory({.User = nonExistenUser + "@ldap", .Password = password}); + auto loginProvider = factory->CreateProvider(loginConnection.GetCoreFacility()); + TString expectedErrorMessage = "LDAP user " + nonExistenUser + " does not exist. LDAP search for filter uid=" + nonExistenUser + " on server localhost return no entries"; + UNIT_ASSERT_EXCEPTION_CONTAINS(loginProvider->GetAuthInfo(), yexception, expectedErrorMessage); + + loginConnection.Stop(); + ldapServer.Stop(); + } + + Y_UNIT_TEST(LdapAuthWithInvalidPassword) { + TString login = "ldapUser"; + TString password = "wrongLdapUserPassword"; + + LdapMock::TLdapMockResponses responses; + responses.BindResponses.push_back({{{.Login = "cn=robouser,dc=search,dc=yandex,dc=net", .Password = "robouserPassword"}}, {.Status = LdapMock::EStatus::SUCCESS}}); + responses.BindResponses.push_back({{{.Login = "uid=" + login + ",dc=search,dc=yandex,dc=net", .Password = password}}, {.Status = LdapMock::EStatus::INVALID_CREDENTIALS}}); + + LdapMock::TSearchRequestInfo fetchUserSearchRequestInfo { + { + .BaseDn = "dc=search,dc=yandex,dc=net", + .Scope = 2, + .DerefAliases = 0, + .Filter = {.Type = LdapMock::EFilterType::LDAP_FILTER_EQUALITY, .Attribute = "uid", .Value = login}, + .Attributes = {"1.1"} + } + }; + + std::vector<LdapMock::TSearchEntry> fetchUserSearchResponseEntries { + { + .Dn = "uid=" + login + ",dc=search,dc=yandex,dc=net" + } + }; + + LdapMock::TSearchResponseInfo fetchUserSearchResponseInfo { + .ResponseEntries = fetchUserSearchResponseEntries, + .ResponseDone = {.Status = LdapMock::EStatus::SUCCESS} + }; + responses.SearchResponses.push_back({fetchUserSearchRequestInfo, fetchUserSearchResponseInfo}); + + TLoginClientConnection loginConnection(InitLdapSettings); + LdapMock::TLdapSimpleServer ldapServer(loginConnection.GetLdapPort(), responses); + + auto factory = CreateLoginCredentialsProviderFactory({.User = login + "@ldap", .Password = password}); + auto loginProvider = factory->CreateProvider(loginConnection.GetCoreFacility()); + TString expectedErrorMessage = "LDAP login failed for user uid=" + login + ",dc=search,dc=yandex,dc=net on server localhost\nInvalid credentials"; + UNIT_ASSERT_EXCEPTION_CONTAINS(loginProvider->GetAuthInfo(), yexception, expectedErrorMessage); + + loginConnection.Stop(); + ldapServer.Stop(); + } +} +} //namespace NKikimr |