Skip to content

Commit c4d70ec

Browse files
authored
[Ldap] Merge ldap fixes to stable 24-3 (#8326)
1 parent 4ee430b commit c4d70ec

20 files changed

+1272
-279
lines changed

ydb/core/protos/auth.proto

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ message TAuthConfig {
5454
optional bool UseBuiltinDomain = 78 [default = true];
5555
optional string AccessServiceType = 79 [default = "Yandex_v2"]; // For now the following values are supported: "Yandex_v2", "Nebius_v1"
5656
optional string CertificateAuthenticationDomain = 80 [default = "cert"];
57+
optional bool EnableLoginAuthentication = 81 [default = true];
5758
}
5859

5960
message TUserRegistryConfig {
@@ -103,6 +104,10 @@ message TLdapAuthentication {
103104
optional TCertRequire CertRequire = 3 [default = DEMAND];
104105
}
105106

107+
message TExtendedSettings {
108+
optional bool EnableNestedGroupsSearch = 1 [default = false];
109+
}
110+
106111
optional string Host = 1; // DEPRECATED: Use Hosts instead it
107112
optional uint32 Port = 2;
108113
optional string BaseDn = 3;
@@ -114,4 +119,5 @@ message TLdapAuthentication {
114119
optional string RequestedGroupAttribute = 9;
115120
repeated string Hosts = 10;
116121
optional string Scheme = 11 [default = "ldap"];
122+
optional TExtendedSettings ExtendedSettings = 12;
117123
}

ydb/core/security/ldap_auth_provider/ldap_auth_provider.cpp

Lines changed: 109 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
#include <ydb/library/actors/core/log.h>
33
#include <ydb/core/base/ticket_parser.h>
44
#include <ydb/core/security/ticket_parser_log.h>
5+
#include <ydb/core/util/address_classifier.h>
6+
#include <queue>
57
#include "ldap_auth_provider.h"
68
#include "ldap_utils.h"
79

@@ -69,6 +71,7 @@ class TLdapAuthProvider : public NActors::TActorBootstrapped<TLdapAuthProvider>
6971
TLdapAuthProvider(const NKikimrProto::TLdapAuthentication& settings)
7072
: Settings(settings)
7173
, FilterCreator(Settings)
74+
, UrisCreator(Settings, Settings.GetPort() != 0 ? Settings.GetPort() : NKikimrLdap::GetPort(Settings.GetScheme()))
7275
{
7376
const TString& requestedGroupAttribute = Settings.GetRequestedGroupAttribute();
7477
RequestedAttributes[0] = const_cast<char*>(requestedGroupAttribute.empty() ? "memberOf" : requestedGroupAttribute.c_str());
@@ -135,18 +138,33 @@ class TLdapAuthProvider : public NActors::TActorBootstrapped<TLdapAuthProvider>
135138
}
136139
LDAPMessage* entry = NKikimrLdap::FirstEntry(ld, searchUserResponse.SearchMessage);
137140
BerElement* ber = nullptr;
138-
std::vector<TString> groupsDn;
141+
std::vector<TString> directUserGroups;
139142
char* attribute = NKikimrLdap::FirstAttribute(ld, entry, &ber);
140143
if (attribute != nullptr) {
141-
groupsDn = NKikimrLdap::GetAllValuesOfAttribute(ld, entry, attribute);
144+
directUserGroups = NKikimrLdap::GetAllValuesOfAttribute(ld, entry, attribute);
142145
NKikimrLdap::MemFree(attribute);
143146
}
144147
if (ber) {
145148
NKikimrLdap::BerFree(ber, 0);
146149
}
150+
std::vector<TString> allUserGroups;
151+
auto& extendedSettings = Settings.GetExtendedSettings();
152+
if (extendedSettings.GetEnableNestedGroupsSearch() && !directUserGroups.empty()) {
153+
// Active Directory has special matching rule to fetch nested groups in one request it is MatchingRuleInChain
154+
// We don`t know what is ldap server. Is it Active Directory or OpenLdap or other server?
155+
// If using MatchingRuleInChain return empty list of groups it means that ldap server isn`t Active Directory
156+
// but it is known that there are groups and we are trying to do tree traversal
157+
allUserGroups = TryToGetGroupsUseMatchingRuleInChain(ld, entry);
158+
if (allUserGroups.empty()) {
159+
allUserGroups = std::move(directUserGroups);
160+
GetNestedGroups(ld, &allUserGroups);
161+
}
162+
} else {
163+
allUserGroups = std::move(directUserGroups);
164+
}
147165
NKikimrLdap::MsgFree(entry);
148166
NKikimrLdap::Unbind(ld);
149-
Send(ev->Sender, new TEvLdapAuthProvider::TEvEnrichGroupsResponse(request->Key, request->User, groupsDn));
167+
Send(ev->Sender, new TEvLdapAuthProvider::TEvEnrichGroupsResponse(request->Key, request->User, allUserGroups));
150168
}
151169

152170
TInitAndBindResponse InitAndBind(LDAP** ld, std::function<THolder<IEventBase>(const TEvLdapAuthProvider::EStatus&, const TEvLdapAuthProvider::TError&)> eventFabric) {
@@ -173,7 +191,7 @@ class TLdapAuthProvider : public NActors::TActorBootstrapped<TLdapAuthProvider>
173191
result = NKikimrLdap::Bind(*ld, Settings.GetBindDn(), Settings.GetBindPassword());
174192
if (!NKikimrLdap::IsSuccess(result)) {
175193
TEvLdapAuthProvider::TError error {
176-
.Message = "Could not perform initial LDAP bind for dn " + Settings.GetBindDn() + " on server " + UrisList + "\n"
194+
.Message = "Could not perform initial LDAP bind for dn " + Settings.GetBindDn() + " on server " + UrisCreator.GetUris() + "\n"
177195
+ NKikimrLdap::ErrorToString(result),
178196
.Retryable = NKikimrLdap::IsRetryableError(result)
179197
};
@@ -202,12 +220,10 @@ class TLdapAuthProvider : public NActors::TActorBootstrapped<TLdapAuthProvider>
202220
}
203221
}
204222

205-
const ui32 port = Settings.GetPort() != 0 ? Settings.GetPort() : NKikimrLdap::GetPort(Settings.GetScheme());
206-
UrisList = GetUris(port);
207-
result = NKikimrLdap::Init(ld, Settings.GetScheme(), UrisList, port);
223+
result = NKikimrLdap::Init(ld, Settings.GetScheme(), UrisCreator.GetUris(), UrisCreator.GetConfiguredPort());
208224
if (!NKikimrLdap::IsSuccess(result)) {
209225
return {{TEvLdapAuthProvider::EStatus::UNAVAILABLE,
210-
{.Message = "Could not initialize LDAP connection for uris: " + UrisList + ". " + NKikimrLdap::LdapError(*ld),
226+
{.Message = "Could not initialize LDAP connection for uris: " + UrisCreator.GetUris() + ". " + NKikimrLdap::LdapError(*ld),
211227
.Retryable = false}}};
212228
}
213229

@@ -237,14 +253,14 @@ class TLdapAuthProvider : public NActors::TActorBootstrapped<TLdapAuthProvider>
237253
char* dn = NKikimrLdap::GetDn(*request.Ld, request.Entry);
238254
if (dn == nullptr) {
239255
return {{TEvLdapAuthProvider::EStatus::UNAUTHORIZED,
240-
{.Message = "Could not get dn for the first entry matching " + FilterCreator.GetFilter(request.Login) + " on server " + UrisList + "\n"
256+
{.Message = "Could not get dn for the first entry matching " + FilterCreator.GetFilter(request.Login) + " on server " + UrisCreator.GetUris() + "\n"
241257
+ NKikimrLdap::LdapError(*request.Ld),
242258
.Retryable = false}}};
243259
}
244260
TEvLdapAuthProvider::TError error;
245261
int result = NKikimrLdap::Bind(*request.Ld, dn, request.Password);
246262
if (!NKikimrLdap::IsSuccess(result)) {
247-
error.Message = "LDAP login failed for user " + TString(dn) + " on server " + UrisList + "\n"
263+
error.Message = "LDAP login failed for user " + TString(dn) + " on server " + UrisCreator.GetUris() + "\n"
248264
+ NKikimrLdap::ErrorToString((result));
249265
error.Retryable = NKikimrLdap::IsRetryableError(result);
250266
}
@@ -266,7 +282,7 @@ class TLdapAuthProvider : public NActors::TActorBootstrapped<TLdapAuthProvider>
266282
TSearchUserResponse response;
267283
if (!NKikimrLdap::IsSuccess(result)) {
268284
response.Status = NKikimrLdap::ErrorToStatus(result);
269-
response.Error = {.Message = "Could not search for filter " + searchFilter + " on server " + UrisList + "\n"
285+
response.Error = {.Message = "Could not search for filter " + searchFilter + " on server " + UrisCreator.GetUris() + "\n"
270286
+ NKikimrLdap::ErrorToString(result),
271287
.Retryable = NKikimrLdap::IsRetryableError(result)};
272288
return response;
@@ -275,11 +291,11 @@ class TLdapAuthProvider : public NActors::TActorBootstrapped<TLdapAuthProvider>
275291
if (countEntries != 1) {
276292
if (countEntries == 0) {
277293
response.Error = {.Message = "LDAP user " + request.User + " does not exist. "
278-
"LDAP search for filter " + searchFilter + " on server " + UrisList + " return no entries",
294+
"LDAP search for filter " + searchFilter + " on server " + UrisCreator.GetUris() + " return no entries",
279295
.Retryable = false};
280296
} else {
281297
response.Error = {.Message = "LDAP user " + request.User + " is not unique. "
282-
"LDAP search for filter " + searchFilter + " on server " + UrisList + " return " + countEntries + " entries",
298+
"LDAP search for filter " + searchFilter + " on server " + UrisCreator.GetUris() + " return " + countEntries + " entries",
283299
.Retryable = false};
284300
}
285301
response.Status = TEvLdapAuthProvider::EStatus::UNAUTHORIZED;
@@ -290,6 +306,85 @@ class TLdapAuthProvider : public NActors::TActorBootstrapped<TLdapAuthProvider>
290306
return response;
291307
}
292308

309+
std::vector<TString> TryToGetGroupsUseMatchingRuleInChain(LDAP* ld, LDAPMessage* entry) const {
310+
static const TString matchingRuleInChain = "1.2.840.113556.1.4.1941"; // Only Active Directory supports
311+
TStringBuilder filter;
312+
char* dn = NKikimrLdap::GetDn(ld, entry);
313+
filter << "(member:" << matchingRuleInChain << ":=" << dn << ')';
314+
NKikimrLdap::MemFree(dn);
315+
dn = nullptr;
316+
LDAPMessage* searchMessage = nullptr;
317+
int result = NKikimrLdap::Search(ld, Settings.GetBaseDn(), NKikimrLdap::EScope::SUBTREE, filter, NKikimrLdap::noAttributes, 0, &searchMessage);
318+
if (!NKikimrLdap::IsSuccess(result)) {
319+
return {};
320+
}
321+
const int countEntries = NKikimrLdap::CountEntries(ld, searchMessage);
322+
if (countEntries == 0) {
323+
NKikimrLdap::MsgFree(searchMessage);
324+
return {};
325+
}
326+
std::vector<TString> groups;
327+
groups.reserve(countEntries);
328+
for (LDAPMessage* groupEntry = NKikimrLdap::FirstEntry(ld, searchMessage); groupEntry != nullptr; groupEntry = NKikimrLdap::NextEntry(ld, groupEntry)) {
329+
dn = NKikimrLdap::GetDn(ld, groupEntry);
330+
groups.push_back(dn);
331+
NKikimrLdap::MemFree(dn);
332+
dn = nullptr;
333+
}
334+
NKikimrLdap::MsgFree(searchMessage);
335+
return groups;
336+
}
337+
338+
void GetNestedGroups(LDAP* ld, std::vector<TString>* groups) {
339+
std::unordered_set<TString> viewedGroups(groups->cbegin(), groups->cend());
340+
std::queue<TString> queue;
341+
for (const auto& group : *groups) {
342+
queue.push(group);
343+
}
344+
while (!queue.empty()) {
345+
TStringBuilder filter;
346+
filter << "(|";
347+
filter << "(entryDn=" << queue.front() << ')';
348+
queue.pop();
349+
//should filter string is separated into several batches
350+
while (!queue.empty()) {
351+
// entryDn specific for OpenLdap, may get this value from config
352+
filter << "(entryDn=" << queue.front() << ')';
353+
queue.pop();
354+
}
355+
filter << ')';
356+
LDAPMessage* searchMessage = nullptr;
357+
int result = NKikimrLdap::Search(ld, Settings.GetBaseDn(), NKikimrLdap::EScope::SUBTREE, filter, RequestedAttributes, 0, &searchMessage);
358+
if (!NKikimrLdap::IsSuccess(result)) {
359+
return;
360+
}
361+
if (NKikimrLdap::CountEntries(ld, searchMessage) == 0) {
362+
NKikimrLdap::MsgFree(searchMessage);
363+
return;
364+
}
365+
for (LDAPMessage* groupEntry = NKikimrLdap::FirstEntry(ld, searchMessage); groupEntry != nullptr; groupEntry = NKikimrLdap::NextEntry(ld, groupEntry)) {
366+
BerElement* ber = nullptr;
367+
std::vector<TString> foundGroups;
368+
char* attribute = NKikimrLdap::FirstAttribute(ld, groupEntry, &ber);
369+
if (attribute != nullptr) {
370+
foundGroups = NKikimrLdap::GetAllValuesOfAttribute(ld, groupEntry, attribute);
371+
NKikimrLdap::MemFree(attribute);
372+
}
373+
if (ber) {
374+
NKikimrLdap::BerFree(ber, 0);
375+
}
376+
for (const auto& newGroup : foundGroups) {
377+
if (!viewedGroups.contains(newGroup)) {
378+
viewedGroups.insert(newGroup);
379+
queue.push(newGroup);
380+
groups->push_back(newGroup);
381+
}
382+
}
383+
}
384+
NKikimrLdap::MsgFree(searchMessage);
385+
}
386+
}
387+
293388
TInitializeLdapConnectionResponse CheckRequiredSettingsParameters() const {
294389
if (Settings.GetHosts().empty() && Settings.GetHost().empty()) {
295390
return {TEvLdapAuthProvider::EStatus::UNAVAILABLE, {.Message = "List of ldap server hosts is empty", .Retryable = false}};
@@ -306,42 +401,11 @@ class TLdapAuthProvider : public NActors::TActorBootstrapped<TLdapAuthProvider>
306401
return {TEvLdapAuthProvider::EStatus::SUCCESS, {}};
307402
}
308403

309-
TString GetUris(ui32 port) const {
310-
TStringBuilder uris;
311-
if (Settings.HostsSize() > 0) {
312-
for (const auto& host : Settings.GetHosts()) {
313-
uris << CreateUri(host, port) << " ";
314-
}
315-
uris.remove(uris.size() - 1);
316-
} else {
317-
uris << CreateUri(Settings.GetHost(), port);
318-
}
319-
return uris;
320-
}
321-
322-
TString CreateUri(const TString& endpoint, ui32 port) const {
323-
TStringBuilder uri;
324-
uri << Settings.GetScheme() << "://" << endpoint;
325-
if (!HasEndpointPort(endpoint)) {
326-
uri << ':' << port;
327-
}
328-
return uri;
329-
}
330-
331-
static bool HasEndpointPort(const TString& endpoint) {
332-
size_t colonPos = endpoint.rfind(':');
333-
if (colonPos == TString::npos) {
334-
return false;
335-
}
336-
++colonPos;
337-
return (endpoint.size() - colonPos) > 0;
338-
}
339-
340404
private:
341405
const NKikimrProto::TLdapAuthentication Settings;
342406
const TSearchFilterCreator FilterCreator;
407+
const TLdapUrisCreator UrisCreator;
343408
char* RequestedAttributes[2];
344-
TString UrisList;
345409
};
346410

347411
IActor* CreateLdapAuthProvider(const NKikimrProto::TLdapAuthentication& settings) {

ydb/core/security/ldap_auth_provider/ldap_auth_provider_linux.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,10 @@ LDAPMessage* FirstEntry(LDAP* ld, LDAPMessage* chain) {
7777
return ldap_first_entry(ld, chain);
7878
}
7979

80+
LDAPMessage* NextEntry(LDAP* ld, LDAPMessage* entry) {
81+
return ldap_next_entry(ld, entry);
82+
}
83+
8084
char* FirstAttribute(LDAP* ld, LDAPMessage* entry, BerElement** berout) {
8185
return ldap_first_attribute(ld, entry, berout);
8286
}

0 commit comments

Comments
 (0)