From d517f98085f2b59939204359107b0cf3c1ee2576 Mon Sep 17 00:00:00 2001 From: Vasily Gerasimov Date: Sun, 7 Dec 2025 15:00:46 +0100 Subject: [PATCH 1/4] Add --mon-key option with the same meaning as the --ic-key/--grpc-key options --- ydb/core/config/init/init_impl.h | 17 +++++++++-------- ydb/core/driver_lib/run/config_parser.cpp | 6 ++++-- ydb/core/driver_lib/run/config_parser.h | 1 + ydb/core/driver_lib/run/run.cpp | 5 ++--- ydb/core/mon/mon.cpp | 4 +++- ydb/core/mon/mon.h | 4 +++- ydb/core/protos/config.proto | 1 + ydb/library/actors/http/http_proxy_ssl.h | 3 ++- 8 files changed, 25 insertions(+), 16 deletions(-) diff --git a/ydb/core/config/init/init_impl.h b/ydb/core/config/init/init_impl.h index a54d031b7149..9d5d8607582c 100644 --- a/ydb/core/config/init/init_impl.h +++ b/ydb/core/config/init/init_impl.h @@ -297,6 +297,7 @@ struct TCommonAppOptions { ui32 MonitoringThreads = 10; ui32 MonitoringMaxRequestsPerSecond = 0; TString MonitoringCertificateFile; + TString MonitoringPrivateKeyFile; TString RestartsCountFile = ""; size_t CompileInflightLimit = 100000; // MiniKQLCompileService TString UDFsDir; @@ -393,7 +394,8 @@ struct TCommonAppOptions { .RequiredArgument("NAME").StoreResult(&TenantName); opts.AddLongOption("mon-port", "Monitoring port").OptionalArgument("NUM").StoreResult(&MonitoringPort); opts.AddLongOption("mon-address", "Monitoring address").OptionalArgument("ADDR").StoreResult(&MonitoringAddress); - opts.AddLongOption("mon-cert", "Monitoring certificate (https)").OptionalArgument("PATH").StoreResult(&MonitoringCertificateFile); + opts.AddLongOption("mon-cert", "Path to monitoring certificate file (https)").OptionalArgument("PATH").StoreResult(&MonitoringCertificateFile); + opts.AddLongOption("mon-key", "Path to monitoring private key file (https)").OptionalArgument("PATH").StoreResult(&MonitoringPrivateKeyFile); opts.AddLongOption("mon-threads", "Monitoring http server threads").RequiredArgument("NUM").StoreResult(&MonitoringThreads); opts.AddLongOption("suppress-version-check", "Suppress version compatibility checking via IC").NoArgument().SetFlag(&SuppressVersionCheck); @@ -598,13 +600,12 @@ struct TCommonAppOptions { ConfigUpdateTracer.AddUpdate(NKikimrConsole::TConfigItem::MonitoringConfigItem, TConfigItemInfo::EUpdateKind::UpdateExplicitly); } if (MonitoringCertificateFile) { - TString sslCertificate = TUnbufferedFileInput(MonitoringCertificateFile).ReadAll(); - if (!sslCertificate.empty()) { - appConfig.MutableMonitoringConfig()->SetMonitoringCertificate(sslCertificate); - ConfigUpdateTracer.AddUpdate(NKikimrConsole::TConfigItem::MonitoringConfigItem, TConfigItemInfo::EUpdateKind::UpdateExplicitly); - } else { - ythrow yexception() << "invalid ssl certificate file"; - } + appConfig.MutableMonitoringConfig()->SetMonitoringCertificateFile(MonitoringCertificateFile); + ConfigUpdateTracer.AddUpdate(NKikimrConsole::TConfigItem::MonitoringConfigItem, TConfigItemInfo::EUpdateKind::UpdateExplicitly); + } + if (MonitoringPrivateKeyFile) { + appConfig.MutableMonitoringConfig()->SetMonitoringPrivateKeyFile(MonitoringPrivateKeyFile); + ConfigUpdateTracer.AddUpdate(NKikimrConsole::TConfigItem::MonitoringConfigItem, TConfigItemInfo::EUpdateKind::UpdateExplicitly); } if (SqsHttpPort) { appConfig.MutableSqsConfig()->MutableHttpServerConfig()->SetPort(SqsHttpPort); diff --git a/ydb/core/driver_lib/run/config_parser.cpp b/ydb/core/driver_lib/run/config_parser.cpp index 9087f3e78b03..9b64a6abdf03 100644 --- a/ydb/core/driver_lib/run/config_parser.cpp +++ b/ydb/core/driver_lib/run/config_parser.cpp @@ -265,7 +265,8 @@ void TRunCommandConfigParser::ParseRunOpts(int argc, char **argv) { opts.AddLongOption("proxy", "Bind to proxy(-ies)").RequiredArgument("ADDR").AppendTo(&RunOpts.ProxyBindToProxy); opts.AddLongOption("mon-port", "Monitoring port").OptionalArgument("NUM").StoreResult(&RunOpts.MonitoringPort); opts.AddLongOption("mon-address", "Monitoring address").OptionalArgument("ADDR").StoreResult(&RunOpts.MonitoringAddress); - opts.AddLongOption("mon-cert", "Monitoring certificate (https)").OptionalArgument("PATH").StoreResult(&RunOpts.MonitoringCertificateFile); + opts.AddLongOption("mon-cert", "Path to monitoring certificate file (https)").OptionalArgument("PATH").StoreResult(&RunOpts.MonitoringCertificateFile); + opts.AddLongOption("mon-key", "Path to monitoring private key file (https)").OptionalArgument("PATH").StoreResult(&RunOpts.MonitoringPrivateKeyFile); opts.AddLongOption("mon-threads", "Monitoring http server threads").RequiredArgument("NUM").StoreResult(&RunOpts.MonitoringThreads); SetupLastGetOptForConfigFiles(opts); @@ -371,7 +372,8 @@ void TRunCommandConfigParser::ApplyParsedOptions() { Config.AppConfig.MutableMonitoringConfig()->SetMonitoringThreads(RunOpts.MonitoringThreads); Config.AppConfig.MutableMonitoringConfig()->SetMaxRequestsPerSecond(RunOpts.MonitoringMaxRequestsPerSecond); Config.AppConfig.MutableMonitoringConfig()->SetInactivityTimeout(ToString(RunOpts.MonitoringInactivityTimeout.Seconds())); - Config.AppConfig.MutableMonitoringConfig()->SetMonitoringCertificate(TUnbufferedFileInput(RunOpts.MonitoringCertificateFile).ReadAll()); + Config.AppConfig.MutableMonitoringConfig()->SetMonitoringCertificateFile(RunOpts.MonitoringCertificateFile); + Config.AppConfig.MutableMonitoringConfig()->SetMonitoringPrivateKeyFile(RunOpts.MonitoringPrivateKeyFile); Config.AppConfig.MutableRestartsCountConfig()->SetRestartsCountFile(RunOpts.RestartsCountFile); } diff --git a/ydb/core/driver_lib/run/config_parser.h b/ydb/core/driver_lib/run/config_parser.h index f53fd11705a7..6aad92ada130 100644 --- a/ydb/core/driver_lib/run/config_parser.h +++ b/ydb/core/driver_lib/run/config_parser.h @@ -49,6 +49,7 @@ class TRunCommandConfigParser { ui32 MonitoringPort; TString MonitoringAddress; TString MonitoringCertificateFile; + TString MonitoringPrivateKeyFile; ui32 MonitoringThreads; ui32 MonitoringMaxRequestsPerSecond; TDuration MonitoringInactivityTimeout; diff --git a/ydb/core/driver_lib/run/run.cpp b/ydb/core/driver_lib/run/run.cpp index bb1908481018..c751ec8a37da 100644 --- a/ydb/core/driver_lib/run/run.cpp +++ b/ydb/core/driver_lib/run/run.cpp @@ -611,9 +611,8 @@ void TKikimrRunner::InitializeMonitoring(const TKikimrRunConfig& runConfig, bool monConfig.Certificate = appConfig.GetMonitoringConfig().GetMonitoringCertificate(); monConfig.MaxRequestsPerSecond = appConfig.GetMonitoringConfig().GetMaxRequestsPerSecond(); monConfig.InactivityTimeout = TDuration::Parse(appConfig.GetMonitoringConfig().GetInactivityTimeout()); - if (appConfig.GetMonitoringConfig().HasMonitoringCertificateFile()) { - monConfig.Certificate = TUnbufferedFileInput(appConfig.GetMonitoringConfig().GetMonitoringCertificateFile()).ReadAll(); - } + monConfig.CertificateFile = appConfig.GetMonitoringConfig().GetMonitoringCertificateFile(); + monConfig.PrivateKeyFile = appConfig.GetMonitoringConfig().GetMonitoringPrivateKeyFile(); monConfig.RedirectMainPageTo = appConfig.GetMonitoringConfig().GetRedirectMainPageTo(); if (includeHostName) { if (appConfig.HasNameserviceConfig() && appConfig.GetNameserviceConfig().NodeSize() > 0) { diff --git a/ydb/core/mon/mon.cpp b/ydb/core/mon/mon.cpp index 22ca68762dea..c4e271e0d808 100644 --- a/ydb/core/mon/mon.cpp +++ b/ydb/core/mon/mon.cpp @@ -1439,7 +1439,9 @@ std::future TMon::Start(TActorSystem* actorSystem) { "application/yaml", }; addPort->SslCertificatePem = Config.Certificate; - addPort->Secure = !Config.Certificate.empty(); + addPort->CertificateFile = Config.CertificateFile; + addPort->PrivateKeyFile = Config.PrivateKeyFile; + addPort->Secure = !Config.Certificate.empty() || !Config.CertificateFile.empty() || !Config.PrivateKeyFile.empty(); addPort->MaxRequestsPerSecond = Config.MaxRequestsPerSecond; std::promise promise; diff --git a/ydb/core/mon/mon.h b/ydb/core/mon/mon.h index 0b387ddf2cae..083b3b92802f 100644 --- a/ydb/core/mon/mon.h +++ b/ydb/core/mon/mon.h @@ -35,7 +35,9 @@ class TMon { TRequestAuthorizer Authorizer = DefaultAuthorizer; TVector AllowedSIDs; TString RedirectMainPageTo; - TString Certificate; + TString Certificate; // certificate/private key data in PEM format + TString CertificateFile; // certificate file path in PEM format // can contain private key if PrivateKeyFile is not set + TString PrivateKeyFile; // private key file path for the certificate in PEM format ui32 MaxRequestsPerSecond = 0; TDuration InactivityTimeout = TDuration::Minutes(2); TString AllowOrigin; diff --git a/ydb/core/protos/config.proto b/ydb/core/protos/config.proto index b52a1422d889..a5cd87102148 100644 --- a/ydb/core/protos/config.proto +++ b/ydb/core/protos/config.proto @@ -609,6 +609,7 @@ message TMonitoringConfig { optional string RedirectMainPageTo = 13 [default = "monitoring/"]; optional string MonitoringCertificate = 14 [(Ydb.sensitive) = true]; optional string MonitoringCertificateFile = 15; + optional string MonitoringPrivateKeyFile = 20; optional string MemAllocDumpPathPrefix = 16; optional uint32 MaxRequestsPerSecond = 17 [default = 0]; optional string InactivityTimeout = 18 [default = "2m"]; diff --git a/ydb/library/actors/http/http_proxy_ssl.h b/ydb/library/actors/http/http_proxy_ssl.h index 9953430b1ce4..b26708314a22 100644 --- a/ydb/library/actors/http/http_proxy_ssl.h +++ b/ydb/library/actors/http/http_proxy_ssl.h @@ -61,7 +61,8 @@ struct TSslHelpers { // TODO(xenoxeno): more diagnostics? return nullptr; } - res = SSL_CTX_use_PrivateKey_file(ctx.Get(), key.c_str(), SSL_FILETYPE_PEM); + // Load key. The key can be set through explicit key field or with the same file with certificate + res = SSL_CTX_use_PrivateKey_file(ctx.Get(), key.empty() ? certificate.c_str() : key.c_str(), SSL_FILETYPE_PEM); if (res < 0) { // TODO(xenoxeno): more diagnostics? return nullptr; From 0ffefb07a10f63bc6fc6a67220e601edb2b523f6 Mon Sep 17 00:00:00 2001 From: Vasily Gerasimov Date: Mon, 8 Dec 2025 13:26:34 +0200 Subject: [PATCH 2/4] Update comment Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- ydb/core/mon/mon.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ydb/core/mon/mon.h b/ydb/core/mon/mon.h index 083b3b92802f..fd298e0d5f5f 100644 --- a/ydb/core/mon/mon.h +++ b/ydb/core/mon/mon.h @@ -36,7 +36,7 @@ class TMon { TVector AllowedSIDs; TString RedirectMainPageTo; TString Certificate; // certificate/private key data in PEM format - TString CertificateFile; // certificate file path in PEM format // can contain private key if PrivateKeyFile is not set + TString CertificateFile; // certificate file path in PEM format (OpenSSL feature: may optionally contain both certificate chain and private key in the same PEM file if PrivateKeyFile is not set) TString PrivateKeyFile; // private key file path for the certificate in PEM format ui32 MaxRequestsPerSecond = 0; TDuration InactivityTimeout = TDuration::Minutes(2); From 4cb5702ae01dc2af4b1de01b4fcd060ae30ce03c Mon Sep 17 00:00:00 2001 From: Vasily Gerasimov Date: Mon, 8 Dec 2025 13:27:15 +0200 Subject: [PATCH 3/4] Choose secure connection if certificate is set Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- ydb/core/mon/mon.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ydb/core/mon/mon.cpp b/ydb/core/mon/mon.cpp index c4e271e0d808..17a6e18fcc9f 100644 --- a/ydb/core/mon/mon.cpp +++ b/ydb/core/mon/mon.cpp @@ -1441,7 +1441,7 @@ std::future TMon::Start(TActorSystem* actorSystem) { addPort->SslCertificatePem = Config.Certificate; addPort->CertificateFile = Config.CertificateFile; addPort->PrivateKeyFile = Config.PrivateKeyFile; - addPort->Secure = !Config.Certificate.empty() || !Config.CertificateFile.empty() || !Config.PrivateKeyFile.empty(); + addPort->Secure = !Config.Certificate.empty() || !Config.CertificateFile.empty(); addPort->MaxRequestsPerSecond = Config.MaxRequestsPerSecond; std::promise promise; From 6e9f78818badb23166647e3f5a1e10811d90f2c9 Mon Sep 17 00:00:00 2001 From: Vasily Gerasimov Date: Mon, 8 Dec 2025 15:39:07 +0100 Subject: [PATCH 4/4] Add existence checks --- ydb/core/config/init/init_impl.h | 24 +++++++++++++++++++++++ ydb/core/driver_lib/run/config_parser.cpp | 15 ++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/ydb/core/config/init/init_impl.h b/ydb/core/config/init/init_impl.h index 9d5d8607582c..4b3cde0e1904 100644 --- a/ydb/core/config/init/init_impl.h +++ b/ydb/core/config/init/init_impl.h @@ -35,6 +35,7 @@ #include #include #include +#include #include #include #include @@ -1218,6 +1219,7 @@ class TInitialConfiguratorImpl Option(nullptr, TCfg::TTracingConfigFieldTag{}); Option(nullptr, TCfg::TFailureInjectionConfigFieldTag{}); + ValidateCertPaths(); CommonAppOptions.ApplyFields(AppConfig, Env, ConfigUpdateTracer); // MessageBus options. @@ -1671,6 +1673,28 @@ class TInitialConfiguratorImpl Logger.Out() << "Successfully applied dynamic config from seed nodes" << Endl; ApplyConfigForNode(yamlConfig); } + + void ValidateCertPaths() const { + auto ensureFileExists = [](const TString& path, TStringBuf optName) { + if (path.empty()) { + return; + } + TFsPath fspath(path); + TFileStat filestat; + if (!fspath.Stat(filestat) || !filestat.IsFile()) { + ythrow yexception() << "File passed to --" << optName << " does not exist: " << path; + } + }; + + ensureFileExists(CommonAppOptions.PathToInterconnectCertFile, "cert/ic-cert"); + ensureFileExists(CommonAppOptions.PathToInterconnectPrivateKeyFile, "key/ic-key"); + ensureFileExists(CommonAppOptions.PathToInterconnectCaFile, "ca/ic-ca"); + ensureFileExists(CommonAppOptions.GrpcSslSettings.PathToGrpcCertFile, "grpc-cert"); + ensureFileExists(CommonAppOptions.GrpcSslSettings.PathToGrpcPrivateKeyFile, "grpc-key"); + ensureFileExists(CommonAppOptions.GrpcSslSettings.PathToGrpcCaFile, "grpc-ca"); + ensureFileExists(CommonAppOptions.MonitoringCertificateFile, "mon-cert"); + ensureFileExists(CommonAppOptions.MonitoringPrivateKeyFile, "mon-key"); + } }; std::unique_ptr MakeDefaultInitialConfigurator( diff --git a/ydb/core/driver_lib/run/config_parser.cpp b/ydb/core/driver_lib/run/config_parser.cpp index 9b64a6abdf03..d953ea5aa556 100644 --- a/ydb/core/driver_lib/run/config_parser.cpp +++ b/ydb/core/driver_lib/run/config_parser.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include @@ -305,6 +306,20 @@ void TRunCommandConfigParser::ParseRunOpts(int argc, char **argv) { } void TRunCommandConfigParser::ApplyParsedOptions() { + auto ensureFileExists = [](const TString& path, TStringBuf optName) { + if (path.empty()) { + return; + } + TFsPath fspath(path); + TFileStat filestat; + if (!fspath.Stat(filestat) || !filestat.IsFile()) { + ythrow yexception() << "File passed to --" << optName << " does not exist: " << path; + } + }; + + ensureFileExists(RunOpts.MonitoringCertificateFile, "mon-cert"); + ensureFileExists(RunOpts.MonitoringPrivateKeyFile, "mon-key"); + // apply global options Config.AppConfig.MutableInterconnectConfig()->SetStartTcp(GlobalOpts.StartTcp); auto logConfig = Config.AppConfig.MutableLogConfig();