diff options
author | hcpp <hcpp@ydb.tech> | 2023-11-08 12:09:41 +0300 |
---|---|---|
committer | hcpp <hcpp@ydb.tech> | 2023-11-08 12:56:14 +0300 |
commit | a361f5b98b98b44ea510d274f6769164640dd5e1 (patch) | |
tree | c47c80962c6e2e7b06798238752fd3da0191a3f6 /library/cpp/tvmauth/client/misc/tool | |
parent | 9478806fde1f4d40bd5a45e7cbe77237dab613e9 (diff) | |
download | ydb-a361f5b98b98b44ea510d274f6769164640dd5e1.tar.gz |
metrics have been added
Diffstat (limited to 'library/cpp/tvmauth/client/misc/tool')
-rw-r--r-- | library/cpp/tvmauth/client/misc/tool/meta_info.cpp | 208 | ||||
-rw-r--r-- | library/cpp/tvmauth/client/misc/tool/meta_info.h | 69 | ||||
-rw-r--r-- | library/cpp/tvmauth/client/misc/tool/roles_fetcher.cpp | 81 | ||||
-rw-r--r-- | library/cpp/tvmauth/client/misc/tool/roles_fetcher.h | 49 | ||||
-rw-r--r-- | library/cpp/tvmauth/client/misc/tool/settings.cpp | 37 | ||||
-rw-r--r-- | library/cpp/tvmauth/client/misc/tool/settings.h | 178 | ||||
-rw-r--r-- | library/cpp/tvmauth/client/misc/tool/threaded_updater.cpp | 442 | ||||
-rw-r--r-- | library/cpp/tvmauth/client/misc/tool/threaded_updater.h | 65 |
8 files changed, 1129 insertions, 0 deletions
diff --git a/library/cpp/tvmauth/client/misc/tool/meta_info.cpp b/library/cpp/tvmauth/client/misc/tool/meta_info.cpp new file mode 100644 index 0000000000..9a0ae228fe --- /dev/null +++ b/library/cpp/tvmauth/client/misc/tool/meta_info.cpp @@ -0,0 +1,208 @@ +#include "meta_info.h" + +#include <library/cpp/json/json_reader.h> + +#include <util/string/builder.h> + +namespace NTvmAuth::NTvmTool { + TString TMetaInfo::TConfig::ToString() const { + TStringStream s; + s << "self_tvm_id=" << SelfTvmId << ", " + << "bb_env=" << BbEnv << ", " + << "idm_slug=" << (IdmSlug ? IdmSlug : "<NULL>") << ", " + << "dsts=["; + + for (const auto& pair : DstAliases) { + s << "(" << pair.first << ":" << pair.second << ")"; + } + + s << "]"; + + return std::move(s.Str()); + } + + TMetaInfo::TMetaInfo(TLoggerPtr logger) + : Logger_(std::move(logger)) + { + } + + TMetaInfo::TConfigPtr TMetaInfo::Init(TKeepAliveHttpClient& client, + const TClientSettings& settings) { + ApplySettings(settings); + + TryPing(client); + const TString metaString = Fetch(client); + if (Logger_) { + TStringStream s; + s << "Meta info fetched from " << settings.GetHostname() << ":" << settings.GetPort(); + Logger_->Debug(s.Str()); + } + + try { + Config_.Set(ParseMetaString(metaString, SelfAlias_)); + } catch (const yexception& e) { + ythrow TNonRetriableException() << "Malformed json from tvmtool: " << e.what(); + } + TConfigPtr cfg = Config_.Get(); + Y_ENSURE_EX(cfg, TNonRetriableException() << "Alias '" << SelfAlias_ << "' not found in meta info"); + + if (Logger_) { + Logger_->Info("Meta: " + cfg->ToString()); + } + + return cfg; + } + + TString TMetaInfo::GetRequestForTickets(const TConfig& config) { + Y_ENSURE(!config.DstAliases.empty()); + + TStringStream s; + s << "/tvm/tickets" + << "?src=" << config.SelfTvmId + << "&dsts="; + + for (const auto& pair : config.DstAliases) { + s << pair.second << ","; // avoid aliases - url-encoding required + } + s.Str().pop_back(); + + return s.Str(); + } + + bool TMetaInfo::TryUpdateConfig(TKeepAliveHttpClient& client) { + const TString metaString = Fetch(client); + + TConfigPtr config; + try { + config = ParseMetaString(metaString, SelfAlias_); + } catch (const yexception& e) { + ythrow TNonRetriableException() << "Malformed json from tvmtool: " << e.what(); + } + Y_ENSURE_EX(config, TNonRetriableException() << "Alias '" << SelfAlias_ << "' not found in meta info"); + + TConfigPtr oldConfig = Config_.Get(); + if (*config == *oldConfig) { + return false; + } + + if (Logger_) { + Logger_->Info(TStringBuilder() + << "Meta was updated. Old: (" << oldConfig->ToString() + << "). New: (" << config->ToString() << ")"); + } + + Config_ = config; + return true; + } + + void TMetaInfo::TryPing(TKeepAliveHttpClient& client) { + try { + TStringStream s; + TKeepAliveHttpClient::THttpCode code = client.DoGet("/tvm/ping", &s); + if (code < 200 || 300 <= code) { + throw yexception() << "(" << code << ") " << s.Str(); + } + } catch (const std::exception& e) { + ythrow TNonRetriableException() << "Failed to connect to tvmtool: " << e.what(); + } + } + + TString TMetaInfo::Fetch(TKeepAliveHttpClient& client) const { + TStringStream res; + TKeepAliveHttpClient::THttpCode code; + try { + code = client.DoGet("/tvm/private_api/__meta__", &res, AuthHeader_); + } catch (const std::exception& e) { + ythrow TRetriableException() << "Failed to fetch meta data from tvmtool: " << e.what(); + } + + if (code != 200) { + Y_ENSURE_EX(code != 404, + TNonRetriableException() << "Library does not support so old tvmtool. You need tvmtool>=1.1.0"); + + TStringStream err; + err << "Failed to fetch meta from tvmtool: " << client.GetHost() << ":" << client.GetPort() + << " (" << code << "): " << res.Str(); + Y_ENSURE_EX(!(500 <= code && code < 600), TRetriableException() << err.Str()); + ythrow TNonRetriableException() << err.Str(); + } + + return res.Str(); + } + + static TMetaInfo::TDstAliases::value_type ParsePair(const NJson::TJsonValue& val, const TString& meta) { + NJson::TJsonValue jAlias; + Y_ENSURE(val.GetValue("alias", &jAlias), meta); + Y_ENSURE(jAlias.IsString(), meta); + + NJson::TJsonValue jClientId; + Y_ENSURE(val.GetValue("client_id", &jClientId), meta); + Y_ENSURE(jClientId.IsInteger(), meta); + + return {jAlias.GetString(), jClientId.GetInteger()}; + } + + TMetaInfo::TConfigPtr TMetaInfo::ParseMetaString(const TString& meta, const TString& self) { + NJson::TJsonValue jDoc; + Y_ENSURE(NJson::ReadJsonTree(meta, &jDoc), meta); + + NJson::TJsonValue jEnv; + Y_ENSURE(jDoc.GetValue("bb_env", &jEnv), meta); + + NJson::TJsonValue jTenants; + Y_ENSURE(jDoc.GetValue("tenants", &jTenants), meta); + Y_ENSURE(jTenants.IsArray(), meta); + + for (const NJson::TJsonValue& jTen : jTenants.GetArray()) { + NJson::TJsonValue jSelf; + Y_ENSURE(jTen.GetValue("self", &jSelf), meta); + auto selfPair = ParsePair(jSelf, meta); + if (selfPair.first != self) { + continue; + } + + TConfigPtr config = std::make_shared<TConfig>(); + config->SelfTvmId = selfPair.second; + config->BbEnv = BbEnvFromString(jEnv.GetString(), meta); + + { + NJson::TJsonValue jSlug; + if (jTen.GetValue("idm_slug", &jSlug)) { + config->IdmSlug = jSlug.GetString(); + } + } + + NJson::TJsonValue jDsts; + Y_ENSURE(jTen.GetValue("dsts", &jDsts), meta); + Y_ENSURE(jDsts.IsArray(), meta); + for (const NJson::TJsonValue& jDst : jDsts.GetArray()) { + config->DstAliases.insert(ParsePair(jDst, meta)); + } + + return config; + } + + return {}; + } + + void TMetaInfo::ApplySettings(const TClientSettings& settings) { + AuthHeader_ = {{"Authorization", settings.GetAuthToken()}}; + SelfAlias_ = settings.GetSelfAlias(); + } + + EBlackboxEnv TMetaInfo::BbEnvFromString(const TString& env, const TString& meta) { + if (env == "Prod") { + return EBlackboxEnv::Prod; + } else if (env == "Test") { + return EBlackboxEnv::Test; + } else if (env == "ProdYaTeam") { + return EBlackboxEnv::ProdYateam; + } else if (env == "TestYaTeam") { + return EBlackboxEnv::TestYateam; + } else if (env == "Stress") { + return EBlackboxEnv::Stress; + } + + ythrow yexception() << "'bb_env'=='" << env << "'. " << meta; + } +} diff --git a/library/cpp/tvmauth/client/misc/tool/meta_info.h b/library/cpp/tvmauth/client/misc/tool/meta_info.h new file mode 100644 index 0000000000..9dd4f0dbf8 --- /dev/null +++ b/library/cpp/tvmauth/client/misc/tool/meta_info.h @@ -0,0 +1,69 @@ +#pragma once + +#include "settings.h" + +#include <library/cpp/tvmauth/client/misc/utils.h> + +#include <library/cpp/tvmauth/client/logger.h> + +#include <library/cpp/http/simple/http_client.h> + +namespace NTvmAuth::NTvmTool { + class TMetaInfo { + public: + using TDstAliases = THashMap<TClientSettings::TAlias, TTvmId>; + + struct TConfig { + TTvmId SelfTvmId = 0; + EBlackboxEnv BbEnv = EBlackboxEnv::Prod; + TString IdmSlug; + TDstAliases DstAliases; + + bool AreTicketsRequired() const { + return !DstAliases.empty(); + } + + TString ToString() const; + + bool operator==(const TConfig& c) const { + return SelfTvmId == c.SelfTvmId && + BbEnv == c.BbEnv && + IdmSlug == c.IdmSlug && + DstAliases == c.DstAliases; + } + }; + using TConfigPtr = std::shared_ptr<TConfig>; + + public: + TMetaInfo(TLoggerPtr logger); + + TConfigPtr Init(TKeepAliveHttpClient& client, + const TClientSettings& settings); + + static TString GetRequestForTickets(const TMetaInfo::TConfig& config); + + const TKeepAliveHttpClient::THeaders& GetAuthHeader() const { + return AuthHeader_; + } + + TConfigPtr GetConfig() const { + return Config_.Get(); + } + + bool TryUpdateConfig(TKeepAliveHttpClient& client); + + protected: + void TryPing(TKeepAliveHttpClient& client); + TString Fetch(TKeepAliveHttpClient& client) const; + static TConfigPtr ParseMetaString(const TString& meta, const TString& self); + void ApplySettings(const TClientSettings& settings); + static EBlackboxEnv BbEnvFromString(const TString& env, const TString& meta); + + protected: + NUtils::TProtectedValue<TConfigPtr> Config_; + TKeepAliveHttpClient::THeaders AuthHeader_; + + TLoggerPtr Logger_; + TString SelfAlias_; + }; +} diff --git a/library/cpp/tvmauth/client/misc/tool/roles_fetcher.cpp b/library/cpp/tvmauth/client/misc/tool/roles_fetcher.cpp new file mode 100644 index 0000000000..05b0856edc --- /dev/null +++ b/library/cpp/tvmauth/client/misc/tool/roles_fetcher.cpp @@ -0,0 +1,81 @@ +#include "roles_fetcher.h" + +#include <library/cpp/tvmauth/client/misc/roles/parser.h> + +#include <library/cpp/http/misc/httpcodes.h> +#include <library/cpp/string_utils/quote/quote.h> + +#include <util/string/builder.h> +#include <util/string/join.h> + +namespace NTvmAuth::NTvmTool { + TRolesFetcher::TRolesFetcher(const TRolesFetcherSettings& settings, TLoggerPtr logger) + : Settings_(settings) + , Logger_(std::move(logger)) + { + } + + bool TRolesFetcher::IsTimeToUpdate(TDuration sinceUpdate) const { + return Settings_.UpdatePeriod < sinceUpdate; + } + + bool TRolesFetcher::ShouldWarn(TDuration sinceUpdate) const { + return Settings_.WarnPeriod < sinceUpdate; + } + + bool TRolesFetcher::AreRolesOk() const { + return bool(GetCurrentRoles()); + } + + NUtils::TFetchResult TRolesFetcher::FetchActualRoles(const TKeepAliveHttpClient::THeaders& authHeader, + TKeepAliveHttpClient& client) const { + const TRequest req = CreateRequest(authHeader); + + TStringStream out; + THttpHeaders outHeaders; + + TKeepAliveHttpClient::THttpCode code = client.DoGet( + req.Url, + &out, + req.Headers, + &outHeaders); + + return {code, std::move(outHeaders), "/v2/roles", out.Str(), {}}; + } + + void TRolesFetcher::Update(NUtils::TFetchResult&& fetchResult) { + if (fetchResult.Code == HTTP_NOT_MODIFIED) { + Y_ENSURE(CurrentRoles_.Get(), + "tvmtool did not return any roles because current roles are actual," + " but there are no roles in memory - this should never happen"); + return; + } + + Y_ENSURE(fetchResult.Code == HTTP_OK, + "Unexpected code from tvmtool: " << fetchResult.Code << ". " << fetchResult.Response); + + CurrentRoles_.Set(NRoles::TParser::Parse(std::make_shared<TString>(std::move(fetchResult.Response)))); + + Logger_->Debug( + TStringBuilder() << "Succeed to update roles with revision " + << CurrentRoles_.Get()->GetMeta().Revision); + } + + NTvmAuth::NRoles::TRolesPtr TRolesFetcher::GetCurrentRoles() const { + return CurrentRoles_.Get(); + } + + TRolesFetcher::TRequest TRolesFetcher::CreateRequest(const TKeepAliveHttpClient::THeaders& authHeader) const { + TRequest request{ + .Url = "/v2/roles?self=" + CGIEscapeRet(Settings_.SelfAlias), + .Headers = authHeader, + }; + + NRoles::TRolesPtr roles = CurrentRoles_.Get(); + if (roles) { + request.Headers.emplace(IfNoneMatch_, Join("", "\"", roles->GetMeta().Revision, "\"")); + } + + return request; + } +} diff --git a/library/cpp/tvmauth/client/misc/tool/roles_fetcher.h b/library/cpp/tvmauth/client/misc/tool/roles_fetcher.h new file mode 100644 index 0000000000..8c60b59610 --- /dev/null +++ b/library/cpp/tvmauth/client/misc/tool/roles_fetcher.h @@ -0,0 +1,49 @@ +#pragma once + +#include <library/cpp/tvmauth/client/misc/fetch_result.h> +#include <library/cpp/tvmauth/client/misc/utils.h> +#include <library/cpp/tvmauth/client/misc/roles/roles.h> + +#include <library/cpp/tvmauth/client/logger.h> + +#include <util/datetime/base.h> +#include <util/generic/string.h> + +namespace NTvmAuth::NTvmTool { + struct TRolesFetcherSettings { + TString SelfAlias; + TDuration UpdatePeriod = TDuration::Minutes(1); + TDuration WarnPeriod = TDuration::Minutes(20); + }; + + class TRolesFetcher { + public: + TRolesFetcher(const TRolesFetcherSettings& settings, TLoggerPtr logger); + + bool IsTimeToUpdate(TDuration sinceUpdate) const; + bool ShouldWarn(TDuration sinceUpdate) const; + bool AreRolesOk() const; + + NUtils::TFetchResult FetchActualRoles(const TKeepAliveHttpClient::THeaders& authHeader, + TKeepAliveHttpClient& client) const; + void Update(NUtils::TFetchResult&& fetchResult); + + NTvmAuth::NRoles::TRolesPtr GetCurrentRoles() const; + + protected: + struct TRequest { + TString Url; + TKeepAliveHttpClient::THeaders Headers; + }; + + protected: + TRequest CreateRequest(const TKeepAliveHttpClient::THeaders& authHeader) const; + + private: + const TRolesFetcherSettings Settings_; + const TLoggerPtr Logger_; + const TString IfNoneMatch_ = "If-None-Match"; + + NUtils::TProtectedValue<NTvmAuth::NRoles::TRolesPtr> CurrentRoles_; + }; +} diff --git a/library/cpp/tvmauth/client/misc/tool/settings.cpp b/library/cpp/tvmauth/client/misc/tool/settings.cpp new file mode 100644 index 0000000000..894501f19d --- /dev/null +++ b/library/cpp/tvmauth/client/misc/tool/settings.cpp @@ -0,0 +1,37 @@ +#include "settings.h" + +#include <library/cpp/string_utils/url/url.h> + +#include <util/system/env.h> + +namespace NTvmAuth::NTvmTool { + TClientSettings::TClientSettings(const TAlias& selfAias) + : SelfAias_(selfAias) + , Hostname_("localhost") + , Port_(1) + , SocketTimeout_(TDuration::Seconds(5)) + , ConnectTimeout_(TDuration::Seconds(30)) + { + AuthToken_ = GetEnv("TVMTOOL_LOCAL_AUTHTOKEN"); + if (!AuthToken_) { + AuthToken_ = GetEnv("QLOUD_TVM_TOKEN"); + } + TStringBuf auth(AuthToken_); + FixSpaces(auth); + AuthToken_ = auth; + + const TString url = GetEnv("DEPLOY_TVM_TOOL_URL"); + if (url) { + TStringBuf scheme, host; + TryGetSchemeHostAndPort(url, scheme, host, Port_); + } + + Y_ENSURE_EX(SelfAias_, TBrokenTvmClientSettings() << "Alias for your TVM client cannot be empty"); + } + + void TClientSettings::FixSpaces(TStringBuf& str) { + while (str && isspace(str.back())) { + str.Chop(1); + } + } +} diff --git a/library/cpp/tvmauth/client/misc/tool/settings.h b/library/cpp/tvmauth/client/misc/tool/settings.h new file mode 100644 index 0000000000..1267ca1527 --- /dev/null +++ b/library/cpp/tvmauth/client/misc/tool/settings.h @@ -0,0 +1,178 @@ +#pragma once + +#include <library/cpp/tvmauth/client/misc/settings.h> + +#include <library/cpp/tvmauth/client/exception.h> + +#include <library/cpp/tvmauth/checked_user_ticket.h> + +#include <util/datetime/base.h> +#include <util/generic/maybe.h> + +namespace NTvmAuth::NTvmTool { + /** + * Uses local http-interface to get state: http://localhost/tvm/. + * This interface can be provided with tvmtool (local daemon) or Qloud/YP (local http api in container). + * See more: https://wiki.yandex-team.ru/passport/tvm2/qloud/. + * + * Most part of settings will be fetched from tvmtool on start of client. + * You need to use aliases for TVM-clients (src and dst) which you specified in tvmtool or Qloud/YP interface + */ + class TClientSettings: public NTvmAuth::TClientSettings { + public: + /*! + * Sets default values: + * - hostname == "localhost" + * - port detected with env["DEPLOY_TVM_TOOL_URL"] (provided with Yandex.Deploy), + * otherwise port == 1 (it is ok for Qloud) + * - authToken: env["TVMTOOL_LOCAL_AUTHTOKEN"] (provided with Yandex.Deploy), + * otherwise env["QLOUD_TVM_TOKEN"] (provided with Qloud) + * + * AuthToken is protection from SSRF. + * + * @param selfAias - alias for your TVM client, which you specified in tvmtool or YD interface + */ + TClientSettings(const TAlias& selfAias); + + /*! + * Look at comment for ctor + * @param port + */ + TClientSettings& SetPort(ui16 port) { + Port_ = port; + return *this; + } + + /*! + * Default value: hostname == "localhost" + * @param hostname + */ + TClientSettings& SetHostname(const TString& hostname) { + Y_ENSURE_EX(hostname, TBrokenTvmClientSettings() << "Hostname cannot be empty"); + Hostname_ = hostname; + return *this; + } + + TClientSettings& SetSocketTimeout(TDuration socketTimeout) { + SocketTimeout_ = socketTimeout; + return *this; + } + + TClientSettings& SetConnectTimeout(TDuration connectTimeout) { + ConnectTimeout_ = connectTimeout; + return *this; + } + + /*! + * Look at comment for ctor + * @param token + */ + TClientSettings& SetAuthToken(TStringBuf token) { + FixSpaces(token); + Y_ENSURE_EX(token, TBrokenTvmClientSettings() << "Auth token cannot be empty"); + AuthToken_ = token; + return *this; + } + + /*! + * Blackbox environmet is provided by tvmtool for client. + * You can override it for your purpose with limitations: + * (env from tvmtool) -> (override) + * - Prod/ProdYateam -> Prod/ProdYateam + * - Test/TestYateam -> Test/TestYateam + * - Stress -> Stress + * + * You can contact tvm-dev@yandex-team.ru if limitations are too strict + * @param env + */ + TClientSettings& OverrideBlackboxEnv(EBlackboxEnv env) { + BbEnv_ = env; + return *this; + } + + /*! + * By default client checks src from ServiceTicket or default uid from UserTicket - + * to prevent you from forgetting to check it yourself. + * It does binary checks only: + * ticket gets status NoRoles, if there is no role for src or default uid. + * You need to check roles on your own if you have a non-binary role system or + * you have disabled ShouldCheckSrc/ShouldCheckDefaultUid + * + * You may need to disable this check in the following cases: + * - You use GetRoles() to provide verbose message (with revision). + * Double check may be inconsistent: + * binary check inside client uses revision of roles X - i.e. src 100500 has no role, + * exact check in your code uses revision of roles Y - i.e. src 100500 has some roles. + */ + bool ShouldCheckSrc = true; + bool ShouldCheckDefaultUid = true; + /*! + * By default client checks dst from ServiceTicket. If this check is switched off + * incorrect dst does not result in error of checked ticket status + * DANGEROUS: This case you must check dst manualy using @link TCheckedServiceTicket::GetDst() + */ + bool ShouldCheckDst = true; + + // In case of unsuccessful initialization at startup the client will be initialized in the background + bool EnableLazyInitialization = false; + + // DEPRECATED API + // TODO: get rid of it: PASSP-35377 + public: + // Deprecated: set attributes directly + TClientSettings& SetShouldCheckSrc(bool val = true) { + ShouldCheckSrc = val; + return *this; + } + + // Deprecated: set attributes directly + TClientSettings& SetSShouldCheckDefaultUid(bool val = true) { + ShouldCheckDefaultUid = val; + return *this; + } + + public: // for TAsyncUpdaterBase + const TAlias& GetSelfAlias() const { + return SelfAias_; + } + + const TString& GetHostname() const { + return Hostname_; + } + + ui16 GetPort() const { + return Port_; + } + + TDuration GetSocketTimeout() const { + return SocketTimeout_; + } + + TDuration GetConnectTimeout() const { + return ConnectTimeout_; + } + + const TString& GetAuthToken() const { + Y_ENSURE_EX(AuthToken_, TBrokenTvmClientSettings() + << "Auth token cannot be empty. " + << "Env 'TVMTOOL_LOCAL_AUTHTOKEN' and 'QLOUD_TVM_TOKEN' are empty."); + return AuthToken_; + } + + TMaybe<EBlackboxEnv> GetOverridedBlackboxEnv() const { + return BbEnv_; + } + + private: + void FixSpaces(TStringBuf& str); + + private: + TAlias SelfAias_; + TString Hostname_; + ui16 Port_; + TDuration SocketTimeout_; + TDuration ConnectTimeout_; + TString AuthToken_; + TMaybe<EBlackboxEnv> BbEnv_; + }; +} diff --git a/library/cpp/tvmauth/client/misc/tool/threaded_updater.cpp b/library/cpp/tvmauth/client/misc/tool/threaded_updater.cpp new file mode 100644 index 0000000000..82dd57a77a --- /dev/null +++ b/library/cpp/tvmauth/client/misc/tool/threaded_updater.cpp @@ -0,0 +1,442 @@ +#include "threaded_updater.h" + +#include <library/cpp/tvmauth/client/misc/checker.h> +#include <library/cpp/tvmauth/client/misc/default_uid_checker.h> +#include <library/cpp/tvmauth/client/misc/getter.h> +#include <library/cpp/tvmauth/client/misc/src_checker.h> +#include <library/cpp/tvmauth/client/misc/utils.h> + +#include <library/cpp/json/json_reader.h> + +#include <util/generic/hash_set.h> +#include <util/stream/str.h> +#include <util/string/ascii.h> +#include <util/string/builder.h> +#include <util/string/cast.h> + +namespace NTvmAuth::NTvmTool { + TAsyncUpdaterPtr TThreadedUpdater::Create(const TClientSettings& settings, TLoggerPtr logger) { + Y_ENSURE_EX(logger, TNonRetriableException() << "Logger is required"); + THolder<TThreadedUpdater> p(new TThreadedUpdater(settings, std::move(logger))); + + try { + p->Init(settings); + } catch (const TRetriableException& e) { + if (!settings.EnableLazyInitialization) { + throw e; + } + } + + p->StartWorker(); + return p.Release(); + } + + TThreadedUpdater::~TThreadedUpdater() { + StopWorker(); // Required here to avoid using of deleted members + } + + TClientStatus TThreadedUpdater::GetStatus() const { + const TClientStatus::ECode state = GetState(); + return TClientStatus(state, GetLastError(state == TClientStatus::Ok)); + } + + TString TThreadedUpdater::GetServiceTicketFor(const TClientSettings::TAlias& dst) const { + Y_ENSURE_EX(IsInited(), TNotInitializedException() << "Client is not initialized"); + + if (!MetaInfo_.GetConfig()->AreTicketsRequired()) { + throw TBrokenTvmClientSettings() << "Need to enable ServiceTickets fetching"; + } + auto c = GetCachedServiceTickets(); + return TServiceTicketGetter::GetTicket(dst, c); + } + + TString TThreadedUpdater::GetServiceTicketFor(const TTvmId dst) const { + Y_ENSURE_EX(IsInited(), TNotInitializedException() << "Client is not initialized"); + + if (!MetaInfo_.GetConfig()->AreTicketsRequired()) { + throw TBrokenTvmClientSettings() << "Need to enable ServiceTickets fetching"; + } + auto c = GetCachedServiceTickets(); + return TServiceTicketGetter::GetTicket(dst, c); + } + + TCheckedServiceTicket TThreadedUpdater::CheckServiceTicket(TStringBuf ticket, const TServiceContext::TCheckFlags& flags) const { + Y_ENSURE_EX(IsInited(), TNotInitializedException() << "Client is not initialized"); + + TServiceContextPtr c = GetCachedServiceContext(); + TCheckedServiceTicket res = TServiceTicketChecker::Check(ticket, c, flags); + if (Settings_.ShouldCheckSrc && RolesFetcher_ && res) { + NRoles::TRolesPtr roles = GetRoles(); + return TSrcChecker::Check(std::move(res), roles); + } + return res; + } + + TCheckedUserTicket TThreadedUpdater::CheckUserTicket(TStringBuf ticket, TMaybe<EBlackboxEnv> overridenEnv) const { + Y_ENSURE_EX(IsInited(), TNotInitializedException() << "Client is not initialized"); + + auto c = GetCachedUserContext(overridenEnv); + TCheckedUserTicket res = TUserTicketChecker::Check(ticket, c); + if (Settings_.ShouldCheckDefaultUid && RolesFetcher_ && res && res.GetEnv() == EBlackboxEnv::ProdYateam) { + NRoles::TRolesPtr roles = GetRoles(); + return TDefaultUidChecker::Check(std::move(res), roles); + } + return res; + } + + NRoles::TRolesPtr TThreadedUpdater::GetRoles() const { + Y_ENSURE_EX(IsInited(), TNotInitializedException() << "Client is not initialized"); + + Y_ENSURE_EX(RolesFetcher_, + TBrokenTvmClientSettings() << "Roles were not configured in settings"); + + return RolesFetcher_->GetCurrentRoles(); + } + + TClientStatus::ECode TThreadedUpdater::GetState() const { + const TInstant now = TInstant::Now(); + + if (!IsInited()) { + return TClientStatus::NotInitialized; + } + + const TMetaInfo::TConfigPtr config = MetaInfo_.GetConfig(); + + if ((config->AreTicketsRequired() && AreServiceTicketsInvalid(now)) || ArePublicKeysInvalid(now)) { + return TClientStatus::Error; + } + + if (config->AreTicketsRequired()) { + if (!GetCachedServiceTickets() || config->DstAliases.size() > GetCachedServiceTickets()->TicketsByAlias.size()) { + return TClientStatus::Error; + } + } + + const TDuration st = now - GetUpdateTimeOfServiceTickets(); + const TDuration pk = now - GetUpdateTimeOfPublicKeys(); + + if ((config->AreTicketsRequired() && st > ServiceTicketsDurations_.Expiring) || pk > PublicKeysDurations_.Expiring) { + return TClientStatus::Warning; + } + + if (RolesFetcher_ && RolesFetcher_->ShouldWarn(now - GetUpdateTimeOfRoles())) { + return TClientStatus::Warning; + } + + if (IsConfigWarnTime()) { + return TClientStatus::Warning; + } + + return TClientStatus::Ok; + } + + TThreadedUpdater::TThreadedUpdater(const TClientSettings& settings, TLoggerPtr logger) + : TThreadedUpdaterBase(TDuration::Seconds(5), logger, settings.GetHostname(), settings.GetPort(), settings.GetSocketTimeout(), settings.GetConnectTimeout()) + , MetaInfo_(logger) + , ConfigWarnDelay_(TDuration::Seconds(30)) + , Settings_(settings) + { + ServiceTicketsDurations_.RefreshPeriod = TDuration::Minutes(10); + PublicKeysDurations_.RefreshPeriod = TDuration::Minutes(10); + } + + void TThreadedUpdater::Init(const TClientSettings& settings) { + const TMetaInfo::TConfigPtr config = MetaInfo_.Init(GetClient(), settings); + LastVisitForConfig_ = TInstant::Now(); + + SetBbEnv(config->BbEnv, settings.GetOverridedBlackboxEnv()); + if (settings.GetOverridedBlackboxEnv()) { + LogInfo(TStringBuilder() + << "Meta: override blackbox env: " << config->BbEnv + << "->" << *settings.GetOverridedBlackboxEnv()); + } + + if (config->IdmSlug) { + RolesFetcher_ = std::make_unique<TRolesFetcher>( + TRolesFetcherSettings{ + .SelfAlias = settings.GetSelfAlias(), + }, + Logger_); + } + + ui8 tries = 3; + do { + UpdateState(); + } while (!IsEverythingOk(*config) && --tries > 0); + + if (!IsEverythingOk(*config)) { + ThrowLastError(); + } + SetInited(true); + } + + void TThreadedUpdater::UpdateState() { + bool wasUpdated = false; + try { + wasUpdated = MetaInfo_.TryUpdateConfig(GetClient()); + LastVisitForConfig_ = TInstant::Now(); + ClearError(EScope::TvmtoolConfig); + } catch (const std::exception& e) { + ProcessError(EType::Retriable, EScope::TvmtoolConfig, e.what()); + LogWarning(TStringBuilder() << "Error while fetching of tvmtool config: " << e.what()); + } + if (IsConfigWarnTime()) { + LogError(TStringBuilder() << "Tvmtool config have not been refreshed for too long period"); + } + + TMetaInfo::TConfigPtr config = MetaInfo_.GetConfig(); + + if (wasUpdated || IsTimeToUpdateServiceTickets(*config, LastVisitForServiceTickets_)) { + try { + const TInstant updateTime = UpdateServiceTickets(*config); + SetUpdateTimeOfServiceTickets(updateTime); + LastVisitForServiceTickets_ = TInstant::Now(); + + if (AreServiceTicketsOk(*config)) { + ClearError(EScope::ServiceTickets); + } + LogDebug(TStringBuilder() << "Tickets fetched from tvmtool: " << updateTime); + } catch (const std::exception& e) { + ProcessError(EType::Retriable, EScope::ServiceTickets, e.what()); + LogWarning(TStringBuilder() << "Error while fetching of tickets: " << e.what()); + } + + if (TInstant::Now() - GetUpdateTimeOfServiceTickets() > ServiceTicketsDurations_.Expiring) { + LogError("Service tickets have not been refreshed for too long period"); + } + } + + if (wasUpdated || IsTimeToUpdatePublicKeys(LastVisitForPublicKeys_)) { + try { + const TInstant updateTime = UpdateKeys(*config); + SetUpdateTimeOfPublicKeys(updateTime); + LastVisitForPublicKeys_ = TInstant::Now(); + + if (ArePublicKeysOk()) { + ClearError(EScope::PublicKeys); + } + LogDebug(TStringBuilder() << "Public keys fetched from tvmtool: " << updateTime); + } catch (const std::exception& e) { + ProcessError(EType::Retriable, EScope::PublicKeys, e.what()); + LogWarning(TStringBuilder() << "Error while fetching of public keys: " << e.what()); + } + + if (TInstant::Now() - GetUpdateTimeOfPublicKeys() > PublicKeysDurations_.Expiring) { + LogError("Public keys have not been refreshed for too long period"); + } + } + + if (RolesFetcher_ && (wasUpdated || RolesFetcher_->IsTimeToUpdate(TInstant::Now() - GetUpdateTimeOfRoles()))) { + try { + RolesFetcher_->Update(RolesFetcher_->FetchActualRoles(MetaInfo_.GetAuthHeader(), GetClient())); + SetUpdateTimeOfRoles(TInstant::Now()); + + if (RolesFetcher_->AreRolesOk()) { + ClearError(EScope::Roles); + } + } catch (const std::exception& e) { + ProcessError(EType::Retriable, EScope::Roles, e.what()); + LogWarning(TStringBuilder() << "Failed to update roles: " << e.what()); + } + + if (RolesFetcher_->ShouldWarn(TInstant::Now() - GetUpdateTimeOfRoles())) { + LogError("Roles have not been refreshed for too long period"); + } + } + } + + TInstant TThreadedUpdater::UpdateServiceTickets(const TMetaInfo::TConfig& config) { + const std::pair<TString, TInstant> tickets = FetchServiceTickets(config); + + if (TInstant::Now() - tickets.second >= ServiceTicketsDurations_.Invalid) { + throw yexception() << "Service tickets are too old: " << tickets.second; + } + + TPairTicketsErrors p = ParseFetchTicketsResponse(tickets.first, config.DstAliases); + SetServiceTickets(MakeIntrusiveConst<TServiceTickets>(std::move(p.Tickets), + std::move(p.Errors), + config.DstAliases)); + return tickets.second; + } + + std::pair<TString, TInstant> TThreadedUpdater::FetchServiceTickets(const TMetaInfo::TConfig& config) const { + TStringStream s; + THttpHeaders headers; + + const TString request = TMetaInfo::GetRequestForTickets(config); + auto code = GetClient().DoGet(request, &s, MetaInfo_.GetAuthHeader(), &headers); + Y_ENSURE(code == 200, ProcessHttpError(EScope::ServiceTickets, request, code, s.Str())); + + return {s.Str(), GetBirthTimeFromResponse(headers, "tickets")}; + } + + static THashSet<TTvmId> GetAllTvmIds(const TMetaInfo::TDstAliases& dsts) { + THashSet<TTvmId> res; + res.reserve(dsts.size()); + + for (const auto& pair : dsts) { + res.insert(pair.second); + } + + return res; + } + + TAsyncUpdaterBase::TPairTicketsErrors TThreadedUpdater::ParseFetchTicketsResponse(const TString& resp, + const TMetaInfo::TDstAliases& dsts) const { + const THashSet<TTvmId> allTvmIds = GetAllTvmIds(dsts); + + TServiceTickets::TMapIdStr tickets; + TServiceTickets::TMapIdStr errors; + + auto procErr = [this](const TString& msg) { + ProcessError(EType::NonRetriable, EScope::ServiceTickets, msg); + LogError(msg); + }; + + NJson::TJsonValue doc; + Y_ENSURE(NJson::ReadJsonTree(resp, &doc), "Invalid json from tvmtool: " << resp); + + for (const auto& pair : doc.GetMap()) { + NJson::TJsonValue tvmId; + unsigned long long tvmIdNum = 0; + + if (!pair.second.GetValue("tvm_id", &tvmId) || + !tvmId.GetUInteger(&tvmIdNum)) { + procErr(TStringBuilder() + << "Failed to get 'tvm_id' from key, should never happend '" + << pair.first << "': " << resp); + continue; + } + + if (!allTvmIds.contains(tvmIdNum)) { + continue; + } + + NJson::TJsonValue val; + if (!pair.second.GetValue("ticket", &val)) { + TString err; + if (pair.second.GetValue("error", &val)) { + err = val.GetString(); + } else { + err = "Failed to get 'ticket' and 'error', should never happend: " + pair.first; + } + + procErr(TStringBuilder() + << "Failed to get ServiceTicket for " << pair.first + << " (" << tvmIdNum << "): " << err); + + errors.insert({tvmIdNum, std::move(err)}); + continue; + } + + tickets.insert({tvmIdNum, val.GetString()}); + } + + // This work-around is required because of bug in old verions of tvmtool: PASSP-24829 + for (const auto& pair : dsts) { + if (!tickets.contains(pair.second) && !errors.contains(pair.second)) { + TString err = "Missing tvm_id in response, should never happend: " + pair.first; + + procErr(TStringBuilder() + << "Failed to get ServiceTicket for " << pair.first + << " (" << pair.second << "): " << err); + + errors.emplace(pair.second, std::move(err)); + } + } + + return {std::move(tickets), std::move(errors)}; + } + + TInstant TThreadedUpdater::UpdateKeys(const TMetaInfo::TConfig& config) { + const std::pair<TString, TInstant> keys = FetchPublicKeys(); + + if (TInstant::Now() - keys.second >= PublicKeysDurations_.Invalid) { + throw yexception() << "Public keys are too old: " << keys.second; + } + + SetServiceContext(MakeIntrusiveConst<TServiceContext>( + TServiceContext::CheckingFactory(config.SelfTvmId, keys.first))); + SetUserContext(keys.first); + + return keys.second; + } + + std::pair<TString, TInstant> TThreadedUpdater::FetchPublicKeys() const { + TStringStream s; + THttpHeaders headers; + + auto code = GetClient().DoGet("/tvm/keys", &s, MetaInfo_.GetAuthHeader(), &headers); + Y_ENSURE(code == 200, ProcessHttpError(EScope::PublicKeys, "/tvm/keys", code, s.Str())); + + return {s.Str(), GetBirthTimeFromResponse(headers, "public keys")}; + } + + TInstant TThreadedUpdater::GetBirthTimeFromResponse(const THttpHeaders& headers, TStringBuf errMsg) { + auto it = std::find_if(headers.begin(), + headers.end(), + [](const THttpInputHeader& h) { + return AsciiEqualsIgnoreCase(h.Name(), "X-Ya-Tvmtool-Data-Birthtime"); + }); + Y_ENSURE(it != headers.end(), "Failed to fetch bithtime of " << errMsg << " from tvmtool"); + + ui64 time = 0; + Y_ENSURE(TryIntFromString<10>(it->Value(), time), + "Bithtime of " << errMsg << " from tvmtool must be unixtime. Got: " << it->Value()); + + return TInstant::Seconds(time); + } + + bool TThreadedUpdater::IsTimeToUpdateServiceTickets(const TMetaInfo::TConfig& config, + TInstant lastUpdate) const { + return config.AreTicketsRequired() && + TInstant::Now() - lastUpdate > ServiceTicketsDurations_.RefreshPeriod; + } + + bool TThreadedUpdater::IsTimeToUpdatePublicKeys(TInstant lastUpdate) const { + return TInstant::Now() - lastUpdate > PublicKeysDurations_.RefreshPeriod; + } + + bool TThreadedUpdater::IsEverythingOk(const TMetaInfo::TConfig& config) const { + if (RolesFetcher_ && !RolesFetcher_->AreRolesOk()) { + return false; + } + return AreServiceTicketsOk(config) && ArePublicKeysOk(); + } + + bool TThreadedUpdater::AreServiceTicketsOk(const TMetaInfo::TConfig& config) const { + return AreServiceTicketsOk(config.DstAliases.size()); + } + + bool TThreadedUpdater::AreServiceTicketsOk(size_t requiredCount) const { + if (requiredCount == 0) { + return true; + } + + auto c = GetCachedServiceTickets(); + return c && c->TicketsByAlias.size() == requiredCount; + } + + bool TThreadedUpdater::ArePublicKeysOk() const { + return GetCachedServiceContext() && GetCachedUserContext(); + } + + bool TThreadedUpdater::IsConfigWarnTime() const { + return LastVisitForConfig_ + ConfigWarnDelay_ < TInstant::Now(); + } + + void TThreadedUpdater::Worker() { + if (IsInited()) { + UpdateState(); + } else { + try { + Init(Settings_); + } catch (const TRetriableException& e) { + // Still not initialized + } catch (const std::exception& e) { + // Can't retry, so we mark client as initialized and now GetStatus() will return TClientStatus::Error + SetInited(true); + } + } + } +} diff --git a/library/cpp/tvmauth/client/misc/tool/threaded_updater.h b/library/cpp/tvmauth/client/misc/tool/threaded_updater.h new file mode 100644 index 0000000000..e007553f81 --- /dev/null +++ b/library/cpp/tvmauth/client/misc/tool/threaded_updater.h @@ -0,0 +1,65 @@ +#pragma once + +#include "meta_info.h" +#include "roles_fetcher.h" + +#include <library/cpp/tvmauth/client/misc/async_updater.h> +#include <library/cpp/tvmauth/client/misc/threaded_updater.h> + +#include <atomic> + +namespace NTvmAuth::NTvmTool { + class TThreadedUpdater: public TThreadedUpdaterBase { + public: + static TAsyncUpdaterPtr Create(const TClientSettings& settings, TLoggerPtr logger); + ~TThreadedUpdater(); + + TClientStatus GetStatus() const override; + TString GetServiceTicketFor(const TClientSettings::TAlias& dst) const override; + TString GetServiceTicketFor(const TTvmId dst) const override; + TCheckedServiceTicket CheckServiceTicket(TStringBuf ticket, const TServiceContext::TCheckFlags& flags = TServiceContext::TCheckFlags{}) const override; + TCheckedUserTicket CheckUserTicket(TStringBuf ticket, TMaybe<EBlackboxEnv> overrideEnv = {}) const override; + NRoles::TRolesPtr GetRoles() const override; + + protected: // for tests + TClientStatus::ECode GetState() const; + + TThreadedUpdater(const TClientSettings& settings, TLoggerPtr logger); + + void Init(const TClientSettings& settings); + void UpdateState(); + + TInstant UpdateServiceTickets(const TMetaInfo::TConfig& config); + std::pair<TString, TInstant> FetchServiceTickets(const TMetaInfo::TConfig& config) const; + TPairTicketsErrors ParseFetchTicketsResponse(const TString& resp, + const TMetaInfo::TDstAliases& dsts) const; + + TInstant UpdateKeys(const TMetaInfo::TConfig& config); + std::pair<TString, TInstant> FetchPublicKeys() const; + + static TInstant GetBirthTimeFromResponse(const THttpHeaders& headers, TStringBuf errMsg); + + bool IsTimeToUpdateServiceTickets(const TMetaInfo::TConfig& config, TInstant lastUpdate) const; + bool IsTimeToUpdatePublicKeys(TInstant lastUpdate) const; + + bool IsEverythingOk(const TMetaInfo::TConfig& config) const; + bool AreServiceTicketsOk(const TMetaInfo::TConfig& config) const; + bool AreServiceTicketsOk(size_t requiredCount) const; + bool ArePublicKeysOk() const; + bool IsConfigWarnTime() const; + + private: + void Worker() override; + + protected: + TMetaInfo MetaInfo_; + TInstant LastVisitForServiceTickets_; + TInstant LastVisitForPublicKeys_; + TInstant LastVisitForConfig_; + TDuration ConfigWarnDelay_; + std::unique_ptr<TRolesFetcher> RolesFetcher_; + + private: + const TClientSettings Settings_; + }; +} |