diff options
author | xenoxeno <xeno@ydb.tech> | 2023-09-15 16:04:28 +0300 |
---|---|---|
committer | xenoxeno <xeno@ydb.tech> | 2023-09-15 16:22:01 +0300 |
commit | f08fad547ba421f6693a02af6f34deb0b5ae6e11 (patch) | |
tree | ee023b3c9a42aa36cf7bef1fe2e7a7aa2c6d72be | |
parent | a74a9c76aa1222d0f420396c2f5b10c90389f5ca (diff) | |
download | ydb-f08fad547ba421f6693a02af6f34deb0b5ae6e11.tar.gz |
switch web login service to http handlers to avoid promise lock-out KIKIMR-19359
-rw-r--r-- | library/cpp/actors/http/http.cpp | 2 | ||||
-rw-r--r-- | library/cpp/actors/http/http.h | 2 | ||||
-rw-r--r-- | ydb/core/driver_lib/run/run.cpp | 6 | ||||
-rw-r--r-- | ydb/core/mon/async_http_mon.cpp | 18 | ||||
-rw-r--r-- | ydb/core/mon/async_http_mon.h | 2 | ||||
-rw-r--r-- | ydb/core/mon/mon.h | 1 | ||||
-rw-r--r-- | ydb/core/mon/sync_http_mon.cpp | 4 | ||||
-rw-r--r-- | ydb/core/mon/sync_http_mon.h | 1 | ||||
-rw-r--r-- | ydb/core/security/login_page.cpp | 363 | ||||
-rw-r--r-- | ydb/core/security/login_page.h | 8 | ||||
-rw-r--r-- | ydb/core/tx/tx_proxy/proxy_impl.cpp | 2 |
11 files changed, 187 insertions, 222 deletions
diff --git a/library/cpp/actors/http/http.cpp b/library/cpp/actors/http/http.cpp index 3f3687ccef..22d5409343 100644 --- a/library/cpp/actors/http/http.cpp +++ b/library/cpp/actors/http/http.cpp @@ -770,7 +770,7 @@ size_t THeaders::Parse(TStringBuf headers) { return headers.begin() - start; } -TStringBuf THeaders::operator [](TStringBuf name) const { +const TStringBuf THeaders::operator [](TStringBuf name) const { return Get(name); } diff --git a/library/cpp/actors/http/http.h b/library/cpp/actors/http/http.h index c859c21a6d..2711b307c8 100644 --- a/library/cpp/actors/http/http.h +++ b/library/cpp/actors/http/http.h @@ -109,7 +109,7 @@ struct THeaders { THeaders() = default; THeaders(TStringBuf headers); THeaders(const THeaders&) = delete; - TStringBuf operator [](TStringBuf name) const; + const TStringBuf operator [](TStringBuf name) const; bool Has(TStringBuf name) const; TStringBuf Get(TStringBuf name) const; // raw size_t Parse(TStringBuf headers); diff --git a/ydb/core/driver_lib/run/run.cpp b/ydb/core/driver_lib/run/run.cpp index eac36b8893..cb4c6de3db 100644 --- a/ydb/core/driver_lib/run/run.cpp +++ b/ydb/core/driver_lib/run/run.cpp @@ -460,8 +460,8 @@ void TKikimrRunner::InitializeMonitoring(const TKikimrRunConfig& runConfig, bool void TKikimrRunner::InitializeMonitoringLogin(const TKikimrRunConfig&) { if (Monitoring) { - Monitoring->Register(CreateLoginPage(ActorSystem.Get())); - Monitoring->Register(CreateLogoutPage(ActorSystem.Get())); + Monitoring->RegisterHandler("/login", MakeWebLoginServiceId()); + Monitoring->RegisterHandler("/logout", MakeWebLoginServiceId()); } } @@ -1215,6 +1215,8 @@ void TKikimrRunner::InitializeActorSystem( serviceInitializers->InitializeServices(setup.Get(), AppData.Get()); if (Monitoring) { + setup->LocalServices.emplace_back(MakeWebLoginServiceId(), TActorSetupCmd(CreateWebLoginService(), + TMailboxType::HTSwap, AppData->UserPoolId)); setup->LocalServices.emplace_back(NCrossRef::MakeCrossRefActorId(), TActorSetupCmd(NCrossRef::CreateCrossRefActor(), TMailboxType::HTSwap, AppData->SystemPoolId)); setup->LocalServices.emplace_back(MakeMonVDiskStreamId(), TActorSetupCmd(CreateMonVDiskStreamActor(), diff --git a/ydb/core/mon/async_http_mon.cpp b/ydb/core/mon/async_http_mon.cpp index 220ff17217..a74e579a2a 100644 --- a/ydb/core/mon/async_http_mon.cpp +++ b/ydb/core/mon/async_http_mon.cpp @@ -731,7 +731,11 @@ void TAsyncHttpMon::Start(TActorSystem* actorSystem) { ActorSystem->Send(HttpProxyActorId, new NHttp::TEvHttpProxy::TEvRegisterHandler("/", HttpMonServiceActorId)); ActorSystem->Send(HttpProxyActorId, new NHttp::TEvHttpProxy::TEvRegisterHandler("/node", NodeProxyServiceActorId)); for (auto& pageInfo : ActorMonPages) { - RegisterActorMonPage(pageInfo); + if (pageInfo.Page) { + RegisterActorMonPage(pageInfo); + } else if (pageInfo.Handler) { + ActorSystem->Send(HttpProxyActorId, new NHttp::TEvHttpProxy::TEvRegisterHandler(pageInfo.Path, pageInfo.Handler)); + } } ActorMonPages.clear(); } @@ -817,6 +821,18 @@ NMonitoring::IMonPage* TAsyncHttpMon::RegisterCountersPage(const TString& path, return page; } +void TAsyncHttpMon::RegisterHandler(const TString& path, const TActorId& handler) { + if (ActorSystem) { + ActorSystem->Send(HttpProxyActorId, new NHttp::TEvHttpProxy::TEvRegisterHandler(path, handler)); + } else { + TGuard<TMutex> g(Mutex); + ActorMonPages.emplace_back(TActorMonPageInfo{ + .Handler = handler, + .Path = path, + }); + } +} + NMonitoring::IMonPage* TAsyncHttpMon::FindPage(const TString& relPath) { return IndexMonPage->FindPage(relPath); } diff --git a/ydb/core/mon/async_http_mon.h b/ydb/core/mon/async_http_mon.h index 3bed45c106..a6f7a2e448 100644 --- a/ydb/core/mon/async_http_mon.h +++ b/ydb/core/mon/async_http_mon.h @@ -25,6 +25,7 @@ public: NMonitoring::IMonPage* RegisterActorPage(TRegisterActorPageFields fields) override; NMonitoring::IMonPage* RegisterCountersPage(const TString& path, const TString& title, TIntrusivePtr<::NMonitoring::TDynamicCounters> counters) override; NMonitoring::IMonPage* FindPage(const TString& relPath) override; + void RegisterHandler(const TString& path, const TActorId& handler) override; protected: TConfig Config; @@ -36,6 +37,7 @@ protected: struct TActorMonPageInfo { NMonitoring::TMonPagePtr Page; + TActorId Handler; TString Path; }; diff --git a/ydb/core/mon/mon.h b/ydb/core/mon/mon.h index ab69f0c3f8..f4314ec6b0 100644 --- a/ydb/core/mon/mon.h +++ b/ydb/core/mon/mon.h @@ -55,6 +55,7 @@ public: const TString& title, bool preTag, TActorSystem* actorSystem, const TActorId& actorId, bool useAuth = true, bool sortPages = true); virtual NMonitoring::IMonPage* RegisterCountersPage(const TString& path, const TString& title, TIntrusivePtr<::NMonitoring::TDynamicCounters> counters) = 0; virtual NMonitoring::IMonPage* FindPage(const TString& relPath) = 0; + virtual void RegisterHandler(const TString& path, const TActorId& handler) = 0; }; } // NActors diff --git a/ydb/core/mon/sync_http_mon.cpp b/ydb/core/mon/sync_http_mon.cpp index 8506344933..e8a5040bb7 100644 --- a/ydb/core/mon/sync_http_mon.cpp +++ b/ydb/core/mon/sync_http_mon.cpp @@ -108,4 +108,8 @@ namespace NActors { IMonPage* TSyncHttpMon::FindPage(const TString& relPath) { return TBase::FindPage(relPath); } + + void TSyncHttpMon::RegisterHandler(const TString& path, const TActorId& handler) { + ALOG_ERROR(NActorsServices::HTTP, "Cannot register actor handler " << handler << " in sync mon for " << path); + } } // NActors diff --git a/ydb/core/mon/sync_http_mon.h b/ydb/core/mon/sync_http_mon.h index 9a61980c77..be8b97a431 100644 --- a/ydb/core/mon/sync_http_mon.h +++ b/ydb/core/mon/sync_http_mon.h @@ -24,6 +24,7 @@ public: NMonitoring::IMonPage* RegisterCountersPage(const TString& path, const TString& title, TIntrusivePtr<::NMonitoring::TDynamicCounters> counters) override; void OutputIndexPage(IOutputStream& out) override; NMonitoring::IMonPage* FindPage(const TString& relPath) override; + void RegisterHandler(const TString& path, const TActorId& handler) override; protected: typedef NMonitoring::TMonService2 TBase; diff --git a/ydb/core/security/login_page.cpp b/ydb/core/security/login_page.cpp index d4fa195dd9..f3ada553e5 100644 --- a/ydb/core/security/login_page.cpp +++ b/ydb/core/security/login_page.cpp @@ -1,6 +1,7 @@ #include "login_page.h" #include "login_shared_func.h" +#include <library/cpp/actors/http/http_proxy.h> #include <library/cpp/json/json_value.h> #include <library/cpp/json/json_reader.h> #include <library/cpp/json/json_writer.h> @@ -24,22 +25,18 @@ using THttpResponsePtr = THolder<NMon::IEvHttpInfoRes>; class TLoginRequest : public NActors::TActorBootstrapped<TLoginRequest> { public: + using TBase = NActors::TActorBootstrapped<TLoginRequest>; + static constexpr NKikimrServices::TActivity::EType ActorActivityType() { return NKikimrServices::TActivity::ACTORLIB_COMMON; } - TLoginRequest(IMonHttpRequest& request, NThreading::TPromise<THttpResponsePtr> result) - : Request(request) - , Result(result) + TLoginRequest(NHttp::TEvHttpProxy::TEvHttpIncomingRequest::TPtr& ev) + : Sender(ev->Sender) + , Request(ev->Get()->Request) { } - ~TLoginRequest() { - if (!Result.HasValue()) { - Result.SetValue(nullptr); - } - } - STATEFN(StateWork) { switch (ev->GetTypeRewrite()) { hFunc(TEvents::TEvPoisonPill, HandlePoisonPill); @@ -51,52 +48,27 @@ public: } } - static TString GetMethod(HTTP_METHOD method) { - switch (method) { - case HTTP_METHOD_UNDEFINED: return "UNDEFINED"; - case HTTP_METHOD_OPTIONS: return "OPTIONS"; - case HTTP_METHOD_GET: return "GET"; - case HTTP_METHOD_HEAD: return "HEAD"; - case HTTP_METHOD_POST: return "POST"; - case HTTP_METHOD_PUT: return "PUT"; - case HTTP_METHOD_DELETE: return "DELETE"; - case HTTP_METHOD_TRACE: return "TRACE"; - case HTTP_METHOD_CONNECT: return "CONNECT"; - case HTTP_METHOD_EXTENSION: return "EXTENSION"; - default: return "UNKNOWN"; - } - } - void Bootstrap() { - LOG_WARN_S(*TlsActivationContext, NActorsServices::HTTP, - Request.GetRemoteAddr() - << " " << GetMethod(Request.GetMethod()) - << " " << Request.GetUri()); + ALOG_WARN(NActorsServices::HTTP, Request->Address << " " << Request->Method << " " << Request->URL); - if (Request.GetMethod() == HTTP_METHOD_OPTIONS) { + if (Request->Method == "OPTIONS") { return ReplyOptionsAndPassAway(); } - if (Request.GetMethod() != HTTP_METHOD_POST) { - return ReplyErrorAndPassAway("400 Bad Request", "Invalid method"); + if (Request->Method != "POST") { + return ReplyErrorAndPassAway("400", "Bad Request", "Invalid method"); } - if (Request.GetHeader("Content-Type").Before(';') != "application/json") { - return ReplyErrorAndPassAway("400 Bad Request", "Invalid Content-Type"); + NHttp::THeaders headers(Request->Headers); + + if (headers.Get("Content-Type").Before(';') != "application/json") { + return ReplyErrorAndPassAway("400", "Bad Request", "Invalid Content-Type"); } NJson::TJsonValue postData; - if (!NJson::ReadJsonTree(Request.GetPostContent(), &postData)) { - return ReplyErrorAndPassAway("400 Bad Request", "Invalid JSON data"); - } - - if (postData.Has("database")) { - Database = postData["database"].GetStringRobust(); - } else { - TDomainsInfo* domainsInfo = AppData()->DomainsInfo.Get(); - const TDomainsInfo::TDomain& domain = *domainsInfo->Domains.begin()->second.Get(); - Database = "/" + domain.Name; + if (!NJson::ReadJsonTree(Request->Body, &postData)) { + return ReplyErrorAndPassAway("400", "Bad Request", "Invalid JSON data"); } TString login; @@ -105,20 +77,43 @@ public: if (postData.GetValuePointer("user", &jsonUser)) { login = jsonUser->GetStringRobust(); } else { - return ReplyErrorAndPassAway("400 Bad Request", "User must be specified"); + return ReplyErrorAndPassAway("400", "Bad Request", "User must be specified"); } NJson::TJsonValue* jsonPassword; if (postData.GetValuePointer("password", &jsonPassword)) { password = jsonPassword->GetStringRobust(); } else { - return ReplyErrorAndPassAway("400 Bad Request", "Password must be specified"); + return ReplyErrorAndPassAway("400", "Bad Request", "Password must be specified"); + } + + if (postData.Has("database")) { + Database = postData["database"].GetStringRobust(); } AuthCredentials = PrepareCredentials(login, password, AppData()->AuthConfig); - auto sendParameters = GetSendParameters(AuthCredentials, Database); - Send(sendParameters.Recipient, sendParameters.Event.Release()); + if (AuthCredentials.AuthType == TAuthCredentials::EAuthType::Ldap) { + ALOG_DEBUG(NActorsServices::HTTP, "Login: Requesting LDAP provider for user " << AuthCredentials.Login); + Send(MakeLdapAuthProviderID(), new TEvLdapAuthProvider::TEvAuthenticateRequest(AuthCredentials.Login, AuthCredentials.Password)); + } else { + TDomainsInfo* domainsInfo = AppData()->DomainsInfo.Get(); + const TDomainsInfo::TDomain& domain = *domainsInfo->Domains.begin()->second.Get(); + TString rootDatabase = "/" + domain.Name; + ui64 rootSchemeShardTabletId = domain.SchemeRoot; + if (true /*!Database.empty() && Database != rootDatabase*/) { + Database = rootDatabase; + auto dest(MakeSchemeCacheID()); + auto actr = TlsActivationContext->ExecutorThread.ActorSystem->LookupLocalService(dest); + + ALOG_DEBUG(NActorsServices::HTTP, "Login: Requesting schemecache (" << dest << " -> " << actr << ") for database " << Database); + + Send(dest, new TEvTxProxySchemeCache::TEvNavigateKeySet(CreateNavigateKeySetRequest(Database).Release())); + } else { + Database = rootDatabase; + RequestSchemeShard(rootSchemeShardTabletId); + } + } Become(&TThis::StateWork, Timeout, new TEvents::TEvWakeup()); } @@ -128,39 +123,45 @@ public: return clientConfig; } + void RequestSchemeShard(ui64 schemeShardTabletId) { + ALOG_DEBUG(NActorsServices::HTTP, "Login: Requesting schemeshard " << schemeShardTabletId << " for user " << AuthCredentials.Login); + IActor* pipe = NTabletPipe::CreateClient(SelfId(), schemeShardTabletId, GetPipeClientConfig()); + PipeClient = RegisterWithSameMailbox(pipe); + THolder<TEvSchemeShard::TEvLogin> request = MakeHolder<TEvSchemeShard::TEvLogin>(); + request.Get()->Record = CreateLoginRequest(AuthCredentials, AppData()->AuthConfig); + NTabletPipe::SendData(SelfId(), PipeClient, request.Release()); + } + void HandleNavigate(TEvTxProxySchemeCache::TEvNavigateKeySetResult::TPtr& ev) { const NSchemeCache::TSchemeCacheNavigate* response = ev->Get()->Request.Get(); if (response->ResultSet.size() == 1) { if (response->ResultSet.front().Status == NSchemeCache::TSchemeCacheNavigate::EStatus::Ok) { const NSchemeCache::TSchemeCacheNavigate::TEntry& entry = response->ResultSet.front(); ui64 schemeShardTabletId = entry.DomainInfo->ExtractSchemeShard(); - IActor* pipe = NTabletPipe::CreateClient(SelfId(), schemeShardTabletId, GetPipeClientConfig()); - TActorId pipeClient = RegisterWithSameMailbox(pipe); - THolder<TEvSchemeShard::TEvLogin> request = MakeHolder<TEvSchemeShard::TEvLogin>(); - request.Get()->Record = CreateLoginRequest(AuthCredentials, AppData()->AuthConfig); - NTabletPipe::SendData(SelfId(), pipeClient, request.Release()); + RequestSchemeShard(schemeShardTabletId); return; } else { - ReplyErrorAndPassAway("503 Service Unavailable", TStringBuilder() + ReplyErrorAndPassAway("503", "Service Unavailable", TStringBuilder() << "Status " << static_cast<int>(response->ResultSet.front().Status)); } } else { - ReplyErrorAndPassAway("503 Service Unavailable", "Scheme error"); + ReplyErrorAndPassAway("503", "Service Unavailable", "Scheme error"); } } void Handle(TEvLdapAuthProvider::TEvAuthenticateResponse::TPtr& ev) { TEvLdapAuthProvider::TEvAuthenticateResponse* response = ev->Get(); if (response->Status == TEvLdapAuthProvider::EStatus::SUCCESS) { + ALOG_DEBUG(NActorsServices::HTTP, "Login: Requesting schemecache for database " << Database); Send(MakeSchemeCacheID(), new TEvTxProxySchemeCache::TEvNavigateKeySet(CreateNavigateKeySetRequest(Database).Release())); } else { - ReplyErrorAndPassAway("403 Forbidden", response->Error.Message); + ReplyErrorAndPassAway("403", "Forbidden", response->Error.Message); } } void HandleResult(TEvSchemeShard::TEvLoginResult::TPtr& ev) { if (ev->Get()->Record.GetError()) { - ReplyErrorAndPassAway("403 Forbidden", ev->Get()->Record.GetError()); + ReplyErrorAndPassAway("403", "Forbidden", ev->Get()->Record.GetError()); } else { ReplyCookieAndPassAway(ev->Get()->Record.GetToken()); } @@ -168,7 +169,7 @@ public: void HandleConnect(TEvTabletPipe::TEvClientConnected::TPtr& ev) { if (ev->Get()->Status != NKikimrProto::OK) { - ReplyErrorAndPassAway("503 Service Unavailable", "SchemeShard is not available"); + ReplyErrorAndPassAway("503", "Service Unavailable", "SchemeShard is not available"); } } @@ -177,159 +178,92 @@ public: } void HandleTimeout() { - ReplyErrorAndPassAway("504 Gateway Timeout", "Timeout"); + ReplyErrorAndPassAway("504", "Gateway Timeout", "Timeout"); } void ReplyOptionsAndPassAway() { - Result.SetValue(MakeHolder<NMon::TEvHttpInfoRes>( - "HTTP/1.1 204 No Content\r\n" - "Allow: OPTIONS, POST\r\n" - "Connection: Keep-Alive\r\n\r\n", 0, NMon::IEvHttpInfoRes::EContentType::Custom)); + NHttp::THeadersBuilder headers; + headers.Set("Allow", "OPTIONS, POST"); + Send(Sender, new NHttp::TEvHttpProxy::TEvHttpOutgoingResponse(Request->CreateResponse("204", "No Content", headers))); PassAway(); } - TString GetCORS() { + void SetCORS(NHttp::THeadersBuilder& headers) { TStringBuilder res; - TString origin = TString(Request.GetHeader("Origin")); + TString origin = TString(NHttp::THeaders(Request->Headers)["Origin"]); if (origin.empty()) { origin = "*"; } - res << "Access-Control-Allow-Origin: " << origin << "\r\n"; - res << "Access-Control-Allow-Credentials: true\r\n"; - res << "Access-Control-Allow-Headers: Content-Type,Authorization,Origin,Accept\r\n"; - res << "Access-Control-Allow-Methods: OPTIONS, GET, POST\r\n"; - return res; + headers.Set("Access-Control-Allow-Origin", origin); + headers.Set("Access-Control-Allow-Credentials", "true"); + headers.Set("Access-Control-Allow-Headers", "Content-Type,Authorization,Origin,Accept"); + headers.Set("Access-Control-Allow-Methods", "OPTIONS, GET, POST"); } void ReplyCookieAndPassAway(const TString& cookie) { - TStringStream response; + ALOG_DEBUG(NActorsServices::HTTP, "Login success for " << AuthCredentials.Login); + NHttp::THeadersBuilder headers; + SetCORS(headers); TDuration maxAge = (ToInstant(NLogin::TLoginProvider::GetTokenExpiresAt(cookie)) - TInstant::Now()); - response << "HTTP/1.1 200 OK\r\n"; - response << "Set-Cookie: ydb_session_id=" << cookie << "; Max-Age=" << maxAge.Seconds() << "\r\n"; - response << GetCORS(); - response << "\r\n"; - Result.SetValue(MakeHolder<NMon::TEvHttpInfoRes>(response.Str(), 0, NMon::IEvHttpInfoRes::EContentType::Custom)); + headers.Set("Set-Cookie", TStringBuilder() << "ydb_session_id=" << cookie << "; Max-Age=" << maxAge.Seconds()); + Send(Sender, new NHttp::TEvHttpProxy::TEvHttpOutgoingResponse(Request->CreateResponse("200", "OK", headers))); PassAway(); } - void ReplyErrorAndPassAway(const TString& status, const TString& error) { + void ReplyErrorAndPassAway(const TString& status, const TString& message, const TString& error) { + ALOG_ERROR(NActorsServices::HTTP, "Login: " << error); + NHttp::THeadersBuilder headers; + SetCORS(headers); + headers.Set("Content-Type", "application/json"); NJson::TJsonValue body; body["error"] = error; - TStringStream response; - TString responseBody = NJson::WriteJson(body, false); - response << "HTTP/1.1 " << status << "\r\n"; - response << "Content-Type: application/json\r\n"; - response << "Content-Length: " << responseBody.Size() << "\r\n"; - response << GetCORS(); - response << "\r\n"; - response << responseBody; - Result.SetValue(MakeHolder<NMon::TEvHttpInfoRes>(response.Str(), 0, NMon::IEvHttpInfoRes::EContentType::Custom)); + Send(Sender, new NHttp::TEvHttpProxy::TEvHttpOutgoingResponse(Request->CreateResponse(status, message, headers, NJson::WriteJson(body, false)))); PassAway(); } + void PassAway() override { + if (PipeClient) { + NTabletPipe::CloseClient(TBase::SelfId(), PipeClient); + } + TBase::PassAway(); + } + protected: - IMonHttpRequest& Request; - NThreading::TPromise<THttpResponsePtr> Result; + TActorId Sender; + NHttp::THttpIncomingRequestPtr Request; TDuration Timeout = TDuration::Seconds(60); TString Database; - -private: + TActorId PipeClient; TAuthCredentials AuthCredentials; }; -class TLoginMonPage: public IMonPage { -public: - TLoginMonPage(TActorSystem* actorSystem, const TString& path) - : IMonPage(path, {}) - , ActorSystem(actorSystem) - { - } - - void Output(IMonHttpRequest &request) override { - auto promise = NThreading::NewPromise<THttpResponsePtr>(); - auto future = promise.GetFuture(); - - ActorSystem->Register(new TLoginRequest(request, promise)); - - THttpResponsePtr result = future.ExtractValue(TDuration::Max()); - - if (result) { - Output(request, *result); - } - } - -private: - void Output(IMonHttpRequest& request, const NMon::IEvHttpInfoRes& result) const { - result.Output(request.Output()); - } - -private: - TActorSystem* ActorSystem; -}; - class TLogoutRequest : public NActors::TActorBootstrapped<TLogoutRequest> { public: static constexpr NKikimrServices::TActivity::EType ActorActivityType() { return NKikimrServices::TActivity::ACTORLIB_COMMON; } - TLogoutRequest(IMonHttpRequest& request, NThreading::TPromise<THttpResponsePtr> result) - : Request(request) - , Result(result) + TLogoutRequest(NHttp::TEvHttpProxy::TEvHttpIncomingRequest::TPtr& ev) + : Sender(ev->Sender) + , Request(ev->Get()->Request) { } - ~TLogoutRequest() { - if (!Result.HasValue()) { - Result.SetValue(nullptr); - } - } - STATEFN(StateWork) { switch (ev->GetTypeRewrite()) { hFunc(TEvents::TEvPoisonPill, HandlePoisonPill); - cFunc(TEvents::TSystem::Wakeup, HandleTimeout); - } - } - - static TString GetMethod(HTTP_METHOD method) { - switch (method) { - case HTTP_METHOD_UNDEFINED: return "UNDEFINED"; - case HTTP_METHOD_OPTIONS: return "OPTIONS"; - case HTTP_METHOD_GET: return "GET"; - case HTTP_METHOD_HEAD: return "HEAD"; - case HTTP_METHOD_POST: return "POST"; - case HTTP_METHOD_PUT: return "PUT"; - case HTTP_METHOD_DELETE: return "DELETE"; - case HTTP_METHOD_TRACE: return "TRACE"; - case HTTP_METHOD_CONNECT: return "CONNECT"; - case HTTP_METHOD_EXTENSION: return "EXTENSION"; - default: return "UNKNOWN"; } } void Bootstrap() { - LOG_WARN_S(*TlsActivationContext, NActorsServices::HTTP, - Request.GetRemoteAddr() - << " " << GetMethod(Request.GetMethod()) - << " " << Request.GetUri()); + ALOG_WARN(NActorsServices::HTTP, Request->Address << " " << Request->Method << " " << Request->URL); - if (Request.GetMethod() == HTTP_METHOD_OPTIONS) { + if (Request->Method == "OPTIONS") { return ReplyOptionsAndPassAway(); } - if (Request.GetMethod() != HTTP_METHOD_POST) { - return ReplyErrorAndPassAway("400 Bad Request", "Invalid method"); - } - - if (Request.GetHeader("Content-Type").Before(';') != "application/json") { - return ReplyErrorAndPassAway("400 Bad Request", "Invalid Content-Type"); - } - - NJson::TJsonValue postData; - - if (!NJson::ReadJsonTree(Request.GetPostContent(), &postData)) { - return ReplyErrorAndPassAway("400 Bad Request", "Invalid JSON data"); + if (Request->Method != "POST") { + return ReplyErrorAndPassAway("400", "Bad Request", "Invalid method"); } ReplyDeleteCookieAndPassAway(); @@ -339,87 +273,88 @@ public: PassAway(); } - void HandleTimeout() { - ReplyErrorAndPassAway("504 Gateway Timeout", "Timeout"); - } - void ReplyOptionsAndPassAway() { - Result.SetValue(MakeHolder<NMon::TEvHttpInfoRes>( - "HTTP/1.1 204 No Content\r\n" - "Allow: OPTIONS, POST\r\n" - "Connection: Keep-Alive\r\n\r\n", 0, NMon::IEvHttpInfoRes::EContentType::Custom)); + NHttp::THeadersBuilder headers; + headers.Set("Allow", "OPTIONS, POST"); + Send(Sender, new NHttp::TEvHttpProxy::TEvHttpOutgoingResponse(Request->CreateResponse("204", "No Content", headers))); PassAway(); } + void SetCORS(NHttp::THeadersBuilder& headers) { + TStringBuilder res; + TString origin = TString(NHttp::THeaders(Request->Headers)["Origin"]); + if (origin.empty()) { + origin = "*"; + } + headers.Set("Access-Control-Allow-Origin", origin); + headers.Set("Access-Control-Allow-Credentials", "true"); + headers.Set("Access-Control-Allow-Headers", "Content-Type,Authorization,Origin,Accept"); + headers.Set("Access-Control-Allow-Methods", "OPTIONS, GET, POST"); + } + void ReplyDeleteCookieAndPassAway() { - TStringStream response; - response << "HTTP/1.1 200 OK\r\n"; - response << "Set-Cookie: ydb_session_id=; Max-Age=0\r\n"; - response << "\r\n"; - Result.SetValue(MakeHolder<NMon::TEvHttpInfoRes>(response.Str(), 0, NMon::IEvHttpInfoRes::EContentType::Custom)); + ALOG_DEBUG(NActorsServices::HTTP, "Logout success"); + NHttp::THeadersBuilder headers; + SetCORS(headers); + headers.Set("Set-Cookie", "ydb_session_id=; Max-Age=0"); + Send(Sender, new NHttp::TEvHttpProxy::TEvHttpOutgoingResponse(Request->CreateResponse("200", "OK", headers))); PassAway(); } - void ReplyErrorAndPassAway(const TString& status, const TString& error) { + void ReplyErrorAndPassAway(const TString& status, const TString& message, const TString& error) { + ALOG_ERROR(NActorsServices::HTTP, "Logout: " << error); + NHttp::THeadersBuilder headers; + SetCORS(headers); + headers.Set("Content-Type", "application/json"); NJson::TJsonValue body; body["error"] = error; - TStringStream response; - TString responseBody = NJson::WriteJson(body, false); - response << "HTTP/1.1 " << status << "\r\n"; - response << "Content-Type: application/json\r\n"; - response << "Content-Length: " << responseBody.Size() << "\r\n"; - response << "\r\n"; - response << responseBody; - Result.SetValue(MakeHolder<NMon::TEvHttpInfoRes>(response.Str(), 0, NMon::IEvHttpInfoRes::EContentType::Custom)); + Send(Sender, new NHttp::TEvHttpProxy::TEvHttpOutgoingResponse(Request->CreateResponse(status, message, headers, NJson::WriteJson(body, false)))); PassAway(); } protected: - IMonHttpRequest& Request; - NThreading::TPromise<THttpResponsePtr> Result; - TDuration Timeout = TDuration::Seconds(60); + TActorId Sender; + NHttp::THttpIncomingRequestPtr Request; }; -class TLogoutMonPage: public IMonPage { +class TLoginService : public TActor<TLoginService> { public: - TLogoutMonPage(TActorSystem* actorSystem, const TString& path) - : IMonPage(path, {}) - , ActorSystem(actorSystem) - { - } - - void Output(IMonHttpRequest &request) override { - auto promise = NThreading::NewPromise<THttpResponsePtr>(); - auto future = promise.GetFuture(); - - ActorSystem->Register(new TLogoutRequest(request, promise)); + TLoginService() + : TActor(&TLoginService::Work) + {} - THttpResponsePtr result = future.ExtractValue(TDuration::Max()); + static constexpr NKikimrServices::TActivity::EType ActorActivityType() { + return NKikimrServices::TActivity::ACTORLIB_COMMON; + } - if (result) { - Output(request, *result); + STATEFN(Work) { + switch (ev->GetTypeRewrite()) { + hFunc(TEvents::TEvPoisonPill, HandlePoisonPill); + hFunc(NHttp::TEvHttpProxy::TEvHttpIncomingRequest, HandleRequest); } } -private: - void Output(IMonHttpRequest& request, const NMon::IEvHttpInfoRes& result) const { - result.Output(request.Output()); + void HandlePoisonPill(TEvents::TEvPoisonPill::TPtr&) { + PassAway(); } -private: - TActorSystem* ActorSystem; + void HandleRequest(NHttp::TEvHttpProxy::TEvHttpIncomingRequest::TPtr& ev) { + if (ev->Get()->Request->URL == "/login") { + Register(new TLoginRequest(ev)); + } else if (ev->Get()->Request->URL == "/logout") { + Register(new TLogoutRequest(ev)); + } else { + Send(ev->Sender, new NHttp::TEvHttpProxy::TEvHttpOutgoingResponse(ev->Get()->Request->CreateResponseNotFound())); + } + } }; } namespace NKikimr { -IMonPage* CreateLoginPage(TActorSystem* actorSystem, const TString& path) { - return new TLoginMonPage(actorSystem, path); -} - -IMonPage* CreateLogoutPage(TActorSystem* actorSystem, const TString& path) { - return new TLogoutMonPage(actorSystem, path); +NActors::IActor* CreateWebLoginService() { + return new TLoginService(); } } diff --git a/ydb/core/security/login_page.h b/ydb/core/security/login_page.h index fcf80e8c99..f1ef0a8008 100644 --- a/ydb/core/security/login_page.h +++ b/ydb/core/security/login_page.h @@ -3,7 +3,11 @@ namespace NKikimr { -NMonitoring::IMonPage* CreateLoginPage(NActors::TActorSystem* actorSystem, const TString& path = "login"); -NMonitoring::IMonPage* CreateLogoutPage(NActors::TActorSystem* actorSystem, const TString& path = "logout"); +inline NActors::TActorId MakeWebLoginServiceId() { + const char name[12] = "webloginsvc"; + return NActors::TActorId(0, TStringBuf(name, 12)); +} + +NActors::IActor* CreateWebLoginService(); } diff --git a/ydb/core/tx/tx_proxy/proxy_impl.cpp b/ydb/core/tx/tx_proxy/proxy_impl.cpp index 1bef920e11..28c7a2b50d 100644 --- a/ydb/core/tx/tx_proxy/proxy_impl.cpp +++ b/ydb/core/tx/tx_proxy/proxy_impl.cpp @@ -450,7 +450,7 @@ public: Become(&TThis::StateWork); LOG_DEBUG_S(ctx, NKikimrServices::TX_PROXY, "actor# " << SelfId() << - " Become StateWork"); + " Become StateWork (SchemeCache " << Services.SchemeCache << ")"); } static constexpr NKikimrServices::TActivity::EType ActorActivityType() { |