From 8855100772c31619ed2946909b55b16b1cd835e8 Mon Sep 17 00:00:00 2001 From: Andrey Molotkov Date: Fri, 26 Jul 2024 20:28:47 +0300 Subject: [PATCH 1/2] [Ldap] Merge ldap fixes to stable 24-3 (#7126) --- ydb/core/protos/auth.proto | 1 + .../ldap_auth_provider/ldap_auth_provider.cpp | 145 +++- .../ldap_auth_provider_linux.cpp | 4 + .../ldap_auth_provider_ut.cpp | 803 +++++++++++++----- .../ldap_auth_provider_win.cpp | 6 +- .../security/ldap_auth_provider/ldap_compat.h | 1 + .../ldap_auth_provider/ldap_utils.cpp | 70 +- .../security/ldap_auth_provider/ldap_utils.h | 18 + .../ldap_auth_provider/ldap_utils_ut.cpp | 56 +- ydb/core/security/ldap_auth_provider/ya.make | 1 + ydb/core/security/login_page.cpp | 27 +- ydb/core/security/login_shared_func.cpp | 6 +- .../tx/schemeshard/schemeshard__login.cpp | 18 +- .../schemeshard__operation_alter_login.cpp | 5 +- ydb/core/tx/schemeshard/ut_login/ut_login.cpp | 20 + .../service_mocks/ldap_mock/ldap_defines.cpp | 66 +- .../service_mocks/ldap_mock/ldap_defines.h | 12 + .../ldap_mock/ldap_message_processor.cpp | 48 +- .../ldap_mock/ldap_message_processor.h | 2 + ydb/services/ydb/ydb_ldap_login_ut.cpp | 34 +- 20 files changed, 1068 insertions(+), 275 deletions(-) diff --git a/ydb/core/protos/auth.proto b/ydb/core/protos/auth.proto index 6362ddfa33bf..135ddc050df0 100644 --- a/ydb/core/protos/auth.proto +++ b/ydb/core/protos/auth.proto @@ -54,6 +54,7 @@ message TAuthConfig { optional bool UseBuiltinDomain = 78 [default = true]; optional string AccessServiceType = 79 [default = "Yandex_v2"]; // For now the following values are supported: "Yandex_v2", "Nebius_v1" optional string CertificateAuthenticationDomain = 80 [default = "cert"]; + optional bool EnableLoginAuthentication = 81 [default = true]; } message TUserRegistryConfig { diff --git a/ydb/core/security/ldap_auth_provider/ldap_auth_provider.cpp b/ydb/core/security/ldap_auth_provider/ldap_auth_provider.cpp index a3ec33e9adf5..3f206f4a72d3 100644 --- a/ydb/core/security/ldap_auth_provider/ldap_auth_provider.cpp +++ b/ydb/core/security/ldap_auth_provider/ldap_auth_provider.cpp @@ -2,6 +2,8 @@ #include #include #include +#include +#include #include "ldap_auth_provider.h" #include "ldap_utils.h" @@ -69,6 +71,7 @@ class TLdapAuthProvider : public NActors::TActorBootstrapped TLdapAuthProvider(const NKikimrProto::TLdapAuthentication& settings) : Settings(settings) , FilterCreator(Settings) + , UrisCreator(Settings, Settings.GetPort() != 0 ? Settings.GetPort() : NKikimrLdap::GetPort(Settings.GetScheme())) { const TString& requestedGroupAttribute = Settings.GetRequestedGroupAttribute(); RequestedAttributes[0] = const_cast(requestedGroupAttribute.empty() ? "memberOf" : requestedGroupAttribute.c_str()); @@ -135,18 +138,30 @@ class TLdapAuthProvider : public NActors::TActorBootstrapped } LDAPMessage* entry = NKikimrLdap::FirstEntry(ld, searchUserResponse.SearchMessage); BerElement* ber = nullptr; - std::vector groupsDn; + std::vector directUserGroups; char* attribute = NKikimrLdap::FirstAttribute(ld, entry, &ber); if (attribute != nullptr) { - groupsDn = NKikimrLdap::GetAllValuesOfAttribute(ld, entry, attribute); + directUserGroups = NKikimrLdap::GetAllValuesOfAttribute(ld, entry, attribute); NKikimrLdap::MemFree(attribute); } if (ber) { NKikimrLdap::BerFree(ber, 0); } + std::vector allUserGroups; + if (!directUserGroups.empty()) { + // Active Directory has special matching rule to fetch nested groups in one request it is MatchingRuleInChain + // We don`t know what is ldap server. Is it Active Directory or OpenLdap or other server? + // If using MatchingRuleInChain return empty list of groups it means that ldap server isn`t Active Directory + // but it is known that there are groups and we are trying to do tree traversal + allUserGroups = TryToGetGroupsUseMatchingRuleInChain(ld, entry); + if (allUserGroups.empty()) { + allUserGroups = std::move(directUserGroups); + GetNestedGroups(ld, &allUserGroups); + } + } NKikimrLdap::MsgFree(entry); NKikimrLdap::Unbind(ld); - Send(ev->Sender, new TEvLdapAuthProvider::TEvEnrichGroupsResponse(request->Key, request->User, groupsDn)); + Send(ev->Sender, new TEvLdapAuthProvider::TEvEnrichGroupsResponse(request->Key, request->User, allUserGroups)); } TInitAndBindResponse InitAndBind(LDAP** ld, std::function(const TEvLdapAuthProvider::EStatus&, const TEvLdapAuthProvider::TError&)> eventFabric) { @@ -173,7 +188,7 @@ class TLdapAuthProvider : public NActors::TActorBootstrapped 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 " + UrisList + "\n" + .Message = "Could not perform initial LDAP bind for dn " + Settings.GetBindDn() + " on server " + UrisCreator.GetUris() + "\n" + NKikimrLdap::ErrorToString(result), .Retryable = NKikimrLdap::IsRetryableError(result) }; @@ -202,12 +217,10 @@ class TLdapAuthProvider : public NActors::TActorBootstrapped } } - const ui32 port = Settings.GetPort() != 0 ? Settings.GetPort() : NKikimrLdap::GetPort(Settings.GetScheme()); - UrisList = GetUris(port); - result = NKikimrLdap::Init(ld, Settings.GetScheme(), UrisList, port); + result = NKikimrLdap::Init(ld, Settings.GetScheme(), UrisCreator.GetUris(), UrisCreator.GetConfiguredPort()); if (!NKikimrLdap::IsSuccess(result)) { return {{TEvLdapAuthProvider::EStatus::UNAVAILABLE, - {.Message = "Could not initialize LDAP connection for uris: " + UrisList + ". " + NKikimrLdap::LdapError(*ld), + {.Message = "Could not initialize LDAP connection for uris: " + UrisCreator.GetUris() + ". " + NKikimrLdap::LdapError(*ld), .Retryable = false}}}; } @@ -237,14 +250,14 @@ class TLdapAuthProvider : public NActors::TActorBootstrapped 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 " + FilterCreator.GetFilter(request.Login) + " on server " + UrisList + "\n" + {.Message = "Could not get dn for the first entry matching " + FilterCreator.GetFilter(request.Login) + " on server " + UrisCreator.GetUris() + "\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 " + UrisList + "\n" + error.Message = "LDAP login failed for user " + TString(dn) + " on server " + UrisCreator.GetUris() + "\n" + NKikimrLdap::ErrorToString((result)); error.Retryable = NKikimrLdap::IsRetryableError(result); } @@ -266,7 +279,7 @@ class TLdapAuthProvider : public NActors::TActorBootstrapped TSearchUserResponse response; if (!NKikimrLdap::IsSuccess(result)) { response.Status = NKikimrLdap::ErrorToStatus(result); - response.Error = {.Message = "Could not search for filter " + searchFilter + " on server " + UrisList + "\n" + response.Error = {.Message = "Could not search for filter " + searchFilter + " on server " + UrisCreator.GetUris() + "\n" + NKikimrLdap::ErrorToString(result), .Retryable = NKikimrLdap::IsRetryableError(result)}; return response; @@ -275,11 +288,11 @@ class TLdapAuthProvider : public NActors::TActorBootstrapped if (countEntries != 1) { if (countEntries == 0) { response.Error = {.Message = "LDAP user " + request.User + " does not exist. " - "LDAP search for filter " + searchFilter + " on server " + UrisList + " return no entries", + "LDAP search for filter " + searchFilter + " on server " + UrisCreator.GetUris() + " return no entries", .Retryable = false}; } else { response.Error = {.Message = "LDAP user " + request.User + " is not unique. " - "LDAP search for filter " + searchFilter + " on server " + UrisList + " return " + countEntries + " entries", + "LDAP search for filter " + searchFilter + " on server " + UrisCreator.GetUris() + " return " + countEntries + " entries", .Retryable = false}; } response.Status = TEvLdapAuthProvider::EStatus::UNAUTHORIZED; @@ -290,6 +303,79 @@ class TLdapAuthProvider : public NActors::TActorBootstrapped return response; } + std::vector TryToGetGroupsUseMatchingRuleInChain(LDAP* ld, LDAPMessage* entry) const { + static const TString matchingRuleInChain = "1.2.840.113556.1.4.1941"; // Only Active Directory supports + TStringBuilder filter; + filter << "(member:" << matchingRuleInChain << ":=" << NKikimrLdap::GetDn(ld, entry) << ')'; + LDAPMessage* searchMessage = nullptr; + int result = NKikimrLdap::Search(ld, Settings.GetBaseDn(), NKikimrLdap::EScope::SUBTREE, filter, NKikimrLdap::noAttributes, 0, &searchMessage); + if (!NKikimrLdap::IsSuccess(result)) { + return {}; + } + const int countEntries = NKikimrLdap::CountEntries(ld, searchMessage); + if (countEntries == 0) { + NKikimrLdap::MsgFree(searchMessage); + return {}; + } + std::vector groups; + groups.reserve(countEntries); + for (LDAPMessage* groupEntry = NKikimrLdap::FirstEntry(ld, searchMessage); groupEntry != nullptr; groupEntry = NKikimrLdap::NextEntry(ld, groupEntry)) { + groups.push_back(NKikimrLdap::GetDn(ld, groupEntry)); + } + NKikimrLdap::MsgFree(searchMessage); + return groups; + } + + void GetNestedGroups(LDAP* ld, std::vector* groups) { + std::unordered_set viewedGroups(groups->cbegin(), groups->cend()); + std::queue queue; + for (const auto& group : *groups) { + queue.push(group); + } + while (!queue.empty()) { + TStringBuilder filter; + filter << "(|"; + filter << "(entryDn=" << queue.front() << ')'; + queue.pop(); + //should filter string is separated into several batches + while (!queue.empty()) { + // entryDn specific for OpenLdap, may get this value from config + filter << "(entryDn=" << queue.front() << ')'; + queue.pop(); + } + filter << ')'; + LDAPMessage* searchMessage = nullptr; + int result = NKikimrLdap::Search(ld, Settings.GetBaseDn(), NKikimrLdap::EScope::SUBTREE, filter, RequestedAttributes, 0, &searchMessage); + if (!NKikimrLdap::IsSuccess(result)) { + return; + } + if (NKikimrLdap::CountEntries(ld, searchMessage) == 0) { + NKikimrLdap::MsgFree(searchMessage); + return; + } + for (LDAPMessage* groupEntry = NKikimrLdap::FirstEntry(ld, searchMessage); groupEntry != nullptr; groupEntry = NKikimrLdap::NextEntry(ld, groupEntry)) { + BerElement* ber = nullptr; + std::vector foundGroups; + char* attribute = NKikimrLdap::FirstAttribute(ld, groupEntry, &ber); + if (attribute != nullptr) { + foundGroups = NKikimrLdap::GetAllValuesOfAttribute(ld, groupEntry, attribute); + NKikimrLdap::MemFree(attribute); + } + if (ber) { + NKikimrLdap::BerFree(ber, 0); + } + for (const auto& newGroup : foundGroups) { + if (!viewedGroups.contains(newGroup)) { + viewedGroups.insert(newGroup); + queue.push(newGroup); + groups->push_back(newGroup); + } + } + } + NKikimrLdap::MsgFree(searchMessage); + } + } + TInitializeLdapConnectionResponse CheckRequiredSettingsParameters() const { if (Settings.GetHosts().empty() && Settings.GetHost().empty()) { return {TEvLdapAuthProvider::EStatus::UNAVAILABLE, {.Message = "List of ldap server hosts is empty", .Retryable = false}}; @@ -306,42 +392,11 @@ class TLdapAuthProvider : public NActors::TActorBootstrapped return {TEvLdapAuthProvider::EStatus::SUCCESS, {}}; } - TString GetUris(ui32 port) const { - TStringBuilder uris; - if (Settings.HostsSize() > 0) { - for (const auto& host : Settings.GetHosts()) { - uris << CreateUri(host, port) << " "; - } - uris.remove(uris.size() - 1); - } else { - uris << CreateUri(Settings.GetHost(), port); - } - return uris; - } - - TString CreateUri(const TString& endpoint, ui32 port) const { - TStringBuilder uri; - uri << Settings.GetScheme() << "://" << endpoint; - if (!HasEndpointPort(endpoint)) { - uri << ':' << port; - } - return uri; - } - - static bool HasEndpointPort(const TString& endpoint) { - size_t colonPos = endpoint.rfind(':'); - if (colonPos == TString::npos) { - return false; - } - ++colonPos; - return (endpoint.size() - colonPos) > 0; - } - private: const NKikimrProto::TLdapAuthentication Settings; const TSearchFilterCreator FilterCreator; + const TLdapUrisCreator UrisCreator; char* RequestedAttributes[2]; - TString UrisList; }; IActor* CreateLdapAuthProvider(const NKikimrProto::TLdapAuthentication& settings) { diff --git a/ydb/core/security/ldap_auth_provider/ldap_auth_provider_linux.cpp b/ydb/core/security/ldap_auth_provider/ldap_auth_provider_linux.cpp index f7826f019a44..4125182f3d74 100644 --- a/ydb/core/security/ldap_auth_provider/ldap_auth_provider_linux.cpp +++ b/ydb/core/security/ldap_auth_provider/ldap_auth_provider_linux.cpp @@ -77,6 +77,10 @@ LDAPMessage* FirstEntry(LDAP* ld, LDAPMessage* chain) { return ldap_first_entry(ld, chain); } +LDAPMessage* NextEntry(LDAP* ld, LDAPMessage* entry) { + return ldap_next_entry(ld, entry); +} + char* FirstAttribute(LDAP* ld, LDAPMessage* entry, BerElement** berout) { return ldap_first_attribute(ld, entry, berout); } diff --git a/ydb/core/security/ldap_auth_provider/ldap_auth_provider_ut.cpp b/ydb/core/security/ldap_auth_provider/ldap_auth_provider_ut.cpp index 5c5ac98a27ae..3b6f41026f6e 100644 --- a/ydb/core/security/ldap_auth_provider/ldap_auth_provider_ut.cpp +++ b/ydb/core/security/ldap_auth_provider/ldap_auth_provider_ut.cpp @@ -184,23 +184,85 @@ TAutoPtr LdapAuthenticate(TLdapKikimrServer& server, const TString return handle; } +// Scheme of groups +// *-> cn=people,ou=groups,dc=search,dc=yandex,dc=net +// | +// |*-> cn=managers,cn=people,ou=groups,dc=search,dc=yandex,dc=net +// | | +// | |*-> cn=managerOfProject1,cn=managers,cn=people,ou=groups,dc=search,dc=yandex,dc=net +// | | +// | |*-> uid=ldapuser,dc=search,dc=yandex,dc=net +// | +// |*-> cn=developers,cn=people,ou=groups,dc=search,dc=yandex,dc=net +// | +// |*-> cn=project1,cn=developers,cn=people,ou=groups,dc=search,dc=yandex,dc=net +// | +// |*-> uid=ldapuser,dc=search,dc=yandex,dc=net + class TCorrectLdapResponse { public: - static std::vector Groups; + static std::vector DirectGroups; + static std::vector UpdatedDirectGroups; + static std::vector ManagerGroups; + static std::vector DevelopersGroups; + static std::vector PeopleGroups; static LdapMock::TLdapMockResponses GetResponses(const TString& login, const TString& groupAttribute = "memberOf"); + static LdapMock::TLdapMockResponses GetAdResponses(const TString& login, const TString& groupAttribute = "memberOf"); + static LdapMock::TLdapMockResponses GetUpdatedResponses(const TString& login, const TString& groupAttribute = "memberOf"); + static THashSet GetAllGroups(const TString& domain) { + THashSet result; + auto AddGroups = [&result, &domain] (const std::vector& groups) { + std::transform(groups.begin(), groups.end(), std::inserter(result, result.end()), [&domain](const TString& group) { + return TString(group).append(domain); + }); + }; + AddGroups(DirectGroups); + AddGroups(ManagerGroups); + AddGroups(DevelopersGroups); + AddGroups(PeopleGroups); + return result; + } + + static THashSet GetAllUpdatedGroups(const TString& domain) { + THashSet result; + auto AddGroups = [&result, &domain] (const std::vector& groups) { + std::transform(groups.begin(), groups.end(), std::inserter(result, result.end()), [&domain](const TString& group) { + return TString(group).append(domain); + }); + }; + AddGroups(UpdatedDirectGroups); + AddGroups(DevelopersGroups); + AddGroups(PeopleGroups); + return result; + } +}; + +std::vector TCorrectLdapResponse::DirectGroups { + "cn=managerOfProject1,cn=managers,cn=people,ou=groups,dc=search,dc=yandex,dc=net", + "cn=project1,cn=developers,cn=people,ou=groups,dc=search,dc=yandex,dc=net" }; -std::vector TCorrectLdapResponse::Groups { - "ou=groups,dc=search,dc=yandex,dc=net", +std::vector TCorrectLdapResponse::UpdatedDirectGroups { + "cn=project1,cn=developers,cn=people,ou=groups,dc=search,dc=yandex,dc=net" +}; + +std::vector TCorrectLdapResponse::ManagerGroups { + "cn=managers,cn=people,ou=groups,dc=search,dc=yandex,dc=net", +}; + +std::vector TCorrectLdapResponse::DevelopersGroups { + "cn=developers,cn=people,ou=groups,dc=search,dc=yandex,dc=net", +}; + +std::vector TCorrectLdapResponse::PeopleGroups { "cn=people,ou=groups,dc=search,dc=yandex,dc=net", - "cn=developers,ou=groups,dc=search,dc=yandex,dc=net" }; LdapMock::TLdapMockResponses TCorrectLdapResponse::GetResponses(const TString& login, const TString& groupAttribute) { LdapMock::TLdapMockResponses responses; responses.BindResponses.push_back({{{.Login = "cn=robouser,dc=search,dc=yandex,dc=net", .Password = "robouserPassword"}}, {.Status = LdapMock::EStatus::SUCCESS}}); - LdapMock::TSearchRequestInfo fetchGroupsSearchRequestInfo { + LdapMock::TSearchRequestInfo requestDirectedUserGroups { { .BaseDn = "dc=search,dc=yandex,dc=net", .Scope = 2, @@ -210,20 +272,374 @@ LdapMock::TLdapMockResponses TCorrectLdapResponse::GetResponses(const TString& l } }; - std::vector fetchGroupsSearchResponseEntries { + std::vector responseDirectedUserGroupsEntries { { .Dn = "uid=" + login + ",dc=search,dc=yandex,dc=net", .AttributeList = { - {groupAttribute, TCorrectLdapResponse::Groups} + {groupAttribute, TCorrectLdapResponse::DirectGroups} } + }, + }; + + LdapMock::TSearchResponseInfo responseDirectedUserGroups { + .ResponseEntries = responseDirectedUserGroupsEntries, + .ResponseDone = {.Status = LdapMock::EStatus::SUCCESS} + }; + responses.SearchResponses.push_back({requestDirectedUserGroups, responseDirectedUserGroups}); + + std::shared_ptr filterToGetGroupOfManagers = std::make_shared(); + filterToGetGroupOfManagers->Type = LdapMock::EFilterType::LDAP_FILTER_EQUALITY; + filterToGetGroupOfManagers->Attribute = "entryDn"; + filterToGetGroupOfManagers->Value = "cn=managerOfProject1,cn=managers,cn=people,ou=groups,dc=search,dc=yandex,dc=net"; + + std::shared_ptr filterToGetGroupOfDevelopers = std::make_shared(); + filterToGetGroupOfDevelopers->Type = LdapMock::EFilterType::LDAP_FILTER_EQUALITY; + filterToGetGroupOfDevelopers->Attribute = "entryDn"; + filterToGetGroupOfDevelopers->Value = "cn=project1,cn=developers,cn=people,ou=groups,dc=search,dc=yandex,dc=net"; + + std::vector> nestedFiltersToGetGroupsOfManagersAndDevelopers = { + filterToGetGroupOfManagers, + filterToGetGroupOfDevelopers + }; + LdapMock::TSearchRequestInfo requestToGetGroupsOfManagersAndDevelopers { + { + .BaseDn = "dc=search,dc=yandex,dc=net", + .Scope = 2, + .DerefAliases = 0, + .Filter = {.Type = LdapMock::EFilterType::LDAP_FILTER_OR, .NestedFilters = nestedFiltersToGetGroupsOfManagersAndDevelopers}, + .Attributes = {groupAttribute} + } + }; + + std::vector responseEntriesWithGroupsOfManagersAndDevelopers { + { + .Dn = "cn=managerOfProject1,cn=managers,cn=people,ou=groups,dc=search,dc=yandex,dc=net", + .AttributeList = { + {groupAttribute, TCorrectLdapResponse::ManagerGroups} + } + }, + { + .Dn = "cn=project1,cn=developers,cn=people,ou=groups,dc=search,dc=yandex,dc=net", + .AttributeList = { + {groupAttribute, TCorrectLdapResponse::DevelopersGroups} + } + }, + }; + + LdapMock::TSearchResponseInfo responseWithGroupsOfManagersAndDevelopers { + .ResponseEntries = responseEntriesWithGroupsOfManagersAndDevelopers, + .ResponseDone = {.Status = LdapMock::EStatus::SUCCESS} + }; + responses.SearchResponses.push_back({requestToGetGroupsOfManagersAndDevelopers, responseWithGroupsOfManagersAndDevelopers}); + + std::shared_ptr filterToGetGroupPeopleFromManagers = std::make_shared(); + filterToGetGroupPeopleFromManagers->Type = LdapMock::EFilterType::LDAP_FILTER_EQUALITY; + filterToGetGroupPeopleFromManagers->Attribute = "entryDn"; + filterToGetGroupPeopleFromManagers->Value = "cn=managers,cn=people,ou=groups,dc=search,dc=yandex,dc=net"; + + std::shared_ptr filterToGetGroupPeopleFromDevelopers = std::make_shared(); + filterToGetGroupPeopleFromDevelopers->Type = LdapMock::EFilterType::LDAP_FILTER_EQUALITY; + filterToGetGroupPeopleFromDevelopers->Attribute = "entryDn"; + filterToGetGroupPeopleFromDevelopers->Value = "cn=developers,cn=people,ou=groups,dc=search,dc=yandex,dc=net"; + + std::vector> nestedFiltersToGetGroupOfPeople = { + filterToGetGroupPeopleFromManagers, filterToGetGroupPeopleFromDevelopers + }; + LdapMock::TSearchRequestInfo requestToGetGroupOfPeople { + { + .BaseDn = "dc=search,dc=yandex,dc=net", + .Scope = 2, + .DerefAliases = 0, + .Filter = {.Type = LdapMock::EFilterType::LDAP_FILTER_OR, .NestedFilters = nestedFiltersToGetGroupOfPeople}, + .Attributes = {groupAttribute} } }; - LdapMock::TSearchResponseInfo fetchGroupsSearchResponseInfo { - .ResponseEntries = fetchGroupsSearchResponseEntries, + std::vector responseWithGroupOfPeopleEntries { + { + .Dn = "cn=managers,cn=people,ou=groups,dc=search,dc=yandex,dc=net", + .AttributeList = { + {groupAttribute, TCorrectLdapResponse::PeopleGroups} + } + }, + { + .Dn = "cn=developers,cn=people,ou=groups,dc=search,dc=yandex,dc=net", + .AttributeList = { + {groupAttribute, TCorrectLdapResponse::PeopleGroups} + } + }, + }; + + LdapMock::TSearchResponseInfo responseWithGroupOfPeople { + .ResponseEntries = responseWithGroupOfPeopleEntries, .ResponseDone = {.Status = LdapMock::EStatus::SUCCESS} }; - responses.SearchResponses.push_back({fetchGroupsSearchRequestInfo, fetchGroupsSearchResponseInfo}); + responses.SearchResponses.push_back({requestToGetGroupOfPeople, responseWithGroupOfPeople}); + + std::shared_ptr filterToGetParentGroupOfPeople = std::make_shared(); + filterToGetParentGroupOfPeople->Type = LdapMock::EFilterType::LDAP_FILTER_EQUALITY; + filterToGetParentGroupOfPeople->Attribute = "entryDn"; + filterToGetParentGroupOfPeople->Value = "cn=people,ou=groups,dc=search,dc=yandex,dc=net"; + + std::vector> nestedFiltersToGetParentGroupOfPeople = { + filterToGetParentGroupOfPeople + }; + LdapMock::TSearchRequestInfo requestToGetParentGroupOfPeople { + { + .BaseDn = "dc=search,dc=yandex,dc=net", + .Scope = 2, + .DerefAliases = 0, + .Filter = {.Type = LdapMock::EFilterType::LDAP_FILTER_OR, .NestedFilters = nestedFiltersToGetParentGroupOfPeople}, + .Attributes = {groupAttribute} + } + }; + + LdapMock::TSearchResponseInfo responseWithParentGroupOfPeople { + .ResponseEntries = {}, + .ResponseDone = {.Status = LdapMock::EStatus::SUCCESS} + }; + responses.SearchResponses.push_back({requestToGetParentGroupOfPeople, responseWithParentGroupOfPeople}); + + LdapMock::TSearchRequestInfo requestToGetAllNestedGroupsFromAd { + { + .BaseDn = "dc=search,dc=yandex,dc=net", + .Scope = 2, + .DerefAliases = 0, + .Filter = {.Type = LdapMock::EFilterType::LDAP_FILTER_EXT, + .Attribute = "member", + .Value = "uid=ldapuser,dc=search,dc=yandex,dc=net", + .MatchingRule = "1.2.840.113556.1.4.1941", + .DnAttributes = false, + .NestedFilters = {}}, + .Attributes = {"1.1"} + } + }; + + LdapMock::TSearchResponseInfo responseWithAllNestedGroupsFromAd { + .ResponseEntries = {}, // LDAP server is not Active Directory. Return empty entries + .ResponseDone = {.Status = LdapMock::EStatus::SUCCESS} + }; + responses.SearchResponses.push_back({requestToGetAllNestedGroupsFromAd, responseWithAllNestedGroupsFromAd}); + + return responses; +} + +LdapMock::TLdapMockResponses TCorrectLdapResponse::GetUpdatedResponses(const TString& login, const TString& groupAttribute) { + LdapMock::TLdapMockResponses responses; + responses.BindResponses.push_back({{{.Login = "cn=robouser,dc=search,dc=yandex,dc=net", .Password = "robouserPassword"}}, {.Status = LdapMock::EStatus::SUCCESS}}); + + LdapMock::TSearchRequestInfo requestDirectedUserGroups { + { + .BaseDn = "dc=search,dc=yandex,dc=net", + .Scope = 2, + .DerefAliases = 0, + .Filter = {.Type = LdapMock::EFilterType::LDAP_FILTER_EQUALITY, .Attribute = "uid", .Value = login}, + .Attributes = {groupAttribute} + } + }; + + std::vector responseDirectedUserGroupsEntries { + { + .Dn = "uid=" + login + ",dc=search,dc=yandex,dc=net", + .AttributeList = { + {groupAttribute, UpdatedDirectGroups} + } + }, + }; + + LdapMock::TSearchResponseInfo responseDirectedUserGroups { + .ResponseEntries = responseDirectedUserGroupsEntries, + .ResponseDone = {.Status = LdapMock::EStatus::SUCCESS} + }; + responses.SearchResponses.push_back({requestDirectedUserGroups, responseDirectedUserGroups}); + + std::shared_ptr filterToGetGroupOfDevelopers = std::make_shared(); + filterToGetGroupOfDevelopers->Type = LdapMock::EFilterType::LDAP_FILTER_EQUALITY; + filterToGetGroupOfDevelopers->Attribute = "entryDn"; + filterToGetGroupOfDevelopers->Value = "cn=project1,cn=developers,cn=people,ou=groups,dc=search,dc=yandex,dc=net"; + + std::vector> nestedFiltersToGetGroupsOfDevelopers = { + filterToGetGroupOfDevelopers + }; + LdapMock::TSearchRequestInfo requestToGetGroupsOfDevelopers { + { + .BaseDn = "dc=search,dc=yandex,dc=net", + .Scope = 2, + .DerefAliases = 0, + .Filter = {.Type = LdapMock::EFilterType::LDAP_FILTER_OR, .NestedFilters = nestedFiltersToGetGroupsOfDevelopers}, + .Attributes = {groupAttribute} + } + }; + + std::vector responseEntriesWithGroupsOfDevelopers { + { + .Dn = "cn=project1,cn=developers,cn=people,ou=groups,dc=search,dc=yandex,dc=net", + .AttributeList = { + {groupAttribute, TCorrectLdapResponse::DevelopersGroups} + } + }, + }; + + LdapMock::TSearchResponseInfo responseWithGroupsOfDevelopers { + .ResponseEntries = responseEntriesWithGroupsOfDevelopers, + .ResponseDone = {.Status = LdapMock::EStatus::SUCCESS} + }; + responses.SearchResponses.push_back({requestToGetGroupsOfDevelopers, responseWithGroupsOfDevelopers}); + + std::shared_ptr filterToGetGroupPeopleFromDevelopers = std::make_shared(); + filterToGetGroupPeopleFromDevelopers->Type = LdapMock::EFilterType::LDAP_FILTER_EQUALITY; + filterToGetGroupPeopleFromDevelopers->Attribute = "entryDn"; + filterToGetGroupPeopleFromDevelopers->Value = "cn=developers,cn=people,ou=groups,dc=search,dc=yandex,dc=net"; + + std::vector> nestedFiltersToGetGroupOfPeople = { + filterToGetGroupPeopleFromDevelopers + }; + LdapMock::TSearchRequestInfo requestToGetGroupOfPeople { + { + .BaseDn = "dc=search,dc=yandex,dc=net", + .Scope = 2, + .DerefAliases = 0, + .Filter = {.Type = LdapMock::EFilterType::LDAP_FILTER_OR, .NestedFilters = nestedFiltersToGetGroupOfPeople}, + .Attributes = {groupAttribute} + } + }; + + std::vector responseWithGroupOfPeopleEntries { + { + .Dn = "cn=developers,cn=people,ou=groups,dc=search,dc=yandex,dc=net", + .AttributeList = { + {groupAttribute, TCorrectLdapResponse::PeopleGroups} + } + }, + }; + + LdapMock::TSearchResponseInfo responseWithGroupOfPeople { + .ResponseEntries = responseWithGroupOfPeopleEntries, + .ResponseDone = {.Status = LdapMock::EStatus::SUCCESS} + }; + responses.SearchResponses.push_back({requestToGetGroupOfPeople, responseWithGroupOfPeople}); + + std::shared_ptr filterToGetParentGroupOfPeople = std::make_shared(); + filterToGetParentGroupOfPeople->Type = LdapMock::EFilterType::LDAP_FILTER_EQUALITY; + filterToGetParentGroupOfPeople->Attribute = "entryDn"; + filterToGetParentGroupOfPeople->Value = "cn=people,ou=groups,dc=search,dc=yandex,dc=net"; + + std::vector> nestedFiltersToGetParentGroupOfPeople = { + filterToGetParentGroupOfPeople + }; + LdapMock::TSearchRequestInfo requestToGetParentGroupOfPeople { + { + .BaseDn = "dc=search,dc=yandex,dc=net", + .Scope = 2, + .DerefAliases = 0, + .Filter = {.Type = LdapMock::EFilterType::LDAP_FILTER_OR, .NestedFilters = nestedFiltersToGetParentGroupOfPeople}, + .Attributes = {groupAttribute} + } + }; + + LdapMock::TSearchResponseInfo responseWithParentGroupOfPeople { + .ResponseEntries = {}, + .ResponseDone = {.Status = LdapMock::EStatus::SUCCESS} + }; + responses.SearchResponses.push_back({requestToGetParentGroupOfPeople, responseWithParentGroupOfPeople}); + + LdapMock::TSearchRequestInfo requestToGetAllNestedGroupsFromAd { + { + .BaseDn = "dc=search,dc=yandex,dc=net", + .Scope = 2, + .DerefAliases = 0, + .Filter = {.Type = LdapMock::EFilterType::LDAP_FILTER_EXT, + .Attribute = "member", + .Value = "uid=ldapuser,dc=search,dc=yandex,dc=net", + .MatchingRule = "1.2.840.113556.1.4.1941", + .DnAttributes = false, + .NestedFilters = {}}, + .Attributes = {"1.1"} + } + }; + + LdapMock::TSearchResponseInfo responseWithAllNestedGroupsFromAd { + .ResponseEntries = {}, // LDAP server is not Active Directory. Return empty entries + .ResponseDone = {.Status = LdapMock::EStatus::SUCCESS} + }; + responses.SearchResponses.push_back({requestToGetAllNestedGroupsFromAd, responseWithAllNestedGroupsFromAd}); + + return responses; +} + +LdapMock::TLdapMockResponses TCorrectLdapResponse::GetAdResponses(const TString& login, const TString& groupAttribute) { + LdapMock::TLdapMockResponses responses; + responses.BindResponses.push_back({{{.Login = "cn=robouser,dc=search,dc=yandex,dc=net", .Password = "robouserPassword"}}, {.Status = LdapMock::EStatus::SUCCESS}}); + + LdapMock::TSearchRequestInfo requestDirectedUserGroups { + { + .BaseDn = "dc=search,dc=yandex,dc=net", + .Scope = 2, + .DerefAliases = 0, + .Filter = {.Type = LdapMock::EFilterType::LDAP_FILTER_EQUALITY, .Attribute = "uid", .Value = login}, + .Attributes = {groupAttribute} + } + }; + + std::vector responseDirectedUserGroupsEntries { + { + .Dn = "uid=" + login + ",dc=search,dc=yandex,dc=net", + .AttributeList = { + {groupAttribute, TCorrectLdapResponse::DirectGroups} + } + }, + }; + + LdapMock::TSearchResponseInfo responseDirectedUserGroups { + .ResponseEntries = responseDirectedUserGroupsEntries, + .ResponseDone = {.Status = LdapMock::EStatus::SUCCESS} + }; + responses.SearchResponses.push_back({requestDirectedUserGroups, responseDirectedUserGroups}); + + LdapMock::TSearchRequestInfo requestToGetAllNestedGroupsFromAd { + { + .BaseDn = "dc=search,dc=yandex,dc=net", + .Scope = 2, + .DerefAliases = 0, + .Filter = {.Type = LdapMock::EFilterType::LDAP_FILTER_EXT, + .Attribute = "member", + .Value = "uid=ldapuser,dc=search,dc=yandex,dc=net", + .MatchingRule = "1.2.840.113556.1.4.1941", + .DnAttributes = false, + .NestedFilters = {}}, + .Attributes = {"1.1"} + } + }; + + std::vector responseWithAllNestedGroupsFromAdEntries { + { + .Dn = "cn=managerOfProject1,cn=managers,cn=people,ou=groups,dc=search,dc=yandex,dc=net", + .AttributeList = {} + }, + { + .Dn = "cn=project1,cn=developers,cn=people,ou=groups,dc=search,dc=yandex,dc=net", + .AttributeList = {} + }, + { + .Dn = "cn=managers,cn=people,ou=groups,dc=search,dc=yandex,dc=net", + .AttributeList = {} + }, + { + .Dn = "cn=developers,cn=people,ou=groups,dc=search,dc=yandex,dc=net", + .AttributeList = {} + }, + { + .Dn = "cn=people,ou=groups,dc=search,dc=yandex,dc=net", + .AttributeList = {} + }, + }; + + LdapMock::TSearchResponseInfo responseWithAllNestedGroupsFromAd { + .ResponseEntries = responseWithAllNestedGroupsFromAdEntries, + .ResponseDone = {.Status = LdapMock::EStatus::SUCCESS} + }; + responses.SearchResponses.push_back({requestToGetAllNestedGroupsFromAd, responseWithAllNestedGroupsFromAd}); + return responses; } @@ -248,10 +664,7 @@ void CheckRequiredLdapSettings(std::functionToken->GetGroupSIDs(); THashSet groups(fetchedGroups.begin(), fetchedGroups.end()); - THashSet expectedGroups; - std::transform(TCorrectLdapResponse::Groups.begin(), TCorrectLdapResponse::Groups.end(), std::inserter(expectedGroups, expectedGroups.end()), [&ldapDomain](TString& group) { - return group.append(ldapDomain); - }); + THashSet expectedGroups = TCorrectLdapResponse::GetAllGroups(ldapDomain); expectedGroups.insert("all-users@well-known"); UNIT_ASSERT_VALUES_EQUAL(fetchedGroups.size(), expectedGroups.size()); @@ -281,16 +691,31 @@ void LdapFetchGroupsWithDefaultGroupAttributeGood(const ESecurityConnectionType& ldapServer.Stop(); } - Y_UNIT_TEST(LdapFetchGroupsWithDefaultGroupAttributeGood_nonSecure) { - LdapFetchGroupsWithDefaultGroupAttributeGood(ESecurityConnectionType::NON_SECURE); - } + void LdapFetchGroupsFromAdLdapServer(const ESecurityConnectionType& secureType) { + TString login = "ldapuser"; + TString password = "ldapUserPassword"; - Y_UNIT_TEST(LdapFetchGroupsWithDefaultGroupAttributeGood_StartTls) { - LdapFetchGroupsWithDefaultGroupAttributeGood(ESecurityConnectionType::START_TLS); - } + TLdapKikimrServer server(InitLdapSettings, secureType); + LdapMock::TLdapSimpleServer ldapServer(server.GetLdapPort(), TCorrectLdapResponse::GetAdResponses(login), secureType == ESecurityConnectionType::LDAPS_SCHEME); - Y_UNIT_TEST(LdapFetchGroupsWithDefaultGroupAttributeGood_LdapsScheme) { - LdapFetchGroupsWithDefaultGroupAttributeGood(ESecurityConnectionType::LDAPS_SCHEME); + TAutoPtr handle = LdapAuthenticate(server, login, password); + TEvTicketParser::TEvAuthorizeTicketResult* ticketParserResult = handle->Get(); + UNIT_ASSERT_C(ticketParserResult->Error.empty(), ticketParserResult->Error); + UNIT_ASSERT(ticketParserResult->Token != nullptr); + const TString ldapDomain = "@ldap"; + UNIT_ASSERT_VALUES_EQUAL(ticketParserResult->Token->GetUserSID(), login + ldapDomain); + const auto& fetchedGroups = ticketParserResult->Token->GetGroupSIDs(); + THashSet groups(fetchedGroups.begin(), fetchedGroups.end()); + + THashSet expectedGroups = TCorrectLdapResponse::GetAllGroups(ldapDomain); + expectedGroups.insert("all-users@well-known"); + + UNIT_ASSERT_VALUES_EQUAL(fetchedGroups.size(), expectedGroups.size()); + for (const auto& expectedGroup : expectedGroups) { + UNIT_ASSERT_C(groups.contains(expectedGroup), "Can not find " + expectedGroup); + } + + ldapServer.Stop(); } void LdapFetchGroupsWithDefaultGroupAttributeGoodUseListOfHosts(const ESecurityConnectionType& secureType) { @@ -309,10 +734,7 @@ void LdapFetchGroupsWithDefaultGroupAttributeGood(const ESecurityConnectionType& const auto& fetchedGroups = ticketParserResult->Token->GetGroupSIDs(); THashSet groups(fetchedGroups.begin(), fetchedGroups.end()); - THashSet expectedGroups; - std::transform(TCorrectLdapResponse::Groups.begin(), TCorrectLdapResponse::Groups.end(), std::inserter(expectedGroups, expectedGroups.end()), [&ldapDomain](TString& group) { - return group.append(ldapDomain); - }); + THashSet expectedGroups = TCorrectLdapResponse::GetAllGroups(ldapDomain); expectedGroups.insert("all-users@well-known"); UNIT_ASSERT_VALUES_EQUAL(fetchedGroups.size(), expectedGroups.size()); @@ -323,18 +745,6 @@ void LdapFetchGroupsWithDefaultGroupAttributeGood(const ESecurityConnectionType& ldapServer.Stop(); } - Y_UNIT_TEST(LdapFetchGroupsWithDefaultGroupAttributeGoodUseListOfHosts_nonSecure) { - LdapFetchGroupsWithDefaultGroupAttributeGoodUseListOfHosts(ESecurityConnectionType::NON_SECURE); - } - - Y_UNIT_TEST(LdapFetchGroupsWithDefaultGroupAttributeGoodUseListOfHosts_StartTls) { - LdapFetchGroupsWithDefaultGroupAttributeGoodUseListOfHosts(ESecurityConnectionType::START_TLS); - } - - Y_UNIT_TEST(LdapFetchGroupsWithDefaultGroupAttributeGoodUseListOfHosts_LdapsScheme) { - LdapFetchGroupsWithDefaultGroupAttributeGoodUseListOfHosts(ESecurityConnectionType::LDAPS_SCHEME); - } - void LdapFetchGroupsWithCustomGroupAttributeGood(const ESecurityConnectionType& secureType) { TString login = "ldapuser"; TString password = "ldapUserPassword"; @@ -351,10 +761,7 @@ void LdapFetchGroupsWithDefaultGroupAttributeGood(const ESecurityConnectionType& const auto& fetchedGroups = ticketParserResult->Token->GetGroupSIDs(); THashSet groups(fetchedGroups.begin(), fetchedGroups.end()); - THashSet expectedGroups; - std::transform(TCorrectLdapResponse::Groups.begin(), TCorrectLdapResponse::Groups.end(), std::inserter(expectedGroups, expectedGroups.end()), [&ldapDomain](TString& group) { - return group.append(ldapDomain); - }); + THashSet expectedGroups = TCorrectLdapResponse::GetAllGroups(ldapDomain); expectedGroups.insert("all-users@well-known"); UNIT_ASSERT_VALUES_EQUAL(fetchedGroups.size(), expectedGroups.size()); @@ -365,18 +772,6 @@ void LdapFetchGroupsWithDefaultGroupAttributeGood(const ESecurityConnectionType& ldapServer.Stop(); } - Y_UNIT_TEST(LdapFetchGroupsWithCustomGroupAttributeGood_nonSecure) { - LdapFetchGroupsWithCustomGroupAttributeGood(ESecurityConnectionType::NON_SECURE); - } - - Y_UNIT_TEST(LdapFetchGroupsWithCustomGroupAttributeGood_StartTls) { - LdapFetchGroupsWithCustomGroupAttributeGood(ESecurityConnectionType::START_TLS); - } - - Y_UNIT_TEST(LdapFetchGroupsWithCustomGroupAttributeGood_LdapsScheme) { - LdapFetchGroupsWithCustomGroupAttributeGood(ESecurityConnectionType::LDAPS_SCHEME); - } - void LdapFetchGroupsWithDontExistGroupAttribute(const ESecurityConnectionType& secureType) { TString login = "ldapuser"; TString password = "ldapUserPassword"; @@ -424,18 +819,6 @@ void LdapFetchGroupsWithDefaultGroupAttributeGood(const ESecurityConnectionType& ldapServer.Stop(); } - Y_UNIT_TEST(LdapFetchGroupsWithDontExistGroupAttribute_nonSecure) { - LdapFetchGroupsWithDontExistGroupAttribute(ESecurityConnectionType::NON_SECURE); - } - - Y_UNIT_TEST(LdapFetchGroupsWithDontExistGroupAttribute_StartTls) { - LdapFetchGroupsWithDontExistGroupAttribute(ESecurityConnectionType::START_TLS); - } - - Y_UNIT_TEST(LdapFetchGroupsWithDontExistGroupAttribute_LdapsScheme) { - LdapFetchGroupsWithDontExistGroupAttribute(ESecurityConnectionType::LDAPS_SCHEME); - } - void LdapFetchGroupsWithInvalidRobotUserLoginBad(const ESecurityConnectionType& secureType) { TString login = "ldapuser"; TString password = "ldapUserPassword"; @@ -459,18 +842,6 @@ void LdapFetchGroupsWithDefaultGroupAttributeGood(const ESecurityConnectionType& ldapServer.Stop(); } - Y_UNIT_TEST(LdapFetchGroupsWithInvalidRobotUserLoginBad_nonSecure) { - LdapFetchGroupsWithInvalidRobotUserLoginBad(ESecurityConnectionType::NON_SECURE); - } - - Y_UNIT_TEST(LdapFetchGroupsWithInvalidRobotUserLoginBad_StartTls) { - LdapFetchGroupsWithInvalidRobotUserLoginBad(ESecurityConnectionType::START_TLS); - } - - Y_UNIT_TEST(LdapFetchGroupsWithInvalidRobotUserLoginBad_LdapsScheme) { - LdapFetchGroupsWithInvalidRobotUserLoginBad(ESecurityConnectionType::LDAPS_SCHEME); - } - void LdapFetchGroupsWithInvalidRobotUserPasswordBad(const ESecurityConnectionType& secureType) { TString login = "ldapuser"; TString password = "ldapUserPassword"; @@ -494,18 +865,6 @@ void LdapFetchGroupsWithDefaultGroupAttributeGood(const ESecurityConnectionType& ldapServer.Stop(); } - Y_UNIT_TEST(LdapFetchGroupsWithInvalidRobotUserPasswordBad_nonSecure) { - LdapFetchGroupsWithInvalidRobotUserPasswordBad(ESecurityConnectionType::NON_SECURE); - } - - Y_UNIT_TEST(LdapFetchGroupsWithInvalidRobotUserPasswordBad_StartTls) { - LdapFetchGroupsWithInvalidRobotUserPasswordBad(ESecurityConnectionType::START_TLS); - } - - Y_UNIT_TEST(LdapFetchGroupsWithInvalidRobotUserPasswordBad_LdapsScheme) { - LdapFetchGroupsWithInvalidRobotUserPasswordBad(ESecurityConnectionType::LDAPS_SCHEME); - } - void LdapFetchGroupsWithRemovedUserCredentialsBad(const ESecurityConnectionType& secureType) { TString removedUserLogin = "ldapuser"; TString removedUserPassword = "ldapUserPassword"; @@ -544,18 +903,6 @@ void LdapFetchGroupsWithDefaultGroupAttributeGood(const ESecurityConnectionType& ldapServer.Stop(); } - Y_UNIT_TEST(LdapFetchGroupsWithRemovedUserCredentialsBad_nonSecure) { - LdapFetchGroupsWithRemovedUserCredentialsBad(ESecurityConnectionType::NON_SECURE); - } - - Y_UNIT_TEST(LdapFetchGroupsWithRemovedUserCredentialsBad_StartTls) { - LdapFetchGroupsWithRemovedUserCredentialsBad(ESecurityConnectionType::START_TLS); - } - - Y_UNIT_TEST(LdapFetchGroupsWithRemovedUserCredentialsBad_LdapsScheme) { - LdapFetchGroupsWithRemovedUserCredentialsBad(ESecurityConnectionType::LDAPS_SCHEME); - } - void LdapFetchGroupsUseInvalidSearchFilterBad(const ESecurityConnectionType& secureType) { TString login = "ldapuser"; TString password = "ldapUserPassword"; @@ -577,74 +924,14 @@ void LdapFetchGroupsWithDefaultGroupAttributeGood(const ESecurityConnectionType& ldapServer.Stop(); } - Y_UNIT_TEST(LdapFetchGroupsUseInvalidSearchFilterBad_nonSecure) { - LdapFetchGroupsUseInvalidSearchFilterBad(ESecurityConnectionType::NON_SECURE); - } - - Y_UNIT_TEST(LdapFetchGroupsUseInvalidSearchFilterBad_StartTls) { - LdapFetchGroupsUseInvalidSearchFilterBad(ESecurityConnectionType::START_TLS); - } - - Y_UNIT_TEST(LdapFetchGroupsUseInvalidSearchFilterBad_LdapsScheme) { - LdapFetchGroupsUseInvalidSearchFilterBad(ESecurityConnectionType::LDAPS_SCHEME); - } - - Y_UNIT_TEST(LdapServerIsUnavailable) { - CheckRequiredLdapSettings(InitLdapSettingsWithUnavailableHost, "Could not start TLS\nCan't contact LDAP server", ESecurityConnectionType::START_TLS); - } - - Y_UNIT_TEST(LdapRequestWithEmptyHost) { - CheckRequiredLdapSettings(InitLdapSettingsWithEmptyHost, "List of ldap server hosts is empty"); - } - - Y_UNIT_TEST(LdapRequestWithEmptyBaseDn) { - CheckRequiredLdapSettings(InitLdapSettingsWithEmptyBaseDn, "Parameter BaseDn is empty"); - } - - Y_UNIT_TEST(LdapRequestWithEmptyBindDn) { - CheckRequiredLdapSettings(InitLdapSettingsWithEmptyBindDn, "Parameter BindDn is empty"); - } - - Y_UNIT_TEST(LdapRequestWithEmptyBindPassword) { - CheckRequiredLdapSettings(InitLdapSettingsWithEmptyBindPassword, "Parameter BindPassword is empty"); - } - void LdapRefreshGroupsInfoGood(const ESecurityConnectionType& secureType) { TString login = "ldapuser"; TString password = "ldapUserPassword"; auto responses = TCorrectLdapResponse::GetResponses(login); - LdapMock::TLdapMockResponses updatedResponses = responses; - - std::vector newLdapGroups { - "ou=groups,dc=search,dc=yandex,dc=net", - "cn=people,ou=groups,dc=search,dc=yandex,dc=net", - "cn=designers,ou=groups,dc=search,dc=yandex,dc=net" - }; - std::vector newFetchGroupsSearchResponseEntries { - { - .Dn = "uid=" + login + ",dc=search,dc=yandex,dc=net", - .AttributeList = { - {"memberOf", newLdapGroups} - } - } - }; - + LdapMock::TLdapMockResponses updatedResponses = TCorrectLdapResponse::GetUpdatedResponses(login); const TString ldapDomain = "@ldap"; - THashSet newExpectedGroups; - std::transform(newLdapGroups.begin(), newLdapGroups.end(), std::inserter(newExpectedGroups, newExpectedGroups.end()), [&ldapDomain](TString& group) { - return group.append(ldapDomain); - }); - newExpectedGroups.insert("all-users@well-known"); - - LdapMock::TSearchResponseInfo newFetchGroupsSearchResponseInfo { - .ResponseEntries = newFetchGroupsSearchResponseEntries, - .ResponseDone = {.Status = LdapMock::EStatus::SUCCESS} - }; - - auto& searchResponse = updatedResponses.SearchResponses.front(); - searchResponse.second = newFetchGroupsSearchResponseInfo; TLdapKikimrServer server(InitLdapSettings, secureType); LdapMock::TLdapSimpleServer ldapServer(server.GetLdapPort(), {responses, updatedResponses}, secureType == ESecurityConnectionType::LDAPS_SCHEME); @@ -662,10 +949,7 @@ void LdapFetchGroupsWithDefaultGroupAttributeGood(const ESecurityConnectionType& const auto& fetchedGroups = ticketParserResult->Token->GetGroupSIDs(); THashSet groups(fetchedGroups.begin(), fetchedGroups.end()); - THashSet expectedGroups; - std::transform(TCorrectLdapResponse::Groups.begin(), TCorrectLdapResponse::Groups.end(), std::inserter(expectedGroups, expectedGroups.end()), [&ldapDomain](TString& group) { - return group.append(ldapDomain); - }); + THashSet expectedGroups = TCorrectLdapResponse::GetAllGroups(ldapDomain); expectedGroups.insert("all-users@well-known"); UNIT_ASSERT_VALUES_EQUAL(fetchedGroups.size(), expectedGroups.size()); @@ -684,6 +968,10 @@ void LdapFetchGroupsWithDefaultGroupAttributeGood(const ESecurityConnectionType& UNIT_ASSERT_VALUES_EQUAL(ticketParserResult->Token->GetUserSID(), login + "@ldap"); const auto& newFetchedGroups = ticketParserResult->Token->GetGroupSIDs(); THashSet newGroups(newFetchedGroups.begin(), newFetchedGroups.end()); + + THashSet newExpectedGroups = TCorrectLdapResponse::GetAllUpdatedGroups(ldapDomain); + newExpectedGroups.insert("all-users@well-known"); + UNIT_ASSERT_VALUES_EQUAL(newFetchedGroups.size(), newExpectedGroups.size()); for (const auto& expectedGroup : newExpectedGroups) { UNIT_ASSERT_C(newGroups.contains(expectedGroup), "Can not find " + expectedGroup); @@ -692,18 +980,6 @@ void LdapFetchGroupsWithDefaultGroupAttributeGood(const ESecurityConnectionType& ldapServer.Stop(); } - Y_UNIT_TEST(LdapRefreshGroupsInfoGood_nonSecure) { - LdapRefreshGroupsInfoGood(ESecurityConnectionType::NON_SECURE); - } - - Y_UNIT_TEST(LdapRefreshGroupsInfoGood_StartTls) { - LdapRefreshGroupsInfoGood(ESecurityConnectionType::START_TLS); - } - - Y_UNIT_TEST(LdapRefreshGroupsInfoGood_LdapsScheme) { - LdapRefreshGroupsInfoGood(ESecurityConnectionType::LDAPS_SCHEME); - } - void LdapRefreshRemoveUserBad(const ESecurityConnectionType& secureType) { TString login = "ldapuser"; TString password = "ldapUserPassword"; @@ -734,10 +1010,7 @@ void LdapFetchGroupsWithDefaultGroupAttributeGood(const ESecurityConnectionType& const auto& fetchedGroups = ticketParserResult->Token->GetGroupSIDs(); THashSet groups(fetchedGroups.begin(), fetchedGroups.end()); - THashSet expectedGroups; - std::transform(TCorrectLdapResponse::Groups.begin(), TCorrectLdapResponse::Groups.end(), std::inserter(expectedGroups, expectedGroups.end()), [&ldapDomain](TString& group) { - return group.append(ldapDomain); - }); + THashSet expectedGroups = TCorrectLdapResponse::GetAllGroups(ldapDomain); expectedGroups.insert("all-users@well-known"); UNIT_ASSERT_VALUES_EQUAL(fetchedGroups.size(), expectedGroups.size()); @@ -763,18 +1036,164 @@ void LdapFetchGroupsWithDefaultGroupAttributeGood(const ESecurityConnectionType& ldapServer.Stop(); } - Y_UNIT_TEST(LdapRefreshRemoveUserBad_nonSecure) { - LdapRefreshRemoveUserBad(ESecurityConnectionType::NON_SECURE); +Y_UNIT_TEST_SUITE(LdapAuthProviderTest) { + Y_UNIT_TEST(LdapServerIsUnavailable) { + CheckRequiredLdapSettings(InitLdapSettingsWithUnavailableHost, "Could not start TLS\nCan't contact LDAP server", ESecurityConnectionType::START_TLS); } - Y_UNIT_TEST(LdapRefreshRemoveUserBad_StartTls) { - LdapRefreshRemoveUserBad(ESecurityConnectionType::START_TLS); + Y_UNIT_TEST(LdapRequestWithEmptyHost) { + CheckRequiredLdapSettings(InitLdapSettingsWithEmptyHost, "List of ldap server hosts is empty"); + } + + Y_UNIT_TEST(LdapRequestWithEmptyBaseDn) { + CheckRequiredLdapSettings(InitLdapSettingsWithEmptyBaseDn, "Parameter BaseDn is empty"); + } + + Y_UNIT_TEST(LdapRequestWithEmptyBindDn) { + CheckRequiredLdapSettings(InitLdapSettingsWithEmptyBindDn, "Parameter BindDn is empty"); } - Y_UNIT_TEST(LdapRefreshRemoveUserBad_LdapsScheme) { + Y_UNIT_TEST(LdapRequestWithEmptyBindPassword) { + CheckRequiredLdapSettings(InitLdapSettingsWithEmptyBindPassword, "Parameter BindPassword is empty"); + } +} + +Y_UNIT_TEST_SUITE(LdapAuthProviderTest_LdapsScheme) { + Y_UNIT_TEST(LdapFetchGroupsFromAdLdapServer) { + LdapFetchGroupsFromAdLdapServer(ESecurityConnectionType::LDAPS_SCHEME); + } + + Y_UNIT_TEST(LdapFetchGroupsWithDefaultGroupAttributeGood) { + LdapFetchGroupsWithDefaultGroupAttributeGood(ESecurityConnectionType::LDAPS_SCHEME); + } + + Y_UNIT_TEST(LdapFetchGroupsWithDefaultGroupAttributeGoodUseListOfHosts) { + LdapFetchGroupsWithDefaultGroupAttributeGoodUseListOfHosts(ESecurityConnectionType::LDAPS_SCHEME); + } + + Y_UNIT_TEST(LdapFetchGroupsWithCustomGroupAttributeGood) { + LdapFetchGroupsWithCustomGroupAttributeGood(ESecurityConnectionType::LDAPS_SCHEME); + } + + Y_UNIT_TEST(LdapFetchGroupsWithDontExistGroupAttribute) { + LdapFetchGroupsWithDontExistGroupAttribute(ESecurityConnectionType::LDAPS_SCHEME); + } + + Y_UNIT_TEST(LdapFetchGroupsWithInvalidRobotUserLoginBad) { + LdapFetchGroupsWithInvalidRobotUserLoginBad(ESecurityConnectionType::LDAPS_SCHEME); + } + + Y_UNIT_TEST(LdapFetchGroupsWithInvalidRobotUserPasswordBad) { + LdapFetchGroupsWithInvalidRobotUserPasswordBad(ESecurityConnectionType::LDAPS_SCHEME); + } + + Y_UNIT_TEST(LdapFetchGroupsWithRemovedUserCredentialsBad) { + LdapFetchGroupsWithRemovedUserCredentialsBad(ESecurityConnectionType::LDAPS_SCHEME); + } + + Y_UNIT_TEST(LdapFetchGroupsUseInvalidSearchFilterBad) { + LdapFetchGroupsUseInvalidSearchFilterBad(ESecurityConnectionType::LDAPS_SCHEME); + } + + Y_UNIT_TEST(LdapRefreshGroupsInfoGood) { + LdapRefreshGroupsInfoGood(ESecurityConnectionType::LDAPS_SCHEME); + } + + Y_UNIT_TEST(LdapRefreshRemoveUserBad) { LdapRefreshRemoveUserBad(ESecurityConnectionType::LDAPS_SCHEME); } +} + +Y_UNIT_TEST_SUITE(LdapAuthProviderTest_StartTls) { + Y_UNIT_TEST(LdapFetchGroupsFromAdLdapServer) { + LdapFetchGroupsFromAdLdapServer(ESecurityConnectionType::START_TLS); + } + + Y_UNIT_TEST(LdapFetchGroupsWithDefaultGroupAttributeGood) { + LdapFetchGroupsWithDefaultGroupAttributeGood(ESecurityConnectionType::START_TLS); + } + + Y_UNIT_TEST(LdapFetchGroupsWithDefaultGroupAttributeGoodUseListOfHosts) { + LdapFetchGroupsWithDefaultGroupAttributeGoodUseListOfHosts(ESecurityConnectionType::START_TLS); + } + + Y_UNIT_TEST(LdapFetchGroupsWithCustomGroupAttributeGood) { + LdapFetchGroupsWithCustomGroupAttributeGood(ESecurityConnectionType::START_TLS); + } + Y_UNIT_TEST(LdapFetchGroupsWithDontExistGroupAttribute) { + LdapFetchGroupsWithDontExistGroupAttribute(ESecurityConnectionType::START_TLS); + } + + Y_UNIT_TEST(LdapFetchGroupsWithInvalidRobotUserLoginBad) { + LdapFetchGroupsWithInvalidRobotUserLoginBad(ESecurityConnectionType::START_TLS); + } + + Y_UNIT_TEST(LdapFetchGroupsWithInvalidRobotUserPasswordBad) { + LdapFetchGroupsWithInvalidRobotUserPasswordBad(ESecurityConnectionType::START_TLS); + } + + Y_UNIT_TEST(LdapFetchGroupsWithRemovedUserCredentialsBad) { + LdapFetchGroupsWithRemovedUserCredentialsBad(ESecurityConnectionType::START_TLS); + } + + Y_UNIT_TEST(LdapFetchGroupsUseInvalidSearchFilterBad) { + LdapFetchGroupsUseInvalidSearchFilterBad(ESecurityConnectionType::START_TLS); + } + + Y_UNIT_TEST(LdapRefreshGroupsInfoGood) { + LdapRefreshGroupsInfoGood(ESecurityConnectionType::START_TLS); + } + + Y_UNIT_TEST(LdapRefreshRemoveUserBad) { + LdapRefreshRemoveUserBad(ESecurityConnectionType::START_TLS); + } +} + +Y_UNIT_TEST_SUITE(LdapAuthProviderTest_nonSecure) { + Y_UNIT_TEST(LdapFetchGroupsFromAdLdapServer) { + LdapFetchGroupsFromAdLdapServer(ESecurityConnectionType::NON_SECURE); + } + + Y_UNIT_TEST(LdapFetchGroupsWithDefaultGroupAttributeGood) { + LdapFetchGroupsWithDefaultGroupAttributeGood(ESecurityConnectionType::NON_SECURE); + } + + Y_UNIT_TEST(LdapFetchGroupsWithDefaultGroupAttributeGoodUseListOfHosts) { + LdapFetchGroupsWithDefaultGroupAttributeGoodUseListOfHosts(ESecurityConnectionType::NON_SECURE); + } + + Y_UNIT_TEST(LdapFetchGroupsWithCustomGroupAttributeGood) { + LdapFetchGroupsWithCustomGroupAttributeGood(ESecurityConnectionType::NON_SECURE); + } + + Y_UNIT_TEST(LdapFetchGroupsWithDontExistGroupAttribute) { + LdapFetchGroupsWithDontExistGroupAttribute(ESecurityConnectionType::NON_SECURE); + } + + Y_UNIT_TEST(LdapFetchGroupsWithInvalidRobotUserLoginBad) { + LdapFetchGroupsWithInvalidRobotUserLoginBad(ESecurityConnectionType::NON_SECURE); + } + + Y_UNIT_TEST(LdapFetchGroupsWithInvalidRobotUserPasswordBad) { + LdapFetchGroupsWithInvalidRobotUserPasswordBad(ESecurityConnectionType::NON_SECURE); + } + + Y_UNIT_TEST(LdapFetchGroupsWithRemovedUserCredentialsBad) { + LdapFetchGroupsWithRemovedUserCredentialsBad(ESecurityConnectionType::NON_SECURE); + } + + Y_UNIT_TEST(LdapFetchGroupsUseInvalidSearchFilterBad) { + LdapFetchGroupsUseInvalidSearchFilterBad(ESecurityConnectionType::NON_SECURE); + } + + Y_UNIT_TEST(LdapRefreshGroupsInfoGood) { + LdapRefreshGroupsInfoGood(ESecurityConnectionType::NON_SECURE); + } + + Y_UNIT_TEST(LdapRefreshRemoveUserBad) { + LdapRefreshRemoveUserBad(ESecurityConnectionType::NON_SECURE); + } } } // NKikimr diff --git a/ydb/core/security/ldap_auth_provider/ldap_auth_provider_win.cpp b/ydb/core/security/ldap_auth_provider/ldap_auth_provider_win.cpp index 399a1f2916cd..d1fc38a449ce 100644 --- a/ydb/core/security/ldap_auth_provider/ldap_auth_provider_win.cpp +++ b/ydb/core/security/ldap_auth_provider/ldap_auth_provider_win.cpp @@ -1,7 +1,7 @@ #include #include #include -#include "ticket_parser_log.h" +#include #include "ldap_auth_provider.h" #include @@ -66,6 +66,10 @@ LDAPMessage* FirstEntry(LDAP* ld, LDAPMessage* chain) { return ldap_first_entry(ld, chain); } +LDAPMessage* NextEntry(LDAP* ld, LDAPMessage* entry) { + return ldap_next_entry(ld, entry); +} + char* FirstAttribute(LDAP* ld, LDAPMessage* entry, BerElement** berout) { return ldap_first_attribute(ld, entry, berout); } diff --git a/ydb/core/security/ldap_auth_provider/ldap_compat.h b/ydb/core/security/ldap_auth_provider/ldap_compat.h index 15aee21e9c7f..48e04441f082 100644 --- a/ydb/core/security/ldap_auth_provider/ldap_compat.h +++ b/ydb/core/security/ldap_auth_provider/ldap_compat.h @@ -37,6 +37,7 @@ int Search(LDAP* ld, TString LdapError(LDAP* ld); TString ErrorToString(int err); LDAPMessage* FirstEntry(LDAP* ld, LDAPMessage* chain); +LDAPMessage* NextEntry(LDAP* ld, LDAPMessage* entry); char* FirstAttribute(LDAP* ld, LDAPMessage* entry, BerElement** berout); void MemFree(char* p); void BerFree(BerElement* ber, int freebuf); diff --git a/ydb/core/security/ldap_auth_provider/ldap_utils.cpp b/ydb/core/security/ldap_auth_provider/ldap_utils.cpp index 3087cbaf137a..355c77aba9f7 100644 --- a/ydb/core/security/ldap_auth_provider/ldap_utils.cpp +++ b/ydb/core/security/ldap_auth_provider/ldap_utils.cpp @@ -1,11 +1,13 @@ #include +#include +#include #include "ldap_utils.h" namespace NKikimr { TSearchFilterCreator::TSearchFilterCreator(const NKikimrProto::TLdapAuthentication& settings) : Settings(settings) - {} +{} TString TSearchFilterCreator::GetFilter(const TString& userName) const { if (!Settings.GetSearchFilter().empty()) { @@ -34,4 +36,70 @@ TString TSearchFilterCreator::GetFormatSearchFilter(const TString& userName) con return result.Str(); } +TLdapUrisCreator::TLdapUrisCreator(const NKikimrProto::TLdapAuthentication& settings, ui32 configuredPort) + : Settings(settings) + , Scheme(Settings.GetScheme() == "ldaps" ? Settings.GetScheme() : "ldap") + , ConfiguredPort(configuredPort) +{} + +TString TLdapUrisCreator::GetUris() const { + if (Uris.empty()) { + Uris = CreateUrisList(); + } + return Uris; +} + +ui32 TLdapUrisCreator::GetConfiguredPort() const { + return ConfiguredPort; +} + +TString TLdapUrisCreator::CreateUrisList() const { + TStringBuilder uris; + if (Settings.HostsSize() > 0) { + for (const auto& host : Settings.GetHosts()) { + uris << CreateUri(host) << " "; + } + uris.remove(uris.size() - 1); + } else { + uris << CreateUri(Settings.GetHost()); + } + return uris; +} + +TString TLdapUrisCreator::CreateUri(const TString& address) const { + TString hostname; + ui32 port = 0; + size_t first_colon_pos = address.find(':'); + if (first_colon_pos != TString::npos) { + size_t last_colon_pos = address.rfind(':'); + if (last_colon_pos == first_colon_pos) { + // only one colon, simple case + try { + port = FromString(address.substr(first_colon_pos + 1)); + } catch (TFromStringException& ex) { + port = 0; + } + hostname = address.substr(0, first_colon_pos); + } else { + // ipv6? + size_t closing_bracket_pos = address.rfind(']'); + if (closing_bracket_pos == TString::npos || closing_bracket_pos > last_colon_pos) { + // whole address is ipv6 host + hostname = address; + } else { + try { + port = FromString(address.substr(last_colon_pos + 1)); + } catch (TFromStringException& ex) { + port = 0; + } + hostname = address.substr(0, last_colon_pos); + } + } + } else { + hostname = address; + } + port = (port != 0) ? port : ConfiguredPort; + return TStringBuilder() << Scheme << "://" << hostname << ':' << port; +} + } // namespace NKikimr diff --git a/ydb/core/security/ldap_auth_provider/ldap_utils.h b/ydb/core/security/ldap_auth_provider/ldap_utils.h index 62fd188d825e..5cfb3f957251 100644 --- a/ydb/core/security/ldap_auth_provider/ldap_utils.h +++ b/ydb/core/security/ldap_auth_provider/ldap_utils.h @@ -16,4 +16,22 @@ class TSearchFilterCreator { const NKikimrProto::TLdapAuthentication& Settings; }; +class TLdapUrisCreator { +public: + TLdapUrisCreator(const NKikimrProto::TLdapAuthentication& settings, ui32 configuredPort); + + TString GetUris() const; + ui32 GetConfiguredPort() const; + +private: + TString CreateUrisList() const; + TString CreateUri(const TString& address) const; + +private: + const NKikimrProto::TLdapAuthentication& Settings; + const TString Scheme; + const ui32 ConfiguredPort; + mutable TString Uris; +}; + } // namespace NKikimr diff --git a/ydb/core/security/ldap_auth_provider/ldap_utils_ut.cpp b/ydb/core/security/ldap_auth_provider/ldap_utils_ut.cpp index 26a0cb5a5caf..c615e18ab164 100644 --- a/ydb/core/security/ldap_auth_provider/ldap_utils_ut.cpp +++ b/ydb/core/security/ldap_auth_provider/ldap_utils_ut.cpp @@ -3,7 +3,7 @@ namespace NKikimr { -Y_UNIT_TEST_SUITE(TLdapUtilsTest) { +Y_UNIT_TEST_SUITE(TLdapUtilsSearchFilterCreatorTest) { Y_UNIT_TEST(GetDefaultFilter) { NKikimrProto::TLdapAuthentication settings; TSearchFilterCreator filterCreator(settings); @@ -62,4 +62,58 @@ Y_UNIT_TEST_SUITE(TLdapUtilsTest) { } } +Y_UNIT_TEST_SUITE(TLdapUtilsUrisCreatorTest) { + Y_UNIT_TEST(CreateUrisFromHostnames) { + NKikimrProto::TLdapAuthentication settings; + *settings.AddHosts() = "test.hostname-001"; + *settings.AddHosts() = "test.hostname-002:1234"; + *settings.AddHosts() = "test.hostname-003:"; + + TLdapUrisCreator urisCreator(settings, 389); + UNIT_ASSERT_VALUES_EQUAL("ldap://test.hostname-001:389 ldap://test.hostname-002:1234 ldap://test.hostname-003:389", urisCreator.GetUris()); + } + + Y_UNIT_TEST(CreateUrisFromIpV4List) { + NKikimrProto::TLdapAuthentication settings; + *settings.AddHosts() = "192.168.0.1"; + *settings.AddHosts() = "192.168.0.2:1234"; + *settings.AddHosts() = "192.168.0.3:"; + + TLdapUrisCreator urisCreator(settings, 389); + UNIT_ASSERT_VALUES_EQUAL("ldap://192.168.0.1:389 ldap://192.168.0.2:1234 ldap://192.168.0.3:389", urisCreator.GetUris()); + } + + Y_UNIT_TEST(CreateUrisFromIpV6List) { + NKikimrProto::TLdapAuthentication settings; + *settings.AddHosts() = "[2a02:6b8:bf00::]"; + *settings.AddHosts() = "[2a02:6b8:bf01::]:1234"; + *settings.AddHosts() = "[2a02:6b8:bf02::]:"; + + TLdapUrisCreator urisCreator(settings, 389); + UNIT_ASSERT_VALUES_EQUAL("ldap://[2a02:6b8:bf00::]:389 ldap://[2a02:6b8:bf01::]:1234 ldap://[2a02:6b8:bf02::]:389", urisCreator.GetUris()); + } + + Y_UNIT_TEST(CreateUrisFromHostnamesLdapsScheme) { + NKikimrProto::TLdapAuthentication settings; + *settings.AddHosts() = "test.hostname-001"; + *settings.AddHosts() = "test.hostname-002:1234"; + *settings.AddHosts() = "test.hostname-003:"; + settings.SetScheme("ldaps"); + + TLdapUrisCreator urisCreator(settings, 389); + UNIT_ASSERT_VALUES_EQUAL("ldaps://test.hostname-001:389 ldaps://test.hostname-002:1234 ldaps://test.hostname-003:389", urisCreator.GetUris()); + } + + Y_UNIT_TEST(CreateUrisFromHostnamesUnknownScheme) { + NKikimrProto::TLdapAuthentication settings; + *settings.AddHosts() = "test.hostname-001"; + *settings.AddHosts() = "test.hostname-002:1234"; + *settings.AddHosts() = "test.hostname-003:"; + settings.SetScheme("http"); + + TLdapUrisCreator urisCreator(settings, 389); + UNIT_ASSERT_VALUES_EQUAL("ldap://test.hostname-001:389 ldap://test.hostname-002:1234 ldap://test.hostname-003:389", urisCreator.GetUris()); + } +} + } // namespace NKikimr diff --git a/ydb/core/security/ldap_auth_provider/ya.make b/ydb/core/security/ldap_auth_provider/ya.make index abdc3f210725..a0c9671eda1e 100644 --- a/ydb/core/security/ldap_auth_provider/ya.make +++ b/ydb/core/security/ldap_auth_provider/ya.make @@ -25,6 +25,7 @@ ENDIF() PEERDIR( ydb/core/base ydb/core/protos + ydb/core/util ) END() diff --git a/ydb/core/security/login_page.cpp b/ydb/core/security/login_page.cpp index cb6db091f421..e6fc7dc4e7db 100644 --- a/ydb/core/security/login_page.cpp +++ b/ydb/core/security/login_page.cpp @@ -96,17 +96,7 @@ class TLoginRequest : public NActors::TActorBootstrapped { ALOG_DEBUG(NActorsServices::HTTP, "Login: Requesting LDAP provider for user " << AuthCredentials.Login); Send(MakeLdapAuthProviderID(), new TEvLdapAuthProvider::TEvAuthenticateRequest(AuthCredentials.Login, AuthCredentials.Password)); } else { - auto *domain = AppData()->DomainsInfo->GetDomain(); - TString rootDatabase = "/" + domain->Name; - ui64 rootSchemeShardTabletId = domain->SchemeRoot; - if (!Database.empty() && Database != rootDatabase) { - Database = rootDatabase; - ALOG_DEBUG(NActorsServices::HTTP, "Login: Requesting schemecache for database " << Database); - Send(MakeSchemeCacheID(), new TEvTxProxySchemeCache::TEvNavigateKeySet(CreateNavigateKeySetRequest(Database).Release())); - } else { - Database = rootDatabase; - RequestSchemeShard(rootSchemeShardTabletId); - } + RequestLoginProvider(); } Become(&TThis::StateWork, Timeout, new TEvents::TEvWakeup()); } @@ -146,10 +136,23 @@ class TLoginRequest : public NActors::TActorBootstrapped { void Handle(TEvLdapAuthProvider::TEvAuthenticateResponse::TPtr& ev) { TEvLdapAuthProvider::TEvAuthenticateResponse* response = ev->Get(); if (response->Status == TEvLdapAuthProvider::EStatus::SUCCESS) { + RequestLoginProvider(); + } else { + ReplyErrorAndPassAway("403", "Forbidden", response->Error.Message); + } + } + + void RequestLoginProvider() { + auto *domain = AppData()->DomainsInfo->GetDomain(); + TString rootDatabase = "/" + domain->Name; + ui64 rootSchemeShardTabletId = domain->SchemeRoot; + if (!Database.empty() && Database != rootDatabase) { + Database = rootDatabase; ALOG_DEBUG(NActorsServices::HTTP, "Login: Requesting schemecache for database " << Database); Send(MakeSchemeCacheID(), new TEvTxProxySchemeCache::TEvNavigateKeySet(CreateNavigateKeySetRequest(Database).Release())); } else { - ReplyErrorAndPassAway("403", "Forbidden", response->Error.Message); + Database = rootDatabase; + RequestSchemeShard(rootSchemeShardTabletId); } } diff --git a/ydb/core/security/login_shared_func.cpp b/ydb/core/security/login_shared_func.cpp index ec74e5290f7f..f013534bf821 100644 --- a/ydb/core/security/login_shared_func.cpp +++ b/ydb/core/security/login_shared_func.cpp @@ -19,9 +19,9 @@ THolder CreateNavigateKeySetRequest(const TS 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}; + const TString domain = "@" + config.GetLdapAuthenticationDomain(); + if (login.EndsWith(domain)) { + return {.AuthType = TAuthCredentials::EAuthType::Ldap, .Login = login.substr(0, login.size() - domain.size()), .Password = password}; } } return {.AuthType = TAuthCredentials::EAuthType::Internal, .Login = login, .Password = password}; diff --git a/ydb/core/tx/schemeshard/schemeshard__login.cpp b/ydb/core/tx/schemeshard/schemeshard__login.cpp index 64dfe223f458..562ace048ad9 100644 --- a/ydb/core/tx/schemeshard/schemeshard__login.cpp +++ b/ydb/core/tx/schemeshard/schemeshard__login.cpp @@ -1,5 +1,6 @@ #include "schemeshard_impl.h" #include +#include namespace NKikimr { namespace NSchemeShard { @@ -72,13 +73,18 @@ struct TSchemeShard::TTxLogin : TSchemeShard::TRwTxBase { Self->PublishToSchemeBoard(TTxId(), {SubDomainPathId}, ctx); } - NLogin::TLoginProvider::TLoginUserResponse LoginResponse = Self->LoginProvider.LoginUser(GetLoginRequest()); THolder result = MakeHolder(); - if (LoginResponse.Error) { - result->Record.SetError(LoginResponse.Error); - } - if (LoginResponse.Token) { - result->Record.SetToken(LoginResponse.Token); + const auto& loginRequest = GetLoginRequest(); + if (loginRequest.ExternalAuth || AppData(ctx)->AuthConfig.GetEnableLoginAuthentication()) { + NLogin::TLoginProvider::TLoginUserResponse LoginResponse = Self->LoginProvider.LoginUser(loginRequest); + if (LoginResponse.Error) { + result->Record.SetError(LoginResponse.Error); + } + if (LoginResponse.Token) { + result->Record.SetToken(LoginResponse.Token); + } + } else { + result->Record.SetError("Login authentication is disabled"); } LOG_DEBUG_S(ctx, NKikimrServices::FLAT_TX_SCHEMESHARD, diff --git a/ydb/core/tx/schemeshard/schemeshard__operation_alter_login.cpp b/ydb/core/tx/schemeshard/schemeshard__operation_alter_login.cpp index a4a5fd6d2533..48f9f400bdb2 100644 --- a/ydb/core/tx/schemeshard/schemeshard__operation_alter_login.cpp +++ b/ydb/core/tx/schemeshard/schemeshard__operation_alter_login.cpp @@ -1,6 +1,7 @@ #include "schemeshard__operation_part.h" #include "schemeshard__operation_common.h" #include "schemeshard_impl.h" +#include namespace { @@ -15,7 +16,9 @@ class TAlterLogin: public TSubOperationBase { NIceDb::TNiceDb db(context.GetTxc().DB); // do not track is there are direct writes happen TTabletId ssId = context.SS->SelfTabletId(); auto result = MakeHolder(OperationId.GetTxId(), ssId); - if (Transaction.GetWorkingDir() != context.SS->LoginProvider.Audience) { + if (!AppData()->AuthConfig.GetEnableLoginAuthentication()) { + result->SetStatus(NKikimrScheme::StatusPreconditionFailed, "Login authentication is disabled"); + } else if (Transaction.GetWorkingDir() != context.SS->LoginProvider.Audience) { result->SetStatus(NKikimrScheme::StatusPreconditionFailed, "Wrong working dir"); } else { const NKikimrConfig::TDomainsConfig::TSecurityConfig& securityConfig = context.SS->GetDomainsConfig().GetSecurityConfig(); diff --git a/ydb/core/tx/schemeshard/ut_login/ut_login.cpp b/ydb/core/tx/schemeshard/ut_login/ut_login.cpp index 1a46fafeb1d8..07df75f1aeee 100644 --- a/ydb/core/tx/schemeshard/ut_login/ut_login.cpp +++ b/ydb/core/tx/schemeshard/ut_login/ut_login.cpp @@ -1,5 +1,6 @@ #include #include +#include using namespace NKikimr; using namespace NSchemeShard; @@ -28,4 +29,23 @@ Y_UNIT_TEST_SUITE(TSchemeShardLoginTest) { auto resultValidate = login.ValidateToken({.Token = resultLogin.token()}); UNIT_ASSERT_VALUES_EQUAL(resultValidate.User, "user1"); } + + Y_UNIT_TEST(DisableBuiltinAuthMechanism) { + TTestBasicRuntime runtime; + TTestEnv env(runtime); + runtime.GetAppData().AuthConfig.SetEnableLoginAuthentication(false); + ui64 txId = 100; + TActorId sender = runtime.AllocateEdgeActor(); + std::unique_ptr transaction(CreateAlterLoginCreateUser(++txId, "user1", "password1")); + transaction->Record.MutableTransaction(0)->SetWorkingDir("/MyRoot"); + ForwardToTablet(runtime, TTestTxConfig::SchemeShard, sender, transaction.release()); + auto resultLogin = Login(runtime, "user1", "password1"); + UNIT_ASSERT_VALUES_EQUAL(resultLogin.error(), "Login authentication is disabled"); + UNIT_ASSERT_VALUES_EQUAL(resultLogin.token(), ""); + auto describe = DescribePath(runtime, TTestTxConfig::SchemeShard, "/MyRoot"); + UNIT_ASSERT(describe.HasPathDescription()); + UNIT_ASSERT(describe.GetPathDescription().HasDomainDescription()); + UNIT_ASSERT(describe.GetPathDescription().GetDomainDescription().HasSecurityState()); + UNIT_ASSERT(describe.GetPathDescription().GetDomainDescription().GetSecurityState().PublicKeysSize() > 0); + } } diff --git a/ydb/library/testlib/service_mocks/ldap_mock/ldap_defines.cpp b/ydb/library/testlib/service_mocks/ldap_mock/ldap_defines.cpp index 06170b7a1c1b..48335443fb04 100644 --- a/ydb/library/testlib/service_mocks/ldap_mock/ldap_defines.cpp +++ b/ydb/library/testlib/service_mocks/ldap_mock/ldap_defines.cpp @@ -1,7 +1,65 @@ +#include #include "ldap_defines.h" namespace LdapMock { +namespace { + +bool checkFilters(const TSearchRequestInfo::TSearchFilter& filter1, const TSearchRequestInfo::TSearchFilter& filter2) { + if (filter1.Type != filter2.Type) { + return false; + } + if (filter1.Attribute != filter2.Attribute) { + return false; + } + if (filter1.Value != filter2.Value) { + return false; + } + if (filter1.Type == EFilterType::LDAP_FILTER_EXT) { + if (filter1.MatchingRule != filter2.MatchingRule) { + return false; + } + if (filter1.DnAttributes != filter2.DnAttributes) { + return false; + } + } + if (filter1.NestedFilters.size() != filter2.NestedFilters.size()) { + return false; + } + return true; +} + +bool AreFiltersEqual(const TSearchRequestInfo::TSearchFilter& filter1, const TSearchRequestInfo::TSearchFilter& filter2) { + if (!checkFilters(filter1, filter2)) { + return false; + } + std::queue> q1; + for (const auto& filter : filter1.NestedFilters) { + q1.push(filter); + } + std::queue> q2; + for (const auto& filter : filter2.NestedFilters) { + q2.push(filter); + } + while (!q1.empty() && !q2.empty()) { + const auto filterQ1 = q1.front(); + q1.pop(); + const auto filterQ2 = q2.front(); + q2.pop(); + if (!checkFilters(*filterQ1, *filterQ2)) { + return false; + } + for (const auto& filter : filterQ1->NestedFilters) { + q1.push(filter); + } + for (const auto& filter : filterQ2->NestedFilters) { + q2.push(filter); + } + } + return true; +} +} // namespace + TBindRequestInfo::TBindRequestInfo(const TString& login, const TString& password) : Login(login) , Password(password) @@ -53,13 +111,7 @@ bool TSearchRequestInfo::operator==(const TSearchRequestInfo& otherRequest) cons } const auto& filter = this->Filter; const auto& expectedFilter = otherRequest.Filter; - if (filter.Type != expectedFilter.Type) { - return false; - } - if (filter.Attribute != expectedFilter.Attribute) { - return false; - } - if (filter.Value != expectedFilter.Value) { + if (!AreFiltersEqual(filter, expectedFilter)) { return false; } if (this->Attributes != otherRequest.Attributes) { diff --git a/ydb/library/testlib/service_mocks/ldap_mock/ldap_defines.h b/ydb/library/testlib/service_mocks/ldap_mock/ldap_defines.h index 4c8e14548c01..02bc7dd8cb04 100644 --- a/ydb/library/testlib/service_mocks/ldap_mock/ldap_defines.h +++ b/ydb/library/testlib/service_mocks/ldap_mock/ldap_defines.h @@ -1,6 +1,7 @@ #pragma once #include #include +#include #include namespace LdapMock { @@ -39,6 +40,13 @@ enum EFilterType { LDAP_FILTER_EXT = 0xA9, }; +enum EExtendedFilterType { + LDAP_FILTER_EXT_OID = 0x81U, + LDAP_FILTER_EXT_TYPE = 0x82U, + LDAP_FILTER_EXT_VALUE = 0x83U, + LDAP_FILTER_EXT_DNATTRS = 0x84U, +}; + enum EElementType { BOOL = 0x01, STRING = 0x04, @@ -69,6 +77,10 @@ struct TSearchRequestInfo { EFilterType Type; TString Attribute; TString Value; + TString MatchingRule; + bool DnAttributes = false; + + std::vector> NestedFilters; }; struct TInitializeList { diff --git a/ydb/library/testlib/service_mocks/ldap_mock/ldap_message_processor.cpp b/ydb/library/testlib/service_mocks/ldap_mock/ldap_message_processor.cpp index 8110e977f001..5e3069ce936f 100644 --- a/ydb/library/testlib/service_mocks/ldap_mock/ldap_message_processor.cpp +++ b/ydb/library/testlib/service_mocks/ldap_mock/ldap_message_processor.cpp @@ -133,7 +133,6 @@ int TLdapRequestProcessor::ExtractMessageId() { std::vector TLdapRequestProcessor::Process(const TLdapMockResponses& responses) { unsigned char protocolOp = GetByte(); - switch (protocolOp) { case EProtocolOp::BIND_OP_REQUEST: { return ProcessBindRequest(responses.BindResponses); @@ -348,7 +347,8 @@ TSearchRequestInfo::TSearchFilter TLdapRequestProcessor::ProcessFilter() { return filter; } case EFilterType::LDAP_FILTER_OR: { - FillFilter(EFilterType::LDAP_FILTER_OR, "or"); + filter.Type = EFilterType::LDAP_FILTER_OR; + ProcessFilterOr(&filter, filterLength); return filter; } case EFilterType::LDAP_FILTER_NOT: { @@ -381,7 +381,8 @@ TSearchRequestInfo::TSearchFilter TLdapRequestProcessor::ProcessFilter() { return filter; } case EFilterType::LDAP_FILTER_EXT: { - FillFilter(EFilterType::LDAP_FILTER_EXT, "ext"); + filter.Type = EFilterType::LDAP_FILTER_EXT; + ProcessFilterExtensibleMatch(&filter, filterLength); return filter; } } @@ -401,4 +402,45 @@ void TLdapRequestProcessor::ProcessFilterEquality(TSearchRequestInfo::TSearchFil } } +void TLdapRequestProcessor::ProcessFilterExtensibleMatch(TSearchRequestInfo::TSearchFilter* filter, size_t lengthFilter) { + const size_t limit = ReadBytes + lengthFilter; + size_t lastCheckedField = 0; + unsigned char elementType = GetByte(); + if (elementType == EExtendedFilterType::LDAP_FILTER_EXT_OID) { + filter->MatchingRule = GetString(); + lastCheckedField = 1; + } + + if (lastCheckedField == 1 && ReadBytes < limit) { + elementType = GetByte(); + } + if (elementType == EExtendedFilterType::LDAP_FILTER_EXT_TYPE) { + filter->Attribute = GetString(); + lastCheckedField = 2; + } + + if (lastCheckedField == 2 && ReadBytes < limit) { + elementType = GetByte(); + } + if (elementType == EExtendedFilterType::LDAP_FILTER_EXT_VALUE) { + filter->Value = GetString(); + } + + if (ReadBytes < limit) { + elementType = GetByte(); + if (elementType == EExtendedFilterType::LDAP_FILTER_EXT_DNATTRS) { + size_t length = GetLength(); + Y_UNUSED(length); + filter->DnAttributes = GetByte(); + } + } +} + +void TLdapRequestProcessor::ProcessFilterOr(TSearchRequestInfo::TSearchFilter* filter, size_t lengthFilter) { + const size_t limit = ReadBytes + lengthFilter; + while (ReadBytes < limit) { + filter->NestedFilters.push_back(std::make_shared(ProcessFilter())); + } +} + } diff --git a/ydb/library/testlib/service_mocks/ldap_mock/ldap_message_processor.h b/ydb/library/testlib/service_mocks/ldap_mock/ldap_message_processor.h index b41607b2e49c..e39d9845f31e 100644 --- a/ydb/library/testlib/service_mocks/ldap_mock/ldap_message_processor.h +++ b/ydb/library/testlib/service_mocks/ldap_mock/ldap_message_processor.h @@ -30,6 +30,8 @@ class TLdapRequestProcessor { std::vector ProcessExtendedRequest(); TSearchRequestInfo::TSearchFilter ProcessFilter(); void ProcessFilterEquality(TSearchRequestInfo::TSearchFilter* filter); + void ProcessFilterExtensibleMatch(TSearchRequestInfo::TSearchFilter* filter, size_t lengthFilter); + void ProcessFilterOr(TSearchRequestInfo::TSearchFilter* filter, size_t lengthFilter); private: TAtomicSharedPtr Socket; diff --git a/ydb/services/ydb/ydb_ldap_login_ut.cpp b/ydb/services/ydb/ydb_ldap_login_ut.cpp index 08003d0591d3..cf685d3b6f92 100644 --- a/ydb/services/ydb/ydb_ldap_login_ut.cpp +++ b/ydb/services/ydb/ydb_ldap_login_ut.cpp @@ -99,9 +99,9 @@ void InitLdapSettingsWithEmptyBindPassword(NKikimrProto::TLdapAuthentication* ld class TLoginClientConnection { public: - TLoginClientConnection(std::function initLdapSettings) + TLoginClientConnection(std::function initLdapSettings, bool isLoginAuthenticationEnabled = true) : CaCertificateFile() - , Server(InitAuthSettings(std::move(initLdapSettings))) + , Server(InitAuthSettings(std::move(initLdapSettings), isLoginAuthenticationEnabled)) , Connection(GetDriverConfig(Server.GetPort())) , Client(Connection) {} @@ -119,7 +119,7 @@ class TLoginClientConnection { } private: - NKikimrConfig::TAppConfig InitAuthSettings(std::function&& initLdapSettings) { + NKikimrConfig::TAppConfig InitAuthSettings(std::function&& initLdapSettings, bool isLoginAuthenticationEnabled = true) { TPortManager tp; LdapPort = tp.GetPort(389); @@ -128,6 +128,7 @@ class TLoginClientConnection { authConfig->SetUseBlackBox(false); authConfig->SetUseLoginProvider(true); + authConfig->SetEnableLoginAuthentication(isLoginAuthenticationEnabled); appConfig.MutableDomainsConfig()->MutableSecurityConfig()->SetEnforceUserTokenRequirement(true); appConfig.MutableFeatureFlags()->SetAllowYdbRequestsWithoutDatabase(false); @@ -378,5 +379,32 @@ Y_UNIT_TEST_SUITE(TGRpcLdapAuthentication) { loginConnection.Stop(); ldapServer.Stop(); } + + Y_UNIT_TEST(LdapAuthSetIncorrectDomain) { + TString login = "ldapuser"; + TString password = "ldapUserPassword"; + const TString incorrectLdapDomain = "@ldap.domain"; // Correct domain is AuthConfig.LdapAuthenticationDomain: "ldap" + + auto factory = CreateLoginCredentialsProviderFactory({.User = login + incorrectLdapDomain, .Password = password}); + TLoginClientConnection loginConnection(InitLdapSettings); + auto loginProvider = factory->CreateProvider(loginConnection.GetCoreFacility()); + UNIT_ASSERT_EXCEPTION_CONTAINS(loginProvider->GetAuthInfo(), yexception, "Invalid user"); + + loginConnection.Stop(); + } + + Y_UNIT_TEST(DisableBuiltinAuthMechanism) { + TString login = "builtinUser"; + TString password = "builtinUserPassword"; + + TLoginClientConnection loginConnection(InitLdapSettings, false); + + auto factory = CreateLoginCredentialsProviderFactory({.User = login, .Password = password}); + auto loginProvider = factory->CreateProvider(loginConnection.GetCoreFacility()); + TStringBuilder expectedErrorMessage; + UNIT_ASSERT_EXCEPTION_CONTAINS(loginProvider->GetAuthInfo(), yexception, "Login authentication is disabled"); + + loginConnection.Stop(); + } } } //namespace NKikimr From 9cf31f1840e864d82324c8da0e6f51eb75afec7a Mon Sep 17 00:00:00 2001 From: Andrey Molotkov Date: Wed, 28 Aug 2024 23:48:14 +0300 Subject: [PATCH 2/2] [ldap] Add flag for disable nested groups search (#8414) --- ydb/core/protos/auth.proto | 5 + .../ldap_auth_provider/ldap_auth_provider.cpp | 15 +- .../ldap_auth_provider_ut.cpp | 202 +++++++++++++++++- 3 files changed, 211 insertions(+), 11 deletions(-) diff --git a/ydb/core/protos/auth.proto b/ydb/core/protos/auth.proto index 135ddc050df0..4a09a0786625 100644 --- a/ydb/core/protos/auth.proto +++ b/ydb/core/protos/auth.proto @@ -104,6 +104,10 @@ message TLdapAuthentication { optional TCertRequire CertRequire = 3 [default = DEMAND]; } + message TExtendedSettings { + optional bool EnableNestedGroupsSearch = 1 [default = false]; + } + optional string Host = 1; // DEPRECATED: Use Hosts instead it optional uint32 Port = 2; optional string BaseDn = 3; @@ -115,4 +119,5 @@ message TLdapAuthentication { optional string RequestedGroupAttribute = 9; repeated string Hosts = 10; optional string Scheme = 11 [default = "ldap"]; + optional TExtendedSettings ExtendedSettings = 12; } diff --git a/ydb/core/security/ldap_auth_provider/ldap_auth_provider.cpp b/ydb/core/security/ldap_auth_provider/ldap_auth_provider.cpp index 3f206f4a72d3..baf841dbf0dc 100644 --- a/ydb/core/security/ldap_auth_provider/ldap_auth_provider.cpp +++ b/ydb/core/security/ldap_auth_provider/ldap_auth_provider.cpp @@ -148,7 +148,8 @@ class TLdapAuthProvider : public NActors::TActorBootstrapped NKikimrLdap::BerFree(ber, 0); } std::vector allUserGroups; - if (!directUserGroups.empty()) { + auto& extendedSettings = Settings.GetExtendedSettings(); + if (extendedSettings.GetEnableNestedGroupsSearch() && !directUserGroups.empty()) { // Active Directory has special matching rule to fetch nested groups in one request it is MatchingRuleInChain // We don`t know what is ldap server. Is it Active Directory or OpenLdap or other server? // If using MatchingRuleInChain return empty list of groups it means that ldap server isn`t Active Directory @@ -158,6 +159,8 @@ class TLdapAuthProvider : public NActors::TActorBootstrapped allUserGroups = std::move(directUserGroups); GetNestedGroups(ld, &allUserGroups); } + } else { + allUserGroups = std::move(directUserGroups); } NKikimrLdap::MsgFree(entry); NKikimrLdap::Unbind(ld); @@ -306,7 +309,10 @@ class TLdapAuthProvider : public NActors::TActorBootstrapped std::vector TryToGetGroupsUseMatchingRuleInChain(LDAP* ld, LDAPMessage* entry) const { static const TString matchingRuleInChain = "1.2.840.113556.1.4.1941"; // Only Active Directory supports TStringBuilder filter; - filter << "(member:" << matchingRuleInChain << ":=" << NKikimrLdap::GetDn(ld, entry) << ')'; + char* dn = NKikimrLdap::GetDn(ld, entry); + filter << "(member:" << matchingRuleInChain << ":=" << dn << ')'; + NKikimrLdap::MemFree(dn); + dn = nullptr; LDAPMessage* searchMessage = nullptr; int result = NKikimrLdap::Search(ld, Settings.GetBaseDn(), NKikimrLdap::EScope::SUBTREE, filter, NKikimrLdap::noAttributes, 0, &searchMessage); if (!NKikimrLdap::IsSuccess(result)) { @@ -320,7 +326,10 @@ class TLdapAuthProvider : public NActors::TActorBootstrapped std::vector groups; groups.reserve(countEntries); for (LDAPMessage* groupEntry = NKikimrLdap::FirstEntry(ld, searchMessage); groupEntry != nullptr; groupEntry = NKikimrLdap::NextEntry(ld, groupEntry)) { - groups.push_back(NKikimrLdap::GetDn(ld, groupEntry)); + dn = NKikimrLdap::GetDn(ld, groupEntry); + groups.push_back(dn); + NKikimrLdap::MemFree(dn); + dn = nullptr; } NKikimrLdap::MsgFree(searchMessage); return groups; diff --git a/ydb/core/security/ldap_auth_provider/ldap_auth_provider_ut.cpp b/ydb/core/security/ldap_auth_provider/ldap_auth_provider_ut.cpp index 3b6f41026f6e..6ceddc0c5749 100644 --- a/ydb/core/security/ldap_auth_provider/ldap_auth_provider_ut.cpp +++ b/ydb/core/security/ldap_auth_provider/ldap_auth_provider_ut.cpp @@ -47,6 +47,8 @@ void InitLdapSettings(NKikimrProto::TLdapAuthentication* ldapSettings, ui16 ldap ldapSettings->SetBindDn("cn=robouser,dc=search,dc=yandex,dc=net"); ldapSettings->SetBindPassword("robouserPassword"); ldapSettings->SetSearchFilter("uid=$username"); + auto extendedSettings = ldapSettings->MutableExtendedSettings(); + extendedSettings->SetEnableNestedGroupsSearch(true); const auto setCertificate = [&ldapSettings] (bool useStartTls, TTempFileHandle& certificateFile) { auto useTls = ldapSettings->MutableUseTls(); @@ -64,6 +66,12 @@ void InitLdapSettings(NKikimrProto::TLdapAuthentication* ldapSettings, ui16 ldap } } +void InitLdapSettingsDisableSearchNestedGroups(NKikimrProto::TLdapAuthentication* ldapSettings, ui16 ldapPort, TTempFileHandle& certificateFile, const ESecurityConnectionType& securityConnectionType) { + InitLdapSettings(ldapSettings, ldapPort, certificateFile, securityConnectionType); + auto extendedSettings = ldapSettings->MutableExtendedSettings(); + extendedSettings->SetEnableNestedGroupsSearch(false); +} + void InitLdapSettingsWithInvalidRobotUserLogin(NKikimrProto::TLdapAuthentication* ldapSettings, ui16 ldapPort, TTempFileHandle& certificateFile, const ESecurityConnectionType& securityConnectionType) { InitLdapSettings(ldapSettings, ldapPort, certificateFile, securityConnectionType); ldapSettings->SetBindDn("cn=invalidRobouser,dc=search,dc=yandex,dc=net"); @@ -206,9 +214,9 @@ class TCorrectLdapResponse { static std::vector ManagerGroups; static std::vector DevelopersGroups; static std::vector PeopleGroups; - static LdapMock::TLdapMockResponses GetResponses(const TString& login, const TString& groupAttribute = "memberOf"); - static LdapMock::TLdapMockResponses GetAdResponses(const TString& login, const TString& groupAttribute = "memberOf"); - static LdapMock::TLdapMockResponses GetUpdatedResponses(const TString& login, const TString& groupAttribute = "memberOf"); + static LdapMock::TLdapMockResponses GetResponses(const TString& login, bool doReturnDirectedGroups = false, const TString& groupAttribute = "memberOf"); + static LdapMock::TLdapMockResponses GetAdResponses(const TString& login, bool doReturnDirectedGroups = false, const TString& groupAttribute = "memberOf"); + static LdapMock::TLdapMockResponses GetUpdatedResponses(const TString& login, bool doReturnDirectedGroups = false, const TString& groupAttribute = "memberOf"); static THashSet GetAllGroups(const TString& domain) { THashSet result; auto AddGroups = [&result, &domain] (const std::vector& groups) { @@ -223,6 +231,17 @@ class TCorrectLdapResponse { return result; } + static THashSet GetDirectedGroups(const TString& domain) { + THashSet result; + auto AddGroups = [&result, &domain] (const std::vector& groups) { + std::transform(groups.begin(), groups.end(), std::inserter(result, result.end()), [&domain](const TString& group) { + return TString(group).append(domain); + }); + }; + AddGroups(DirectGroups); + return result; + } + static THashSet GetAllUpdatedGroups(const TString& domain) { THashSet result; auto AddGroups = [&result, &domain] (const std::vector& groups) { @@ -235,6 +254,17 @@ class TCorrectLdapResponse { AddGroups(PeopleGroups); return result; } + + static THashSet GetUpdatedDirectedGroups(const TString& domain) { + THashSet result; + auto AddGroups = [&result, &domain] (const std::vector& groups) { + std::transform(groups.begin(), groups.end(), std::inserter(result, result.end()), [&domain](const TString& group) { + return TString(group).append(domain); + }); + }; + AddGroups(UpdatedDirectGroups); + return result; + } }; std::vector TCorrectLdapResponse::DirectGroups { @@ -258,7 +288,7 @@ std::vector TCorrectLdapResponse::PeopleGroups { "cn=people,ou=groups,dc=search,dc=yandex,dc=net", }; -LdapMock::TLdapMockResponses TCorrectLdapResponse::GetResponses(const TString& login, const TString& groupAttribute) { +LdapMock::TLdapMockResponses TCorrectLdapResponse::GetResponses(const TString& login, bool doReturnDirectedGroups, const TString& groupAttribute) { LdapMock::TLdapMockResponses responses; responses.BindResponses.push_back({{{.Login = "cn=robouser,dc=search,dc=yandex,dc=net", .Password = "robouserPassword"}}, {.Status = LdapMock::EStatus::SUCCESS}}); @@ -287,6 +317,10 @@ LdapMock::TLdapMockResponses TCorrectLdapResponse::GetResponses(const TString& l }; responses.SearchResponses.push_back({requestDirectedUserGroups, responseDirectedUserGroups}); + if (doReturnDirectedGroups) { + return responses; + } + std::shared_ptr filterToGetGroupOfManagers = std::make_shared(); filterToGetGroupOfManagers->Type = LdapMock::EFilterType::LDAP_FILTER_EQUALITY; filterToGetGroupOfManagers->Attribute = "entryDn"; @@ -424,7 +458,7 @@ LdapMock::TLdapMockResponses TCorrectLdapResponse::GetResponses(const TString& l return responses; } -LdapMock::TLdapMockResponses TCorrectLdapResponse::GetUpdatedResponses(const TString& login, const TString& groupAttribute) { +LdapMock::TLdapMockResponses TCorrectLdapResponse::GetUpdatedResponses(const TString& login, bool doReturnDirectedGroups, const TString& groupAttribute) { LdapMock::TLdapMockResponses responses; responses.BindResponses.push_back({{{.Login = "cn=robouser,dc=search,dc=yandex,dc=net", .Password = "robouserPassword"}}, {.Status = LdapMock::EStatus::SUCCESS}}); @@ -453,6 +487,10 @@ LdapMock::TLdapMockResponses TCorrectLdapResponse::GetUpdatedResponses(const TSt }; responses.SearchResponses.push_back({requestDirectedUserGroups, responseDirectedUserGroups}); + if (doReturnDirectedGroups) { + return responses; + } + std::shared_ptr filterToGetGroupOfDevelopers = std::make_shared(); filterToGetGroupOfDevelopers->Type = LdapMock::EFilterType::LDAP_FILTER_EQUALITY; filterToGetGroupOfDevelopers->Attribute = "entryDn"; @@ -567,7 +605,7 @@ LdapMock::TLdapMockResponses TCorrectLdapResponse::GetUpdatedResponses(const TSt return responses; } -LdapMock::TLdapMockResponses TCorrectLdapResponse::GetAdResponses(const TString& login, const TString& groupAttribute) { +LdapMock::TLdapMockResponses TCorrectLdapResponse::GetAdResponses(const TString& login, bool doReturnDirectedGroups, const TString& groupAttribute) { LdapMock::TLdapMockResponses responses; responses.BindResponses.push_back({{{.Login = "cn=robouser,dc=search,dc=yandex,dc=net", .Password = "robouserPassword"}}, {.Status = LdapMock::EStatus::SUCCESS}}); @@ -596,6 +634,10 @@ LdapMock::TLdapMockResponses TCorrectLdapResponse::GetAdResponses(const TString& }; responses.SearchResponses.push_back({requestDirectedUserGroups, responseDirectedUserGroups}); + if (doReturnDirectedGroups) { + return responses; + } + LdapMock::TSearchRequestInfo requestToGetAllNestedGroupsFromAd { { .BaseDn = "dc=search,dc=yandex,dc=net", @@ -691,6 +733,33 @@ void CheckRequiredLdapSettings(std::function handle = LdapAuthenticate(server, login, password); + TEvTicketParser::TEvAuthorizeTicketResult* ticketParserResult = handle->Get(); + UNIT_ASSERT_C(ticketParserResult->Error.empty(), ticketParserResult->Error); + UNIT_ASSERT(ticketParserResult->Token != nullptr); + const TString ldapDomain = "@ldap"; + UNIT_ASSERT_VALUES_EQUAL(ticketParserResult->Token->GetUserSID(), login + ldapDomain); + const auto& fetchedGroups = ticketParserResult->Token->GetGroupSIDs(); + THashSet groups(fetchedGroups.begin(), fetchedGroups.end()); + + THashSet expectedGroups = TCorrectLdapResponse::GetDirectedGroups(ldapDomain); + expectedGroups.insert("all-users@well-known"); + + UNIT_ASSERT_VALUES_EQUAL(fetchedGroups.size(), expectedGroups.size()); + for (const auto& expectedGroup : expectedGroups) { + UNIT_ASSERT_C(groups.contains(expectedGroup), "Can not find " + expectedGroup); + } + + ldapServer.Stop(); + } + void LdapFetchGroupsFromAdLdapServer(const ESecurityConnectionType& secureType) { TString login = "ldapuser"; TString password = "ldapUserPassword"; @@ -718,6 +787,33 @@ void CheckRequiredLdapSettings(std::function handle = LdapAuthenticate(server, login, password); + TEvTicketParser::TEvAuthorizeTicketResult* ticketParserResult = handle->Get(); + UNIT_ASSERT_C(ticketParserResult->Error.empty(), ticketParserResult->Error); + UNIT_ASSERT(ticketParserResult->Token != nullptr); + const TString ldapDomain = "@ldap"; + UNIT_ASSERT_VALUES_EQUAL(ticketParserResult->Token->GetUserSID(), login + ldapDomain); + const auto& fetchedGroups = ticketParserResult->Token->GetGroupSIDs(); + THashSet groups(fetchedGroups.begin(), fetchedGroups.end()); + + THashSet expectedGroups = TCorrectLdapResponse::GetDirectedGroups(ldapDomain); + expectedGroups.insert("all-users@well-known"); + + UNIT_ASSERT_VALUES_EQUAL(fetchedGroups.size(), expectedGroups.size()); + for (const auto& expectedGroup : expectedGroups) { + UNIT_ASSERT_C(groups.contains(expectedGroup), "Can not find " + expectedGroup); + } + + ldapServer.Stop(); + } + void LdapFetchGroupsWithDefaultGroupAttributeGoodUseListOfHosts(const ESecurityConnectionType& secureType) { TString login = "ldapuser"; TString password = "ldapUserPassword"; @@ -750,7 +846,7 @@ void CheckRequiredLdapSettings(std::function handle = LdapAuthenticate(server, login, password); TEvTicketParser::TEvAuthorizeTicketResult* ticketParserResult = handle->Get(); @@ -928,7 +1024,6 @@ void CheckRequiredLdapSettings(std::functionAllocateEdgeActor(); + runtime->Send(new IEventHandle(MakeTicketParserID(), sender, new TEvTicketParser::TEvAuthorizeTicket(loginResponse.Token)), 0); + TAutoPtr handle; + TEvTicketParser::TEvAuthorizeTicketResult* ticketParserResult = runtime->GrabEdgeEvent(handle); + + UNIT_ASSERT_C(ticketParserResult->Error.empty(), ticketParserResult->Error); + UNIT_ASSERT(ticketParserResult->Token != nullptr); + UNIT_ASSERT_VALUES_EQUAL(ticketParserResult->Token->GetUserSID(), login + ldapDomain); + const auto& fetchedGroups = ticketParserResult->Token->GetGroupSIDs(); + THashSet groups(fetchedGroups.begin(), fetchedGroups.end()); + + THashSet expectedGroups = TCorrectLdapResponse::GetDirectedGroups(ldapDomain); + expectedGroups.insert("all-users@well-known"); + + UNIT_ASSERT_VALUES_EQUAL(fetchedGroups.size(), expectedGroups.size()); + for (const auto& expectedGroup : expectedGroups) { + UNIT_ASSERT_C(groups.contains(expectedGroup), "Can not find " + expectedGroup); + } + + ldapServer.UpdateResponses(); + Sleep(TDuration::Seconds(10)); + + runtime->Send(new IEventHandle(MakeTicketParserID(), sender, new TEvTicketParser::TEvAuthorizeTicket(loginResponse.Token)), 0); + ticketParserResult = runtime->GrabEdgeEvent(handle); + + UNIT_ASSERT_C(ticketParserResult->Error.empty(), ticketParserResult->Error); + UNIT_ASSERT(ticketParserResult->Token != nullptr); + UNIT_ASSERT_VALUES_EQUAL(ticketParserResult->Token->GetUserSID(), login + "@ldap"); + const auto& newFetchedGroups = ticketParserResult->Token->GetGroupSIDs(); + THashSet newGroups(newFetchedGroups.begin(), newFetchedGroups.end()); + + THashSet newExpectedGroups = TCorrectLdapResponse::GetUpdatedDirectedGroups(ldapDomain); + newExpectedGroups.insert("all-users@well-known"); + + UNIT_ASSERT_VALUES_EQUAL(newFetchedGroups.size(), newExpectedGroups.size()); + for (const auto& expectedGroup : newExpectedGroups) { + UNIT_ASSERT_C(newGroups.contains(expectedGroup), "Can not find " + expectedGroup); + } + + ldapServer.Stop(); + } + void LdapRefreshRemoveUserBad(const ESecurityConnectionType& secureType) { TString login = "ldapuser"; TString password = "ldapUserPassword"; @@ -1063,10 +1213,18 @@ Y_UNIT_TEST_SUITE(LdapAuthProviderTest_LdapsScheme) { LdapFetchGroupsFromAdLdapServer(ESecurityConnectionType::LDAPS_SCHEME); } + Y_UNIT_TEST(LdapFetchGroupsDisableRequestToAD) { + LdapFetchGroupsDisableRequestToAD(ESecurityConnectionType::LDAPS_SCHEME); + } + Y_UNIT_TEST(LdapFetchGroupsWithDefaultGroupAttributeGood) { LdapFetchGroupsWithDefaultGroupAttributeGood(ESecurityConnectionType::LDAPS_SCHEME); } + Y_UNIT_TEST(LdapFetchGroupsWithDefaultGroupAttributeDisableNestedGroupsGood) { + LdapFetchGroupsWithDefaultGroupAttributeDisableNestedGroupsGood(ESecurityConnectionType::LDAPS_SCHEME); + } + Y_UNIT_TEST(LdapFetchGroupsWithDefaultGroupAttributeGoodUseListOfHosts) { LdapFetchGroupsWithDefaultGroupAttributeGoodUseListOfHosts(ESecurityConnectionType::LDAPS_SCHEME); } @@ -1099,6 +1257,10 @@ Y_UNIT_TEST_SUITE(LdapAuthProviderTest_LdapsScheme) { LdapRefreshGroupsInfoGood(ESecurityConnectionType::LDAPS_SCHEME); } + Y_UNIT_TEST(LdapRefreshGroupsInfoDisableNestedGroupsGood) { + LdapRefreshGroupsInfoDisableNestedGroupsGood(ESecurityConnectionType::LDAPS_SCHEME); + } + Y_UNIT_TEST(LdapRefreshRemoveUserBad) { LdapRefreshRemoveUserBad(ESecurityConnectionType::LDAPS_SCHEME); } @@ -1109,10 +1271,18 @@ Y_UNIT_TEST_SUITE(LdapAuthProviderTest_StartTls) { LdapFetchGroupsFromAdLdapServer(ESecurityConnectionType::START_TLS); } + Y_UNIT_TEST(LdapFetchGroupsDisableRequestToAD) { + LdapFetchGroupsDisableRequestToAD(ESecurityConnectionType::START_TLS); + } + Y_UNIT_TEST(LdapFetchGroupsWithDefaultGroupAttributeGood) { LdapFetchGroupsWithDefaultGroupAttributeGood(ESecurityConnectionType::START_TLS); } + Y_UNIT_TEST(LdapFetchGroupsWithDefaultGroupAttributeDisableNestedGroupsGood) { + LdapFetchGroupsWithDefaultGroupAttributeDisableNestedGroupsGood(ESecurityConnectionType::START_TLS); + } + Y_UNIT_TEST(LdapFetchGroupsWithDefaultGroupAttributeGoodUseListOfHosts) { LdapFetchGroupsWithDefaultGroupAttributeGoodUseListOfHosts(ESecurityConnectionType::START_TLS); } @@ -1145,6 +1315,10 @@ Y_UNIT_TEST_SUITE(LdapAuthProviderTest_StartTls) { LdapRefreshGroupsInfoGood(ESecurityConnectionType::START_TLS); } + Y_UNIT_TEST(LdapRefreshGroupsInfoDisableNestedGroupsGood) { + LdapRefreshGroupsInfoDisableNestedGroupsGood(ESecurityConnectionType::START_TLS); + } + Y_UNIT_TEST(LdapRefreshRemoveUserBad) { LdapRefreshRemoveUserBad(ESecurityConnectionType::START_TLS); } @@ -1155,10 +1329,18 @@ Y_UNIT_TEST_SUITE(LdapAuthProviderTest_nonSecure) { LdapFetchGroupsFromAdLdapServer(ESecurityConnectionType::NON_SECURE); } + Y_UNIT_TEST(LdapFetchGroupsDisableRequestToAD) { + LdapFetchGroupsDisableRequestToAD(ESecurityConnectionType::NON_SECURE); + } + Y_UNIT_TEST(LdapFetchGroupsWithDefaultGroupAttributeGood) { LdapFetchGroupsWithDefaultGroupAttributeGood(ESecurityConnectionType::NON_SECURE); } + Y_UNIT_TEST(LdapFetchGroupsWithDefaultGroupAttributeDisableNestedGroupsGood) { + LdapFetchGroupsWithDefaultGroupAttributeDisableNestedGroupsGood(ESecurityConnectionType::NON_SECURE); + } + Y_UNIT_TEST(LdapFetchGroupsWithDefaultGroupAttributeGoodUseListOfHosts) { LdapFetchGroupsWithDefaultGroupAttributeGoodUseListOfHosts(ESecurityConnectionType::NON_SECURE); } @@ -1191,6 +1373,10 @@ Y_UNIT_TEST_SUITE(LdapAuthProviderTest_nonSecure) { LdapRefreshGroupsInfoGood(ESecurityConnectionType::NON_SECURE); } + Y_UNIT_TEST(LdapRefreshGroupsInfoDisableNestedGroupsGood) { + LdapRefreshGroupsInfoDisableNestedGroupsGood(ESecurityConnectionType::NON_SECURE); + } + Y_UNIT_TEST(LdapRefreshRemoveUserBad) { LdapRefreshRemoveUserBad(ESecurityConnectionType::NON_SECURE); }