From 98a9dfdf539beaca0f65f9fba2abdbd283dcaeb3 Mon Sep 17 00:00:00 2001 From: Andrei Rykov Date: Thu, 25 Sep 2025 13:43:06 +0200 Subject: [PATCH 1/2] EXT-1423 added basic monitoring tests (#25538) --- ydb/core/mon/events_internal.h | 51 +++++ ydb/core/mon/mon.cpp | 44 +--- ydb/core/mon/mon_ut.cpp | 353 +++++++++++++++++++++++++++++ ydb/core/mon/ut/ya.make | 21 ++ ydb/core/mon/ut_utils/ut_utils.cpp | 133 +++++++++++ ydb/core/mon/ut_utils/ut_utils.h | 74 ++++++ ydb/core/mon/ut_utils/ya.make | 14 ++ ydb/core/mon/ya.make | 10 + ydb/core/viewer/ut/ya.make | 2 + ydb/core/viewer/viewer_ut.cpp | 107 ++------- 10 files changed, 674 insertions(+), 135 deletions(-) create mode 100644 ydb/core/mon/events_internal.h create mode 100644 ydb/core/mon/mon_ut.cpp create mode 100644 ydb/core/mon/ut/ya.make create mode 100644 ydb/core/mon/ut_utils/ut_utils.cpp create mode 100644 ydb/core/mon/ut_utils/ut_utils.h create mode 100644 ydb/core/mon/ut_utils/ya.make diff --git a/ydb/core/mon/events_internal.h b/ydb/core/mon/events_internal.h new file mode 100644 index 000000000000..fbb68252c510 --- /dev/null +++ b/ydb/core/mon/events_internal.h @@ -0,0 +1,51 @@ +#pragma once + +#include + +namespace NMonitoring::NPrivate { + +struct TEvMon { + enum { + EvBegin = EventSpaceBegin(NActors::TEvents::ES_PRIVATE), + + EvMonitoringRequest = EvBegin, + EvMonitoringResponse, + EvRegisterHandler, + EvMonitoringCancelRequest, + EvCleanupProxy, + + End + }; + + static_assert(End < EventSpaceEnd(NActors::TEvents::ES_PRIVATE), "expect End < EventSpaceEnd(TEvents::ES_PRIVATE)"); + + struct TEvMonitoringRequest : NActors::TEventPB { + TEvMonitoringRequest() = default; + }; + + struct TEvMonitoringResponse : NActors::TEventPB { + TEvMonitoringResponse() = default; + }; + + struct TEvRegisterHandler : NActors::TEventLocal { + NActors::TMon::TRegisterHandlerFields Fields; + + TEvRegisterHandler(const NActors::TMon::TRegisterHandlerFields& fields) + : Fields(fields) + {} + }; + + struct TEvMonitoringCancelRequest : NActors::TEventPB { + TEvMonitoringCancelRequest() = default; + }; + + struct TEvCleanupProxy : NActors::TEventLocal { + TString Address; + + TEvCleanupProxy(const TString& address) + : Address(address) + {} + }; +}; + +} // namespace NMonitoring::NPrivate diff --git a/ydb/core/mon/mon.cpp b/ydb/core/mon/mon.cpp index 05a733609a35..9baa6eb00841 100644 --- a/ydb/core/mon/mon.cpp +++ b/ydb/core/mon/mon.cpp @@ -1,5 +1,6 @@ #include "mon.h" #include "mon_impl.h" +#include "events_internal.h" #include "counters_adapter_impl.h" #include @@ -32,51 +33,10 @@ namespace NActors { -struct TEvMon { - enum { - EvMonitoringRequest = NActors::NMon::HttpInfo + 10, - EvMonitoringResponse, - EvRegisterHandler, - EvMonitoringCancelRequest, - EvCleanupProxy, - End - }; - - static_assert(EvMonitoringRequest > NMon::End, "expect EvMonitoringRequest > NMon::End"); - static_assert(End < EventSpaceEnd(NActors::TEvents::ES_MON), "expect End < EventSpaceEnd(NActors::TEvents::ES_MON)"); - - struct TEvMonitoringRequest : TEventPB { - TEvMonitoringRequest() = default; - }; - - struct TEvMonitoringResponse : TEventPB { - TEvMonitoringResponse() = default; - }; - - struct TEvRegisterHandler : TEventLocal { - TMon::TRegisterHandlerFields Fields; - - TEvRegisterHandler(const TMon::TRegisterHandlerFields& fields) - : Fields(fields) - {} - }; - - struct TEvMonitoringCancelRequest : TEventPB { - TEvMonitoringCancelRequest() = default; - }; - - struct TEvCleanupProxy : TEventLocal { - TString Address; - - TEvCleanupProxy(const TString& address) - : Address(address) - {} - }; -}; - namespace { using namespace NKikimr; +using namespace NMonitoring::NPrivate; bool HasJsonContent(NHttp::THttpIncomingRequest* request) { if (request->Method == "POST") { diff --git a/ydb/core/mon/mon_ut.cpp b/ydb/core/mon/mon_ut.cpp new file mode 100644 index 000000000000..e3507ba63ec4 --- /dev/null +++ b/ydb/core/mon/mon_ut.cpp @@ -0,0 +1,353 @@ +#include +#include +#include +#include + +#include +#include +#include + +namespace NMonitoring::NTests { + +using namespace NActors; +using namespace NKikimr; +using namespace NKikimr::Tests; + +void GrantConnect(Tests::TClient& client) { + client.CreateUser("/Root", "username", "password"); + client.GrantConnect("username"); + + const auto alterAttrsStatus = client.AlterUserAttributes("/", "Root", { + { "folder_id", "test_folder_id" }, + { "database_id", "test_database_id" }, + }); + UNIT_ASSERT_EQUAL(alterAttrsStatus, NMsgBusProxy::MSTATUS_OK); +} + +struct THttpMonTestEnvOptions { + enum class ERegKind { + None, + ActorPage, + ActorHandler, + MonPage, + }; + + ERegKind RegKind = ERegKind::None; + TVector ActorAllowedSIDs; + TVector TicketParserGroupSIDs = DEFAULT_TICKET_PARSER_GROUPS; + bool UseAuth = true; +}; + +class THttpMonTestEnv { +public: + THttpMonTestEnv(const THttpMonTestEnvOptions& options = {}) + : Port(PortManager.GetPort(2134)) + , GrpcPort(PortManager.GetPort(2135)) + , MonPort(PortManager.GetPort(8765)) + , Settings(Port) + , Options(std::move(options)) + { + Settings.InitKikimrRunConfig() + .SetNodeCount(1) + .SetUseRealThreads(true) + .SetDomainName("Root") + .SetUseSectorMap(true) + .SetMonitoringPortOffset(MonPort, true); + + auto& securityConfig = *Settings.AppConfig->MutableDomainsConfig()->MutableSecurityConfig(); + securityConfig.SetEnforceUserTokenCheckRequirement(true); + securityConfig.MutableMonitoringAllowedSIDs()->Add("ydb.clusters.monitor@as"); + + Settings.CreateTicketParser = [&](const TTicketParserSettings&) -> NActors::IActor* { + return TicketParser = new TFakeTicketParserActor(Options.TicketParserGroupSIDs); + }; + + Server = std::make_unique(Settings); + Server->EnableGRpc(GrpcPort); + Client = std::make_unique(Settings); + Client->InitRootScheme(); + GrantConnect(*Client); + + Runtime = Server->GetRuntime(); + + TMon* mon = Runtime->GetAppData().Mon; + UNIT_ASSERT(mon != nullptr); + + switch (Options.RegKind) { + case THttpMonTestEnvOptions::ERegKind::None: + break; + case THttpMonTestEnvOptions::ERegKind::ActorPage: { + TestActorPage = new TTestActorPage(); + TestActorId = Runtime->Register(TestActorPage); + mon->RegisterActorPage({ + .RelPath = TEST_MON_PATH, + .ActorSystem = Runtime->GetActorSystem(0), + .ActorId = TestActorId, + .UseAuth = Options.UseAuth, + .AllowedSIDs = Options.ActorAllowedSIDs, + }); + break; + } + case THttpMonTestEnvOptions::ERegKind::ActorHandler: { + TestActorHandler = new TTestActorHandler(); + TestActorId = Runtime->Register(TestActorHandler); + mon->RegisterActorHandler({ + .Path = MakeDefaultUrl(), + .Handler = TestActorId, + .UseAuth = Options.UseAuth, + .AllowedSIDs = Options.ActorAllowedSIDs, + }); + break; + } + case THttpMonTestEnvOptions::ERegKind::MonPage: { + mon->Register(new TTestMonPage()); + break; + } + } + + HttpClient = std::make_unique("localhost", MonPort); + } + + TKeepAliveHttpClient::THeaders MakeAuthHeaders(const TString& token = VALID_TOKEN) const { + TKeepAliveHttpClient::THeaders headers; + headers[AUTHORIZATION_HEADER] = token; + return headers; + } + + TString MakeDefaultUrl() const { + TStringBuilder url; + url << "/" << TEST_MON_PATH; + return url; + } + + TFakeTicketParserActor* GetTicketParser() const { + UNIT_ASSERT(TicketParser); + return TicketParser; + } + + TKeepAliveHttpClient& GetHttpClient() const { + return *HttpClient; + } + +private: + TPortManager PortManager; + ui16 Port; + ui16 GrpcPort; + ui16 MonPort; + TServerSettings Settings; + std::unique_ptr Server; + std::unique_ptr Client; + TTestActorRuntime* Runtime = nullptr; + TTestActorPage* TestActorPage = nullptr; + TTestActorHandler* TestActorHandler = nullptr; + TActorId TestActorId; + TFakeTicketParserActor* TicketParser = nullptr; + std::unique_ptr HttpClient; + THttpMonTestEnvOptions Options; +}; + +Y_UNIT_TEST_SUITE(ActorPage) { + Y_UNIT_TEST(HttpOk) { + THttpMonTestEnv env({ + .RegKind = THttpMonTestEnvOptions::ERegKind::ActorPage, + .ActorAllowedSIDs = {"valid_group"}, + .TicketParserGroupSIDs = {"valid_group"}, + }); + + TStringStream responseStream; + const auto status = env.GetHttpClient().DoGet(env.MakeDefaultUrl(), &responseStream, env.MakeAuthHeaders()); + UNIT_ASSERT_VALUES_EQUAL(status, HTTP_OK); + + const TString response = responseStream.ReadAll(); + UNIT_ASSERT_STRING_CONTAINS(response, TEST_RESPONSE); + + TFakeTicketParserActor* ticketParser = env.GetTicketParser(); + UNIT_ASSERT_VALUES_EQUAL(ticketParser->AuthorizeTicketRequests, 1); + UNIT_ASSERT_VALUES_EQUAL(ticketParser->AuthorizeTicketSuccesses, 1); + UNIT_ASSERT_VALUES_EQUAL(ticketParser->AuthorizeTicketFails, 0); + } + + Y_UNIT_TEST(NoValidGroupForbidden) { + THttpMonTestEnv env({ + .RegKind = THttpMonTestEnvOptions::ERegKind::ActorPage, + .ActorAllowedSIDs = {"valid_group"}, + .TicketParserGroupSIDs = {"wrong_group"}, + }); + + TStringStream responseStream; + const auto status = env.GetHttpClient().DoGet(env.MakeDefaultUrl(), &responseStream, env.MakeAuthHeaders()); + UNIT_ASSERT_VALUES_EQUAL(status, HTTP_FORBIDDEN); + + TFakeTicketParserActor* ticketParser = env.GetTicketParser(); + UNIT_ASSERT_VALUES_EQUAL(ticketParser->AuthorizeTicketRequests, 1); + UNIT_ASSERT_VALUES_EQUAL(ticketParser->AuthorizeTicketSuccesses, 1); + UNIT_ASSERT_VALUES_EQUAL(ticketParser->AuthorizeTicketFails, 0); + } + + Y_UNIT_TEST(InvalidTokenForbidden) { + THttpMonTestEnv env({ + .RegKind = THttpMonTestEnvOptions::ERegKind::ActorPage, + }); + + TStringStream responseStream; + const TString invalidToken = TString("Bearer invalid"); + const auto status = env.GetHttpClient().DoGet(env.MakeDefaultUrl(), &responseStream, env.MakeAuthHeaders(invalidToken)); + UNIT_ASSERT_VALUES_EQUAL(status, HTTP_FORBIDDEN); + + TFakeTicketParserActor* ticketParser = env.GetTicketParser(); + UNIT_ASSERT_VALUES_EQUAL(ticketParser->AuthorizeTicketRequests, 1); + UNIT_ASSERT_VALUES_EQUAL(ticketParser->AuthorizeTicketSuccesses, 0); + UNIT_ASSERT_VALUES_EQUAL(ticketParser->AuthorizeTicketFails, 1); + } + + Y_UNIT_TEST(NoUseAuthOk) { + THttpMonTestEnv env({ + .RegKind = THttpMonTestEnvOptions::ERegKind::ActorPage, + .UseAuth = false, + }); + + TStringStream responseStream; + const TString invalidToken = TString("Bearer invalid"); + const auto status = env.GetHttpClient().DoGet(env.MakeDefaultUrl(), &responseStream, env.MakeAuthHeaders(invalidToken)); + UNIT_ASSERT_VALUES_EQUAL(status, HTTP_OK); + + TFakeTicketParserActor* ticketParser = env.GetTicketParser(); + UNIT_ASSERT_VALUES_EQUAL(ticketParser->AuthorizeTicketRequests, 0); + UNIT_ASSERT_VALUES_EQUAL(ticketParser->AuthorizeTicketSuccesses, 0); + UNIT_ASSERT_VALUES_EQUAL(ticketParser->AuthorizeTicketFails, 0); + } + + Y_UNIT_TEST(OptionsNoContent) { + THttpMonTestEnv env({ + .RegKind = THttpMonTestEnvOptions::ERegKind::ActorPage, + }); + + TStringStream responseStream; + const auto status = env.GetHttpClient().DoRequest("OPTIONS", env.MakeDefaultUrl(), "", &responseStream); + UNIT_ASSERT_VALUES_EQUAL(status, HTTP_NO_CONTENT); + } +} + +Y_UNIT_TEST_SUITE(ActorHandler) { + Y_UNIT_TEST(HttpOk) { + THttpMonTestEnv env({ + .RegKind = THttpMonTestEnvOptions::ERegKind::ActorHandler, + .ActorAllowedSIDs = {"valid_group"}, + .TicketParserGroupSIDs = {"valid_group"}, + }); + + TStringStream responseStream; + const auto status = env.GetHttpClient().DoGet(env.MakeDefaultUrl(), &responseStream, env.MakeAuthHeaders()); + UNIT_ASSERT_VALUES_EQUAL(status, HTTP_OK); + + const TString response = responseStream.ReadAll(); + UNIT_ASSERT_STRING_CONTAINS(response, TEST_RESPONSE); + + TFakeTicketParserActor* ticketParser = env.GetTicketParser(); + UNIT_ASSERT_VALUES_EQUAL(ticketParser->AuthorizeTicketRequests, 1); + UNIT_ASSERT_VALUES_EQUAL(ticketParser->AuthorizeTicketSuccesses, 1); + UNIT_ASSERT_VALUES_EQUAL(ticketParser->AuthorizeTicketFails, 0); + } + + Y_UNIT_TEST(NoValidGroupForbidden) { + THttpMonTestEnv env({ + .RegKind = THttpMonTestEnvOptions::ERegKind::ActorHandler, + .ActorAllowedSIDs = {"valid_group"}, + .TicketParserGroupSIDs = {"wrong_group"}, + }); + + TStringStream responseStream; + const auto status = env.GetHttpClient().DoGet(env.MakeDefaultUrl(), &responseStream, env.MakeAuthHeaders()); + UNIT_ASSERT_VALUES_EQUAL(status, HTTP_FORBIDDEN); + + TFakeTicketParserActor* ticketParser = env.GetTicketParser(); + UNIT_ASSERT_VALUES_EQUAL(ticketParser->AuthorizeTicketRequests, 1); + UNIT_ASSERT_VALUES_EQUAL(ticketParser->AuthorizeTicketSuccesses, 1); + UNIT_ASSERT_VALUES_EQUAL(ticketParser->AuthorizeTicketFails, 0); + } + + Y_UNIT_TEST(InvalidTokenForbidden) { + THttpMonTestEnv env({ + .RegKind = THttpMonTestEnvOptions::ERegKind::ActorHandler, + }); + + TStringStream responseStream; + const TString invalidToken = TString("Bearer invalid"); + const auto status = env.GetHttpClient().DoGet(env.MakeDefaultUrl(), &responseStream, env.MakeAuthHeaders(invalidToken)); + UNIT_ASSERT_VALUES_EQUAL(status, HTTP_FORBIDDEN); + + TFakeTicketParserActor* ticketParser = env.GetTicketParser(); + UNIT_ASSERT_VALUES_EQUAL(ticketParser->AuthorizeTicketRequests, 1); + UNIT_ASSERT_VALUES_EQUAL(ticketParser->AuthorizeTicketSuccesses, 0); + UNIT_ASSERT_VALUES_EQUAL(ticketParser->AuthorizeTicketFails, 1); + } + + Y_UNIT_TEST(NoUseAuthOk) { + THttpMonTestEnv env({ + .RegKind = THttpMonTestEnvOptions::ERegKind::ActorHandler, + .UseAuth = false, + }); + + TStringStream responseStream; + const TString invalidToken = TString("Bearer invalid"); + const auto status = env.GetHttpClient().DoGet(env.MakeDefaultUrl(), &responseStream, env.MakeAuthHeaders(invalidToken)); + UNIT_ASSERT_VALUES_EQUAL(status, HTTP_OK); + + TFakeTicketParserActor* ticketParser = env.GetTicketParser(); + UNIT_ASSERT_VALUES_EQUAL(ticketParser->AuthorizeTicketRequests, 0); + UNIT_ASSERT_VALUES_EQUAL(ticketParser->AuthorizeTicketSuccesses, 0); + UNIT_ASSERT_VALUES_EQUAL(ticketParser->AuthorizeTicketFails, 0); + } + + Y_UNIT_TEST(OptionsNoContent) { + THttpMonTestEnv env({ + .RegKind = THttpMonTestEnvOptions::ERegKind::ActorHandler, + }); + + TStringStream responseStream; + const auto status = env.GetHttpClient().DoRequest("OPTIONS", env.MakeDefaultUrl(), "", &responseStream); + UNIT_ASSERT_VALUES_EQUAL(status, HTTP_NO_CONTENT); + } +} + +Y_UNIT_TEST_SUITE(MonPage) { + Y_UNIT_TEST(HttpOk) { + THttpMonTestEnv env({ + .RegKind = THttpMonTestEnvOptions::ERegKind::MonPage, + }); + + TStringStream responseStream; + const auto status = env.GetHttpClient().DoGet(env.MakeDefaultUrl(), &responseStream, env.MakeAuthHeaders()); + UNIT_ASSERT_VALUES_EQUAL(status, HTTP_OK); + + const TString response = responseStream.ReadAll(); + UNIT_ASSERT_STRING_CONTAINS(response, TEST_RESPONSE); + + TFakeTicketParserActor* ticketParser = env.GetTicketParser(); + UNIT_ASSERT_VALUES_EQUAL(ticketParser->AuthorizeTicketRequests, 0); + UNIT_ASSERT_VALUES_EQUAL(ticketParser->AuthorizeTicketSuccesses, 0); + UNIT_ASSERT_VALUES_EQUAL(ticketParser->AuthorizeTicketFails, 0); + } + + Y_UNIT_TEST(OptionsNoContent) { + THttpMonTestEnv env({ + .RegKind = THttpMonTestEnvOptions::ERegKind::MonPage, + }); + + TStringStream responseStream; + const auto status = env.GetHttpClient().DoRequest("OPTIONS", env.MakeDefaultUrl(), "", &responseStream); + UNIT_ASSERT_VALUES_EQUAL(status, HTTP_NO_CONTENT); + } +} + +Y_UNIT_TEST_SUITE(Other) { + Y_UNIT_TEST(UnknownPathNotFound) { + THttpMonTestEnv env; + + TStringStream responseStream; + const auto status = env.GetHttpClient().DoGet("/wrong_path", &responseStream, env.MakeAuthHeaders()); + UNIT_ASSERT_VALUES_EQUAL(status, HTTP_NOT_FOUND); + } +} + +} // namespace NMonitoring::NTests diff --git a/ydb/core/mon/ut/ya.make b/ydb/core/mon/ut/ya.make new file mode 100644 index 000000000000..ae78f5b6cc7c --- /dev/null +++ b/ydb/core/mon/ut/ya.make @@ -0,0 +1,21 @@ +UNITTEST_FOR(ydb/core/mon) + +FORK_SUBTESTS() + +SIZE(MEDIUM) + +PEERDIR( + ydb/core/mon + ydb/core/mon/ut_utils + ydb/core/testlib/default + ydb/library/aclib + ydb/library/actors/core +) + +SRCS( + mon_ut.cpp +) + +YQL_LAST_ABI_VERSION() + +END() diff --git a/ydb/core/mon/ut_utils/ut_utils.cpp b/ydb/core/mon/ut_utils/ut_utils.cpp new file mode 100644 index 000000000000..1dfe21040048 --- /dev/null +++ b/ydb/core/mon/ut_utils/ut_utils.cpp @@ -0,0 +1,133 @@ +#include "ut_utils.h" + +#include + +namespace NMonitoring::NTests { + +using namespace NActors; +using namespace NKikimr; + +const TString TEST_MON_PATH = "test_mon"; +const TString TEST_RESPONSE = "Test actor"; +const TString AUTHORIZATION_HEADER = "Authorization"; +const TString VALID_TOKEN = "Bearer token"; +const TVector DEFAULT_TICKET_PARSER_GROUPS = {"group_name"}; + +void TTestActorPage::Bootstrap() { + Become(&TTestActorPage::StateWork); +} + +void TTestActorPage::Handle(NMon::TEvHttpInfo::TPtr& ev) { + TStringBuilder body; + body << "

" << TEST_RESPONSE << "

"; + Send(ev->Sender, new NMon::TEvHttpInfoRes(body)); +} + +void TTestActorHandler::Bootstrap() { + Become(&TTestActorHandler::StateWork); +} + +void TTestActorHandler::Handle(NHttp::TEvHttpProxy::TEvHttpIncomingRequest::TPtr& ev) { + TStringBuilder body; + body << "

" << TEST_RESPONSE << "

"; + + TStringBuilder response; + response << "HTTP/1.1 200 OK\r\n" + << "Content-Type: text/html\r\n" + << "Content-Length: " << body.size() << "\r\n" + << "Connection: Close\r\n\r\n" + << body; + + Send(ev->Sender, new NHttp::TEvHttpProxy::TEvHttpOutgoingResponse( + ev->Get()->Request->CreateResponseString(response))); +} + +TTestMonPage::TTestMonPage() + : NMonitoring::IMonPage(TEST_MON_PATH, TString("Test Page")) +{ +} + +void TTestMonPage::Output(NMonitoring::IMonHttpRequest& request) { + const TStringBuf pathInfo = request.GetPathInfo(); + if (!pathInfo.empty() && pathInfo != TStringBuf("/")) { + request.Output() << NMonitoring::HTTPNOTFOUND; + return; + } + + auto& out = request.Output(); + out << NMonitoring::HTTPOKHTML; + out << "

" << TEST_RESPONSE << "

"; +} + +TFakeTicketParserActor::TFakeTicketParserActor(TVector groupSIDs) + : TActor(&TFakeTicketParserActor::StateFunc) + , GroupSIDs(std::move(groupSIDs)) +{} + +void TFakeTicketParserActor::Handle(TEvTicketParser::TEvAuthorizeTicket::TPtr& ev) { + LOG_INFO_S(*TlsActivationContext, NKikimrServices::TICKET_PARSER, "Ticket parser: got TEvAuthorizeTicket event: " << ev->Get()->Ticket << " " << ev->Get()->Database << " " << ev->Get()->Entries.size()); + ++AuthorizeTicketRequests; + + if (ev->Get()->Database != "/Root") { + Fail(ev, TStringBuilder() << "Incorrect database " << ev->Get()->Database); + return; + } + + if (ev->Get()->Ticket != VALID_TOKEN) { + Fail(ev, TStringBuilder() << "Incorrect token " << ev->Get()->Ticket); + return; + } + + bool databaseIdFound = false; + bool folderIdFound = false; + for (const TEvTicketParser::TEvAuthorizeTicket::TEntry& entry : ev->Get()->Entries) { + for (const std::pair& attr : entry.Attributes) { + if (attr.first == "database_id") { + databaseIdFound = true; + if (attr.second != "test_database_id") { + Fail(ev, TStringBuilder() << "Incorrect database_id " << attr.second); + return; + } + } else if (attr.first == "folder_id") { + folderIdFound = true; + if (attr.second != "test_folder_id") { + Fail(ev, TStringBuilder() << "Incorrect folder_id " << attr.second); + return; + } + } + } + } + if (!databaseIdFound) { + Fail(ev, "database_id not found"); + return; + } + if (!folderIdFound) { + Fail(ev, "folder_id not found"); + return; + } + + Success(ev); +} + +void TFakeTicketParserActor::Fail(TEvTicketParser::TEvAuthorizeTicket::TPtr& ev, const TString& message) { + ++AuthorizeTicketFails; + TEvTicketParser::TError err; + err.Retryable = false; + err.Message = message ? message : "Test error"; + LOG_INFO_S(*TlsActivationContext, NKikimrServices::TICKET_PARSER, + "Send TEvAuthorizeTicketResult: " << err.Message); + Send(ev->Sender, new TEvTicketParser::TEvAuthorizeTicketResult(ev->Get()->Ticket, err)); +} + +void TFakeTicketParserActor::Success(TEvTicketParser::TEvAuthorizeTicket::TPtr& ev) { + ++AuthorizeTicketSuccesses; + NACLib::TUserToken::TUserTokenInitFields args; + args.UserSID = "username"; + args.GroupSIDs = GroupSIDs; + TIntrusivePtr userToken = MakeIntrusive(args); + LOG_INFO_S(*TlsActivationContext, NKikimrServices::TICKET_PARSER, + "Send TEvAuthorizeTicketResult success"); + Send(ev->Sender, new TEvTicketParser::TEvAuthorizeTicketResult(ev->Get()->Ticket, userToken)); +} + +} // namespace NMonitoring::NTests diff --git a/ydb/core/mon/ut_utils/ut_utils.h b/ydb/core/mon/ut_utils/ut_utils.h new file mode 100644 index 000000000000..85042b041155 --- /dev/null +++ b/ydb/core/mon/ut_utils/ut_utils.h @@ -0,0 +1,74 @@ +#include +#include + +#include +#include +#include +#include + +namespace NMonitoring::NTests { + +using namespace NActors; +using namespace NKikimr; + +extern const TString TEST_MON_PATH; +extern const TString TEST_RESPONSE; +extern const TString AUTHORIZATION_HEADER; +extern const TString VALID_TOKEN; +extern const TVector DEFAULT_TICKET_PARSER_GROUPS; + +class TTestActorPage : public TActorBootstrapped { +public: + void Bootstrap(); + +private: + void Handle(NMon::TEvHttpInfo::TPtr& ev); + + STFUNC(StateWork) { + switch (ev->GetTypeRewrite()) { + hFunc(NMon::TEvHttpInfo, Handle); + } + } +}; + +class TTestActorHandler : public TActorBootstrapped { +public: + void Bootstrap(); + +private: + void Handle(NHttp::TEvHttpProxy::TEvHttpIncomingRequest::TPtr& ev); + + STFUNC(StateWork) { + switch (ev->GetTypeRewrite()) { + hFunc(NHttp::TEvHttpProxy::TEvHttpIncomingRequest, Handle); + } + } +}; + +class TTestMonPage : public NMonitoring::IMonPage { +public: + TTestMonPage(); + void Output(NMonitoring::IMonHttpRequest& request) override; +}; + +struct TFakeTicketParserActor : public TActor { + TFakeTicketParserActor(TVector groupSIDs); + void Handle(TEvTicketParser::TEvAuthorizeTicket::TPtr& ev); + void Fail(TEvTicketParser::TEvAuthorizeTicket::TPtr& ev, const TString& message); + void Success(TEvTicketParser::TEvAuthorizeTicket::TPtr& ev); + + size_t AuthorizeTicketRequests = 0; + size_t AuthorizeTicketSuccesses = 0; + size_t AuthorizeTicketFails = 0; + TVector GroupSIDs; + + STFUNC(StateFunc) { + switch (ev->GetTypeRewrite()) { + hFunc(TEvTicketParser::TEvAuthorizeTicket, Handle); + default: + break; + } + } +}; + +} // namespace NMonitoring::NTests diff --git a/ydb/core/mon/ut_utils/ya.make b/ydb/core/mon/ut_utils/ya.make new file mode 100644 index 000000000000..2801f4b860b3 --- /dev/null +++ b/ydb/core/mon/ut_utils/ya.make @@ -0,0 +1,14 @@ +LIBRARY() + +SRCS( + ut_utils.cpp +) + +PEERDIR( + ydb/core/protos + ydb/library/aclib +) + +YQL_LAST_ABI_VERSION() + +END() diff --git a/ydb/core/mon/ya.make b/ydb/core/mon/ya.make index 68afd7f3e087..74ba81f1d26d 100644 --- a/ydb/core/mon/ya.make +++ b/ydb/core/mon/ya.make @@ -1,6 +1,7 @@ LIBRARY() SRCS( + events_internal.h mon.cpp mon.h crossref.cpp @@ -25,3 +26,12 @@ PEERDIR( ) END() + +RECURSE( + audit + ut_utils +) + +RECURSE_FOR_TESTS( + ut +) diff --git a/ydb/core/viewer/ut/ya.make b/ydb/core/viewer/ut/ya.make index 66c01bab212d..868cdfd33cd8 100644 --- a/ydb/core/viewer/ut/ya.make +++ b/ydb/core/viewer/ut/ya.make @@ -16,6 +16,8 @@ SRCS( ) PEERDIR( + ydb/core/mon + ydb/core/mon/ut_utils library/cpp/http/misc library/cpp/http/simple ydb/core/testlib/default diff --git a/ydb/core/viewer/viewer_ut.cpp b/ydb/core/viewer/viewer_ut.cpp index 9a27c1c6d3d4..a2480b1528e4 100644 --- a/ydb/core/viewer/viewer_ut.cpp +++ b/ydb/core/viewer/viewer_ut.cpp @@ -1,4 +1,6 @@ #include "ut/ut_utils.h" +#include + #include #include #include @@ -35,6 +37,7 @@ using namespace NKikimrWhiteboard; using namespace NSchemeShard; using namespace Tests; using namespace NMonitoring; +using namespace NMonitoring::NTests; using namespace NKikimr::NViewerTests; using TNavigate = NSchemeCache::TSchemeCacheNavigate; @@ -420,90 +423,9 @@ Y_UNIT_TEST_SUITE(Viewer) { #endif } - struct TFakeTicketParserActor : public TActor { - TFakeTicketParserActor() - : TActor(&TFakeTicketParserActor::StFunc) - {} - - STFUNC(StFunc) { - switch (ev->GetTypeRewrite()) { - hFunc(TEvTicketParser::TEvAuthorizeTicket, Handle); - default: - break; - } - } - - void Handle(TEvTicketParser::TEvAuthorizeTicket::TPtr& ev) { - LOG_INFO_S(*TlsActivationContext, NKikimrServices::TICKET_PARSER, "Ticket parser: got TEvAuthorizeTicket event: " << ev->Get()->Ticket << " " << ev->Get()->Database << " " << ev->Get()->Entries.size()); - ++AuthorizeTicketRequests; - - if (ev->Get()->Database != "/Root") { - Fail(ev, TStringBuilder() << "Incorrect database " << ev->Get()->Database); - return; - } - - if (ev->Get()->Ticket != "test_ydb_token") { - Fail(ev, TStringBuilder() << "Incorrect token " << ev->Get()->Ticket); - return; - } - - bool databaseIdFound = false; - bool folderIdFound = false; - for (const TEvTicketParser::TEvAuthorizeTicket::TEntry& entry : ev->Get()->Entries) { - for (const std::pair& attr : entry.Attributes) { - if (attr.first == "database_id") { - databaseIdFound = true; - if (attr.second != "test_database_id") { - Fail(ev, TStringBuilder() << "Incorrect database_id " << attr.second); - return; - } - } else if (attr.first == "folder_id") { - folderIdFound = true; - if (attr.second != "test_folder_id") { - Fail(ev, TStringBuilder() << "Incorrect folder_id " << attr.second); - return; - } - } - } - } - if (!databaseIdFound) { - Fail(ev, "database_id not found"); - return; - } - if (!folderIdFound) { - Fail(ev, "folder_id not found"); - return; - } - - Success(ev); - } - - void Fail(TEvTicketParser::TEvAuthorizeTicket::TPtr& ev, const TString& message) { - ++AuthorizeTicketFails; - TEvTicketParser::TError err; - err.Retryable = false; - err.Message = message ? message : "Test error"; - LOG_INFO_S(*TlsActivationContext, NKikimrServices::TICKET_PARSER, "Send TEvAuthorizeTicketResult: " << err.Message); - Send(ev->Sender, new TEvTicketParser::TEvAuthorizeTicketResult(ev->Get()->Ticket, err)); - } - - void Success(TEvTicketParser::TEvAuthorizeTicket::TPtr& ev) { - ++AuthorizeTicketSuccesses; - NACLib::TUserToken::TUserTokenInitFields args; - args.UserSID = "username"; - args.GroupSIDs.push_back("group_name"); - TIntrusivePtr userToken = MakeIntrusive(args); - LOG_INFO_S(*TlsActivationContext, NKikimrServices::TICKET_PARSER, "Send TEvAuthorizeTicketResult success"); - Send(ev->Sender, new TEvTicketParser::TEvAuthorizeTicketResult(ev->Get()->Ticket, userToken)); - } - - size_t AuthorizeTicketRequests = 0; - size_t AuthorizeTicketSuccesses = 0; - size_t AuthorizeTicketFails = 0; - }; IActor* CreateFakeTicketParser(const TTicketParserSettings&) { - return new TFakeTicketParserActor(); + return new TFakeTicketParserActor({"group_name"}); } struct TPostQueryArguments { @@ -537,7 +459,7 @@ Y_UNIT_TEST_SUITE(Viewer) { TStringStream responseStream; TKeepAliveHttpClient::THeaders headers; headers["Content-Type"] = "application/json"; - headers["Authorization"] = "test_ydb_token"; + headers["Authorization"] = VALID_TOKEN; const TKeepAliveHttpClient::THttpCode statusCode = httpClient.DoPost("/viewer/query?timeout=600000", NJson::WriteJson(jsonRequest, false), &responseStream, headers); UNIT_ASSERT_EQUAL(statusCode, HTTP_OK); return NJson::ReadJsonTree(&responseStream, /* throwOnError = */ true); @@ -1712,7 +1634,7 @@ Y_UNIT_TEST_SUITE(Viewer) { TStringStream responseStream; TKeepAliveHttpClient::THeaders headers; headers["Content-Type"] = "application/json"; - headers["Authorization"] = "test_ydb_token"; + headers["Authorization"] = VALID_TOKEN; TString requestBody = R"json({ "query": "SELECT cast('311111111113.222222223' as Double);", "database": "/Root", @@ -1756,8 +1678,7 @@ Y_UNIT_TEST_SUITE(Viewer) { TFakeTicketParserActor* ticketParser = nullptr; settings.CreateTicketParser = [&](const TTicketParserSettings&) -> IActor* { - ticketParser = new TFakeTicketParserActor(); - return ticketParser; + return ticketParser = new TFakeTicketParserActor({"group_name"}); }; TServer server(settings); @@ -1775,7 +1696,7 @@ Y_UNIT_TEST_SUITE(Viewer) { TStringStream responseStream; TKeepAliveHttpClient::THeaders headers; headers["Content-Type"] = "application/json"; - headers["Authorization"] = "test_ydb_token"; + headers["Authorization"] = VALID_TOKEN; TString requestBody = R"json({ "query": "SELECT 42;", "database": "/Root", @@ -1838,7 +1759,7 @@ Y_UNIT_TEST_SUITE(Viewer) { TStringStream responseStream; TKeepAliveHttpClient::THeaders headers; headers["Content-Type"] = "application/json"; - headers["Authorization"] = "test_ydb_token"; + headers["Authorization"] = VALID_TOKEN; const TKeepAliveHttpClient::THttpCode statusCode = httpClient.DoPost(TStringBuilder() << "/query/script/execute?timeout=600000" << "&database=%2FRoot", requestBody.Str(), &responseStream, headers); @@ -1852,7 +1773,7 @@ Y_UNIT_TEST_SUITE(Viewer) { TStringStream responseStream; TKeepAliveHttpClient::THeaders headers; headers["Content-Type"] = "application/json"; - headers["Authorization"] = "test_ydb_token"; + headers["Authorization"] = VALID_TOKEN; id = std::regex_replace(id.c_str(), std::regex("/"), "%2F"); const TKeepAliveHttpClient::THttpCode statusCode = httpClient.DoGet(TStringBuilder() << "/operation/get?timeout=600000&id=" << id @@ -1868,7 +1789,7 @@ Y_UNIT_TEST_SUITE(Viewer) { TStringStream responseStream; TKeepAliveHttpClient::THeaders headers; headers["Content-Type"] = "application/json"; - headers["Authorization"] = "test_ydb_token"; + headers["Authorization"] = VALID_TOKEN; const TKeepAliveHttpClient::THttpCode statusCode = httpClient.DoGet(TStringBuilder() << "/operation/list?timeout=600000&kind=scriptexec" << "&database=%2FRoot", &responseStream, headers); @@ -1883,7 +1804,7 @@ Y_UNIT_TEST_SUITE(Viewer) { TStringStream responseStream; TKeepAliveHttpClient::THeaders headers; headers["Content-Type"] = "application/json"; - headers["Authorization"] = "test_ydb_token"; + headers["Authorization"] = VALID_TOKEN; id = std::regex_replace(id.c_str(), std::regex("/"), "%2F"); const TKeepAliveHttpClient::THttpCode statusCode = httpClient.DoGet(TStringBuilder() << "/query/script/fetch?timeout=600000&operation_id=" << id @@ -2001,7 +1922,7 @@ Y_UNIT_TEST_SUITE(Viewer) { TStringStream responseStream; TKeepAliveHttpClient::THeaders headers; headers["Content-Type"] = "application/json"; - headers["Authorization"] = "test_ydb_token"; + headers["Authorization"] = VALID_TOKEN; const TKeepAliveHttpClient::THttpCode statusCode = httpClient.DoPost("/viewer/plan2svg", tinyPlan, &responseStream, headers); const TString response = responseStream.ReadAll(); UNIT_ASSERT_EQUAL_C(statusCode, HTTP_OK, statusCode << ": " << response); @@ -2055,7 +1976,7 @@ Y_UNIT_TEST_SUITE(Viewer) { TStringStream responseStream; TKeepAliveHttpClient::THeaders headers; headers["Content-Type"] = "application/json"; - headers["Authorization"] = "test_ydb_token"; + headers["Authorization"] = VALID_TOKEN; const TKeepAliveHttpClient::THttpCode statusCode = httpClient.DoPost("/viewer/plan2svg", brokenPlan, &responseStream, headers); const TString response = responseStream.ReadAll(); UNIT_ASSERT_EQUAL_C(statusCode, HTTP_BAD_REQUEST, statusCode << ": " << response); From eedcc001f33aaea0837efe3d7a1d3e7ad04dd875 Mon Sep 17 00:00:00 2001 From: Andrei Rykov Date: Mon, 29 Sep 2025 11:25:06 +0200 Subject: [PATCH 2/2] EXT-1423 added auth lwtrace (#25924) --- ydb/core/mon/audit/audit.cpp | 6 ++++- ydb/core/mon/audit/audit.h | 2 +- ydb/core/mon/mon.cpp | 52 +++++++++++++++++++++++++++++++----- ydb/core/mon/mon.h | 2 ++ ydb/core/mon/mon_ut.cpp | 52 ++++++++++++++++++++++++++++++++++++ 5 files changed, 105 insertions(+), 9 deletions(-) diff --git a/ydb/core/mon/audit/audit.cpp b/ydb/core/mon/audit/audit.cpp index 9ce560b5f73d..c1d1748d34d6 100644 --- a/ydb/core/mon/audit/audit.cpp +++ b/ydb/core/mon/audit/audit.cpp @@ -108,7 +108,11 @@ bool TAuditCtx::AuditableRequest(const NHttp::THttpIncomingRequestPtr& request) return true; } -void TAuditCtx::InitAudit(const NHttp::TEvHttpProxy::TEvHttpIncomingRequest::TPtr& ev) { +void TAuditCtx::InitAudit(const NHttp::TEvHttpProxy::TEvHttpIncomingRequest::TPtr& ev, bool needAudit) { + if (!(Auditable = needAudit)) { + return; + } + const auto& request = ev->Get()->Request; const TString method(request->Method); const TString url(request->URL.Before('?')); diff --git a/ydb/core/mon/audit/audit.h b/ydb/core/mon/audit/audit.h index 11f7ed67bcd7..8c06c3bb7405 100644 --- a/ydb/core/mon/audit/audit.h +++ b/ydb/core/mon/audit/audit.h @@ -22,7 +22,7 @@ enum ERequestStatus { class TAuditCtx { public: - void InitAudit(const NHttp::TEvHttpProxy::TEvHttpIncomingRequest::TPtr& ev); + void InitAudit(const NHttp::TEvHttpProxy::TEvHttpIncomingRequest::TPtr& ev, bool needAudit = true); void AddAuditLogParts(const TAuditParts& parts); // TODO: pass request context instead of audit log parts void LogAudit(ERequestStatus status, const TString& reason, NKikimrConfig::TAuditConfig::TLogClassConfig::ELogPhase logPhase); void LogOnReceived(); diff --git a/ydb/core/mon/mon.cpp b/ydb/core/mon/mon.cpp index 9baa6eb00841..bd83aa6d4bfb 100644 --- a/ydb/core/mon/mon.cpp +++ b/ydb/core/mon/mon.cpp @@ -543,10 +543,12 @@ class THttpMonLegacyIndexRequest : public TActorBootstrappedGet()->Request, index) + , NeedAudit(needAudit) {} static constexpr NKikimrServices::TActivity::EType ActorActivityType() { @@ -554,7 +556,7 @@ class THttpMonLegacyIndexRequest : public TActorBootstrapped { public: - THttpMonIndexService(const TActorId& httpProxyActorId, TIntrusivePtr indexMonPage, TMon::TRequestAuthorizer authorizer, const TString& redirectRoot = {}) + THttpMonIndexService(const TActorId& httpProxyActorId, TIntrusivePtr indexMonPage, + TMon::TRequestAuthorizer authorizer, const TString& redirectRoot = {}, bool needMonLegacyAudit = true) : TActor(&THttpMonIndexService::StateWork) , HttpProxyActorId(httpProxyActorId) , IndexMonPage(std::move(indexMonPage)) , Authorizer(std::move(authorizer)) , RedirectRoot(redirectRoot) + , NeedMonLegacyAudit(needMonLegacyAudit) { } @@ -1309,7 +1313,7 @@ class THttpMonIndexService : public TActor { } } - Register(new THttpMonLegacyIndexRequest(std::move(ev), IndexMonPage.Get())); + Register(new THttpMonLegacyIndexRequest(std::move(ev), IndexMonPage.Get(), NeedMonLegacyAudit)); } void Handle(TEvMon::TEvRegisterHandler::TPtr& ev) { @@ -1331,6 +1335,7 @@ class THttpMonIndexService : public TActor { std::unordered_map Handlers; TMon::TRequestAuthorizer Authorizer; TString RedirectRoot; + bool NeedMonLegacyAudit; }; @@ -1340,6 +1345,36 @@ TMon::TMon(TConfig config) { } +void TMon::RegisterLwtrace() { + NLwTraceMonPage::RegisterPages(IndexMonPage.Get()); + NLwTraceMonPage::ProbeRegistry().AddProbesList(LWTRACE_GET_PROBES(ACTORLIB_PROVIDER)); + NLwTraceMonPage::ProbeRegistry().AddProbesList(LWTRACE_GET_PROBES(MONITORING_PROVIDER)); + + TVector monitoringAllowedSIDs; + NKikimr::TAppData* appData = ActorSystem->AppData(); + if (appData) { + { + const auto& protoAllowedSIDs = appData->DomainsConfig.GetSecurityConfig().GetMonitoringAllowedSIDs(); + for (const auto& sid : protoAllowedSIDs) { + monitoringAllowedSIDs.emplace_back(sid); + } + } + { + const auto& protoAllowedSIDs = appData->DomainsConfig.GetSecurityConfig().GetAdministrationAllowedSIDs(); + for (const auto& sid : protoAllowedSIDs) { + monitoringAllowedSIDs.emplace_back(sid); + } + } + } + + RegisterActorHandler({ + .Path = "/trace", + .Handler = HttpAuthMonServiceActorId, + .UseAuth = true, + .AllowedSIDs = monitoringAllowedSIDs, + }); +} + std::future TMon::Start(TActorSystem* actorSystem) { Y_ABORT_UNLESS(actorSystem); TGuard g(Mutex); @@ -1355,9 +1390,6 @@ std::future TMon::Start(TActorSystem* actorSystem) { Register(new NMonitoring::TBootstrapFontsSvgMonPage); Register(new NMonitoring::TBootstrapFontsTtfMonPage); Register(new NMonitoring::TBootstrapFontsWoffMonPage); - NLwTraceMonPage::RegisterPages(IndexMonPage.Get()); - NLwTraceMonPage::ProbeRegistry().AddProbesList(LWTRACE_GET_PROBES(ACTORLIB_PROVIDER)); - NLwTraceMonPage::ProbeRegistry().AddProbesList(LWTRACE_GET_PROBES(MONITORING_PROVIDER)); if (ActorSystem->AppData()) { auto metricsRoot = NKikimr::GetServiceCounters(ActorSystem->AppData()->Counters, "utils")->GetSubgroup("subsystem", "mon"); Metrics = std::make_shared(std::move(metricsRoot)); @@ -1373,6 +1405,11 @@ std::future TMon::Start(TActorSystem* actorSystem) { new THttpMonIndexService(HttpProxyActorId, IndexMonPage, Config.Authorizer, Config.RedirectMainPageTo), TMailboxType::ReadAsFilled, executorPool); + HttpAuthMonServiceActorId = ActorSystem->Register( + new THttpMonIndexService(HttpMonServiceActorId, IndexMonPage, Config.Authorizer, Config.RedirectMainPageTo, false), + TMailboxType::ReadAsFilled, + executorPool); + RegisterLwtrace(); auto nodeProxyActorId = ActorSystem->Register( new THttpMonServiceNodeProxy(HttpProxyActorId), TMailboxType::ReadAsFilled, @@ -1424,6 +1461,7 @@ void TMon::Stop() { } ActorSystem->Send(NodeProxyServiceActorId, new TEvents::TEvPoisonPill); ActorSystem->Send(HttpMonServiceActorId, new TEvents::TEvPoisonPill); + ActorSystem->Send(HttpAuthMonServiceActorId, new TEvents::TEvPoisonPill); ActorSystem->Send(HttpProxyActorId, new TEvents::TEvPoisonPill); ActorSystem = nullptr; } diff --git a/ydb/core/mon/mon.h b/ydb/core/mon/mon.h index 90a4b9a0eafe..7b68da1b2049 100644 --- a/ydb/core/mon/mon.h +++ b/ydb/core/mon/mon.h @@ -51,6 +51,7 @@ class TMon { void Register(NMonitoring::IMonPage* page); NMonitoring::TIndexMonPage* RegisterIndexPage(const TString& path, const TString& title); + void RegisterLwtrace(); struct TRegisterActorPageFields { TString Title; @@ -97,6 +98,7 @@ class TMon { TActorSystem* ActorSystem = {}; TActorId HttpProxyActorId; TActorId HttpMonServiceActorId; + TActorId HttpAuthMonServiceActorId; TActorId NodeProxyServiceActorId; struct TActorMonPageInfo { diff --git a/ydb/core/mon/mon_ut.cpp b/ydb/core/mon/mon_ut.cpp index e3507ba63ec4..0315f4621f58 100644 --- a/ydb/core/mon/mon_ut.cpp +++ b/ydb/core/mon/mon_ut.cpp @@ -307,6 +307,11 @@ Y_UNIT_TEST_SUITE(ActorHandler) { TStringStream responseStream; const auto status = env.GetHttpClient().DoRequest("OPTIONS", env.MakeDefaultUrl(), "", &responseStream); UNIT_ASSERT_VALUES_EQUAL(status, HTTP_NO_CONTENT); + + TFakeTicketParserActor* ticketParser = env.GetTicketParser(); + UNIT_ASSERT_VALUES_EQUAL(ticketParser->AuthorizeTicketRequests, 0); + UNIT_ASSERT_VALUES_EQUAL(ticketParser->AuthorizeTicketSuccesses, 0); + UNIT_ASSERT_VALUES_EQUAL(ticketParser->AuthorizeTicketFails, 0); } } @@ -337,6 +342,11 @@ Y_UNIT_TEST_SUITE(MonPage) { TStringStream responseStream; const auto status = env.GetHttpClient().DoRequest("OPTIONS", env.MakeDefaultUrl(), "", &responseStream); UNIT_ASSERT_VALUES_EQUAL(status, HTTP_NO_CONTENT); + + TFakeTicketParserActor* ticketParser = env.GetTicketParser(); + UNIT_ASSERT_VALUES_EQUAL(ticketParser->AuthorizeTicketRequests, 0); + UNIT_ASSERT_VALUES_EQUAL(ticketParser->AuthorizeTicketSuccesses, 0); + UNIT_ASSERT_VALUES_EQUAL(ticketParser->AuthorizeTicketFails, 0); } } @@ -348,6 +358,48 @@ Y_UNIT_TEST_SUITE(Other) { const auto status = env.GetHttpClient().DoGet("/wrong_path", &responseStream, env.MakeAuthHeaders()); UNIT_ASSERT_VALUES_EQUAL(status, HTTP_NOT_FOUND); } + + Y_UNIT_TEST(TraceHttpOk) { + THttpMonTestEnv env({ + .TicketParserGroupSIDs = {"ydb.clusters.monitor@as"}, + }); + + TStringStream responseStream; + const auto status = env.GetHttpClient().DoGet("/trace", &responseStream, env.MakeAuthHeaders()); + UNIT_ASSERT_VALUES_EQUAL(status, HTTP_OK); + + TFakeTicketParserActor* ticketParser = env.GetTicketParser(); + UNIT_ASSERT_VALUES_EQUAL(ticketParser->AuthorizeTicketRequests, 1); + UNIT_ASSERT_VALUES_EQUAL(ticketParser->AuthorizeTicketSuccesses, 1); + UNIT_ASSERT_VALUES_EQUAL(ticketParser->AuthorizeTicketFails, 0); + } + + Y_UNIT_TEST(TraceNoValidGroupForbidden) { + THttpMonTestEnv env; + + TStringStream responseStream; + const auto status = env.GetHttpClient().DoGet("/trace", &responseStream, env.MakeAuthHeaders()); + UNIT_ASSERT_VALUES_EQUAL(status, HTTP_FORBIDDEN); + + TFakeTicketParserActor* ticketParser = env.GetTicketParser(); + UNIT_ASSERT_VALUES_EQUAL(ticketParser->AuthorizeTicketRequests, 1); + UNIT_ASSERT_VALUES_EQUAL(ticketParser->AuthorizeTicketSuccesses, 1); + UNIT_ASSERT_VALUES_EQUAL(ticketParser->AuthorizeTicketFails, 0); + } + + Y_UNIT_TEST(TraceInvalidTokenForbidden) { + THttpMonTestEnv env; + + TStringStream responseStream; + const TString invalidToken = TString("Bearer invalid"); + const auto status = env.GetHttpClient().DoGet("/trace", &responseStream, env.MakeAuthHeaders(invalidToken)); + UNIT_ASSERT_VALUES_EQUAL(status, HTTP_FORBIDDEN); + + TFakeTicketParserActor* ticketParser = env.GetTicketParser(); + UNIT_ASSERT_VALUES_EQUAL(ticketParser->AuthorizeTicketRequests, 1); + UNIT_ASSERT_VALUES_EQUAL(ticketParser->AuthorizeTicketSuccesses, 0); + UNIT_ASSERT_VALUES_EQUAL(ticketParser->AuthorizeTicketFails, 1); + } } } // namespace NMonitoring::NTests