Skip to content

Commit

Permalink
[Ldap] Merge ldap fixes to stable 24-3 (#7126)
Browse files Browse the repository at this point in the history
  • Loading branch information
molotkov-and committed Aug 27, 2024
1 parent 57fe08e commit 56150f5
Show file tree
Hide file tree
Showing 20 changed files with 1,068 additions and 275 deletions.
1 change: 1 addition & 0 deletions ydb/core/protos/auth.proto
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
145 changes: 100 additions & 45 deletions ydb/core/security/ldap_auth_provider/ldap_auth_provider.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
#include <ydb/library/actors/core/log.h>
#include <ydb/core/base/ticket_parser.h>
#include <ydb/core/security/ticket_parser_log.h>
#include <ydb/core/util/address_classifier.h>
#include <queue>
#include "ldap_auth_provider.h"
#include "ldap_utils.h"

Expand Down Expand Up @@ -69,6 +71,7 @@ class TLdapAuthProvider : public NActors::TActorBootstrapped<TLdapAuthProvider>
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<char*>(requestedGroupAttribute.empty() ? "memberOf" : requestedGroupAttribute.c_str());
Expand Down Expand Up @@ -135,18 +138,30 @@ class TLdapAuthProvider : public NActors::TActorBootstrapped<TLdapAuthProvider>
}
LDAPMessage* entry = NKikimrLdap::FirstEntry(ld, searchUserResponse.SearchMessage);
BerElement* ber = nullptr;
std::vector<TString> groupsDn;
std::vector<TString> 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<TString> 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<THolder<IEventBase>(const TEvLdapAuthProvider::EStatus&, const TEvLdapAuthProvider::TError&)> eventFabric) {
Expand All @@ -173,7 +188,7 @@ class TLdapAuthProvider : public NActors::TActorBootstrapped<TLdapAuthProvider>
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)
};
Expand Down Expand Up @@ -202,12 +217,10 @@ class TLdapAuthProvider : public NActors::TActorBootstrapped<TLdapAuthProvider>
}
}

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}}};
}

Expand Down Expand Up @@ -237,14 +250,14 @@ class TLdapAuthProvider : public NActors::TActorBootstrapped<TLdapAuthProvider>
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);
}
Expand All @@ -266,7 +279,7 @@ class TLdapAuthProvider : public NActors::TActorBootstrapped<TLdapAuthProvider>
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;
Expand All @@ -275,11 +288,11 @@ class TLdapAuthProvider : public NActors::TActorBootstrapped<TLdapAuthProvider>
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;
Expand All @@ -290,6 +303,79 @@ class TLdapAuthProvider : public NActors::TActorBootstrapped<TLdapAuthProvider>
return response;
}

std::vector<TString> 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<TString> 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<TString>* groups) {
std::unordered_set<TString> viewedGroups(groups->cbegin(), groups->cend());
std::queue<TString> 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<TString> 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}};
Expand All @@ -306,42 +392,11 @@ class TLdapAuthProvider : public NActors::TActorBootstrapped<TLdapAuthProvider>
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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
Loading

0 comments on commit 56150f5

Please sign in to comment.