aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authormolotkov-and <molotkov-and@ydb.tech>2022-12-09 14:57:39 +0300
committermolotkov-and <molotkov-and@ydb.tech>2022-12-09 14:57:39 +0300
commitb15acf7c8637a386caddf80b785a6eaeb72cac4f (patch)
tree7aad655b77aa06cdc1cd66302f79ae3329fd2e12
parent4a6a9c06500a68ad7e7f3fbb56be9cb789a74b86 (diff)
downloadydb-b15acf7c8637a386caddf80b785a6eaeb72cac4f.tar.gz
Move cloud authentification to oss
-rw-r--r--ydb/core/security/CMakeLists.txt2
-rw-r--r--ydb/core/security/ticket_parser.cpp50
-rw-r--r--ydb/core/security/ticket_parser_impl.h1238
-rw-r--r--ydb/core/security/ticket_parser_ut.cpp702
-rw-r--r--ydb/library/testlib/service_mocks/access_service_mock.h83
-rw-r--r--ydb/library/ycloud/impl/CMakeLists.txt1
-rw-r--r--ydb/library/ycloud/impl/access_service_ut.cpp1
-rw-r--r--ydb/library/ycloud/impl/folder_service_ut.cpp1
-rw-r--r--ydb/library/ycloud/impl/service_account_service_ut.cpp1
-rw-r--r--ydb/library/ycloud/impl/user_account_service_ut.cpp1
10 files changed, 1720 insertions, 360 deletions
diff --git a/ydb/core/security/CMakeLists.txt b/ydb/core/security/CMakeLists.txt
index 070ef0911fc..e63989e7c55 100644
--- a/ydb/core/security/CMakeLists.txt
+++ b/ydb/core/security/CMakeLists.txt
@@ -18,6 +18,8 @@ target_link_libraries(ydb-core-security PUBLIC
cpp-openssl-io
ydb-core-base
ydb-core-protos
+ library-ycloud-api
+ library-ycloud-impl
ydb-library-aclib
library-aclib-protos
ydb-library-login
diff --git a/ydb/core/security/ticket_parser.cpp b/ydb/core/security/ticket_parser.cpp
index 900c9e3b8db..26de3e97b0e 100644
--- a/ydb/core/security/ticket_parser.cpp
+++ b/ydb/core/security/ticket_parser.cpp
@@ -1,7 +1,3 @@
-#include <library/cpp/actors/core/log.h>
-#include <library/cpp/actors/core/hfunc.h>
-#include <ydb/library/security/util.h>
-#include <util/string/vector.h>
#include "ticket_parser_impl.h"
#include "ticket_parser.h"
@@ -18,6 +14,7 @@ class TTicketParser : public TTicketParserImpl<TTicketParser> {
enum class ETokenType {
Unknown,
Unsupported,
+ AccessService,
Builtin,
Login,
};
@@ -27,51 +24,6 @@ class TTicketParser : public TTicketParserImpl<TTicketParser> {
THashMap<TString, TTokenRecord>& GetUserTokens() {
return UserTokens;
}
-
- static TStringStream GetKey(TEvTicketParser::TEvAuthorizeTicket* request) {
- return request->Ticket;
- }
-
- TTokenRefreshRecord MakeTokenRefreshRecord(const TString& key, const TTokenRecord& record) const {
- return {key, record.ExpireTime};
- }
-
- bool IsTicketEmpty(const TStringBuf ticket, TEvTicketParser::TEvAuthorizeTicket::TPtr&) {
- return ticket.empty();
- }
-
- void Handle(TEvTicketParser::TEvRefreshTicket::TPtr& ev, const TActorContext&) {
- UserTokens.erase(ev->Get()->Ticket);
- }
-
- void Refresh(const TString&, const TTokenRecord&, const TActorContext&) {
- // we don't need to refresh anything
- }
-
- static void MakeHtmlTable(TStringBuilder& html) {
- html << "table.ticket-parser-proplist > tbody > tr > td { padding: 1px 3px; } ";
- html << "table.ticket-parser-proplist > tbody > tr > td:first-child { font-weight: bold; text-align: right; } ";
- html << "table.simple-table1 th { margin: 0px 3px; text-align: center; } ";
- html << "table.simple-table1 > tbody > tr > td:nth-child(6) { text-align: right; }";
- html << "table.simple-table1 > tbody > tr > td:nth-child(7) { text-align: right; }";
- html << "table.simple-table1 > tbody > tr > td:nth-child(8) { white-space: nowrap; }";
- html << "table.simple-table1 > tbody > tr > td:nth-child(9) { white-space: nowrap; }";
- html << "table.simple-table1 > tbody > tr > td:nth-child(10) { white-space: nowrap; }";
- html << "table.table-hover tbody tr:hover > td { background-color: #9dddf2; }";
- }
-
-public:
- void StateWork(TAutoPtr<NActors::IEventHandle>& ev, const NActors::TActorContext& ctx) {
- switch (ev->GetTypeRewrite()) {
- HFunc(TEvTicketParser::TEvAuthorizeTicket, TBase::Handle);
- HFunc(TEvTicketParser::TEvRefreshTicket, Handle);
- HFunc(TEvTicketParser::TEvDiscardTicket, TBase::Handle);
- HFunc(TEvTicketParser::TEvUpdateLoginSecurityState, TBase::Handle);
- HFunc(NMon::TEvHttpInfo, TBase::Handle);
- CFunc(TEvents::TSystem::Wakeup, HandleRefresh);
- CFunc(TEvents::TSystem::PoisonPill, Die);
- }
- }
};
IActor* CreateTicketParser(const NKikimrProto::TAuthConfig& authConfig) {
diff --git a/ydb/core/security/ticket_parser_impl.h b/ydb/core/security/ticket_parser_impl.h
index 03dfaf1af24..5b22249e726 100644
--- a/ydb/core/security/ticket_parser_impl.h
+++ b/ydb/core/security/ticket_parser_impl.h
@@ -1,12 +1,21 @@
#pragma once
#include <library/cpp/actors/core/log.h>
#include <library/cpp/actors/core/actor_bootstrapped.h>
+#include <library/cpp/actors/core/hfunc.h>
#include <library/cpp/digest/md5/md5.h>
+#include <ydb/library/ycloud/api/access_service.h>
+#include <ydb/library/ycloud/api/user_account_service.h>
+#include <ydb/library/ycloud/api/service_account_service.h>
+#include <ydb/library/ycloud/impl/user_account_service.h>
+#include <ydb/library/ycloud/impl/service_account_service.h>
+#include <ydb/library/ycloud/impl/access_service.h>
+#include <ydb/library/ycloud/impl/grpc_service_cache.h>
#include <ydb/core/base/counters.h>
#include <ydb/core/mon/mon.h>
#include <ydb/core/base/appdata.h>
#include <ydb/core/base/ticket_parser.h>
#include <ydb/library/security/util.h>
+#include <util/string/vector.h>
#include <util/generic/queue.h>
namespace NKikimr {
@@ -21,7 +30,14 @@ class TTicketParserImpl : public TActorBootstrapped<TDerived> {
}
static TString GetKey(TEvTicketParser::TEvAuthorizeTicket* request) {
- TStringStream key(TDerived::GetKey(request));
+ TStringStream key;
+ if (request->Signature.AccessKeyId) {
+ const auto& sign = request->Signature;
+ key << sign.AccessKeyId << "-" << sign.Signature << ":" << sign.StringToSign << ":"
+ << sign.Service << ":" << sign.Region << ":" << sign.SignedAt.NanoSeconds();
+ } else {
+ key << request->Ticket;
+ }
key << ':';
if (request->Database) {
key << request->Database;
@@ -45,10 +61,52 @@ class TTicketParserImpl : public TActorBootstrapped<TDerived> {
return key.Str();
}
-protected:
- NKikimrProto::TAuthConfig Config;
- TString DomainName;
+ static bool IsRetryableGrpcError(const NGrpc::TGrpcStatus& status) {
+ switch (status.GRpcStatusCode) {
+ case grpc::StatusCode::UNAUTHENTICATED:
+ case grpc::StatusCode::PERMISSION_DENIED:
+ case grpc::StatusCode::INVALID_ARGUMENT:
+ case grpc::StatusCode::NOT_FOUND:
+ return false;
+ }
+ return true;
+ }
+
+ struct TPermissionRecord {
+ TString Subject;
+ bool Required = false;
+ yandex::cloud::priv::servicecontrol::v1::Subject::TypeCase SubjectType;
+ TEvTicketParser::TError Error;
+ TStackVec<std::pair<TString, TString>> Attributes;
+ bool IsPermissionReady() const {
+ return !Subject.empty() || !Error.empty();
+ }
+
+ bool IsPermissionOk() const {
+ return !Subject.empty();
+ }
+
+ bool IsRequired() const {
+ return Required;
+ }
+ };
+
+ template <typename TRequestType>
+ struct TEvRequestWithKey : TRequestType {
+ TString Key;
+
+ TEvRequestWithKey(const TString& key)
+ : Key(key)
+ {}
+ };
+
+ using TEvAccessServiceAuthenticateRequest = TEvRequestWithKey<NCloud::TEvAccessService::TEvAuthenticateRequest>;
+ using TEvAccessServiceAuthorizeRequest = TEvRequestWithKey<NCloud::TEvAccessService::TEvAuthorizeRequest>;
+ using TEvAccessServiceGetUserAccountRequest = TEvRequestWithKey<NCloud::TEvUserAccountService::TEvGetUserAccountRequest>;
+ using TEvAccessServiceGetServiceAccountRequest = TEvRequestWithKey<NCloud::TEvServiceAccountService::TEvGetServiceAccountRequest>;
+
+ TString DomainName;
::NMonitoring::TDynamicCounters::TCounterPtr CounterTicketsReceived;
::NMonitoring::TDynamicCounters::TCounterPtr CounterTicketsSuccess;
::NMonitoring::TDynamicCounters::TCounterPtr CounterTicketsErrors;
@@ -56,6 +114,7 @@ protected:
::NMonitoring::TDynamicCounters::TCounterPtr CounterTicketsErrorsPermanent;
::NMonitoring::TDynamicCounters::TCounterPtr CounterTicketsBuiltin;
::NMonitoring::TDynamicCounters::TCounterPtr CounterTicketsLogin;
+ ::NMonitoring::TDynamicCounters::TCounterPtr CounterTicketsAS;
::NMonitoring::TDynamicCounters::TCounterPtr CounterTicketsCacheHit;
::NMonitoring::TDynamicCounters::TCounterPtr CounterTicketsCacheMiss;
::NMonitoring::THistogramPtr CounterTicketsBuildTime;
@@ -65,107 +124,13 @@ protected:
TDuration MinErrorRefreshTime = TDuration::Seconds(1); // between this and next time we will try to refresh retryable error
TDuration MaxErrorRefreshTime = TDuration::Minutes(1);
TDuration LifeTime = TDuration::Hours(1); // for how long ticket will remain in the cache after last access
- TDuration ExpireTime = TDuration::Hours(24); // after what time ticket will expired and removed from cache
-
- auto ParseTokenType(const TStringBuf tokenType) const {
- if (tokenType == "Login") {
- if (UseLoginProvider) {
- return TDerived::ETokenType::Login;
- } else {
- return TDerived::ETokenType::Unsupported;
- }
- }
- return TDerived::ETokenType::Unknown;
- }
-
- struct TTokenRecordBase {
- TTokenRecordBase(const TTokenRecordBase&) = delete;
- TTokenRecordBase& operator =(const TTokenRecordBase&) = delete;
-
- TString Ticket;
- typename TDerived::ETokenType TokenType = TDerived::ETokenType::Unknown;
- TString Subject; // login
- TEvTicketParser::TError Error;
- TIntrusivePtr<NACLib::TUserToken> Token;
- TString SerializedToken;
- TDeque<THolder<TEventHandle<TEvTicketParser::TEvAuthorizeTicket>>> AuthorizeRequests;
- ui64 ResponsesLeft = 0;
- TInstant InitTime;
- TInstant RefreshTime;
- TInstant ExpireTime;
- TInstant AccessTime;
- TDuration CurrentMaxRefreshTime = TDuration::Seconds(1);
- TDuration CurrentMinRefreshTime = TDuration::Seconds(1);
- TString PeerName;
- TString Database;
- TStackVec<TString> AdditionalSIDs;
-
- TTokenRecordBase(const TStringBuf ticket)
- : Ticket(ticket)
- {}
-
- template <typename T>
- void SetErrorRefreshTime(TTicketParserImpl<T>* ticketParser, TInstant now) {
- if (Error.Retryable) {
- if (CurrentMaxRefreshTime < ticketParser->MaxErrorRefreshTime) {
- CurrentMaxRefreshTime += ticketParser->MinErrorRefreshTime;
- }
- CurrentMinRefreshTime = ticketParser->MinErrorRefreshTime;
- } else {
- CurrentMaxRefreshTime = ticketParser->RefreshTime;
- CurrentMinRefreshTime = CurrentMaxRefreshTime / 2;
- }
- SetRefreshTime(now);
- }
-
- template <typename T>
- void SetOkRefreshTime(TTicketParserImpl<T>* ticketParser, TInstant now) {
- CurrentMaxRefreshTime = ticketParser->RefreshTime;
- CurrentMinRefreshTime = CurrentMaxRefreshTime / 2;
- SetRefreshTime(now);
- }
-
- void SetRefreshTime(TInstant now) {
- if (CurrentMinRefreshTime < CurrentMaxRefreshTime) {
- TDuration currentDuration = CurrentMaxRefreshTime - CurrentMinRefreshTime;
- TDuration refreshDuration = CurrentMinRefreshTime + TDuration::MilliSeconds(RandomNumber<double>() * currentDuration.MilliSeconds());
- RefreshTime = now + refreshDuration;
- } else {
- RefreshTime = now + CurrentMinRefreshTime;
- }
- }
- TString GetSubject() const {
- return Subject;
- }
-
- TString GetAuthType() const {
- switch (TokenType) {
- case TDerived::ETokenType::Unknown:
- return "Unknown";
- case TDerived::ETokenType::Unsupported:
- return "Unsupported";
- case TDerived::ETokenType::Builtin:
- return "Builtin";
- case TDerived::ETokenType::Login:
- return "Login";
- }
- }
-
- bool IsOfflineToken() const {
- switch (TokenType) {
- case TDerived::ETokenType::Builtin:
- case TDerived::ETokenType::Login:
- return true;
- default:
- return false;
- }
- }
-
- bool IsTokenReady() const {
- return Token != nullptr;
- }
- };
+ TActorId AccessServiceValidator;
+ TActorId UserAccountService;
+ TActorId ServiceAccountService;
+ TString UserAccountDomain;
+ TString AccessServiceDomain;
+ TString ServiceDomain;
struct TTokenRefreshRecord {
TString Key;
@@ -180,10 +145,6 @@ protected:
std::unordered_map<TString, NLogin::TLoginProvider> LoginProviders;
bool UseLoginProvider = false;
- static TStringBuf GetTicketFromKey(const TStringBuf key) {
- return key.Before(':');
- }
-
TInstant GetExpireTime(TInstant now) const {
return now + ExpireTime;
}
@@ -196,21 +157,96 @@ protected:
return LifeTime;
}
- static void EnrichUserTokenWithBuiltins(const TTokenRecordBase& tokenRecord) {
- const TString& allAuthenticatedUsers = AppData()->AllAuthenticatedUsers;
- if (!allAuthenticatedUsers.empty()) {
- tokenRecord.Token->AddGroupSID(allAuthenticatedUsers);
- }
- for (const TString& sid : tokenRecord.AdditionalSIDs) {
- tokenRecord.Token->AddGroupSID(sid);
+ template <typename TTokenRecord>
+ void RequestAccessServiceAuthorization(const TString& key, TTokenRecord& record, const TActorContext& ctx) const {
+ for (const auto& [perm, permRecord] : record.Permissions) {
+ const TString& permission(perm);
+ LOG_TRACE_S(ctx, NKikimrServices::TICKET_PARSER,
+ "Ticket " << MaskTicket(record.Ticket)
+ << " asking for AccessServiceAuthorization(" << permission << ")");
+ THolder<TEvAccessServiceAuthorizeRequest> request = MakeHolder<TEvAccessServiceAuthorizeRequest>(key);
+ if (record.Signature.AccessKeyId) {
+ const auto& sign = record.Signature;
+ auto& signature = *request->Request.mutable_signature();
+ signature.set_access_key_id(sign.AccessKeyId);
+ signature.set_string_to_sign(sign.StringToSign);
+ signature.set_signature(sign.Signature);
+
+ auto& v4params = *signature.mutable_v4_parameters();
+ v4params.set_service(sign.Service);
+ v4params.set_region(sign.Region);
+
+ v4params.mutable_signed_at()->set_seconds(sign.SignedAt.Seconds());
+ v4params.mutable_signed_at()->set_nanos(sign.SignedAt.NanoSeconds() % 1000000000ull);
+ } else {
+ request->Request.set_iam_token(record.Ticket);
+ }
+ request->Request.set_permission(permission);
+
+ if (const auto databaseId = record.GetAttributeValue(permission, "database_id"); databaseId) {
+ auto* resourcePath = request->Request.add_resource_path();
+ resourcePath->set_id(databaseId);
+ resourcePath->set_type("ydb.database");
+ } else if (const auto serviceAccountId = record.GetAttributeValue(permission, "service_account_id"); serviceAccountId) {
+ auto* resourcePath = request->Request.add_resource_path();
+ resourcePath->set_id(serviceAccountId);
+ resourcePath->set_type("iam.serviceAccount");
+ }
+
+ if (const auto folderId = record.GetAttributeValue(permission, "folder_id"); folderId) {
+ auto* resourcePath = request->Request.add_resource_path();
+ resourcePath->set_id(folderId);
+ resourcePath->set_type("resource-manager.folder");
+ }
+
+ if (const auto cloudId = record.GetAttributeValue(permission, "cloud_id"); cloudId) {
+ auto* resourcePath = request->Request.add_resource_path();
+ resourcePath->set_id(cloudId);
+ resourcePath->set_type("resource-manager.cloud");
+ }
+
+ record.ResponsesLeft++;
+ ctx.Send(AccessServiceValidator, request.Release());
}
}
template <typename TTokenRecord>
- void InitTokenRecord(const TString&, TTokenRecord& record, const TActorContext&, TInstant) {
- if (record.TokenType == TDerived::ETokenType::Unknown && record.ResponsesLeft == 0) {
- record.Error.Message = "Could not find correct token validator";
- record.Error.Retryable = false;
+ void RequestAccessServiceAuthentication(const TString& key, TTokenRecord& record, const TActorContext& ctx) const {
+ LOG_TRACE_S(ctx, NKikimrServices::TICKET_PARSER,
+ "Ticket " << MaskTicket(record.Ticket)
+ << " asking for AccessServiceAuthentication");
+ THolder<TEvAccessServiceAuthenticateRequest> request = MakeHolder<TEvAccessServiceAuthenticateRequest>(key);
+ if (record.Signature.AccessKeyId) {
+ const auto& sign = record.Signature;
+ auto& signature = *request->Request.mutable_signature();
+ signature.set_access_key_id(sign.AccessKeyId);
+ signature.set_string_to_sign(sign.StringToSign);
+ signature.set_signature(sign.Signature);
+
+ auto& v4params = *signature.mutable_v4_parameters();
+ v4params.set_service(sign.Service);
+ v4params.set_region(sign.Region);
+
+ v4params.mutable_signed_at()->set_seconds(sign.SignedAt.Seconds());
+ v4params.mutable_signed_at()->set_nanos(sign.SignedAt.NanoSeconds() % 1000000000ull);
+ } else {
+ request->Request.set_iam_token(record.Ticket);
+ }
+ record.ResponsesLeft++;
+ ctx.Send(AccessServiceValidator, request.Release());
+ }
+
+
+ TString GetSubjectName(const yandex::cloud::priv::servicecontrol::v1::Subject& subject) {
+ switch (subject.type_case()) {
+ case yandex::cloud::priv::servicecontrol::v1::Subject::TypeCase::kUserAccount:
+ return subject.user_account().id() + "@" + AccessServiceDomain;
+ case yandex::cloud::priv::servicecontrol::v1::Subject::TypeCase::kServiceAccount:
+ return subject.service_account().id() + "@" + AccessServiceDomain;
+ case yandex::cloud::priv::servicecontrol::v1::Subject::TypeCase::kAnonymousAccount:
+ return "anonymous" "@" + AccessServiceDomain;
+ default:
+ return "Unknown subject type";
}
}
@@ -297,90 +333,6 @@ protected:
return false;
}
- template <typename TTokenRecord>
- void InitTokenRecord(const TString& key, TTokenRecord& record, const TActorContext& ctx) {
- TInstant now = ctx.Now();
- record.InitTime = now;
- record.AccessTime = now;
- record.ExpireTime = GetExpireTime(now);
- record.RefreshTime = GetRefreshTime(now);
-
- if (record.Error) {
- return;
- }
-
- if (CanInitBuiltinToken(key, record, ctx) ||
- CanInitLoginToken(key, record, ctx)) {
- return;
- }
-
- GetDerived()->InitTokenRecord(key, record, ctx, now);
- }
-
- template <typename TTokenRecord>
- void EnrichUserToken(const TTokenRecord& record) const {
- EnrichUserTokenWithBuiltins(record);
- }
-
- template <typename TTokenRecord>
- void SetToken(const TString& key, TTokenRecord& record, TIntrusivePtr<NACLib::TUserToken> token, const TActorContext& ctx) {
- TInstant now = ctx.Now();
- record.Error.clear();
- record.Token = token;
- GetDerived()->EnrichUserToken(record);
- if (!token->GetUserSID().empty()) {
- record.Subject = token->GetUserSID();
- }
- record.SerializedToken = token->SerializeAsString();
- if (!record.ExpireTime) {
- record.ExpireTime = GetExpireTime(now);
- }
- if (record.IsOfflineToken()) {
- record.RefreshTime = record.ExpireTime;
- } else {
- record.SetOkRefreshTime(this, now);
- }
- CounterTicketsSuccess->Inc();
- CounterTicketsBuildTime->Collect((now - record.InitTime).MilliSeconds());
- LOG_DEBUG_S(ctx, NKikimrServices::TICKET_PARSER, "Ticket " << MaskTicket(record.Ticket) << " ("
- << record.PeerName << ") has now valid token of " << record.Subject);
- RefreshQueue.push(GetDerived()->MakeTokenRefreshRecord(key, record));
- }
-
- template <typename TTokenRecord>
- void SetError(const TString& key, TTokenRecord& record, const TEvTicketParser::TError& error, const TActorContext& ctx) {
- record.Error = error;
- if (record.Error.Retryable) {
- record.ExpireTime = GetExpireTime(ctx.Now());
- record.SetErrorRefreshTime(this, ctx.Now());
- CounterTicketsErrorsRetryable->Inc();
- LOG_DEBUG_S(ctx, NKikimrServices::TICKET_PARSER, "Ticket " << MaskTicket(record.Ticket) << " ("
- << record.PeerName << ") has now retryable error message '" << error.Message << "'");
- } else {
- record.Token = nullptr;
- record.SerializedToken.clear();
- record.SetOkRefreshTime(this, ctx.Now());
- CounterTicketsErrorsPermanent->Inc();
- LOG_DEBUG_S(ctx, NKikimrServices::TICKET_PARSER, "Ticket " << MaskTicket(record.Ticket) << " ("
- << record.PeerName << ") has now permanent error message '" << error.Message << "'");
- }
- CounterTicketsErrors->Inc();
- RefreshQueue.push(GetDerived()->MakeTokenRefreshRecord(key, record));
- }
-
- void Respond(TTokenRecordBase& record, const TActorContext& ctx) {
- if (record.IsTokenReady()) {
- for (const auto& request : record.AuthorizeRequests) {
- ctx.Send(request->Sender, new TEvTicketParser::TEvAuthorizeTicketResult(record.Ticket, record.Token, record.SerializedToken), 0, request->Cookie);
- }
- } else {
- for (const auto& request : record.AuthorizeRequests) {
- ctx.Send(request->Sender, new TEvTicketParser::TEvAuthorizeTicketResult(record.Ticket, record.Error), 0, request->Cookie);
- }
- }
- record.AuthorizeRequests.clear();
- }
-
void CrackTicket(const TString& ticketBody, TStringBuf& ticket, TStringBuf& ticketType) {
ticket = ticketBody;
ticketType = ticket.NextTok(' ');
@@ -390,31 +342,6 @@ protected:
}
}
- template <typename TTokenRecord>
- void TokenRecordSetup(TTokenRecord& record, TEvTicketParser::TEvAuthorizeTicket::TPtr& ev) {
- record.PeerName = std::move(ev->Get()->PeerName);
- record.Database = std::move(ev->Get()->Database);
- }
-
- template <typename TTokenRecord>
- void SetTokenType(TTokenRecord& record, TStringBuf&, const TStringBuf ticketType) {
- if (ticketType) {
- record.TokenType = GetDerived()->ParseTokenType(ticketType);
- switch (record.TokenType) {
- case TDerived::ETokenType::Unsupported:
- record.Error.Message = "Token is not supported";
- record.Error.Retryable = false;
- break;
- case TDerived::ETokenType::Unknown:
- record.Error.Message = "Unknown token";
- record.Error.Retryable = false;
- break;
- default:
- break;
- }
- }
- }
-
void Handle(TEvTicketParser::TEvAuthorizeTicket::TPtr& ev, const TActorContext& ctx) {
TStringBuf ticket;
TStringBuf ticketType;
@@ -425,7 +352,8 @@ protected:
ui64 cookie = ev->Cookie;
CounterTicketsReceived->Inc();
- if (GetDerived()->IsTicketEmpty(ticket, ev)) {
+ const auto& signature = ev->Get()->Signature;
+ if (ticket.empty() && !signature.AccessKeyId) {
TEvTicketParser::TError error;
error.Message = "Ticket is empty";
error.Retryable = false;
@@ -458,9 +386,18 @@ protected:
}
auto& record = it->second;
- GetDerived()->TokenRecordSetup(record, ev);
+ record.PeerName = std::move(ev->Get()->PeerName);
+ record.Database = std::move(ev->Get()->Database);
+ record.Signature = ev->Get()->Signature;
+ for (const auto& entry: ev->Get()->Entries) {
+ for (const auto& permission : entry.Permissions) {
+ auto& permissionRecord = record.Permissions[permission.Permission];
+ permissionRecord.Attributes = entry.Attributes;
+ permissionRecord.Required = permission.Required;
+ }
+ }
- GetDerived()->SetTokenType(record, ticket, ticketType);
+ SetTokenType(record, ticket, ticketType);
InitTokenRecord(key, record, ctx);
if (record.Error) {
@@ -476,15 +413,270 @@ protected:
record.AuthorizeRequests.emplace_back(ev.Release());
}
- static TString GetLoginProviderKeys(const NLogin::TLoginProvider& loginProvider) {
- TStringBuilder keys;
- for (const auto& [key, pubKey, privKey, expiresAt] : loginProvider.Keys) {
- if (!keys.empty()) {
- keys << ",";
+ void Handle(NCloud::TEvAccessService::TEvAuthenticateResponse::TPtr& ev, const TActorContext& ctx) {
+ NCloud::TEvAccessService::TEvAuthenticateResponse* response = ev->Get();
+ TEvAccessServiceAuthenticateRequest* request = response->Request->Get<TEvAccessServiceAuthenticateRequest>();
+ auto& userTokens = GetDerived()->GetUserTokens();
+ auto it = userTokens.find(request->Key);
+ if (it == userTokens.end()) {
+ // wtf? it should be there
+ LOG_ERROR_S(ctx, NKikimrServices::TICKET_PARSER, "Ticket " << MaskTicket(request->Request.iam_token()) << " has expired during build");
+ } else {
+ const auto& key = it->first;
+ auto& record = it->second;
+ record.ResponsesLeft--;
+ if (response->Status.Ok()) {
+ switch (response->Response.subject().type_case()) {
+ case yandex::cloud::priv::servicecontrol::v1::Subject::TypeCase::kUserAccount:
+ case yandex::cloud::priv::servicecontrol::v1::Subject::TypeCase::kServiceAccount:
+ case yandex::cloud::priv::servicecontrol::v1::Subject::TypeCase::kAnonymousAccount:
+ record.Subject = GetSubjectName(response->Response.subject());
+ break;
+ default:
+ record.Subject.clear();
+ SetError(key, record, {"Unknown subject type", false}, ctx);
+ break;
+ }
+ record.TokenType = TDerived::ETokenType::AccessService;
+ if (!record.Subject.empty()) {
+ switch (response->Response.subject().type_case()) {
+ case yandex::cloud::priv::servicecontrol::v1::Subject::TypeCase::kUserAccount:
+ if (UserAccountService) {
+ LOG_TRACE_S(ctx, NKikimrServices::TICKET_PARSER,
+ "Ticket " << MaskTicket(record.Ticket)
+ << " asking for UserAccount(" << record.Subject << ")");
+ THolder<TEvAccessServiceGetUserAccountRequest> request = MakeHolder<TEvAccessServiceGetUserAccountRequest>(key);
+ request->Token = record.Ticket;
+ request->Request.set_user_account_id(TString(TStringBuf(record.Subject).NextTok('@')));
+ ctx.Send(UserAccountService, request.Release());
+ record.ResponsesLeft++;
+ return;
+ }
+ break;
+ case yandex::cloud::priv::servicecontrol::v1::Subject::TypeCase::kServiceAccount:
+ if (ServiceAccountService) {
+ LOG_TRACE_S(ctx, NKikimrServices::TICKET_PARSER,
+ "Ticket " << MaskTicket(record.Ticket)
+ << " asking for ServiceAccount(" << record.Subject << ")");
+ THolder<TEvAccessServiceGetServiceAccountRequest> request = MakeHolder<TEvAccessServiceGetServiceAccountRequest>(key);
+ request->Token = record.Ticket;
+ request->Request.set_service_account_id(TString(TStringBuf(record.Subject).NextTok('@')));
+ ctx.Send(ServiceAccountService, request.Release());
+ record.ResponsesLeft++;
+ return;
+ }
+ break;
+ default:
+ break;
+ }
+ SetToken(key, record, new NACLib::TUserToken({
+ .OriginalUserToken = record.Ticket,
+ .UserSID = record.Subject,
+ .AuthType = record.GetAuthType()
+ }), ctx);
+ }
+ } else {
+ if (record.ResponsesLeft == 0 && (record.TokenType == TDerived::ETokenType::Unknown || record.TokenType == TDerived::ETokenType::AccessService)) {
+ bool retryable = IsRetryableGrpcError(response->Status);
+ SetError(key, record, {response->Status.Msg, retryable}, ctx);
+ }
+ }
+ if (record.ResponsesLeft == 0) {
+ Respond(record, ctx);
}
- keys << key;
}
- return keys;
+ }
+
+ void Handle(NCloud::TEvUserAccountService::TEvGetUserAccountResponse::TPtr& ev, const TActorContext& ctx) {
+ TEvAccessServiceGetUserAccountRequest* request = ev->Get()->Request->Get<TEvAccessServiceGetUserAccountRequest>();
+ auto& userTokens = GetDerived()->GetUserTokens();
+ auto it = userTokens.find(request->Key);
+ if (it == userTokens.end()) {
+ // wtf? it should be there
+ LOG_ERROR_S(ctx, NKikimrServices::TICKET_PARSER, "Ticket has expired during build (TEvGetUserAccountResponse)");
+ } else {
+ const auto& key = it->first;
+ auto& record = it->second;
+ record.ResponsesLeft--;
+ if (!ev->Get()->Status.Ok()) {
+ SetError(key, record, {ev->Get()->Status.Msg}, ctx);
+ } else {
+ GetDerived()->SetToken(key, record, ev, ctx);
+ }
+ if (record.ResponsesLeft == 0) {
+ Respond(record, ctx);
+ }
+ }
+ }
+
+ void Handle(NCloud::TEvServiceAccountService::TEvGetServiceAccountResponse::TPtr& ev, const TActorContext& ctx) {
+ TEvAccessServiceGetServiceAccountRequest* request = ev->Get()->Request->Get<TEvAccessServiceGetServiceAccountRequest>();
+ auto& userTokens = GetDerived()->GetUserTokens();
+ auto it = userTokens.find(request->Key);
+ if (it == userTokens.end()) {
+ // wtf? it should be there
+ LOG_ERROR_S(ctx, NKikimrServices::TICKET_PARSER, "Ticket has expired during build (TEvGetServiceAccountResponse)");
+ } else {
+ const auto& key = it->first;
+ auto& record = it->second;
+ record.ResponsesLeft--;
+ if (!ev->Get()->Status.Ok()) {
+ SetError(key, record, {ev->Get()->Status.Msg}, ctx);
+ } else {
+ SetToken(key, record, new NACLib::TUserToken(record.Ticket, ev->Get()->Response.name() + "@" + ServiceDomain, {}), ctx);
+ }
+ if (record.ResponsesLeft == 0) {
+ Respond(record, ctx);
+ }
+ }
+ }
+
+ void Handle(NCloud::TEvAccessService::TEvAuthorizeResponse::TPtr& ev, const TActorContext& ctx) {
+ NCloud::TEvAccessService::TEvAuthorizeResponse* response = ev->Get();
+ TEvAccessServiceAuthorizeRequest* request = response->Request->Get<TEvAccessServiceAuthorizeRequest>();
+ const TString& key(request->Key);
+ auto& userTokens = GetDerived()->GetUserTokens();
+ auto itToken = userTokens.find(key);
+ if (itToken == userTokens.end()) {
+ LOG_ERROR_S(ctx, NKikimrServices::TICKET_PARSER,
+ "Ticket(key) "
+ << MaskTicket(key)
+ << " has expired during permission check");
+ } else {
+ auto& record = itToken->second;
+ TString permission = request->Request.permission();
+ TString subject;
+ yandex::cloud::priv::servicecontrol::v1::Subject::TypeCase subjectType = yandex::cloud::priv::servicecontrol::v1::Subject::TypeCase::TYPE_NOT_SET;
+ auto itPermission = record.Permissions.find(permission);
+ if (itPermission != record.Permissions.end()) {
+ if (response->Status.Ok()) {
+ subject = GetSubjectName(response->Response.subject());
+ subjectType = response->Response.subject().type_case();
+ itPermission->second.Subject = subject;
+ itPermission->second.SubjectType = subjectType;
+ itPermission->second.Error.clear();
+ LOG_TRACE_S(ctx, NKikimrServices::TICKET_PARSER,
+ "Ticket "
+ << MaskTicket(record.Ticket)
+ << " permission "
+ << permission
+ << " now has a valid subject \""
+ << subject
+ << "\"");
+ } else {
+ bool retryable = IsRetryableGrpcError(response->Status);
+ itPermission->second.Error = {response->Status.Msg, retryable};
+ if (itPermission->second.Subject.empty() || !retryable) {
+ itPermission->second.Subject.clear();
+ LOG_TRACE_S(ctx, NKikimrServices::TICKET_PARSER,
+ "Ticket "
+ << MaskTicket(record.Ticket)
+ << " permission "
+ << permission
+ << " now has a permanent error \""
+ << itPermission->second.Error
+ << "\" "
+ << " retryable:"
+ << retryable);
+ } else if (retryable) {
+ LOG_TRACE_S(ctx, NKikimrServices::TICKET_PARSER,
+ "Ticket "
+ << MaskTicket(record.Ticket)
+ << " permission "
+ << permission
+ << " now has a retryable error \""
+ << response->Status.Msg
+ << "\"");
+ }
+ }
+ } else {
+ LOG_WARN_S(ctx, NKikimrServices::TICKET_PARSER, "Received response for unknown permission " << permission << " for ticket " << MaskTicket(record.Ticket));
+ }
+ if (--record.ResponsesLeft == 0) {
+ ui32 permissionsOk = 0;
+ ui32 retryableErrors = 0;
+ bool requiredPermissionFailed = false;
+ TEvTicketParser::TError error;
+ for (const auto& [permission, rec] : record.Permissions) {
+ if (rec.IsPermissionOk()) {
+ ++permissionsOk;
+ if (subject.empty()) {
+ subject = rec.Subject;
+ subjectType = rec.SubjectType;
+ }
+ } else if (rec.IsRequired()) {
+ TString id;
+ if (TString folderId = record.GetAttributeValue(permission, "folder_id")) {
+ id += "folder_id " + folderId;
+ } else if (TString cloudId = record.GetAttributeValue(permission, "cloud_id")) {
+ id += "cloud_id " + cloudId;
+ } else if (TString serviceAccountId = record.GetAttributeValue(permission, "service_account_id")) {
+ id += "service_account_id " + serviceAccountId;
+ }
+ error = rec.Error;
+ error.Message = permission + " for " + id + " - " + error.Message;
+ requiredPermissionFailed = true;
+ break;
+ } else {
+ if (rec.Error.Retryable) {
+ ++retryableErrors;
+ error = rec.Error;
+ break;
+ } else if (!error) {
+ error = rec.Error;
+ }
+ }
+ }
+ if (permissionsOk > 0 && retryableErrors == 0 && !requiredPermissionFailed) {
+ record.TokenType = TDerived::ETokenType::AccessService;
+ switch (subjectType) {
+ case yandex::cloud::priv::servicecontrol::v1::Subject::TypeCase::kUserAccount:
+ if (UserAccountService) {
+ LOG_TRACE_S(ctx, NKikimrServices::TICKET_PARSER,
+ "Ticket " << MaskTicket(record.Ticket)
+ << " asking for UserAccount(" << subject << ")");
+ THolder<TEvAccessServiceGetUserAccountRequest> request = MakeHolder<TEvAccessServiceGetUserAccountRequest>(key);
+ request->Token = record.Ticket;
+ request->Request.set_user_account_id(TString(TStringBuf(subject).NextTok('@')));
+ ctx.Send(UserAccountService, request.Release());
+ record.ResponsesLeft++;
+ return;
+ }
+ break;
+ case yandex::cloud::priv::servicecontrol::v1::Subject::TypeCase::kServiceAccount:
+ if (ServiceAccountService) {
+ LOG_TRACE_S(ctx, NKikimrServices::TICKET_PARSER,
+ "Ticket " << MaskTicket(record.Ticket)
+ << " asking for ServiceAccount(" << subject << ")");
+ THolder<TEvAccessServiceGetServiceAccountRequest> request = MakeHolder<TEvAccessServiceGetServiceAccountRequest>(key);
+ request->Token = record.Ticket;
+ request->Request.set_service_account_id(TString(TStringBuf(subject).NextTok('@')));
+ ctx.Send(ServiceAccountService, request.Release());
+ record.ResponsesLeft++;
+ return;
+ }
+ break;
+ default:
+ break;
+ }
+ SetToken(request->Key, record, new NACLib::TUserToken(record.Ticket, subject, {}), ctx);
+ } else if (record.ResponsesLeft == 0 && (record.TokenType == TDerived::ETokenType::Unknown || record.TokenType == TDerived::ETokenType::AccessService)) {
+ SetError(request->Key, record, error, ctx);
+ }
+ }
+ if (record.ResponsesLeft == 0) {
+ Respond(record, ctx);
+ }
+ }
+ }
+
+ void Handle(TEvTicketParser::TEvRefreshTicket::TPtr& ev, const TActorContext& ctx) {
+ const TString& ticket(ev->Get()->Ticket);
+ auto& userTokens = GetDerived()->GetUserTokens();
+ auto it = userTokens.find(ticket);
+ if (it != userTokens.end()) {
+ RefreshTicket(ticket, it->second, ctx);
+ }
}
void Handle(TEvTicketParser::TEvUpdateLoginSecurityState::TPtr& ev, const TActorContext& ctx) {
@@ -510,7 +702,10 @@ protected:
}
auto& record = it->second;
if ((record.ExpireTime > ctx.Now()) && (record.AccessTime + GetLifeTime() > ctx.Now())) {
- GetDerived()->Refresh(key, record, ctx);
+ LOG_DEBUG_S(ctx, NKikimrServices::TICKET_PARSER, "Refreshing ticket " << MaskTicket(record.Ticket));
+ if (!RefreshTicket(key, record, ctx)) {
+ RefreshQueue.push({key, record.RefreshTime});
+ }
} else {
LOG_DEBUG_S(ctx, NKikimrServices::TICKET_PARSER, "Expired ticket " << MaskTicket(record.Ticket));
if (!record.AuthorizeRequests.empty()) {
@@ -523,59 +718,6 @@ protected:
ctx.Schedule(RefreshPeriod, new NActors::TEvents::TEvWakeup());
}
- static TStringBuf HtmlBool(bool v) {
- return v ? "<span style='font-weight:bold'>&#x2611;</span>" : "<span style='font-weight:bold'>&#x2610;</span>";
- }
-
- template <typename TTokenRecord>
- static void WriteTokenRecordInfo(TStringBuilder& html, const TTokenRecord& record) {
- html << "<tr><td>Subject</td><td>" << record.Subject << "</td></tr>";
- html << "<tr><td>Additional SIDs</td><td>" << JoinStrings(record.AdditionalSIDs.begin(), record.AdditionalSIDs.end(), ", ") << "</td></tr>";
- html << "<tr><td>Error</td><td>" << record.Error << "</td></tr>";
- html << "<tr><td>Requests Infly</td><td>" << record.AuthorizeRequests.size() << "</td></tr>";
- html << "<tr><td>Responses Left</td><td>" << record.ResponsesLeft << "</td></tr>";
- TDerived::WriteTokenRecordTimesInfo(html, record);
- html << "<tr><td>Peer Name</td><td>" << record.PeerName << "</td></tr>";
- if (record.Token != nullptr) {
- html << "<tr><td>User SID</td><td>" << record.Token->GetUserSID() << "</td></tr>";
- for (const TString& group : record.Token->GetGroupSIDs()) {
- html << "<tr><td>Group SID</td><td>" << group << "</td></tr>";
- }
- }
- }
-
- template <typename TTokenRecord>
- static void WriteTokenRecordTimesInfo(TStringBuilder& html, const TTokenRecord& record) {
- html << "<tr><td>Expire Time</td><td>" << record.ExpireTime << "</td></tr>";
- html << "<tr><td>Access Time</td><td>" << record.AccessTime << "</td></tr>";
- }
-
- void WriteRefreshTimeValues(TStringBuilder& html) {
- html << "<tr><td>Refresh Period</td><td>" << RefreshPeriod << "</td></tr>";
- }
-
- void WriteAuthorizeMethods(TStringBuilder& html) {
- html << "<tr><td>Login</td><td>" << HtmlBool(UseLoginProvider) << "</td></tr>";
- }
-
- template <typename TTokenRecord>
- static void WriteTokenRecordTimesValues(TStringBuilder& html, const TTokenRecord& record) {
- html << "<td>" << record.ExpireTime << "</td>";
- html << "<td>" << record.AccessTime << "</td>";
- }
-
- template <typename TTokenRecord>
- static void WriteTokenRecordValues(TStringBuilder& html, const TString& key, const TTokenRecord& record) {
- html << "<td>" << record.Database << "</td>";
- html << "<td>" << record.Subject << "</td>";
- html << "<td>" << record.Error << "</td>";
- html << "<td>" << "<a href='ticket_parser?token=" << MD5::Calc(key) << "'>" << HtmlBool(record.Token != nullptr) << "</a>" << "</td>";
- html << "<td>" << record.AuthorizeRequests.size() << "</td>";
- html << "<td>" << record.ResponsesLeft << "</td>";
- TDerived::WriteTokenRecordTimesValues(html, record);
- html << "<td>" << record.PeerName << "</td>";
- }
-
void Handle(NMon::TEvHttpInfo::TPtr& ev, const TActorContext& ctx) {
const auto& params = ev->Get()->Request.GetParams();
TStringBuilder html;
@@ -624,7 +766,7 @@ protected:
html << "<head>";
html << "<script>$('.container').css('width', 'auto');</script>";
html << "<style>";
- TDerived::MakeHtmlTable(html);
+ MakeHtmlTable(html);
html << "</style>";
html << "</head>";
html << "<div style='margin-bottom: 10px; margin-left: 100px'>";
@@ -634,7 +776,7 @@ protected:
if (!RefreshQueue.empty()) {
html << "<tr><td>Refresh Queue Time</td><td>" << RefreshQueue.top().RefreshTime << "</td></tr>";
}
- GetDerived()->WriteRefreshTimeValues(html);
+ WriteRefreshTimeValues(html);
html << "<tr><td>Life Time</td><td>" << LifeTime << "</td></tr>";
html << "<tr><td>Expire Time</td><td>" << ExpireTime << "</td></tr>";
if (UseLoginProvider) {
@@ -676,6 +818,421 @@ protected:
ctx.Send(ev->Sender, new NMon::TEvHttpInfoRes(html));
}
+ static void MakeHtmlTable(TStringBuilder& html) {
+ html << "table.ticket-parser-proplist > tbody > tr > td { padding: 1px 3px; } ";
+ html << "table.ticket-parser-proplist > tbody > tr > td:first-child { font-weight: bold; text-align: right; } ";
+ html << "table.simple-table1 th { margin: 0px 3px; text-align: center; } ";
+ html << "table.simple-table1 > tbody > tr > td:nth-child(2) { text-align: right; }";
+ html << "table.simple-table1 > tbody > tr > td:nth-child(7) { text-align: right; }";
+ html << "table.simple-table1 > tbody > tr > td:nth-child(8) { text-align: right; }";
+ html << "table.simple-table1 > tbody > tr > td:nth-child(9) { white-space: nowrap; }";
+ html << "table.simple-table1 > tbody > tr > td:nth-child(10) { white-space: nowrap; }";
+ html << "table.simple-table1 > tbody > tr > td:nth-child(11) { white-space: nowrap; }";
+ html << "table.simple-table1 > tbody > tr > td:nth-child(12) { white-space: nowrap; }";
+ html << "table.table-hover tbody tr:hover > td { background-color: #9dddf2; }";
+ }
+
+ void WriteRefreshTimeValues(TStringBuilder& html) {
+ html << "<tr><td>Refresh Period</td><td>" << RefreshPeriod << "</td></tr>";
+ html << "<tr><td>Refresh Time</td><td>" << RefreshTime << "</td></tr>";
+ html << "<tr><td>Min Error Refresh Time</td><td>" << MinErrorRefreshTime << "</td></tr>";
+ html << "<tr><td>Max Error Refresh Time</td><td>" << MaxErrorRefreshTime << "</td></tr>";
+ }
+
+protected:
+ NKikimrProto::TAuthConfig Config;
+ TDuration ExpireTime = TDuration::Hours(24); // after what time ticket will expired and removed from cache
+
+ auto ParseTokenType(const TStringBuf tokenType) const {
+ if (tokenType == "Login") {
+ if (UseLoginProvider) {
+ return TDerived::ETokenType::Login;
+ } else {
+ return TDerived::ETokenType::Unsupported;
+ }
+ }
+ if (tokenType == "Bearer" || tokenType == "IAM") {
+ if (AccessServiceValidator) {
+ return TDerived::ETokenType::AccessService;
+ } else {
+ return TDerived::ETokenType::Unsupported;
+ }
+ }
+ return TDerived::ETokenType::Unknown;
+ }
+
+ struct TTokenRecordBase {
+ TTokenRecordBase(const TTokenRecordBase&) = delete;
+ TTokenRecordBase& operator =(const TTokenRecordBase&) = delete;
+
+ TString Ticket;
+ typename TDerived::ETokenType TokenType = TDerived::ETokenType::Unknown;
+ NKikimr::TEvTicketParser::TEvAuthorizeTicket::TAccessKeySignature Signature;
+ THashMap<TString, TPermissionRecord> Permissions;
+ TString Subject; // login
+ TEvTicketParser::TError Error;
+ TIntrusivePtr<NACLib::TUserToken> Token;
+ TString SerializedToken;
+ TDeque<THolder<TEventHandle<TEvTicketParser::TEvAuthorizeTicket>>> AuthorizeRequests;
+ ui64 ResponsesLeft = 0;
+ TInstant InitTime;
+ TInstant RefreshTime;
+ TInstant ExpireTime;
+ TInstant AccessTime;
+ TDuration CurrentMaxRefreshTime = TDuration::Seconds(1);
+ TDuration CurrentMinRefreshTime = TDuration::Seconds(1);
+ TString PeerName;
+ TString Database;
+ TStackVec<TString> AdditionalSIDs;
+
+ TTokenRecordBase(const TStringBuf ticket)
+ : Ticket(ticket)
+ {}
+
+ TString GetAttributeValue(const TString& permission, const TString& key) const {
+ if (auto it = Permissions.find(permission); it != Permissions.end()) {
+ for (const auto& pr : it->second.Attributes) {
+ if (pr.first == key) {
+ return pr.second;
+ }
+ }
+ }
+ return TString();
+ }
+
+ template <typename T>
+ void SetErrorRefreshTime(TTicketParserImpl<T>* ticketParser, TInstant now) {
+ if (Error.Retryable) {
+ if (CurrentMaxRefreshTime < ticketParser->MaxErrorRefreshTime) {
+ CurrentMaxRefreshTime += ticketParser->MinErrorRefreshTime;
+ }
+ CurrentMinRefreshTime = ticketParser->MinErrorRefreshTime;
+ } else {
+ CurrentMaxRefreshTime = ticketParser->RefreshTime;
+ CurrentMinRefreshTime = CurrentMaxRefreshTime / 2;
+ }
+ SetRefreshTime(now);
+ }
+
+ template <typename T>
+ void SetOkRefreshTime(TTicketParserImpl<T>* ticketParser, TInstant now) {
+ CurrentMaxRefreshTime = ticketParser->RefreshTime;
+ CurrentMinRefreshTime = CurrentMaxRefreshTime / 2;
+ SetRefreshTime(now);
+ }
+
+ void SetRefreshTime(TInstant now) {
+ if (CurrentMinRefreshTime < CurrentMaxRefreshTime) {
+ TDuration currentDuration = CurrentMaxRefreshTime - CurrentMinRefreshTime;
+ TDuration refreshDuration = CurrentMinRefreshTime + TDuration::MilliSeconds(RandomNumber<double>() * currentDuration.MilliSeconds());
+ RefreshTime = now + refreshDuration;
+ } else {
+ RefreshTime = now + CurrentMinRefreshTime;
+ }
+ }
+
+ TString GetSubject() const {
+ return Subject;
+ }
+
+ TString GetAuthType() const {
+ switch (TokenType) {
+ case TDerived::ETokenType::Unknown:
+ return "Unknown";
+ case TDerived::ETokenType::Unsupported:
+ return "Unsupported";
+ case TDerived::ETokenType::Builtin:
+ return "Builtin";
+ case TDerived::ETokenType::Login:
+ return "Login";
+ case TDerived::ETokenType::AccessService:
+ return "AccessService";
+ }
+ }
+
+ bool IsOfflineToken() const {
+ switch (TokenType) {
+ case TDerived::ETokenType::Builtin:
+ case TDerived::ETokenType::Login:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ bool IsTokenReady() const {
+ return Token != nullptr;
+ }
+ };
+
+ static TStringBuf GetTicketFromKey(const TStringBuf key) {
+ return key.Before(':');
+ }
+
+ void EnrichUserTokenWithBuiltins(const TTokenRecordBase& record) {
+ const TString& allAuthenticatedUsers = AppData()->AllAuthenticatedUsers;
+ if (!allAuthenticatedUsers.empty()) {
+ record.Token->AddGroupSID(allAuthenticatedUsers);
+ }
+ for (const TString& sid : record.AdditionalSIDs) {
+ record.Token->AddGroupSID(sid);
+ }
+ if (!record.Permissions.empty()) {
+ TString subject;
+ TVector<TString> groups;
+ for (const auto& [permission, rec] : record.Permissions) {
+ if (rec.IsPermissionOk()) {
+ subject = rec.Subject;
+ AddPermissionSids(groups, record, permission);
+ }
+ }
+ if (subject) {
+ groups.emplace_back(subject);
+ }
+ if (record.Subject && record.Subject != subject) {
+ groups.emplace_back(record.Subject);
+ }
+
+ for (const TString& group : groups) {
+ record.Token->AddGroupSID(group);
+ }
+ }
+ }
+
+ void AddPermissionSids(TVector<TString>& sids, const TTokenRecordBase& record, const TString& permission) const {
+ sids.emplace_back(permission + '@' + AccessServiceDomain);
+ sids.emplace_back(permission + '-' + record.GetAttributeValue(permission, "database_id") + '@' + AccessServiceDomain);
+ }
+
+ template <typename TTokenRecord>
+ void InitTokenRecord(const TString& key, TTokenRecord& record, const TActorContext& ctx, TInstant) {
+ if (GetDerived()->CanInitAccessServiceToken(record)) {
+ if (AccessServiceValidator) {
+ if (record.Permissions) {
+ RequestAccessServiceAuthorization(key, record, ctx);
+ } else {
+ RequestAccessServiceAuthentication(key, record, ctx);
+ }
+ CounterTicketsAS->Inc();
+ }
+ }
+
+ if (record.TokenType == TDerived::ETokenType::Unknown && record.ResponsesLeft == 0) {
+ record.Error.Message = "Could not find correct token validator";
+ record.Error.Retryable = false;
+ }
+ }
+
+ template <typename TTokenRecord>
+ bool CanInitAccessServiceToken(const TTokenRecord& record) {
+ return record.TokenType == TDerived::ETokenType::Unknown || record.TokenType == TDerived::ETokenType::AccessService;
+ }
+
+ template <typename TTokenRecord>
+ void InitTokenRecord(const TString& key, TTokenRecord& record, const TActorContext& ctx) {
+ TInstant now = ctx.Now();
+ record.InitTime = now;
+ record.AccessTime = now;
+ record.ExpireTime = GetExpireTime(now);
+ record.RefreshTime = GetRefreshTime(now);
+
+ if (record.Error) {
+ return;
+ }
+
+ if (CanInitBuiltinToken(key, record, ctx) ||
+ CanInitLoginToken(key, record, ctx)) {
+ return;
+ }
+
+ GetDerived()->InitTokenRecord(key, record, ctx, now);
+ }
+
+ template <typename TTokenRecord>
+ void SetToken(const TString& key, TTokenRecord& record, TIntrusivePtr<NACLib::TUserToken> token, const TActorContext& ctx) {
+ TInstant now = ctx.Now();
+ record.Error.clear();
+ record.Token = token;
+ EnrichUserTokenWithBuiltins(record);
+ if (!token->GetUserSID().empty()) {
+ record.Subject = token->GetUserSID();
+ }
+ record.SerializedToken = token->SerializeAsString();
+ if (!record.ExpireTime) {
+ record.ExpireTime = GetExpireTime(now);
+ }
+ if (record.IsOfflineToken()) {
+ record.RefreshTime = record.ExpireTime;
+ } else {
+ record.SetOkRefreshTime(this, now);
+ }
+ CounterTicketsSuccess->Inc();
+ CounterTicketsBuildTime->Collect((now - record.InitTime).MilliSeconds());
+ LOG_DEBUG_S(ctx, NKikimrServices::TICKET_PARSER, "Ticket " << MaskTicket(record.Ticket) << " ("
+ << record.PeerName << ") has now valid token of " << record.Subject);
+ RefreshQueue.push({.Key = key, .RefreshTime = record.RefreshTime});
+ }
+
+ template <typename TTokenRecord>
+ void SetError(const TString& key, TTokenRecord& record, const TEvTicketParser::TError& error, const TActorContext& ctx) {
+ record.Error = error;
+ if (record.Error.Retryable) {
+ record.ExpireTime = GetExpireTime(ctx.Now());
+ record.SetErrorRefreshTime(this, ctx.Now());
+ CounterTicketsErrorsRetryable->Inc();
+ LOG_DEBUG_S(ctx, NKikimrServices::TICKET_PARSER, "Ticket " << MaskTicket(record.Ticket) << " ("
+ << record.PeerName << ") has now retryable error message '" << error.Message << "'");
+ } else {
+ record.Token = nullptr;
+ record.SerializedToken.clear();
+ record.SetOkRefreshTime(this, ctx.Now());
+ CounterTicketsErrorsPermanent->Inc();
+ LOG_DEBUG_S(ctx, NKikimrServices::TICKET_PARSER, "Ticket " << MaskTicket(record.Ticket) << " ("
+ << record.PeerName << ") has now permanent error message '" << error.Message << "'");
+ }
+ CounterTicketsErrors->Inc();
+ RefreshQueue.push({.Key = key, .RefreshTime = record.RefreshTime});
+ }
+
+ void Respond(TTokenRecordBase& record, const TActorContext& ctx) {
+ if (record.IsTokenReady()) {
+ for (const auto& request : record.AuthorizeRequests) {
+ ctx.Send(request->Sender, new TEvTicketParser::TEvAuthorizeTicketResult(record.Ticket, record.Token, record.SerializedToken), 0, request->Cookie);
+ }
+ } else {
+ for (const auto& request : record.AuthorizeRequests) {
+ ctx.Send(request->Sender, new TEvTicketParser::TEvAuthorizeTicketResult(record.Ticket, record.Error), 0, request->Cookie);
+ }
+ }
+ record.AuthorizeRequests.clear();
+ }
+
+ template <typename TTokenRecord>
+ void SetTokenType(TTokenRecord& record, TStringBuf& ticket, const TStringBuf ticketType) {
+ if (ticketType) {
+ record.TokenType = GetDerived()->ParseTokenType(ticketType);
+ switch (record.TokenType) {
+ case TDerived::ETokenType::Unsupported:
+ record.Error.Message = "Token is not supported";
+ record.Error.Retryable = false;
+ break;
+ case TDerived::ETokenType::Unknown:
+ record.Error.Message = "Unknown token";
+ record.Error.Retryable = false;
+ break;
+ default:
+ break;
+ }
+ }
+ if (record.Signature.AccessKeyId) {
+ ticket = record.Signature.AccessKeyId;
+ record.TokenType = TDerived::ETokenType::AccessService;
+ }
+ }
+
+ static TString GetLoginProviderKeys(const NLogin::TLoginProvider& loginProvider) {
+ TStringBuilder keys;
+ for (const auto& [key, pubKey, privKey, expiresAt] : loginProvider.Keys) {
+ if (!keys.empty()) {
+ keys << ",";
+ }
+ keys << key;
+ }
+ return keys;
+ }
+
+ template <typename TTokenRecord>
+ void SetToken(const TString& key, TTokenRecord& record, const NCloud::TEvUserAccountService::TEvGetUserAccountResponse::TPtr& ev, const TActorContext& ctx) {
+ SetToken(key, record, new NACLib::TUserToken({
+ .OriginalUserToken = record.Ticket,
+ .UserSID = ev->Get()->Response.yandex_passport_user_account().login() + "@" + UserAccountDomain,
+ .AuthType = record.GetAuthType()
+ }), ctx);
+ }
+
+ template <typename TTokenRecord>
+ void ResetTokenRecord(TTokenRecord& record, const TActorContext& ctx) {
+ record.Subject.clear();
+ record.InitTime = ctx.Now();
+ }
+
+ template <typename TTokenRecord>
+ bool CanRefreshTicket(const TString& key, TTokenRecord& record, const TActorContext& ctx) {
+ if (AccessServiceValidator && (record.TokenType == TDerived::ETokenType::AccessService || record.TokenType == TDerived::ETokenType::Unknown)) {
+ GetDerived()->ResetTokenRecord(record, ctx);
+ if (record.Permissions) {
+ RequestAccessServiceAuthorization(key, record, ctx);
+ } else {
+ RequestAccessServiceAuthentication(key, record, ctx);
+ }
+ return true;
+ }
+ return false;
+ }
+
+ template <typename TTokenRecord>
+ bool RefreshTicket(const TString& key, TTokenRecord& record, const TActorContext& ctx) {
+ if (record.ResponsesLeft == 0 && record.AuthorizeRequests.empty()) {
+ if (GetDerived()->CanRefreshTicket(key, record, ctx)) {
+ return true;
+ }
+ }
+ record.RefreshTime = GetRefreshTime(ctx.Now());
+ return false;
+ }
+
+ static TStringBuf HtmlBool(bool v) {
+ return v ? "<span style='font-weight:bold'>&#x2611;</span>" : "<span style='font-weight:bold'>&#x2610;</span>";
+ }
+
+ template <typename TTokenRecord>
+ static void WriteTokenRecordInfo(TStringBuilder& html, const TTokenRecord& record) {
+ html << "<tr><td>Database</td><td>" << record.Database << "</td></tr>";
+ html << "<tr><td>Subject</td><td>" << record.Subject << "</td></tr>";
+ html << "<tr><td>Additional SIDs</td><td>" << JoinStrings(record.AdditionalSIDs.begin(), record.AdditionalSIDs.end(), ", ") << "</td></tr>";
+ html << "<tr><td>Error</td><td>" << record.Error << "</td></tr>";
+ html << "<tr><td>Requests Infly</td><td>" << record.AuthorizeRequests.size() << "</td></tr>";
+ html << "<tr><td>Responses Left</td><td>" << record.ResponsesLeft << "</td></tr>";
+ html << "<tr><td>Refresh Time</td><td>" << record.RefreshTime << "</td></tr>";
+ html << "<tr><td>Expire Time</td><td>" << record.ExpireTime << "</td></tr>";
+ html << "<tr><td>Access Time</td><td>" << record.AccessTime << "</td></tr>";
+ html << "<tr><td>Peer Name</td><td>" << record.PeerName << "</td></tr>";
+ if (record.Token != nullptr) {
+ html << "<tr><td>User SID</td><td>" << record.Token->GetUserSID() << "</td></tr>";
+ for (const TString& group : record.Token->GetGroupSIDs()) {
+ html << "<tr><td>Group SID</td><td>" << group << "</td></tr>";
+ }
+ }
+ for (const auto& [permissionName, perm] : record.Permissions) {
+ html << "<tr><td></td><td></td></tr>";
+ html << "<tr><td>Permission \"" << permissionName << (perm.Required ? " (required)" : "") << "\"</td><td>" << (perm.Error ? "Error:" + perm.Error.ToString() : perm.Subject) << "</td></tr>";
+ for (const auto& [attributeName, value] : perm.Attributes) {
+ html << "<tr><td>Attribute \"" << attributeName << "\"</td><td>" << value << "</td></tr>";
+ }
+ }
+ }
+
+ void WriteAuthorizeMethods(TStringBuilder& html) {
+ html << "<tr><td>Login</td><td>" << HtmlBool(UseLoginProvider) << "</td></tr>";
+ html << "<tr><td>Access Service</td><td>" << HtmlBool((bool)AccessServiceValidator) << "</td></tr>";
+ html << "<tr><td>User Account Service</td><td>" << HtmlBool((bool)UserAccountService) << "</td></tr>";
+ html << "<tr><td>Service Account Service</td><td>" << HtmlBool((bool)ServiceAccountService) << "</td></tr>";
+ }
+
+ template <typename TTokenRecord>
+ static void WriteTokenRecordValues(TStringBuilder& html, const TString& key, const TTokenRecord& record) {
+ html << "<td>" << record.Database << "</td>";
+ html << "<td>" << record.Subject << "</td>";
+ html << "<td>" << record.Error << "</td>";
+ html << "<td>" << "<a href='ticket_parser?token=" << MD5::Calc(key) << "'>" << HtmlBool(record.Token != nullptr) << "</a>" << "</td>";
+ html << "<td>" << record.AuthorizeRequests.size() << "</td>";
+ html << "<td>" << record.ResponsesLeft << "</td>";
+ html << "<td>" << record.RefreshTime << "</td>";
+ html << "<td>" << record.ExpireTime << "</td>";
+ html << "<td>" << record.AccessTime << "</td>";
+ html << "<td>" << record.PeerName << "</td>";
+ }
+
void InitCounters(::NMonitoring::TDynamicCounterPtr counters) {
CounterTicketsReceived = counters->GetCounter("TicketsReceived", true);
CounterTicketsSuccess = counters->GetCounter("TicketsSuccess", true);
@@ -684,13 +1241,75 @@ protected:
CounterTicketsErrorsPermanent = counters->GetCounter("TicketsErrorsPermanent", true);
CounterTicketsBuiltin = counters->GetCounter("TicketsBuiltin", true);
CounterTicketsLogin = counters->GetCounter("TicketsLogin", true);
+ CounterTicketsAS = counters->GetCounter("TicketsAS", true);
CounterTicketsCacheHit = counters->GetCounter("TicketsCacheHit", true);
CounterTicketsCacheMiss = counters->GetCounter("TicketsCacheMiss", true);
CounterTicketsBuildTime = counters->GetHistogram("TicketsBuildTimeMs",
NMonitoring::ExplicitHistogram({0, 1, 5, 10, 50, 100, 500, 1000, 2000, 5000, 10000, 30000, 60000}));
}
- void InitAuthProvider(const NActors::TActorContext&) {
+ void InitAuthProvider(const NActors::TActorContext& ctx) {
+ AccessServiceDomain = Config.GetAccessServiceDomain();
+ UserAccountDomain = Config.GetUserAccountDomain();
+ ServiceDomain = Config.GetServiceDomain();
+
+ if (Config.GetUseAccessService()) {
+ NCloud::TAccessServiceSettings settings;
+ settings.Endpoint = Config.GetAccessServiceEndpoint();
+ if (Config.GetUseAccessServiceTLS()) {
+ settings.CertificateRootCA = TUnbufferedFileInput(Config.GetPathToRootCA()).ReadAll();
+ }
+ settings.GrpcKeepAliveTimeMs = Config.GetAccessServiceGrpcKeepAliveTimeMs();
+ settings.GrpcKeepAliveTimeoutMs = Config.GetAccessServiceGrpcKeepAliveTimeoutMs();
+ AccessServiceValidator = ctx.Register(NCloud::CreateAccessService(settings), TMailboxType::Simple, AppData(ctx)->IOPoolId);
+ if (Config.GetCacheAccessServiceAuthentication()) {
+ AccessServiceValidator = ctx.Register(NCloud::CreateGrpcServiceCache<NCloud::TEvAccessService::TEvAuthenticateRequest, NCloud::TEvAccessService::TEvAuthenticateResponse>(
+ AccessServiceValidator,
+ Config.GetGrpcCacheSize(),
+ TDuration::MilliSeconds(Config.GetGrpcSuccessLifeTime()),
+ TDuration::MilliSeconds(Config.GetGrpcErrorLifeTime())), TMailboxType::Simple, AppData(ctx)->UserPoolId);
+ }
+ if (Config.GetCacheAccessServiceAuthorization()) {
+ AccessServiceValidator = ctx.Register(NCloud::CreateGrpcServiceCache<NCloud::TEvAccessService::TEvAuthorizeRequest, NCloud::TEvAccessService::TEvAuthorizeResponse>(
+ AccessServiceValidator,
+ Config.GetGrpcCacheSize(),
+ TDuration::MilliSeconds(Config.GetGrpcSuccessLifeTime()),
+ TDuration::MilliSeconds(Config.GetGrpcErrorLifeTime())), TMailboxType::Simple, AppData(ctx)->UserPoolId);
+ }
+ }
+
+ if (Config.GetUseUserAccountService()) {
+ NCloud::TUserAccountServiceSettings settings;
+ settings.Endpoint = Config.GetUserAccountServiceEndpoint();
+ if (Config.GetUseUserAccountServiceTLS()) {
+ settings.CertificateRootCA = TUnbufferedFileInput(Config.GetPathToRootCA()).ReadAll();
+ }
+ UserAccountService = ctx.Register(CreateUserAccountService(settings), TMailboxType::Simple, AppData(ctx)->IOPoolId);
+ if (Config.GetCacheUserAccountService()) {
+ UserAccountService = ctx.Register(NCloud::CreateGrpcServiceCache<NCloud::TEvUserAccountService::TEvGetUserAccountRequest, NCloud::TEvUserAccountService::TEvGetUserAccountResponse>(
+ UserAccountService,
+ Config.GetGrpcCacheSize(),
+ TDuration::MilliSeconds(Config.GetGrpcSuccessLifeTime()),
+ TDuration::MilliSeconds(Config.GetGrpcErrorLifeTime())), TMailboxType::Simple, AppData(ctx)->UserPoolId);
+ }
+ }
+
+ if (Config.GetUseServiceAccountService()) {
+ NCloud::TServiceAccountServiceSettings settings;
+ settings.Endpoint = Config.GetServiceAccountServiceEndpoint();
+ if (Config.GetUseServiceAccountServiceTLS()) {
+ settings.CertificateRootCA = TUnbufferedFileInput(Config.GetPathToRootCA()).ReadAll();
+ }
+ ServiceAccountService = ctx.Register(NCloud::CreateServiceAccountService(settings), TMailboxType::Simple, AppData(ctx)->IOPoolId);
+ if (Config.GetCacheServiceAccountService()) {
+ ServiceAccountService = ctx.Register(NCloud::CreateGrpcServiceCache<NCloud::TEvServiceAccountService::TEvGetServiceAccountRequest, NCloud::TEvServiceAccountService::TEvGetServiceAccountResponse>(
+ ServiceAccountService,
+ Config.GetGrpcCacheSize(),
+ TDuration::MilliSeconds(Config.GetGrpcSuccessLifeTime()),
+ TDuration::MilliSeconds(Config.GetGrpcErrorLifeTime())), TMailboxType::Simple, AppData(ctx)->UserPoolId);
+ }
+ }
+
if (Config.GetUseLoginProvider()) {
UseLoginProvider = true;
}
@@ -705,6 +1324,19 @@ protected:
ExpireTime = TDuration::Parse(Config.GetExpireTime());
}
+ void Die(const TActorContext& ctx) override {
+ if (AccessServiceValidator) {
+ ctx.Send(AccessServiceValidator, new TEvents::TEvPoisonPill);
+ }
+ if (UserAccountService) {
+ ctx.Send(UserAccountService, new TEvents::TEvPoisonPill);
+ }
+ if (ServiceAccountService) {
+ ctx.Send(ServiceAccountService, new TEvents::TEvPoisonPill);
+ }
+ TBase::Die(ctx);
+ }
+
public:
static constexpr NKikimrServices::TActivity::EType ActorActivityType() { return NKikimrServices::TActivity::TICKET_PARSER_ACTOR; }
@@ -731,6 +1363,22 @@ public:
TBase::Become(&TDerived::StateWork);
}
+ void StateWork(TAutoPtr<NActors::IEventHandle>& ev, const NActors::TActorContext& ctx) {
+ switch (ev->GetTypeRewrite()) {
+ HFunc(TEvTicketParser::TEvAuthorizeTicket, Handle);
+ HFunc(TEvTicketParser::TEvRefreshTicket, Handle);
+ HFunc(TEvTicketParser::TEvDiscardTicket, Handle);
+ HFunc(TEvTicketParser::TEvUpdateLoginSecurityState, Handle);
+ HFunc(NCloud::TEvAccessService::TEvAuthenticateResponse, Handle);
+ HFunc(NCloud::TEvAccessService::TEvAuthorizeResponse, Handle);
+ HFunc(NCloud::TEvUserAccountService::TEvGetUserAccountResponse, Handle);
+ HFunc(NCloud::TEvServiceAccountService::TEvGetServiceAccountResponse, Handle);
+ HFunc(NMon::TEvHttpInfo, Handle);
+ CFunc(TEvents::TSystem::Wakeup, HandleRefresh);
+ CFunc(TEvents::TSystem::PoisonPill, Die);
+ }
+ }
+
TTicketParserImpl(const NKikimrProto::TAuthConfig& authConfig)
: Config(authConfig) {}
};
diff --git a/ydb/core/security/ticket_parser_ut.cpp b/ydb/core/security/ticket_parser_ut.cpp
index d3f02b4968a..b4590a91c54 100644
--- a/ydb/core/security/ticket_parser_ut.cpp
+++ b/ydb/core/security/ticket_parser_ut.cpp
@@ -3,12 +3,18 @@
#include <ydb/public/sdk/cpp/client/ydb_table/table.h>
#include <library/cpp/testing/unittest/registar.h>
#include <library/cpp/testing/unittest/tests_data.h>
+#include <ydb/library/ycloud/api/access_service.h>
+#include <ydb/library/ycloud/api/user_account_service.h>
+#include <ydb/library/testlib/service_mocks/user_account_service_mock.h>
+#include <ydb/library/testlib/service_mocks/access_service_mock.h>
#include <ydb/public/lib/deprecated/kicli/kicli.h>
#include "ticket_parser.h"
namespace NKikimr {
+using TAccessServiceMock = TTicketParserAccessServiceMock;
+
Y_UNIT_TEST_SUITE(TTicketParserTest) {
Y_UNIT_TEST(LoginGood) {
@@ -21,6 +27,7 @@ Y_UNIT_TEST_SUITE(TTicketParserTest) {
authConfig.SetUseLoginProvider(true);
auto settings = TServerSettings(kikimrPort, authConfig);
settings.SetDomainName("Root");
+ settings.CreateTicketParser = NKikimr::CreateTicketParser;
TServer server(settings);
server.EnableGRpc(grpcPort);
server.GetRuntime()->SetLogPriority(NKikimrServices::TICKET_PARSER, NLog::PRI_TRACE);
@@ -48,7 +55,7 @@ Y_UNIT_TEST_SUITE(TTicketParserTest) {
TEvTicketParser::TEvAuthorizeTicketResult* result = runtime->GrabEdgeEvent<TEvTicketParser::TEvAuthorizeTicketResult>(handle);
UNIT_ASSERT(result->Error.empty());
UNIT_ASSERT(result->Token != nullptr);
- UNIT_ASSERT_EQUAL(result->Token->GetUserSID(), "user1");
+ UNIT_ASSERT_VALUES_EQUAL(result->Token->GetUserSID(), "user1");
}
Y_UNIT_TEST(LoginGoodWithGroups) {
@@ -61,6 +68,7 @@ Y_UNIT_TEST_SUITE(TTicketParserTest) {
authConfig.SetUseLoginProvider(true);
auto settings = TServerSettings(kikimrPort, authConfig);
settings.SetDomainName("Root");
+ settings.CreateTicketParser = NKikimr::CreateTicketParser;
TServer server(settings);
server.EnableGRpc(grpcPort);
server.GetRuntime()->SetLogPriority(NKikimrServices::TICKET_PARSER, NLog::PRI_TRACE);
@@ -85,7 +93,7 @@ Y_UNIT_TEST_SUITE(TTicketParserTest) {
auto loginResponse = provider.LoginUser({.User = "user1", .Password = "password1"});
- UNIT_ASSERT_EQUAL(loginResponse.Error, "");
+ UNIT_ASSERT_VALUES_EQUAL(loginResponse.Error, "");
runtime->Send(new IEventHandle(MakeTicketParserID(), sender, new TEvTicketParser::TEvAuthorizeTicket(loginResponse.Token)), 0);
@@ -94,7 +102,7 @@ Y_UNIT_TEST_SUITE(TTicketParserTest) {
TEvTicketParser::TEvAuthorizeTicketResult* result = runtime->GrabEdgeEvent<TEvTicketParser::TEvAuthorizeTicketResult>(handle);
UNIT_ASSERT(result->Error.empty());
UNIT_ASSERT(result->Token != nullptr);
- UNIT_ASSERT_EQUAL(result->Token->GetUserSID(), "user1");
+ UNIT_ASSERT_VALUES_EQUAL(result->Token->GetUserSID(), "user1");
UNIT_ASSERT(result->Token->IsExist("group1"));
}
@@ -108,6 +116,7 @@ Y_UNIT_TEST_SUITE(TTicketParserTest) {
authConfig.SetUseLoginProvider(true);
auto settings = TServerSettings(kikimrPort, authConfig);
settings.SetDomainName("Root");
+ settings.CreateTicketParser = NKikimr::CreateTicketParser;
TServer server(settings);
server.EnableGRpc(grpcPort);
server.GetRuntime()->SetLogPriority(NKikimrServices::TICKET_PARSER, NLog::PRI_TRACE);
@@ -131,7 +140,7 @@ Y_UNIT_TEST_SUITE(TTicketParserTest) {
TEvTicketParser::TEvAuthorizeTicketResult* result = runtime->GrabEdgeEvent<TEvTicketParser::TEvAuthorizeTicketResult>(handle);
UNIT_ASSERT(!result->Error.empty());
- UNIT_ASSERT_EQUAL(result->Error.Message, "Token is not in correct format");
+ UNIT_ASSERT_VALUES_EQUAL(result->Error.Message, "Token is not in correct format");
}
Y_UNIT_TEST(LoginEmptyTicketBad) {
@@ -173,7 +182,690 @@ Y_UNIT_TEST_SUITE(TTicketParserTest) {
TEvTicketParser::TEvAuthorizeTicketResult* result = runtime->GrabEdgeEvent<TEvTicketParser::TEvAuthorizeTicketResult>(handle);
UNIT_ASSERT(!result->Error.empty());
UNIT_ASSERT(result->Token == nullptr);
- UNIT_ASSERT_EQUAL(result->Error.Message, "Ticket is empty");
+ UNIT_ASSERT_VALUES_EQUAL(result->Error.Message, "Ticket is empty");
+ }
+
+ Y_UNIT_TEST(AccessServiceAuthenticationOk) {
+ using namespace Tests;
+
+ TPortManager tp;
+ ui16 port = tp.GetPort(2134);
+ ui16 grpcPort = tp.GetPort(2135);
+ ui16 accessServicePort = tp.GetPort(4284);
+ TString accessServiceEndpoint = "localhost:" + ToString(accessServicePort);
+ NKikimrProto::TAuthConfig authConfig;
+ authConfig.SetUseBlackBox(false);
+ authConfig.SetUseAccessService(true);
+ authConfig.SetUseAccessServiceTLS(false);
+ authConfig.SetAccessServiceEndpoint(accessServiceEndpoint);
+ authConfig.SetUseStaff(false);
+ auto settings = TServerSettings(port, authConfig);
+ settings.SetDomainName("Root");
+ settings.CreateTicketParser = NKikimr::CreateTicketParser;
+ TServer server(settings);
+ server.EnableGRpc(grpcPort);
+ server.GetRuntime()->SetLogPriority(NKikimrServices::TICKET_PARSER, NLog::PRI_TRACE);
+ server.GetRuntime()->SetLogPriority(NKikimrServices::GRPC_CLIENT, NLog::PRI_TRACE);
+ TClient client(settings);
+ NClient::TKikimr kikimr(client.GetClientConfig());
+ client.InitRootScheme();
+ TTestActorRuntime* runtime = server.GetRuntime();
+
+ TString userToken = "user1";
+
+ // Access Server Mock
+ NKikimr::TAccessServiceMock accessServiceMock;
+ grpc::ServerBuilder builder;
+ builder.AddListeningPort(accessServiceEndpoint, grpc::InsecureServerCredentials()).RegisterService(&accessServiceMock);
+ std::unique_ptr<grpc::Server> accessServer(builder.BuildAndStart());
+
+ runtime->Send(new IEventHandle(MakeTicketParserID(), runtime->AllocateEdgeActor(), new TEvTicketParser::TEvAuthorizeTicket("Bearer " + userToken)), 0);
+
+ TAutoPtr<IEventHandle> handle;
+ TEvTicketParser::TEvAuthorizeTicketResult* result = runtime->GrabEdgeEvent<TEvTicketParser::TEvAuthorizeTicketResult>(handle);
+ UNIT_ASSERT(result->Error.empty());
+ }
+
+ Y_UNIT_TEST(AuthenticationWithUserAccount) {
+ using namespace Tests;
+
+ TPortManager tp;
+ ui16 port = tp.GetPort(2134);
+ ui16 grpcPort = tp.GetPort(2135);
+ TString accessServiceEndpoint = "localhost:" + ToString(tp.GetPort(4284));
+ TString userAccountServiceEndpoint = "localhost:" + ToString(tp.GetPort(4285));
+ NKikimrProto::TAuthConfig authConfig;
+ authConfig.SetUseBlackBox(false);
+ authConfig.SetUseStaff(false);
+ authConfig.SetUseAccessService(true);
+ authConfig.SetUseAccessServiceTLS(false);
+ authConfig.SetAccessServiceEndpoint(accessServiceEndpoint);
+ authConfig.SetUseUserAccountService(true);
+ authConfig.SetUseUserAccountServiceTLS(false);
+ authConfig.SetUserAccountServiceEndpoint(userAccountServiceEndpoint);
+ auto settings = TServerSettings(port, authConfig);
+ settings.SetDomainName("Root");
+ settings.CreateTicketParser = NKikimr::CreateTicketParser;
+ TServer server(settings);
+ server.EnableGRpc(grpcPort);
+ server.GetRuntime()->SetLogPriority(NKikimrServices::TICKET_PARSER, NLog::PRI_TRACE);
+ server.GetRuntime()->SetLogPriority(NKikimrServices::GRPC_CLIENT, NLog::PRI_TRACE);
+ TClient client(settings);
+ NClient::TKikimr kikimr(client.GetClientConfig());
+ client.InitRootScheme();
+
+ TString userToken = "user1";
+
+ // Access Server Mock
+ NKikimr::TAccessServiceMock accessServiceMock;
+ grpc::ServerBuilder builder1;
+ builder1.AddListeningPort(accessServiceEndpoint, grpc::InsecureServerCredentials()).RegisterService(&accessServiceMock);
+ std::unique_ptr<grpc::Server> accessServer(builder1.BuildAndStart());
+
+ // User Account Service Mock
+ TUserAccountServiceMock userAccountServiceMock;
+ auto& user1 = userAccountServiceMock.UserAccountData["user1"];
+ user1.mutable_yandex_passport_user_account()->set_login("login1");
+ grpc::ServerBuilder builder2;
+ builder2.AddListeningPort(userAccountServiceEndpoint, grpc::InsecureServerCredentials()).RegisterService(&userAccountServiceMock);
+ std::unique_ptr<grpc::Server> userAccountServer(builder2.BuildAndStart());
+
+ TTestActorRuntime* runtime = server.GetRuntime();
+ TActorId sender = runtime->AllocateEdgeActor();
+ TAutoPtr<IEventHandle> handle;
+
+ runtime->Send(new IEventHandle(MakeTicketParserID(), sender, new TEvTicketParser::TEvAuthorizeTicket(userToken)), 0);
+ TEvTicketParser::TEvAuthorizeTicketResult* result = runtime->GrabEdgeEvent<TEvTicketParser::TEvAuthorizeTicketResult>(handle);
+ UNIT_ASSERT(result->Error.empty());
+ UNIT_ASSERT_VALUES_EQUAL(result->Token->GetUserSID(), "login1@passport");
+ }
+
+ Y_UNIT_TEST(AuthenticationUnavailable) {
+ using namespace Tests;
+
+ TPortManager tp;
+ ui16 port = tp.GetPort(2134);
+ ui16 grpcPort = tp.GetPort(2135);
+ ui16 servicePort = tp.GetPort(4284);
+ TString accessServiceEndpoint = "localhost:" + ToString(servicePort);
+ NKikimrProto::TAuthConfig authConfig;
+ authConfig.SetUseBlackBox(false);
+ authConfig.SetUseAccessService(true);
+ authConfig.SetUseAccessServiceTLS(false);
+ authConfig.SetAccessServiceEndpoint(accessServiceEndpoint);
+ authConfig.SetUseStaff(false);
+ auto settings = TServerSettings(port, authConfig);
+ settings.SetDomainName("Root");
+ settings.CreateTicketParser = NKikimr::CreateTicketParser;
+ TServer server(settings);
+ server.EnableGRpc(grpcPort);
+ server.GetRuntime()->SetLogPriority(NKikimrServices::TICKET_PARSER, NLog::PRI_TRACE);
+ server.GetRuntime()->SetLogPriority(NKikimrServices::GRPC_CLIENT, NLog::PRI_TRACE);
+ TClient client(settings);
+ NClient::TKikimr kikimr(client.GetClientConfig());
+ client.InitRootScheme();
+
+ TString userToken = "user1";
+
+ // Access Server Mock
+ NKikimr::TAccessServiceMock accessServiceMock;
+ grpc::ServerBuilder builder;
+ builder.AddListeningPort(accessServiceEndpoint, grpc::InsecureServerCredentials()).RegisterService(&accessServiceMock);
+ std::unique_ptr<grpc::Server> accessServer(builder.BuildAndStart());
+
+ TTestActorRuntime* runtime = server.GetRuntime();
+ TActorId sender = runtime->AllocateEdgeActor();
+ TAutoPtr<IEventHandle> handle;
+
+ accessServiceMock.UnavailableTokens.insert(userToken);
+ runtime->Send(new IEventHandle(MakeTicketParserID(), sender, new TEvTicketParser::TEvAuthorizeTicket(userToken)), 0);
+ TEvTicketParser::TEvAuthorizeTicketResult* result = runtime->GrabEdgeEvent<TEvTicketParser::TEvAuthorizeTicketResult>(handle);
+ UNIT_ASSERT(!result->Error.empty());
+ UNIT_ASSERT(result->Error.Retryable);
+ UNIT_ASSERT_VALUES_EQUAL(result->Error.Message, "Service Unavailable");
+ }
+
+ Y_UNIT_TEST(AuthenticationUnsupported) {
+ using namespace Tests;
+
+ TPortManager tp;
+ ui16 port = tp.GetPort(2134);
+ ui16 grpcPort = tp.GetPort(2135);
+ ui16 servicePort = tp.GetPort(4284);
+ TString accessServiceEndpoint = "localhost:" + ToString(servicePort);
+ NKikimrProto::TAuthConfig authConfig;
+ authConfig.SetUseBlackBox(false);
+ authConfig.SetUseLoginProvider(false);
+ authConfig.SetUseAccessService(true);
+ authConfig.SetUseAccessServiceTLS(false);
+ authConfig.SetAccessServiceEndpoint(accessServiceEndpoint);
+ authConfig.SetUseStaff(false);
+ auto settings = TServerSettings(port, authConfig);
+ settings.SetDomainName("Root");
+ settings.CreateTicketParser = NKikimr::CreateTicketParser;
+ TServer server(settings);
+ server.EnableGRpc(grpcPort);
+ server.GetRuntime()->SetLogPriority(NKikimrServices::TICKET_PARSER, NLog::PRI_TRACE);
+ server.GetRuntime()->SetLogPriority(NKikimrServices::GRPC_CLIENT, NLog::PRI_TRACE);
+ TClient client(settings);
+ NClient::TKikimr kikimr(client.GetClientConfig());
+ client.InitRootScheme();
+
+ TString userToken = "Login user1";
+
+ // Access Server Mock
+ NKikimr::TAccessServiceMock accessServiceMock;
+ grpc::ServerBuilder builder;
+ builder.AddListeningPort(accessServiceEndpoint, grpc::InsecureServerCredentials()).RegisterService(&accessServiceMock);
+ std::unique_ptr<grpc::Server> accessServer(builder.BuildAndStart());
+
+ TTestActorRuntime* runtime = server.GetRuntime();
+ TActorId sender = runtime->AllocateEdgeActor();
+ TAutoPtr<IEventHandle> handle;
+
+ accessServiceMock.UnavailableTokens.insert(userToken);
+ runtime->Send(new IEventHandle(MakeTicketParserID(), sender, new TEvTicketParser::TEvAuthorizeTicket(userToken)), 0);
+ TEvTicketParser::TEvAuthorizeTicketResult* result = runtime->GrabEdgeEvent<TEvTicketParser::TEvAuthorizeTicketResult>(handle);
+ UNIT_ASSERT(!result->Error.empty());
+ UNIT_ASSERT(!result->Error.Retryable);
+ UNIT_ASSERT_VALUES_EQUAL(result->Error.Message, "Token is not supported");
+ }
+
+ Y_UNIT_TEST(AuthenticationUnknown) {
+ using namespace Tests;
+
+ TPortManager tp;
+ ui16 port = tp.GetPort(2134);
+ ui16 grpcPort = tp.GetPort(2135);
+ ui16 servicePort = tp.GetPort(4284);
+ TString accessServiceEndpoint = "localhost:" + ToString(servicePort);
+ NKikimrProto::TAuthConfig authConfig;
+ authConfig.SetUseBlackBox(false);
+ authConfig.SetUseAccessService(true);
+ authConfig.SetUseAccessServiceTLS(false);
+ authConfig.SetAccessServiceEndpoint(accessServiceEndpoint);
+ authConfig.SetUseStaff(false);
+ auto settings = TServerSettings(port, authConfig);
+ settings.SetDomainName("Root");
+ settings.CreateTicketParser = NKikimr::CreateTicketParser;
+ TServer server(settings);
+ server.EnableGRpc(grpcPort);
+ server.GetRuntime()->SetLogPriority(NKikimrServices::TICKET_PARSER, NLog::PRI_TRACE);
+ server.GetRuntime()->SetLogPriority(NKikimrServices::GRPC_CLIENT, NLog::PRI_TRACE);
+ TClient client(settings);
+ NClient::TKikimr kikimr(client.GetClientConfig());
+ client.InitRootScheme();
+
+ TString userToken = "bebebe user1";
+
+ // Access Server Mock
+ TAccessServiceMock accessServiceMock;
+ grpc::ServerBuilder builder;
+ builder.AddListeningPort(accessServiceEndpoint, grpc::InsecureServerCredentials()).RegisterService(&accessServiceMock);
+ std::unique_ptr<grpc::Server> accessServer(builder.BuildAndStart());
+
+ TTestActorRuntime* runtime = server.GetRuntime();
+ TActorId sender = runtime->AllocateEdgeActor();
+ TAutoPtr<IEventHandle> handle;
+
+ accessServiceMock.UnavailableTokens.insert(userToken);
+ runtime->Send(new IEventHandle(MakeTicketParserID(), sender, new TEvTicketParser::TEvAuthorizeTicket(userToken)), 0);
+ TEvTicketParser::TEvAuthorizeTicketResult* result = runtime->GrabEdgeEvent<TEvTicketParser::TEvAuthorizeTicketResult>(handle);
+ UNIT_ASSERT(!result->Error.empty());
+ UNIT_ASSERT(!result->Error.Retryable);
+ UNIT_ASSERT_VALUES_EQUAL(result->Error.Message, "Unknown token");
+ }
+
+ Y_UNIT_TEST(Authorization) {
+ using namespace Tests;
+
+ TPortManager tp;
+ ui16 port = tp.GetPort(2134);
+ ui16 grpcPort = tp.GetPort(2135);
+ ui16 servicePort = tp.GetPort(4284);
+ TString accessServiceEndpoint = "localhost:" + ToString(servicePort);
+ NKikimrProto::TAuthConfig authConfig;
+ authConfig.SetUseBlackBox(false);
+ authConfig.SetUseAccessService(true);
+ authConfig.SetUseAccessServiceTLS(false);
+ authConfig.SetAccessServiceEndpoint(accessServiceEndpoint);
+ authConfig.SetUseStaff(false);
+ auto settings = TServerSettings(port, authConfig);
+ settings.SetDomainName("Root");
+ settings.CreateTicketParser = NKikimr::CreateTicketParser;
+ TServer server(settings);
+ server.EnableGRpc(grpcPort);
+ server.GetRuntime()->SetLogPriority(NKikimrServices::TICKET_PARSER, NLog::PRI_TRACE);
+ server.GetRuntime()->SetLogPriority(NKikimrServices::GRPC_CLIENT, NLog::PRI_TRACE);
+ TClient client(settings);
+ NClient::TKikimr kikimr(client.GetClientConfig());
+ client.InitRootScheme();
+
+ TString userToken = "user1";
+
+ // Access Server Mock
+ NKikimr::TAccessServiceMock accessServiceMock;
+ grpc::ServerBuilder builder;
+ builder.AddListeningPort(accessServiceEndpoint, grpc::InsecureServerCredentials()).RegisterService(&accessServiceMock);
+ std::unique_ptr<grpc::Server> accessServer(builder.BuildAndStart());
+
+ TTestActorRuntime* runtime = server.GetRuntime();
+ TActorId sender = runtime->AllocateEdgeActor();
+ TAutoPtr<IEventHandle> handle;
+
+ // Authorization successful.
+ runtime->Send(new IEventHandle(MakeTicketParserID(), sender, new TEvTicketParser::TEvAuthorizeTicket(
+ userToken,
+ {{"folder_id", "aaaa1234"}, {"database_id", "bbbb4554"}},
+ {"something.read"})), 0);
+ TEvTicketParser::TEvAuthorizeTicketResult* result = runtime->GrabEdgeEvent<TEvTicketParser::TEvAuthorizeTicketResult>(handle);
+ UNIT_ASSERT(result->Error.empty());
+ UNIT_ASSERT(result->Token->IsExist("something.read-bbbb4554@as"));
+ UNIT_ASSERT(!result->Token->IsExist("something.write-bbbb4554@as"));
+
+ // Authorization failure with not enough permissions.
+ runtime->Send(new IEventHandle(MakeTicketParserID(), sender, new TEvTicketParser::TEvAuthorizeTicket(
+ userToken,
+ {{"folder_id", "aaaa1234"}, {"database_id", "bbbb4554"}},
+ {"something.write"})), 0);
+ result = runtime->GrabEdgeEvent<TEvTicketParser::TEvAuthorizeTicketResult>(handle);
+ UNIT_ASSERT(!result->Error.empty());
+ UNIT_ASSERT_VALUES_EQUAL(result->Error.Message, "Access Denied");
+ UNIT_ASSERT(!result->Error.Retryable);
+
+ // Authorization successful.
+ runtime->Send(new IEventHandle(MakeTicketParserID(), sender, new TEvTicketParser::TEvAuthorizeTicket(
+ userToken,
+ {{"folder_id", "aaaa1234"}, {"database_id", "bbbb4554"}},
+ {"something.read"})), 0);
+ result = runtime->GrabEdgeEvent<TEvTicketParser::TEvAuthorizeTicketResult>(handle);
+ UNIT_ASSERT(result->Error.empty());
+ UNIT_ASSERT(result->Token->IsExist("something.read-bbbb4554@as"));
+ UNIT_ASSERT(!result->Token->IsExist("something.write-bbbb4554@as"));
+
+ // Authorization failure with invalid token.
+ runtime->Send(new IEventHandle(MakeTicketParserID(), sender, new TEvTicketParser::TEvAuthorizeTicket(
+ "invalid",
+ {{"folder_id", "aaaa1234"}, {"database_id", "bbbb4554"}},
+ {"something.read"})), 0);
+ result = runtime->GrabEdgeEvent<TEvTicketParser::TEvAuthorizeTicketResult>(handle);
+ UNIT_ASSERT(!result->Error.empty());
+ UNIT_ASSERT(!result->Error.Retryable);
+ UNIT_ASSERT_VALUES_EQUAL(result->Error.Message, "Access Denied");
+
+ // Authorization failure with access denied token.
+ runtime->Send(new IEventHandle(MakeTicketParserID(), sender, new TEvTicketParser::TEvAuthorizeTicket(
+ "invalid-token1",
+ {{"folder_id", "aaaa1234"}, {"database_id", "bbbb4554"}},
+ {"something.read"})), 0);
+ result = runtime->GrabEdgeEvent<TEvTicketParser::TEvAuthorizeTicketResult>(handle);
+ UNIT_ASSERT(!result->Error.empty());
+ UNIT_ASSERT(!result->Error.Retryable);
+ UNIT_ASSERT_VALUES_EQUAL(result->Error.Message, "Access Denied");
+
+ // Authorization failure with wrong folder_id.
+ accessServiceMock.AllowedResourceIds.emplace("cccc1234");
+ runtime->Send(new IEventHandle(MakeTicketParserID(), sender, new TEvTicketParser::TEvAuthorizeTicket(
+ userToken,
+ {{"folder_id", "XXXXXXXX"}, {"database_id", "XXXXXXXX"}},
+ {"something.read"})), 0);
+ result = runtime->GrabEdgeEvent<TEvTicketParser::TEvAuthorizeTicketResult>(handle);
+ UNIT_ASSERT(!result->Error.empty());
+ UNIT_ASSERT(!result->Error.Retryable);
+ UNIT_ASSERT_VALUES_EQUAL(result->Error.Message, "Access Denied");
+
+ // Authorization successful with right folder_id.
+ accessServiceMock.AllowedResourceIds.emplace("aaaa1234");
+ runtime->Send(new IEventHandle(MakeTicketParserID(), sender, new TEvTicketParser::TEvAuthorizeTicket(
+ userToken,
+ {{"folder_id", "aaaa1234"}, {"database_id", "XXXXXXXX"}},
+ {"something.read"})), 0);
+ result = runtime->GrabEdgeEvent<TEvTicketParser::TEvAuthorizeTicketResult>(handle);
+ UNIT_ASSERT(result->Error.empty());
+ UNIT_ASSERT(result->Token->IsExist("something.read-XXXXXXXX@as"));
+
+ // Authorization successful with right database_id.
+ accessServiceMock.AllowedResourceIds.clear();
+ accessServiceMock.AllowedResourceIds.emplace("bbbb4554");
+ runtime->Send(new IEventHandle(MakeTicketParserID(), sender, new TEvTicketParser::TEvAuthorizeTicket(
+ userToken,
+ {{"folder_id", "XXXXXXXX"}, {"database_id", "bbbb4554"}},
+ {"something.read"})), 0);
+ result = runtime->GrabEdgeEvent<TEvTicketParser::TEvAuthorizeTicketResult>(handle);
+ UNIT_ASSERT(result->Error.empty());
+ UNIT_ASSERT(result->Token->IsExist("something.read-bbbb4554@as"));
+ }
+
+ Y_UNIT_TEST(AuthorizationWithRequiredPermissions) {
+ using namespace Tests;
+
+ TPortManager tp;
+ ui16 port = tp.GetPort(2134);
+ ui16 grpcPort = tp.GetPort(2135);
+ ui16 servicePort = tp.GetPort(4284);
+ TString accessServiceEndpoint = "localhost:" + ToString(servicePort);
+ NKikimrProto::TAuthConfig authConfig;
+ authConfig.SetUseBlackBox(false);
+ authConfig.SetUseAccessService(true);
+ authConfig.SetUseAccessServiceTLS(false);
+ authConfig.SetAccessServiceEndpoint(accessServiceEndpoint);
+ authConfig.SetUseStaff(false);
+ auto settings = TServerSettings(port, authConfig);
+ settings.SetDomainName("Root");
+ settings.CreateTicketParser = NKikimr::CreateTicketParser;
+ TServer server(settings);
+ server.EnableGRpc(grpcPort);
+ server.GetRuntime()->SetLogPriority(NKikimrServices::TICKET_PARSER, NLog::PRI_TRACE);
+ server.GetRuntime()->SetLogPriority(NKikimrServices::GRPC_CLIENT, NLog::PRI_TRACE);
+ TClient client(settings);
+ NClient::TKikimr kikimr(client.GetClientConfig());
+ client.InitRootScheme();
+
+ TString userToken = "user1";
+
+ // Access Server Mock
+ NKikimr::TAccessServiceMock accessServiceMock;
+ grpc::ServerBuilder builder;
+ builder.AddListeningPort(accessServiceEndpoint, grpc::InsecureServerCredentials()).RegisterService(&accessServiceMock);
+ std::unique_ptr<grpc::Server> accessServer(builder.BuildAndStart());
+
+ TTestActorRuntime* runtime = server.GetRuntime();
+ TActorId sender = runtime->AllocateEdgeActor();
+ TAutoPtr<IEventHandle> handle;
+
+ // Authorization successful.
+ runtime->Send(new IEventHandle(MakeTicketParserID(), sender, new TEvTicketParser::TEvAuthorizeTicket(
+ userToken,
+ {{"folder_id", "aaaa1234"}, {"database_id", "bbbb4554"}},
+ TVector<TEvTicketParser::TEvAuthorizeTicket::TPermission>{TEvTicketParser::TEvAuthorizeTicket::Optional("something.read"), TEvTicketParser::TEvAuthorizeTicket::Optional("something.write")})), 0);
+ TEvTicketParser::TEvAuthorizeTicketResult* result = runtime->GrabEdgeEvent<TEvTicketParser::TEvAuthorizeTicketResult>(handle);
+ UNIT_ASSERT(result->Error.empty());
+ UNIT_ASSERT(result->Token->IsExist("something.read-bbbb4554@as"));
+ UNIT_ASSERT(!result->Token->IsExist("something.write-bbbb4554@as"));
+
+ // Authorization failure with not enough permissions.
+ runtime->Send(new IEventHandle(MakeTicketParserID(), sender, new TEvTicketParser::TEvAuthorizeTicket(
+ userToken,
+ {{"folder_id", "aaaa1234"}, {"database_id", "bbbb4554"}},
+ TVector<TEvTicketParser::TEvAuthorizeTicket::TPermission>{TEvTicketParser::TEvAuthorizeTicket::Optional("something.read"), TEvTicketParser::TEvAuthorizeTicket::Required("something.write")})), 0);
+ result = runtime->GrabEdgeEvent<TEvTicketParser::TEvAuthorizeTicketResult>(handle);
+ UNIT_ASSERT(!result->Error.empty());
+ UNIT_ASSERT(!result->Error.Retryable);
+ UNIT_ASSERT_VALUES_EQUAL(result->Error.Message, "something.write for folder_id aaaa1234 - Access Denied");
+ }
+
+ Y_UNIT_TEST(AuthorizationWithUserAccount) {
+ using namespace Tests;
+
+ TPortManager tp;
+ ui16 port = tp.GetPort(2134);
+ ui16 grpcPort = tp.GetPort(2135);
+ TString accessServiceEndpoint = "localhost:" + ToString(tp.GetPort(4284));
+ TString userAccountServiceEndpoint = "localhost:" + ToString(tp.GetPort(4285));
+ NKikimrProto::TAuthConfig authConfig;
+ authConfig.SetUseBlackBox(false);
+ authConfig.SetUseStaff(false);
+ authConfig.SetUseAccessService(true);
+ authConfig.SetUseAccessServiceTLS(false);
+ authConfig.SetAccessServiceEndpoint(accessServiceEndpoint);
+ authConfig.SetUseUserAccountService(true);
+ authConfig.SetUseUserAccountServiceTLS(false);
+ authConfig.SetUserAccountServiceEndpoint(userAccountServiceEndpoint);
+ // placemark1
+ authConfig.SetCacheAccessServiceAuthorization(false);
+ //
+ auto settings = TServerSettings(port, authConfig);
+ settings.SetDomainName("Root");
+ settings.CreateTicketParser = NKikimr::CreateTicketParser;
+ TServer server(settings);
+ server.EnableGRpc(grpcPort);
+ server.GetRuntime()->SetLogPriority(NKikimrServices::TICKET_PARSER, NLog::PRI_TRACE);
+ server.GetRuntime()->SetLogPriority(NKikimrServices::GRPC_CLIENT, NLog::PRI_TRACE);
+ TClient client(settings);
+ NClient::TKikimr kikimr(client.GetClientConfig());
+ client.InitRootScheme();
+
+ TString userToken = "user1";
+
+ // Access Server Mock
+ NKikimr::TAccessServiceMock accessServiceMock;
+ grpc::ServerBuilder builder1;
+ builder1.AddListeningPort(accessServiceEndpoint, grpc::InsecureServerCredentials()).RegisterService(&accessServiceMock);
+ std::unique_ptr<grpc::Server> accessServer(builder1.BuildAndStart());
+
+ // User Account Service Mock
+ TUserAccountServiceMock userAccountServiceMock;
+ auto& user1 = userAccountServiceMock.UserAccountData["user1"];
+ user1.mutable_yandex_passport_user_account()->set_login("login1");
+ grpc::ServerBuilder builder2;
+ builder2.AddListeningPort(userAccountServiceEndpoint, grpc::InsecureServerCredentials()).RegisterService(&userAccountServiceMock);
+ std::unique_ptr<grpc::Server> userAccountServer(builder2.BuildAndStart());
+
+ TTestActorRuntime* runtime = server.GetRuntime();
+ TActorId sender = runtime->AllocateEdgeActor();
+ TAutoPtr<IEventHandle> handle;
+
+ // Authorization successful.
+ runtime->Send(new IEventHandle(MakeTicketParserID(), sender, new TEvTicketParser::TEvAuthorizeTicket(
+ userToken,
+ {{"folder_id", "aaaa1234"}, {"database_id", "bbbb4554"}},
+ {"something.read"})), 0);
+ TEvTicketParser::TEvAuthorizeTicketResult* result = runtime->GrabEdgeEvent<TEvTicketParser::TEvAuthorizeTicketResult>(handle);
+ UNIT_ASSERT(result->Error.empty());
+ UNIT_ASSERT(result->Token->IsExist("something.read-bbbb4554@as"));
+ UNIT_ASSERT(!result->Token->IsExist("something.write-bbbb4554@as"));
+ UNIT_ASSERT_VALUES_EQUAL(result->Token->GetUserSID(), "login1@passport");
+
+ // Authorization failure with not enough permissions.
+ runtime->Send(new IEventHandle(MakeTicketParserID(), sender, new TEvTicketParser::TEvAuthorizeTicket(
+ userToken,
+ {{"folder_id", "aaaa1234"}, {"database_id", "bbbb4554"}},
+ {"something.write"})), 0);
+ result = runtime->GrabEdgeEvent<TEvTicketParser::TEvAuthorizeTicketResult>(handle);
+ UNIT_ASSERT(!result->Error.empty());
+ UNIT_ASSERT(!result->Error.Retryable);
+ UNIT_ASSERT_VALUES_EQUAL(result->Error.Message, "Access Denied");
+
+ // Authorization successful.
+ runtime->Send(new IEventHandle(MakeTicketParserID(), sender, new TEvTicketParser::TEvAuthorizeTicket(
+ userToken,
+ {{"folder_id", "aaaa1234"}, {"database_id", "bbbb4554"}},
+ {"something.read"})), 0);
+ result = runtime->GrabEdgeEvent<TEvTicketParser::TEvAuthorizeTicketResult>(handle);
+ UNIT_ASSERT(result->Error.empty());
+ UNIT_ASSERT(result->Token->IsExist("something.read-bbbb4554@as"));
+ UNIT_ASSERT(!result->Token->IsExist("something.write-bbbb4554@as"));
+ UNIT_ASSERT_VALUES_EQUAL(result->Token->GetUserSID(), "login1@passport");
+
+ accessServiceMock.AllowedUserPermissions.insert("user1-something.write");
+
+ // Authorization successful - 2
+ runtime->Send(new IEventHandle(MakeTicketParserID(), sender, new TEvTicketParser::TEvAuthorizeTicket(
+ userToken,
+ {{"folder_id", "aaaa1234"}, {"database_id", "bbbb4554"}},
+ TVector<TString>{"something.read", "something.write"})), 0);
+ result = runtime->GrabEdgeEvent<TEvTicketParser::TEvAuthorizeTicketResult>(handle);
+ UNIT_ASSERT(result->Error.empty());
+ UNIT_ASSERT(result->Token->IsExist("something.read-bbbb4554@as"));
+ // placemark 1
+ UNIT_ASSERT(result->Token->IsExist("something.write-bbbb4554@as"));
+ UNIT_ASSERT_VALUES_EQUAL(result->Token->GetUserSID(), "login1@passport");
+ }
+
+ Y_UNIT_TEST(AuthorizationWithUserAccount2) {
+ using namespace Tests;
+
+ TPortManager tp;
+ ui16 port = tp.GetPort(2134);
+ ui16 grpcPort = tp.GetPort(2135);
+ TString accessServiceEndpoint = "localhost:" + ToString(tp.GetPort(4284));
+ TString userAccountServiceEndpoint = "localhost:" + ToString(tp.GetPort(4285));
+ NKikimrProto::TAuthConfig authConfig;
+ authConfig.SetUseBlackBox(false);
+ authConfig.SetUseStaff(false);
+ authConfig.SetUseAccessService(true);
+ authConfig.SetUseAccessServiceTLS(false);
+ authConfig.SetAccessServiceEndpoint(accessServiceEndpoint);
+ authConfig.SetUseUserAccountService(true);
+ authConfig.SetUseUserAccountServiceTLS(false);
+ authConfig.SetUserAccountServiceEndpoint(userAccountServiceEndpoint);
+ auto settings = TServerSettings(port, authConfig);
+ settings.SetDomainName("Root");
+ settings.CreateTicketParser = NKikimr::CreateTicketParser;
+ TServer server(settings);
+ server.EnableGRpc(grpcPort);
+ server.GetRuntime()->SetLogPriority(NKikimrServices::TICKET_PARSER, NLog::PRI_TRACE);
+ server.GetRuntime()->SetLogPriority(NKikimrServices::GRPC_CLIENT, NLog::PRI_TRACE);
+ TClient client(settings);
+ NClient::TKikimr kikimr(client.GetClientConfig());
+ client.InitRootScheme();
+
+ TString userToken = "user1";
+
+ // Access Server Mock
+ NKikimr::TAccessServiceMock accessServiceMock;
+ grpc::ServerBuilder builder1;
+ builder1.AddListeningPort(accessServiceEndpoint, grpc::InsecureServerCredentials()).RegisterService(&accessServiceMock);
+ std::unique_ptr<grpc::Server> accessServer(builder1.BuildAndStart());
+
+ // User Account Service Mock
+ TUserAccountServiceMock userAccountServiceMock;
+ auto& user1 = userAccountServiceMock.UserAccountData["user1"];
+ user1.mutable_yandex_passport_user_account()->set_login("login1");
+ grpc::ServerBuilder builder2;
+ builder2.AddListeningPort(userAccountServiceEndpoint, grpc::InsecureServerCredentials()).RegisterService(&userAccountServiceMock);
+ std::unique_ptr<grpc::Server> userAccountServer(builder2.BuildAndStart());
+
+ TTestActorRuntime* runtime = server.GetRuntime();
+ TActorId sender = runtime->AllocateEdgeActor();
+ TAutoPtr<IEventHandle> handle;
+
+ accessServiceMock.AllowedUserPermissions.insert("user1-something.write");
+ accessServiceMock.AllowedUserPermissions.erase("user1-something.list");
+ accessServiceMock.AllowedUserPermissions.erase("user1-something.read");
+
+ // Authorization successful - 2
+ runtime->Send(new IEventHandle(MakeTicketParserID(), sender, new TEvTicketParser::TEvAuthorizeTicket(
+ userToken,
+ {{"folder_id", "aaaa1234"}, {"database_id", "bbbb4554"}},
+ {"something.list", "something.read", "something.write", "something.eat", "somewhere.sleep"})), 0);
+ TEvTicketParser::TEvAuthorizeTicketResult* result = runtime->GrabEdgeEvent<TEvTicketParser::TEvAuthorizeTicketResult>(handle);
+ UNIT_ASSERT(result->Error.empty());
+ UNIT_ASSERT(!result->Token->IsExist("something.read-bbbb4554@as"));
+ UNIT_ASSERT(!result->Token->IsExist("something.list-bbbb4554@as"));
+ UNIT_ASSERT(result->Token->IsExist("something.write-bbbb4554@as"));
+ UNIT_ASSERT_VALUES_EQUAL(result->Token->GetUserSID(), "login1@passport");
+ }
+
+ Y_UNIT_TEST(AuthorizationUnavailable) {
+ using namespace Tests;
+
+ TPortManager tp;
+ ui16 port = tp.GetPort(2134);
+ ui16 grpcPort = tp.GetPort(2135);
+ ui16 servicePort = tp.GetPort(4284);
+ TString accessServiceEndpoint = "localhost:" + ToString(servicePort);
+ NKikimrProto::TAuthConfig authConfig;
+ authConfig.SetUseBlackBox(false);
+ authConfig.SetUseAccessService(true);
+ authConfig.SetUseAccessServiceTLS(false);
+ authConfig.SetAccessServiceEndpoint(accessServiceEndpoint);
+ authConfig.SetUseStaff(false);
+ auto settings = TServerSettings(port, authConfig);
+ settings.SetDomainName("Root");
+ settings.CreateTicketParser = NKikimr::CreateTicketParser;
+ TServer server(settings);
+ server.EnableGRpc(grpcPort);
+ server.GetRuntime()->SetLogPriority(NKikimrServices::TICKET_PARSER, NLog::PRI_TRACE);
+ server.GetRuntime()->SetLogPriority(NKikimrServices::GRPC_CLIENT, NLog::PRI_TRACE);
+ TClient client(settings);
+ NClient::TKikimr kikimr(client.GetClientConfig());
+ client.InitRootScheme();
+
+ TString userToken = "user1";
+
+ // Access Server Mock
+ NKikimr::TAccessServiceMock accessServiceMock;
+ grpc::ServerBuilder builder;
+ builder.AddListeningPort(accessServiceEndpoint, grpc::InsecureServerCredentials()).RegisterService(&accessServiceMock);
+ std::unique_ptr<grpc::Server> accessServer(builder.BuildAndStart());
+
+ TTestActorRuntime* runtime = server.GetRuntime();
+ TActorId sender = runtime->AllocateEdgeActor();
+ TAutoPtr<IEventHandle> handle;
+
+ accessServiceMock.UnavailableUserPermissions.insert(userToken + "-something.write");
+
+ // Authorization unsuccessfull.
+ runtime->Send(new IEventHandle(MakeTicketParserID(), sender, new TEvTicketParser::TEvAuthorizeTicket(
+ userToken,
+ {{"folder_id", "aaaa1234"}, {"database_id", "bbbb4554"}},
+ TVector<TString>{"something.read", "something.write"})), 0);
+ TEvTicketParser::TEvAuthorizeTicketResult* result = runtime->GrabEdgeEvent<TEvTicketParser::TEvAuthorizeTicketResult>(handle);
+ UNIT_ASSERT(!result->Error.empty());
+ UNIT_ASSERT(result->Error.Retryable);
+ UNIT_ASSERT_VALUES_EQUAL(result->Error.Message, "Service Unavailable");
+ }
+
+ Y_UNIT_TEST(AuthorizationModify) {
+ using namespace Tests;
+
+ TPortManager tp;
+ ui16 port = tp.GetPort(2134);
+ ui16 grpcPort = tp.GetPort(2135);
+ ui16 servicePort = tp.GetPort(4284);
+ TString accessServiceEndpoint = "localhost:" + ToString(servicePort);
+ NKikimrProto::TAuthConfig authConfig;
+ authConfig.SetUseBlackBox(false);
+ authConfig.SetUseAccessService(true);
+ authConfig.SetUseAccessServiceTLS(false);
+ authConfig.SetAccessServiceEndpoint(accessServiceEndpoint);
+ authConfig.SetUseStaff(false);
+ auto settings = TServerSettings(port, authConfig);
+ settings.SetDomainName("Root");
+ settings.CreateTicketParser = NKikimr::CreateTicketParser;
+ TServer server(settings);
+ server.EnableGRpc(grpcPort);
+ server.GetRuntime()->SetLogPriority(NKikimrServices::TICKET_PARSER, NLog::PRI_TRACE);
+ server.GetRuntime()->SetLogPriority(NKikimrServices::GRPC_CLIENT, NLog::PRI_TRACE);
+ TClient client(settings);
+ NClient::TKikimr kikimr(client.GetClientConfig());
+ client.InitRootScheme();
+
+ TString userToken = "user1";
+
+ // Access Server Mock
+ NKikimr::TAccessServiceMock accessServiceMock;
+ grpc::ServerBuilder builder;
+ builder.AddListeningPort(accessServiceEndpoint, grpc::InsecureServerCredentials()).RegisterService(&accessServiceMock);
+ std::unique_ptr<grpc::Server> accessServer(builder.BuildAndStart());
+
+ TTestActorRuntime* runtime = server.GetRuntime();
+ TActorId sender = runtime->AllocateEdgeActor();
+ TAutoPtr<IEventHandle> handle;
+
+ // Authorization successful.
+ runtime->Send(new IEventHandle(MakeTicketParserID(), sender, new TEvTicketParser::TEvAuthorizeTicket(
+ userToken,
+ {{"folder_id", "aaaa1234"}, {"database_id", "bbbb4554"}},
+ {"something.read"})), 0);
+ TEvTicketParser::TEvAuthorizeTicketResult* result = runtime->GrabEdgeEvent<TEvTicketParser::TEvAuthorizeTicketResult>(handle);
+ UNIT_ASSERT(result->Error.empty());
+ UNIT_ASSERT(result->Token->IsExist("something.read-bbbb4554@as"));
+ UNIT_ASSERT(!result->Token->IsExist("something.write-bbbb4554@as"));
+
+ accessServiceMock.AllowedUserPermissions.insert(userToken + "-something.write");
+ runtime->Send(new IEventHandle(MakeTicketParserID(), sender, new TEvTicketParser::TEvDiscardTicket(userToken)), 0);
+
+ // Authorization successful with new permissions.
+ runtime->Send(new IEventHandle(MakeTicketParserID(), sender, new TEvTicketParser::TEvAuthorizeTicket(
+ userToken,
+ {{"folder_id", "aaaa1234"}, {"database_id", "bbbb4554"}},
+ TVector<TString>{"something.read", "something.write"})), 0);
+ result = runtime->GrabEdgeEvent<TEvTicketParser::TEvAuthorizeTicketResult>(handle);
+ UNIT_ASSERT(result->Error.empty());
+ UNIT_ASSERT(result->Token->IsExist("something.read-bbbb4554@as"));
+ UNIT_ASSERT(result->Token->IsExist("something.write-bbbb4554@as"));
}
}
}
diff --git a/ydb/library/testlib/service_mocks/access_service_mock.h b/ydb/library/testlib/service_mocks/access_service_mock.h
index 5fb88cda0fd..486f36b63bc 100644
--- a/ydb/library/testlib/service_mocks/access_service_mock.h
+++ b/ydb/library/testlib/service_mocks/access_service_mock.h
@@ -28,11 +28,10 @@ public:
}
}
- virtual grpc::Status Authenticate(
+ grpc::Status Authenticate(
grpc::ServerContext* ctx,
const yandex::cloud::priv::servicecontrol::v1::AuthenticateRequest* request,
- yandex::cloud::priv::servicecontrol::v1::AuthenticateResponse* response) override
- {
+ yandex::cloud::priv::servicecontrol::v1::AuthenticateResponse* response) override {
TString key;
if (request->has_signature()) {
key = request->signature().v4_parameters().service();
@@ -50,11 +49,10 @@ public:
}
}
- virtual grpc::Status Authorize(
+ grpc::Status Authorize(
grpc::ServerContext* ctx,
const yandex::cloud::priv::servicecontrol::v1::AuthorizeRequest* request,
- yandex::cloud::priv::servicecontrol::v1::AuthorizeResponse* response) override
- {
+ yandex::cloud::priv::servicecontrol::v1::AuthorizeResponse* response) override {
const TString& lastResourceId = request->resource_path(request->resource_path_size() - 1).id();
const TString& token = request->signature().access_key_id() + request->iam_token() + "-" + request->permission() + "-" + lastResourceId;
auto it = AuthorizeData.find(token);
@@ -67,3 +65,76 @@ public:
}
}
};
+
+class TTicketParserAccessServiceMock : public yandex::cloud::priv::servicecontrol::v1::AccessService::Service {
+public:
+ std::atomic_uint64_t AuthenticateCount = 0;
+ std::atomic_uint64_t AuthorizeCount= 0;
+
+ THashSet<TString> InvalidTokens = {"invalid"};
+ THashSet<TString> UnavailableTokens;
+ THashSet<TString> AllowedUserTokens = {"user1"};
+ THashMap<TString, TString> AllowedServiceTokens = {{"service1", "root1/folder1"}};
+
+ grpc::Status Authenticate(
+ grpc::ServerContext*,
+ const yandex::cloud::priv::servicecontrol::v1::AuthenticateRequest* request,
+ yandex::cloud::priv::servicecontrol::v1::AuthenticateResponse* response) override {
+
+ ++AuthenticateCount;
+ TString token = request->iam_token();
+ if (InvalidTokens.count(token) > 0) {
+ return grpc::Status(grpc::StatusCode::INVALID_ARGUMENT, "Invalid Token");
+ }
+ if (UnavailableTokens.count(token) > 0) {
+ return grpc::Status(grpc::StatusCode::UNAVAILABLE, "Service Unavailable");
+ }
+ if (AllowedUserTokens.count(token) > 0) {
+ response->mutable_subject()->mutable_user_account()->set_id(token);
+ return grpc::Status::OK;
+ }
+ if (AllowedServiceTokens.count(token) > 0) {
+ response->mutable_subject()->mutable_service_account()->set_id(token);
+ response->mutable_subject()->mutable_service_account()->set_folder_id(AllowedServiceTokens[token]);
+ return grpc::Status::OK;
+ }
+ return grpc::Status(grpc::StatusCode::UNAUTHENTICATED, "Access Denied");
+ }
+
+ THashSet<TString> AllowedUserPermissions = {"user1-something.read"};
+ THashMap<TString, TString> AllowedServicePermissions = {{"service1-something.write", "root1/folder1"}};
+ THashSet<TString> AllowedResourceIds = {};
+ THashSet<TString> UnavailableUserPermissions;
+
+ grpc::Status Authorize(
+ grpc::ServerContext*,
+ const yandex::cloud::priv::servicecontrol::v1::AuthorizeRequest* request,
+ yandex::cloud::priv::servicecontrol::v1::AuthorizeResponse* response) override {
+ ++AuthorizeCount;
+ TString token = request->iam_token();
+ if (UnavailableUserPermissions.count(token + '-' + request->permission()) > 0) {
+ return grpc::Status(grpc::StatusCode::UNAVAILABLE, "Service Unavailable");
+ }
+ bool allowedResource = true;
+ if (!AllowedResourceIds.empty()) {
+ allowedResource = false;
+ for (const auto& resourcePath : request->resource_path()) {
+ if (AllowedResourceIds.count(resourcePath.id()) > 0) {
+ allowedResource = true;
+ }
+ }
+ }
+ if (allowedResource) {
+ if (AllowedUserPermissions.count(token + '-' + request->permission()) > 0) {
+ response->mutable_subject()->mutable_user_account()->set_id(token);
+ return grpc::Status::OK;
+ }
+ if (AllowedServicePermissions.count(token + '-' + request->permission()) > 0) {
+ response->mutable_subject()->mutable_service_account()->set_id(token);
+ response->mutable_subject()->mutable_service_account()->set_folder_id(AllowedServicePermissions[token + '-' + request->permission()]);
+ return grpc::Status::OK;
+ }
+ }
+ return grpc::Status(grpc::StatusCode::UNAUTHENTICATED, "Access Denied");
+ }
+};
diff --git a/ydb/library/ycloud/impl/CMakeLists.txt b/ydb/library/ycloud/impl/CMakeLists.txt
index 09f018185e2..2b758abdf3e 100644
--- a/ydb/library/ycloud/impl/CMakeLists.txt
+++ b/ydb/library/ycloud/impl/CMakeLists.txt
@@ -18,7 +18,6 @@ target_link_libraries(library-ycloud-impl PUBLIC
cpp-grpc-client
library-cpp-json
ydb-core-base
- ydb-core-grpc_services
lib-deprecated-client
lib-deprecated-kicli
)
diff --git a/ydb/library/ycloud/impl/access_service_ut.cpp b/ydb/library/ycloud/impl/access_service_ut.cpp
index 7ebf90fd29f..c4dc207b4ca 100644
--- a/ydb/library/ycloud/impl/access_service_ut.cpp
+++ b/ydb/library/ycloud/impl/access_service_ut.cpp
@@ -2,7 +2,6 @@
#include <library/cpp/actors/core/event_local.h>
#include <ydb/core/testlib/test_client.h>
#include <ydb/library/testlib/service_mocks/access_service_mock.h>
-#include <ydb/core/grpc_services/grpc_helper.h>
#include <library/cpp/grpc/server/grpc_server.h>
#include <ydb/public/lib/deprecated/kicli/kicli.h>
#include <library/cpp/testing/unittest/registar.h>
diff --git a/ydb/library/ycloud/impl/folder_service_ut.cpp b/ydb/library/ycloud/impl/folder_service_ut.cpp
index 3c1261e3297..a3fd4b1bde4 100644
--- a/ydb/library/ycloud/impl/folder_service_ut.cpp
+++ b/ydb/library/ycloud/impl/folder_service_ut.cpp
@@ -2,7 +2,6 @@
#include <library/cpp/actors/core/event_local.h>
#include <ydb/core/testlib/test_client.h>
#include <ydb/library/testlib/service_mocks/folder_service_mock.h>
-#include <ydb/core/grpc_services/grpc_helper.h>
#include <library/cpp/grpc/server/grpc_server.h>
#include <ydb/public/lib/deprecated/kicli/kicli.h>
#include <library/cpp/testing/unittest/registar.h>
diff --git a/ydb/library/ycloud/impl/service_account_service_ut.cpp b/ydb/library/ycloud/impl/service_account_service_ut.cpp
index 55199b81e2a..d81ba7b446b 100644
--- a/ydb/library/ycloud/impl/service_account_service_ut.cpp
+++ b/ydb/library/ycloud/impl/service_account_service_ut.cpp
@@ -3,7 +3,6 @@
#include <ydb/core/testlib/test_client.h>
#include <ydb/core/testlib/test_client.h>
#include <ydb/library/testlib/service_mocks/service_account_service_mock.h>
-#include <ydb/core/grpc_services/grpc_helper.h>
#include <library/cpp/grpc/server/grpc_server.h>
#include <ydb/public/lib/deprecated/kicli/kicli.h>
#include <library/cpp/testing/unittest/registar.h>
diff --git a/ydb/library/ycloud/impl/user_account_service_ut.cpp b/ydb/library/ycloud/impl/user_account_service_ut.cpp
index 0903b1bb000..a561036c6d5 100644
--- a/ydb/library/ycloud/impl/user_account_service_ut.cpp
+++ b/ydb/library/ycloud/impl/user_account_service_ut.cpp
@@ -2,7 +2,6 @@
#include <library/cpp/actors/core/event_local.h>
#include <ydb/core/testlib/test_client.h>
#include <ydb/library/testlib/service_mocks/user_account_service_mock.h>
-#include <ydb/core/grpc_services/grpc_helper.h>
#include <library/cpp/grpc/server/grpc_server.h>
#include <ydb/public/lib/deprecated/kicli/kicli.h>
#include <library/cpp/testing/unittest/registar.h>