Skip to content

Commit 1be0464

Browse files
authored
Ldap refresh error token (#15010)
1 parent a378163 commit 1be0464

File tree

6 files changed

+179
-31
lines changed

6 files changed

+179
-31
lines changed

ydb/core/security/ldap_auth_provider/ldap_auth_provider_linux.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,11 @@ NKikimr::TEvLdapAuthProvider::EStatus ErrorToStatus(int err) {
158158
bool IsRetryableError(int error) {
159159
switch (error) {
160160
case LDAP_SERVER_DOWN:
161+
case LDAP_TIMEOUT:
162+
case LDAP_CONNECT_ERROR:
163+
case LDAP_BUSY:
164+
case LDAP_UNAVAILABLE:
165+
case LDAP_ADMINLIMIT_EXCEEDED:
161166
return true;
162167
}
163168
return false;

ydb/core/security/ldap_auth_provider/ldap_auth_provider_ut.cpp

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1168,6 +1168,61 @@ void CheckRequiredLdapSettings(std::function<void(NKikimrProto::TLdapAuthenticat
11681168
ldapServer.Stop();
11691169
}
11701170

1171+
void LdapRefreshGroupsInfoWithError(const ESecurityConnectionType& secureType) {
1172+
TString login = "ldapuser";
1173+
TString password = "ldapUserPassword";
1174+
1175+
TLdapKikimrServer server(InitLdapSettings, secureType);
1176+
auto responses = TCorrectLdapResponse::GetResponses(login);
1177+
LdapMock::TLdapMockResponses updatedResponses = responses;
1178+
LdapMock::TSearchResponseInfo responseServerBusy {
1179+
.ResponseEntries = {}, // Server is busy, can retry attempt
1180+
.ResponseDone = {.Status = LdapMock::EStatus::BUSY}
1181+
};
1182+
1183+
auto& searchResponse = responses.SearchResponses.front();
1184+
searchResponse.second = responseServerBusy;
1185+
LdapMock::TLdapSimpleServer ldapServer(server.GetLdapPort(), {responses, updatedResponses}, secureType == ESecurityConnectionType::LDAPS_SCHEME);
1186+
1187+
auto loginResponse = GetLoginResponse(server, login, password);
1188+
TTestActorRuntime* runtime = server.GetRuntime();
1189+
TActorId sender = runtime->AllocateEdgeActor();
1190+
runtime->Send(new IEventHandle(MakeTicketParserID(), sender, new TEvTicketParser::TEvAuthorizeTicket(loginResponse.Token)), 0);
1191+
TAutoPtr<IEventHandle> handle;
1192+
TEvTicketParser::TEvAuthorizeTicketResult* ticketParserResult = runtime->GrabEdgeEvent<TEvTicketParser::TEvAuthorizeTicketResult>(handle);
1193+
1194+
// Server is busy, return retryable error
1195+
UNIT_ASSERT_C(!ticketParserResult->Error.empty(), "Expected return error message");
1196+
UNIT_ASSERT(ticketParserResult->Token == nullptr);
1197+
UNIT_ASSERT_STRINGS_EQUAL(ticketParserResult->Error.Message, "Could not login via LDAP");
1198+
UNIT_ASSERT_EQUAL(ticketParserResult->Error.Retryable, true);
1199+
1200+
Sleep(TDuration::Seconds(3));
1201+
ldapServer.UpdateResponses();
1202+
Sleep(TDuration::Seconds(7));
1203+
1204+
runtime->Send(new IEventHandle(MakeTicketParserID(), sender, new TEvTicketParser::TEvAuthorizeTicket(loginResponse.Token)), 0);
1205+
ticketParserResult = runtime->GrabEdgeEvent<TEvTicketParser::TEvAuthorizeTicketResult>(handle);
1206+
1207+
// After refresh ticket, server return success
1208+
UNIT_ASSERT_C(ticketParserResult->Error.empty(), ticketParserResult->Error);
1209+
UNIT_ASSERT(ticketParserResult->Token != nullptr);
1210+
const TString ldapDomain = "@ldap";
1211+
UNIT_ASSERT_VALUES_EQUAL(ticketParserResult->Token->GetUserSID(), login + ldapDomain);
1212+
const auto& fetchedGroups = ticketParserResult->Token->GetGroupSIDs();
1213+
THashSet<TString> groups(fetchedGroups.begin(), fetchedGroups.end());
1214+
1215+
THashSet<TString> expectedGroups = TCorrectLdapResponse::GetAllGroups(ldapDomain);
1216+
expectedGroups.insert("all-users@well-known");
1217+
1218+
UNIT_ASSERT_VALUES_EQUAL(fetchedGroups.size(), expectedGroups.size());
1219+
for (const auto& expectedGroup : expectedGroups) {
1220+
UNIT_ASSERT_C(groups.contains(expectedGroup), "Can not find " + expectedGroup);
1221+
}
1222+
1223+
ldapServer.Stop();
1224+
}
1225+
11711226
Y_UNIT_TEST_SUITE(LdapAuthProviderTest) {
11721227
Y_UNIT_TEST(LdapServerIsUnavailable) {
11731228
CheckRequiredLdapSettings(InitLdapSettingsWithUnavailableHost, "Could not login via LDAP", ESecurityConnectionType::START_TLS);
@@ -1246,6 +1301,10 @@ Y_UNIT_TEST_SUITE(LdapAuthProviderTest_LdapsScheme) {
12461301
Y_UNIT_TEST(LdapRefreshRemoveUserBad) {
12471302
LdapRefreshRemoveUserBad(ESecurityConnectionType::LDAPS_SCHEME);
12481303
}
1304+
1305+
Y_UNIT_TEST(LdapRefreshGroupsInfoWithError) {
1306+
LdapRefreshGroupsInfoWithError(ESecurityConnectionType::LDAPS_SCHEME);
1307+
}
12491308
}
12501309

12511310
Y_UNIT_TEST_SUITE(LdapAuthProviderTest_StartTls) {
@@ -1304,6 +1363,10 @@ Y_UNIT_TEST_SUITE(LdapAuthProviderTest_StartTls) {
13041363
Y_UNIT_TEST(LdapRefreshRemoveUserBad) {
13051364
LdapRefreshRemoveUserBad(ESecurityConnectionType::START_TLS);
13061365
}
1366+
1367+
Y_UNIT_TEST(LdapRefreshGroupsInfoWithError) {
1368+
LdapRefreshGroupsInfoWithError(ESecurityConnectionType::START_TLS);
1369+
}
13071370
}
13081371

13091372
Y_UNIT_TEST_SUITE(LdapAuthProviderTest_nonSecure) {
@@ -1362,6 +1425,10 @@ Y_UNIT_TEST_SUITE(LdapAuthProviderTest_nonSecure) {
13621425
Y_UNIT_TEST(LdapRefreshRemoveUserBad) {
13631426
LdapRefreshRemoveUserBad(ESecurityConnectionType::NON_SECURE);
13641427
}
1428+
1429+
Y_UNIT_TEST(LdapRefreshGroupsInfoWithError) {
1430+
LdapRefreshGroupsInfoWithError(ESecurityConnectionType::NON_SECURE);
1431+
}
13651432
}
13661433

13671434
} // NKikimr

ydb/core/security/ldap_auth_provider/ldap_auth_provider_win.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,11 @@ NKikimr::TEvLdapAuthProvider::EStatus ErrorToStatus(int err) {
147147
bool IsRetryableError(int error) {
148148
switch (error) {
149149
case LDAP_SERVER_DOWN:
150+
case LDAP_TIMEOUT:
151+
case LDAP_CONNECT_ERROR:
152+
case LDAP_BUSY:
153+
case LDAP_UNAVAILABLE:
154+
case LDAP_ADMINLIMIT_EXCEEDED:
150155
return true;
151156
}
152157
return false;

ydb/core/security/ticket_parser_impl.h

Lines changed: 35 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1884,7 +1884,7 @@ class TTicketParserImpl : public TActorBootstrapped<TDerived> {
18841884

18851885
template <typename TTokenRecord>
18861886
bool CanRefreshLoginTicket(const TTokenRecord& record) {
1887-
return record.TokenType == TDerived::ETokenType::Login && record.Error.empty();
1887+
return record.TokenType == TDerived::ETokenType::Login && record.Error.Retryable;
18881888
}
18891889

18901890
template <typename TTokenRecord>
@@ -1905,37 +1905,41 @@ class TTicketParserImpl : public TActorBootstrapped<TDerived> {
19051905

19061906
template <typename TTokenRecord>
19071907
bool RefreshLoginTicket(const TString& key, TTokenRecord& record) {
1908-
GetDerived()->ResetTokenRecord(record);
1909-
const TString userSID = record.GetToken()->GetUserSID();
1910-
if (record.IsExternalAuthEnabled()) {
1911-
return RefreshTicketViaExternalAuthProvider(key, record);
1912-
}
1913-
auto database = NLogin::TLoginProvider::GetTokenAudience(record.Ticket);
1914-
if (database.empty()) {
1915-
database = DomainName;
1916-
}
1917-
const auto& lookupDatabases = GetLookupDatabases(record);
1918-
if (std::find(lookupDatabases.begin(), lookupDatabases.end(), database) == lookupDatabases.end()) {
1919-
return false;
1920-
}
1921-
auto itLoginProvider = LoginProviders.find(database);
1922-
if (itLoginProvider == LoginProviders.end()) {
1923-
return false;
1924-
}
1925-
NLogin::TLoginProvider& loginProvider(itLoginProvider->second);
1926-
if (loginProvider.CheckUserExists(userSID)) {
1927-
const std::vector<TString> providerGroups = loginProvider.GetGroupsMembership(userSID);
1928-
const TVector<NACLib::TSID> groups(providerGroups.begin(), providerGroups.end());
1929-
SetToken(key, record, new NACLib::TUserToken({
1930-
.OriginalUserToken = record.Ticket,
1931-
.UserSID = userSID,
1932-
.GroupSIDs = groups,
1933-
.AuthType = record.GetAuthType()
1934-
}));
1935-
} else {
1936-
SetError(key, record, {.Message = "User not found", .Retryable = false});
1908+
if (record.Error.empty()) {
1909+
GetDerived()->ResetTokenRecord(record);
1910+
const TString userSID = record.GetToken()->GetUserSID();
1911+
if (record.IsExternalAuthEnabled()) {
1912+
return RefreshTicketViaExternalAuthProvider(key, record);
1913+
}
1914+
auto database = NLogin::TLoginProvider::GetTokenAudience(record.Ticket);
1915+
if (database.empty()) {
1916+
database = DomainName;
1917+
}
1918+
const auto& lookupDatabases = GetLookupDatabases(record);
1919+
if (std::find(lookupDatabases.begin(), lookupDatabases.end(), database) == lookupDatabases.end()) {
1920+
return false;
1921+
}
1922+
auto itLoginProvider = LoginProviders.find(database);
1923+
if (itLoginProvider == LoginProviders.end()) {
1924+
return false;
1925+
}
1926+
NLogin::TLoginProvider& loginProvider(itLoginProvider->second);
1927+
if (loginProvider.CheckUserExists(userSID)) {
1928+
const std::vector<TString> providerGroups = loginProvider.GetGroupsMembership(userSID);
1929+
const TVector<NACLib::TSID> groups(providerGroups.begin(), providerGroups.end());
1930+
SetToken(key, record, new NACLib::TUserToken({
1931+
.OriginalUserToken = record.Ticket,
1932+
.UserSID = userSID,
1933+
.GroupSIDs = groups,
1934+
.AuthType = record.GetAuthType()
1935+
}));
1936+
} else {
1937+
SetError(key, record, {.Message = "User not found", .Retryable = false});
1938+
}
1939+
return true;
19371940
}
1938-
return true;
1941+
GetDerived()->ResetTokenRecord(record);
1942+
return CanInitLoginToken(key, record);
19391943
}
19401944

19411945
template <typename TTokenRecord>

ydb/core/security/ticket_parser_ut.cpp

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,72 @@ Y_UNIT_TEST_SUITE(TTicketParserTest) {
278278
UNIT_ASSERT_VALUES_EQUAL(result->Token->GetGroupSIDs().size(), 3);
279279
}
280280

281+
Y_UNIT_TEST(LoginRefreshGroupsWithError) {
282+
using namespace Tests;
283+
TPortManager tp;
284+
ui16 kikimrPort = tp.GetPort(2134);
285+
ui16 grpcPort = tp.GetPort(2135);
286+
NKikimrProto::TAuthConfig authConfig;
287+
authConfig.SetUseBlackBox(false);
288+
authConfig.SetUseLoginProvider(true);
289+
authConfig.SetRefreshTime("5s");
290+
auto settings = TServerSettings(kikimrPort, authConfig);
291+
settings.SetDomainName("Root");
292+
settings.CreateTicketParser = NKikimr::CreateTicketParser;
293+
TServer server(settings);
294+
server.EnableGRpc(grpcPort);
295+
server.GetRuntime()->SetLogPriority(NKikimrServices::TICKET_PARSER, NLog::PRI_TRACE);
296+
server.GetRuntime()->SetLogPriority(NKikimrServices::GRPC_CLIENT, NLog::PRI_TRACE);
297+
TClient client(settings);
298+
NClient::TKikimr kikimr(client.GetClientConfig());
299+
client.InitRootScheme();
300+
TTestActorRuntime* runtime = server.GetRuntime();
301+
302+
NLogin::TLoginProvider provider;
303+
304+
provider.Audience = "/Root";
305+
provider.RotateKeys();
306+
307+
TActorId sender = runtime->AllocateEdgeActor();
308+
309+
provider.CreateGroup({.Group = "group1"});
310+
provider.CreateUser({.User = "user1", .Password = "password1"});
311+
provider.AddGroupMembership({.Group = "group1", .Member = "user1"});
312+
313+
NLogin::TLoginProvider emptyProvider;
314+
emptyProvider.Audience = "/Root";
315+
316+
runtime->Send(new IEventHandle(MakeTicketParserID(), sender, new TEvTicketParser::TEvUpdateLoginSecurityState(emptyProvider.GetSecurityState())), 0);
317+
318+
auto loginResponse = provider.LoginUser({.User = "user1", .Password = "password1"});
319+
320+
UNIT_ASSERT_VALUES_EQUAL(loginResponse.Error, "");
321+
322+
runtime->Send(new IEventHandle(MakeTicketParserID(), sender, new TEvTicketParser::TEvAuthorizeTicket(loginResponse.Token)), 0);
323+
324+
TAutoPtr<IEventHandle> handle;
325+
326+
TEvTicketParser::TEvAuthorizeTicketResult* result = runtime->GrabEdgeEvent<TEvTicketParser::TEvAuthorizeTicketResult>(handle);
327+
UNIT_ASSERT_C(!result->Error.empty(), "Expected return error message");
328+
UNIT_ASSERT(result->Token == nullptr);
329+
UNIT_ASSERT_STRINGS_EQUAL(result->Error.Message, "Security state is empty");
330+
UNIT_ASSERT_EQUAL(result->Error.Retryable, true);
331+
332+
Sleep(TDuration::Seconds(3));
333+
runtime->Send(new IEventHandle(MakeTicketParserID(), sender, new TEvTicketParser::TEvUpdateLoginSecurityState(provider.GetSecurityState())), 0);
334+
Sleep(TDuration::Seconds(7));
335+
336+
runtime->Send(new IEventHandle(MakeTicketParserID(), sender, new TEvTicketParser::TEvAuthorizeTicket(loginResponse.Token)), 0);
337+
338+
result = runtime->GrabEdgeEvent<TEvTicketParser::TEvAuthorizeTicketResult>(handle);
339+
340+
UNIT_ASSERT_C(result->Error.empty(), result->Error);
341+
UNIT_ASSERT(result->Token != nullptr);
342+
UNIT_ASSERT_VALUES_EQUAL(result->Token->GetUserSID(), "user1");
343+
UNIT_ASSERT(result->Token->IsExist("group1"));
344+
UNIT_ASSERT_VALUES_EQUAL(result->Token->GetGroupSIDs().size(), 2);
345+
}
346+
281347
Y_UNIT_TEST(LoginCheckRemovedUser) {
282348
using namespace Tests;
283349
TPortManager tp;

ydb/library/testlib/service_mocks/ldap_mock/ldap_defines.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ enum EStatus {
1111
SUCCESS = 0x00,
1212
PROTOCOL_ERROR = 0x02,
1313
INVALID_CREDENTIALS = 0x31,
14+
BUSY = 0x33,
1415
};
1516

1617
enum EProtocolOp {

0 commit comments

Comments
 (0)