Skip to content

Commit 546620e

Browse files
authored
[Ldap] Merge ldap fixes to stable 24-2 (#7126)
1 parent 1cbae05 commit 546620e

20 files changed

+1071
-276
lines changed

ydb/core/protos/auth.proto

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ message TAuthConfig {
5050
optional string AsSignatureExpireTime = 77 [default = "1m"];
5151
optional bool UseBuiltinDomain = 78 [default = true];
5252
optional string CertificateAuthenticationDomain = 80 [default = "cert"];
53+
optional bool EnableLoginAuthentication = 81 [default = true];
5354
}
5455

5556
message TUserRegistryConfig {

ydb/core/security/ldap_auth_provider/ldap_auth_provider.cpp

Lines changed: 100 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,30 @@ 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+
if (!directUserGroups.empty()) {
152+
// Active Directory has special matching rule to fetch nested groups in one request it is MatchingRuleInChain
153+
// We don`t know what is ldap server. Is it Active Directory or OpenLdap or other server?
154+
// If using MatchingRuleInChain return empty list of groups it means that ldap server isn`t Active Directory
155+
// but it is known that there are groups and we are trying to do tree traversal
156+
allUserGroups = TryToGetGroupsUseMatchingRuleInChain(ld, entry);
157+
if (allUserGroups.empty()) {
158+
allUserGroups = std::move(directUserGroups);
159+
GetNestedGroups(ld, &allUserGroups);
160+
}
161+
}
147162
NKikimrLdap::MsgFree(entry);
148163
NKikimrLdap::Unbind(ld);
149-
Send(ev->Sender, new TEvLdapAuthProvider::TEvEnrichGroupsResponse(request->Key, request->User, groupsDn));
164+
Send(ev->Sender, new TEvLdapAuthProvider::TEvEnrichGroupsResponse(request->Key, request->User, allUserGroups));
150165
}
151166

152167
TInitAndBindResponse InitAndBind(LDAP** ld, std::function<THolder<IEventBase>(const TEvLdapAuthProvider::EStatus&, const TEvLdapAuthProvider::TError&)> eventFabric) {
@@ -173,7 +188,7 @@ class TLdapAuthProvider : public NActors::TActorBootstrapped<TLdapAuthProvider>
173188
result = NKikimrLdap::Bind(*ld, Settings.GetBindDn(), Settings.GetBindPassword());
174189
if (!NKikimrLdap::IsSuccess(result)) {
175190
TEvLdapAuthProvider::TError error {
176-
.Message = "Could not perform initial LDAP bind for dn " + Settings.GetBindDn() + " on server " + UrisList + "\n"
191+
.Message = "Could not perform initial LDAP bind for dn " + Settings.GetBindDn() + " on server " + UrisCreator.GetUris() + "\n"
177192
+ NKikimrLdap::ErrorToString(result),
178193
.Retryable = NKikimrLdap::IsRetryableError(result)
179194
};
@@ -202,12 +217,10 @@ class TLdapAuthProvider : public NActors::TActorBootstrapped<TLdapAuthProvider>
202217
}
203218
}
204219

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);
220+
result = NKikimrLdap::Init(ld, Settings.GetScheme(), UrisCreator.GetUris(), UrisCreator.GetConfiguredPort());
208221
if (!NKikimrLdap::IsSuccess(result)) {
209222
return {{TEvLdapAuthProvider::EStatus::UNAVAILABLE,
210-
{.Message = "Could not initialize LDAP connection for uris: " + UrisList + ". " + NKikimrLdap::LdapError(*ld),
223+
{.Message = "Could not initialize LDAP connection for uris: " + UrisCreator.GetUris() + ". " + NKikimrLdap::LdapError(*ld),
211224
.Retryable = false}}};
212225
}
213226

@@ -237,14 +250,14 @@ class TLdapAuthProvider : public NActors::TActorBootstrapped<TLdapAuthProvider>
237250
char* dn = NKikimrLdap::GetDn(*request.Ld, request.Entry);
238251
if (dn == nullptr) {
239252
return {{TEvLdapAuthProvider::EStatus::UNAUTHORIZED,
240-
{.Message = "Could not get dn for the first entry matching " + FilterCreator.GetFilter(request.Login) + " on server " + UrisList + "\n"
253+
{.Message = "Could not get dn for the first entry matching " + FilterCreator.GetFilter(request.Login) + " on server " + UrisCreator.GetUris() + "\n"
241254
+ NKikimrLdap::LdapError(*request.Ld),
242255
.Retryable = false}}};
243256
}
244257
TEvLdapAuthProvider::TError error;
245258
int result = NKikimrLdap::Bind(*request.Ld, dn, request.Password);
246259
if (!NKikimrLdap::IsSuccess(result)) {
247-
error.Message = "LDAP login failed for user " + TString(dn) + " on server " + UrisList + "\n"
260+
error.Message = "LDAP login failed for user " + TString(dn) + " on server " + UrisCreator.GetUris() + "\n"
248261
+ NKikimrLdap::ErrorToString((result));
249262
error.Retryable = NKikimrLdap::IsRetryableError(result);
250263
}
@@ -266,7 +279,7 @@ class TLdapAuthProvider : public NActors::TActorBootstrapped<TLdapAuthProvider>
266279
TSearchUserResponse response;
267280
if (!NKikimrLdap::IsSuccess(result)) {
268281
response.Status = NKikimrLdap::ErrorToStatus(result);
269-
response.Error = {.Message = "Could not search for filter " + searchFilter + " on server " + UrisList + "\n"
282+
response.Error = {.Message = "Could not search for filter " + searchFilter + " on server " + UrisCreator.GetUris() + "\n"
270283
+ NKikimrLdap::ErrorToString(result),
271284
.Retryable = NKikimrLdap::IsRetryableError(result)};
272285
return response;
@@ -275,11 +288,11 @@ class TLdapAuthProvider : public NActors::TActorBootstrapped<TLdapAuthProvider>
275288
if (countEntries != 1) {
276289
if (countEntries == 0) {
277290
response.Error = {.Message = "LDAP user " + request.User + " does not exist. "
278-
"LDAP search for filter " + searchFilter + " on server " + UrisList + " return no entries",
291+
"LDAP search for filter " + searchFilter + " on server " + UrisCreator.GetUris() + " return no entries",
279292
.Retryable = false};
280293
} else {
281294
response.Error = {.Message = "LDAP user " + request.User + " is not unique. "
282-
"LDAP search for filter " + searchFilter + " on server " + UrisList + " return " + countEntries + " entries",
295+
"LDAP search for filter " + searchFilter + " on server " + UrisCreator.GetUris() + " return " + countEntries + " entries",
283296
.Retryable = false};
284297
}
285298
response.Status = TEvLdapAuthProvider::EStatus::UNAUTHORIZED;
@@ -290,6 +303,79 @@ class TLdapAuthProvider : public NActors::TActorBootstrapped<TLdapAuthProvider>
290303
return response;
291304
}
292305

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

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-
340395
private:
341396
const NKikimrProto::TLdapAuthentication Settings;
342397
const TSearchFilterCreator FilterCreator;
398+
const TLdapUrisCreator UrisCreator;
343399
char* RequestedAttributes[2];
344-
TString UrisList;
345400
};
346401

347402
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)