diff options
author | molotkov-and <molotkov-and@ydb.tech> | 2023-10-13 15:23:49 +0300 |
---|---|---|
committer | molotkov-and <molotkov-and@ydb.tech> | 2023-10-13 16:32:10 +0300 |
commit | 478c3e8fdf17007677dd3aa3bb692cf02c8e5d60 (patch) | |
tree | f34464f6b178a22a5e60845cc325972d2374b485 | |
parent | 1e5b83881360787c84f0973321b51eb75f31703f (diff) | |
download | ydb-478c3e8fdf17007677dd3aa3bb692cf02c8e5d60.tar.gz |
KIKIMR-18761: Add configuration parameter for request groups from LDAP
-rw-r--r-- | ydb/core/protos/auth.proto | 1 | ||||
-rw-r--r-- | ydb/core/security/ldap_auth_provider.cpp | 10 | ||||
-rw-r--r-- | ydb/core/security/ticket_parser_ut.cpp | 92 |
3 files changed, 95 insertions, 8 deletions
diff --git a/ydb/core/protos/auth.proto b/ydb/core/protos/auth.proto index c104727adc6..ce5e1629529 100644 --- a/ydb/core/protos/auth.proto +++ b/ydb/core/protos/auth.proto @@ -104,4 +104,5 @@ message TLdapAuthentication { optional string SearchFilter = 6; optional string SearchAttribute = 7; optional TUseTls UseTls = 8; + optional string RequestedGroupAttribute = 9; } diff --git a/ydb/core/security/ldap_auth_provider.cpp b/ydb/core/security/ldap_auth_provider.cpp index 609290381c7..65281f67a7b 100644 --- a/ydb/core/security/ldap_auth_provider.cpp +++ b/ydb/core/security/ldap_auth_provider.cpp @@ -69,7 +69,11 @@ public: TLdapAuthProvider(const NKikimrProto::TLdapAuthentication& settings) : Settings(settings) , FilterCreator(Settings) - {} + { + const TString& requestedGroupAttribute = Settings.GetRequestedGroupAttribute(); + RequestedAttributes[0] = const_cast<char*>(requestedGroupAttribute.empty() ? "memberOf" : requestedGroupAttribute.c_str()); + RequestedAttributes[1] = nullptr; + } void Bootstrap() { TBase::Become(&TThis::StateWork); @@ -121,10 +125,9 @@ private: return; } - char* requestedAttributes[] = {const_cast<char*>("memberOf"), nullptr}; TSearchUserResponse searchUserResponse = SearchUserRecord({.Ld = ld, .User = request->User, - .RequestedAttributes = requestedAttributes}); + .RequestedAttributes = RequestedAttributes}); if (searchUserResponse.Status != TEvLdapAuthProvider::EStatus::SUCCESS) { NKikimrLdap::Unbind(ld); Send(ev->Sender, new TEvLdapAuthProvider::TEvEnrichGroupsResponse(request->Key, searchUserResponse.Status, searchUserResponse.Error)); @@ -290,6 +293,7 @@ private: private: const NKikimrProto::TLdapAuthentication Settings; const TSearchFilterCreator FilterCreator; + char* RequestedAttributes[2]; }; IActor* CreateLdapAuthProvider(const NKikimrProto::TLdapAuthentication& settings) { diff --git a/ydb/core/security/ticket_parser_ut.cpp b/ydb/core/security/ticket_parser_ut.cpp index 09551363eeb..139b1a7e86c 100644 --- a/ydb/core/security/ticket_parser_ut.cpp +++ b/ydb/core/security/ticket_parser_ut.cpp @@ -77,6 +77,11 @@ void InitLdapSettingsWithUnavaliableHost(NKikimrProto::TLdapAuthentication* ldap ldapSettings->SetHost("unavaliablehost"); } +void InitLdapSettingsWithCustomGroupAttribute(NKikimrProto::TLdapAuthentication* ldapSettings, ui16 ldapPort, TTempFileHandle& certificateFile) { + InitLdapSettings(ldapSettings, ldapPort, certificateFile); + ldapSettings->SetRequestedGroupAttribute("groupDN"); +} + class TLdapKikimrServer { public: TLdapKikimrServer(std::function<void(NKikimrProto::TLdapAuthentication*, ui16, TTempFileHandle&)> initLdapSettings) @@ -146,7 +151,7 @@ TAutoPtr<IEventHandle> LdapAuthenticate(TLdapKikimrServer& server, const TString class TCorrectLdapResponse { public: static std::vector<TString> Groups; - static LdapMock::TLdapMockResponses GetResponses(const TString& login); + static LdapMock::TLdapMockResponses GetResponses(const TString& login, const TString& groupAttribute = "memberOf"); }; std::vector<TString> TCorrectLdapResponse::Groups { @@ -155,7 +160,7 @@ std::vector<TString> TCorrectLdapResponse::Groups { "cn=developers,ou=groups,dc=search,dc=yandex,dc=net" }; -LdapMock::TLdapMockResponses TCorrectLdapResponse::GetResponses(const TString& login) { +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}}); @@ -165,7 +170,7 @@ LdapMock::TLdapMockResponses TCorrectLdapResponse::GetResponses(const TString& l .Scope = 2, .DerefAliases = 0, .Filter = {.Type = LdapMock::EFilterType::LDAP_FILTER_EQUALITY, .Attribute = "uid", .Value = login}, - .Attributes = {"memberOf"} + .Attributes = {groupAttribute} } }; @@ -173,7 +178,7 @@ LdapMock::TLdapMockResponses TCorrectLdapResponse::GetResponses(const TString& l { .Dn = "uid=" + login + ",dc=search,dc=yandex,dc=net", .AttributeList = { - {"memberOf", TCorrectLdapResponse::Groups} + {groupAttribute, TCorrectLdapResponse::Groups} } } }; @@ -507,7 +512,7 @@ Y_UNIT_TEST_SUITE(TTicketParserTest) { UNIT_ASSERT_VALUES_EQUAL(result->Error.Message, "Ticket is empty"); } - Y_UNIT_TEST(LdapFetchGroupsGood) { + Y_UNIT_TEST(LdapFetchGroupsWithDefaultGroupAttributeGood) { TString login = "ldapuser"; TString password = "ldapUserPassword"; @@ -537,6 +542,83 @@ Y_UNIT_TEST_SUITE(TTicketParserTest) { ldapServer.Stop(); } + Y_UNIT_TEST(LdapFetchGroupsWithCustomGroupAttributeGood) { + TString login = "ldapuser"; + TString password = "ldapUserPassword"; + + TLdapKikimrServer server(InitLdapSettingsWithCustomGroupAttribute); + LdapMock::TLdapSimpleServer ldapServer(server.GetLdapPort(), TCorrectLdapResponse::GetResponses(login, "groupDN")); + + TAutoPtr<IEventHandle> handle = LdapAuthenticate(server, login, password); + TEvTicketParser::TEvAuthorizeTicketResult* ticketParserResult = handle->Get<TEvTicketParser::TEvAuthorizeTicketResult>(); + 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<TString> groups(fetchedGroups.begin(), fetchedGroups.end()); + + THashSet<TString> expectedGroups; + std::transform(TCorrectLdapResponse::Groups.begin(), TCorrectLdapResponse::Groups.end(), std::inserter(expectedGroups, expectedGroups.end()), [&ldapDomain](TString& group) { + return group.append(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(); + } + + Y_UNIT_TEST(LdapFetchGroupsWithDontExistGroupAttribute) { + TString login = "ldapuser"; + TString password = "ldapUserPassword"; + + TLdapKikimrServer server(InitLdapSettingsWithCustomGroupAttribute); + + 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 { + { + .BaseDn = "dc=search,dc=yandex,dc=net", + .Scope = 2, + .DerefAliases = 0, + .Filter = {.Type = LdapMock::EFilterType::LDAP_FILTER_EQUALITY, .Attribute = "uid", .Value = login}, + .Attributes = {"groupDN"} + } + }; + + std::vector<LdapMock::TSearchEntry> fetchGroupsSearchResponseEntries { + { + .Dn = "uid=" + login + ",dc=search,dc=yandex,dc=net", + .AttributeList = {} // Return empty group list, attribute 'groupDN' not found + } + }; + + LdapMock::TSearchResponseInfo fetchGroupsSearchResponseInfo { + .ResponseEntries = fetchGroupsSearchResponseEntries, + .ResponseDone = {.Status = LdapMock::EStatus::SUCCESS} + }; + responses.SearchResponses.push_back({fetchGroupsSearchRequestInfo, fetchGroupsSearchResponseInfo}); + + LdapMock::TLdapSimpleServer ldapServer(server.GetLdapPort(), responses); + + TAutoPtr<IEventHandle> handle = LdapAuthenticate(server, login, password); + TEvTicketParser::TEvAuthorizeTicketResult* ticketParserResult = handle->Get<TEvTicketParser::TEvAuthorizeTicketResult>(); + 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(); + UNIT_ASSERT_EQUAL(fetchedGroups.size(), 1); + UNIT_ASSERT_STRINGS_EQUAL(fetchedGroups.front(), "all-users@well-known"); + + ldapServer.Stop(); + } + Y_UNIT_TEST(LdapFetchGroupsWithInvalidRobotUserLoginBad) { TString login = "ldapuser"; TString password = "ldapUserPassword"; |