aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authormolotkov-and <molotkov-and@ydb.tech>2023-08-30 12:26:33 +0300
committermolotkov-and <molotkov-and@ydb.tech>2023-08-30 13:10:29 +0300
commit518a643cc2da2d6313a4bbe2169f9dcaced332f3 (patch)
tree425ebf377cca2457724e179e0332568e8a4263d2
parent7ccc4cc77189900b84644f90037c7490e05ee881 (diff)
downloadydb-518a643cc2da2d6313a4bbe2169f9dcaced332f3.tar.gz
KIKIMR-18218: Authenticate users on Ldap server
-rw-r--r--ydb/core/driver_lib/run/kikimr_services_initializers.cpp8
-rw-r--r--ydb/core/grpc_services/CMakeLists.darwin-x86_64.txt1
-rw-r--r--ydb/core/grpc_services/CMakeLists.linux-aarch64.txt1
-rw-r--r--ydb/core/grpc_services/CMakeLists.linux-x86_64.txt1
-rw-r--r--ydb/core/grpc_services/CMakeLists.windows-x86_64.txt1
-rw-r--r--ydb/core/grpc_services/rpc_login.cpp57
-rw-r--r--ydb/core/grpc_services/ya.make1
-rw-r--r--ydb/core/protos/flat_tx_scheme.proto1
-rw-r--r--ydb/core/security/CMakeLists.darwin-x86_64.txt1
-rw-r--r--ydb/core/security/CMakeLists.linux-aarch64.txt1
-rw-r--r--ydb/core/security/CMakeLists.linux-x86_64.txt1
-rw-r--r--ydb/core/security/CMakeLists.windows-x86_64.txt1
-rw-r--r--ydb/core/security/ldap_auth_provider.cpp76
-rw-r--r--ydb/core/security/ldap_auth_provider.h23
-rw-r--r--ydb/core/security/ldap_auth_provider_linux.cpp12
-rw-r--r--ydb/core/security/ldap_auth_provider_win.cpp12
-rw-r--r--ydb/core/security/ldap_compat.h11
-rw-r--r--ydb/core/security/login_page.cpp36
-rw-r--r--ydb/core/security/login_shared_func.cpp60
-rw-r--r--ydb/core/security/login_shared_func.h30
-rw-r--r--ydb/core/security/ticket_parser_impl.h11
-rw-r--r--ydb/core/security/ticket_parser_ut.cpp1
-rw-r--r--ydb/core/security/ya.make1
-rw-r--r--ydb/core/testlib/test_client.cpp6
-rw-r--r--ydb/core/tx/schemeshard/schemeshard__login.cpp3
-rw-r--r--ydb/services/ydb/ut/CMakeLists.darwin-x86_64.txt3
-rw-r--r--ydb/services/ydb/ut/CMakeLists.linux-aarch64.txt3
-rw-r--r--ydb/services/ydb/ut/CMakeLists.linux-x86_64.txt3
-rw-r--r--ydb/services/ydb/ut/CMakeLists.windows-x86_64.txt3
-rw-r--r--ydb/services/ydb/ut/ya.make3
-rw-r--r--ydb/services/ydb/ydb_ldap_login_ut.cpp295
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