From 55ab495b404eea4700fd5fc91ad31057334b37e6 Mon Sep 17 00:00:00 2001 From: Ruslan Nigmatullin Date: Thu, 17 Oct 2019 17:57:12 -0700 Subject: [PATCH] sds: add ability to reload TLS session ticket keys (#8635) Description: Finish migration of TLS session ticket keys to provider-based API. Risk Level: Medium Testing: added new tests Docs Changes: updated Release Notes: updated Fixes #7397 Signed-off-by: Ruslan Nigmatullin --- api/envoy/api/v2/auth/cert.proto | 2 +- api/envoy/api/v3alpha/auth/cert.proto | 2 +- docs/root/intro/version_history.rst | 1 + include/envoy/secret/secret_manager.h | 31 +++ include/envoy/secret/secret_provider.h | 6 + source/common/secret/sds_api.h | 63 +++++++ source/common/secret/secret_manager_impl.cc | 80 +++++++- source/common/secret/secret_manager_impl.h | 14 ++ source/common/secret/secret_provider_impl.cc | 5 + source/common/secret/secret_provider_impl.h | 20 ++ .../tls/context_config_impl.cc | 117 +++++++++--- .../tls/context_config_impl.h | 22 ++- .../common/secret/secret_manager_impl_test.cc | 177 +++++++++++++++++- .../tls/context_impl_test.cc | 68 ++++++- test/mocks/secret/mocks.h | 9 + 15 files changed, 575 insertions(+), 42 deletions(-) diff --git a/api/envoy/api/v2/auth/cert.proto b/api/envoy/api/v2/auth/cert.proto index ebf199a47432..810e3d07c942 100644 --- a/api/envoy/api/v2/auth/cert.proto +++ b/api/envoy/api/v2/auth/cert.proto @@ -377,7 +377,7 @@ message DownstreamTlsContext { // TLS session ticket key settings. TlsSessionTicketKeys session_ticket_keys = 4; - // [#not-implemented-hide:] + // Config for fetching TLS session ticket keys via SDS API. SdsSecretConfig session_ticket_keys_sds_secret_config = 5; } } diff --git a/api/envoy/api/v3alpha/auth/cert.proto b/api/envoy/api/v3alpha/auth/cert.proto index edd333984126..f72f8cdfac72 100644 --- a/api/envoy/api/v3alpha/auth/cert.proto +++ b/api/envoy/api/v3alpha/auth/cert.proto @@ -380,7 +380,7 @@ message DownstreamTlsContext { // TLS session ticket key settings. TlsSessionTicketKeys session_ticket_keys = 4; - // [#not-implemented-hide:] + // Config for fetching TLS session ticket keys via SDS API. SdsSecretConfig session_ticket_keys_sds_secret_config = 5; } } diff --git a/docs/root/intro/version_history.rst b/docs/root/intro/version_history.rst index 3631dcc6c71a..3733961064a2 100644 --- a/docs/root/intro/version_history.rst +++ b/docs/root/intro/version_history.rst @@ -77,6 +77,7 @@ Version history * router check tool: add support for outputting missing tests in the detailed coverage report. * runtime: allow for the ability to parse boolean values. * runtime: allow for the ability to parse integers as double values and vice-versa. +* sds: added :ref:`session_ticket_keys_sds_secret_config ` for loading TLS Session Ticket Encryption Keys using SDS API. * server: added a post initialization lifecycle event, in addition to the existing startup and shutdown events. * server: added :ref:`per-handler listener stats ` and :ref:`per-worker watchdog stats ` to help diagnosing event diff --git a/include/envoy/secret/secret_manager.h b/include/envoy/secret/secret_manager.h index d37ea920d502..4cbcfc5a920d 100644 --- a/include/envoy/secret/secret_manager.h +++ b/include/envoy/secret/secret_manager.h @@ -44,6 +44,14 @@ class SecretManager { virtual CertificateValidationContextConfigProviderSharedPtr findStaticCertificateValidationContextProvider(const std::string& name) const PURE; + /** + * @param name a name of the static TlsSessionTicketKeysConfigProviderSharedPtr. + * @return the TlsSessionTicketKeysConfigProviderSharedPtr. Returns nullptr + * if the static tls session ticket keys are not found. + */ + virtual TlsSessionTicketKeysConfigProviderSharedPtr + findStaticTlsSessionTicketKeysContextProvider(const std::string& name) const PURE; + /** * @param tls_certificate the protobuf config of the TLS certificate. * @return a TlsCertificateConfigProviderSharedPtr created from tls_certificate. @@ -62,6 +70,13 @@ class SecretManager { const envoy::api::v2::auth::CertificateValidationContext& certificate_validation_context) PURE; + /** + * @param tls_certificate the protobuf config of the TLS session ticket keys. + * @return a TlsSessionTicketKeysConfigProviderSharedPtr created from session_ticket_keys. + */ + virtual TlsSessionTicketKeysConfigProviderSharedPtr createInlineTlsSessionTicketKeysProvider( + const envoy::api::v2::auth::TlsSessionTicketKeys& tls_certificate) PURE; + /** * Finds and returns a dynamic secret provider associated to SDS config. Create * a new one if such provider does not exist. @@ -91,6 +106,22 @@ class SecretManager { findOrCreateCertificateValidationContextProvider( const envoy::api::v2::core::ConfigSource& config_source, const std::string& config_name, Server::Configuration::TransportSocketFactoryContext& secret_provider_context) PURE; + + /** + * Finds and returns a dynamic secret provider associated to SDS config. Create + * a new one if such provider does not exist. + * + * @param config_source a protobuf message object containing a SDS config source. + * @param config_name a name that uniquely refers to the SDS config source. + * @param secret_provider_context context that provides components for creating and initializing + * secret provider. + * @return TlsSessionTicketKeysConfigProviderSharedPtr the dynamic tls session ticket keys secret + * provider. + */ + virtual TlsSessionTicketKeysConfigProviderSharedPtr + findOrCreateTlsSessionTicketKeysContextProvider( + const envoy::api::v2::core::ConfigSource& config_source, const std::string& config_name, + Server::Configuration::TransportSocketFactoryContext& secret_provider_context) PURE; }; } // namespace Secret diff --git a/include/envoy/secret/secret_provider.h b/include/envoy/secret/secret_provider.h index c532e8e32622..8608dba94a16 100644 --- a/include/envoy/secret/secret_provider.h +++ b/include/envoy/secret/secret_provider.h @@ -46,6 +46,7 @@ template class SecretProvider { using TlsCertificatePtr = std::unique_ptr; using CertificateValidationContextPtr = std::unique_ptr; +using TlsSessionTicketKeysPtr = std::unique_ptr; using TlsCertificateConfigProvider = SecretProvider; using TlsCertificateConfigProviderSharedPtr = std::shared_ptr; @@ -55,5 +56,10 @@ using CertificateValidationContextConfigProvider = using CertificateValidationContextConfigProviderSharedPtr = std::shared_ptr; +using TlsSessionTicketKeysConfigProvider = + SecretProvider; +using TlsSessionTicketKeysConfigProviderSharedPtr = + std::shared_ptr; + } // namespace Secret } // namespace Envoy diff --git a/source/common/secret/sds_api.h b/source/common/secret/sds_api.h index b9a803a068f2..695ecd02e8af 100644 --- a/source/common/secret/sds_api.h +++ b/source/common/secret/sds_api.h @@ -82,9 +82,11 @@ class SdsApi : public Config::SubscriptionCallbacks { class TlsCertificateSdsApi; class CertificateValidationContextSdsApi; +class TlsSessionTicketKeysSdsApi; using TlsCertificateSdsApiSharedPtr = std::shared_ptr; using CertificateValidationContextSdsApiSharedPtr = std::shared_ptr; +using TlsSessionTicketKeysSdsApiSharedPtr = std::shared_ptr; /** * TlsCertificateSdsApi implementation maintains and updates dynamic TLS certificate secrets. @@ -198,5 +200,66 @@ class CertificateValidationContextSdsApi : public SdsApi, validation_callback_manager_; }; +/** + * TlsSessionTicketKeysSdsApi implementation maintains and updates dynamic tls session ticket keys + * secrets. + */ +class TlsSessionTicketKeysSdsApi : public SdsApi, public TlsSessionTicketKeysConfigProvider { +public: + static TlsSessionTicketKeysSdsApiSharedPtr + create(Server::Configuration::TransportSocketFactoryContext& secret_provider_context, + const envoy::api::v2::core::ConfigSource& sds_config, const std::string& sds_config_name, + std::function destructor_cb) { + // We need to do this early as we invoke the subscription factory during initialization, which + // is too late to throw. + Config::Utility::checkLocalInfo("TlsSessionTicketKeysSdsApi", + secret_provider_context.localInfo()); + return std::make_shared( + sds_config, sds_config_name, secret_provider_context.clusterManager().subscriptionFactory(), + secret_provider_context.dispatcher().timeSource(), + secret_provider_context.messageValidationVisitor(), secret_provider_context.stats(), + *secret_provider_context.initManager(), destructor_cb); + } + + TlsSessionTicketKeysSdsApi(const envoy::api::v2::core::ConfigSource& sds_config, + const std::string& sds_config_name, + Config::SubscriptionFactory& subscription_factory, + TimeSource& time_source, + ProtobufMessage::ValidationVisitor& validation_visitor, + Stats::Store& stats, Init::Manager& init_manager, + std::function destructor_cb) + : SdsApi(sds_config, sds_config_name, subscription_factory, time_source, validation_visitor, + stats, init_manager, std::move(destructor_cb)) {} + + // SecretProvider + const envoy::api::v2::auth::TlsSessionTicketKeys* secret() const override { + return tls_session_ticket_keys_.get(); + } + + Common::CallbackHandle* addUpdateCallback(std::function callback) override { + return update_callback_manager_.add(callback); + } + + Common::CallbackHandle* addValidationCallback( + std::function callback) override { + return validation_callback_manager_.add(callback); + } + +protected: + void setSecret(const envoy::api::v2::auth::Secret& secret) override { + tls_session_ticket_keys_ = + std::make_unique(secret.session_ticket_keys()); + } + + void validateConfig(const envoy::api::v2::auth::Secret& secret) override { + validation_callback_manager_.runCallbacks(secret.session_ticket_keys()); + } + +private: + Secret::TlsSessionTicketKeysPtr tls_session_ticket_keys_; + Common::CallbackManager + validation_callback_manager_; +}; + } // namespace Secret } // namespace Envoy diff --git a/source/common/secret/secret_manager_impl.cc b/source/common/secret/secret_manager_impl.cc index 411f71ae6d0b..c63af2f50ac5 100644 --- a/source/common/secret/secret_manager_impl.cc +++ b/source/common/secret/secret_manager_impl.cc @@ -39,6 +39,17 @@ void SecretManagerImpl::addStaticSecret(const envoy::api::v2::auth::Secret& secr } break; } + case envoy::api::v2::auth::Secret::TypeCase::kSessionTicketKeys: { + auto secret_provider = + std::make_shared(secret.session_ticket_keys()); + if (!static_session_ticket_keys_providers_ + .insert(std::make_pair(secret.name(), secret_provider)) + .second) { + throw EnvoyException( + fmt::format("Duplicate static TlsSessionTicketKeys secret name {}", secret.name())); + } + break; + } default: throw EnvoyException("Secret type not implemented"); } @@ -57,6 +68,12 @@ SecretManagerImpl::findStaticCertificateValidationContextProvider(const std::str : nullptr; } +TlsSessionTicketKeysConfigProviderSharedPtr +SecretManagerImpl::findStaticTlsSessionTicketKeysContextProvider(const std::string& name) const { + auto secret = static_session_ticket_keys_providers_.find(name); + return (secret != static_session_ticket_keys_providers_.end()) ? secret->second : nullptr; +} + TlsCertificateConfigProviderSharedPtr SecretManagerImpl::createInlineTlsCertificateProvider( const envoy::api::v2::auth::TlsCertificate& tls_certificate) { return std::make_shared(tls_certificate); @@ -69,6 +86,12 @@ SecretManagerImpl::createInlineCertificateValidationContextProvider( certificate_validation_context); } +TlsSessionTicketKeysConfigProviderSharedPtr +SecretManagerImpl::createInlineTlsSessionTicketKeysProvider( + const envoy::api::v2::auth::TlsSessionTicketKeys& tls_session_ticket_keys) { + return std::make_shared(tls_session_ticket_keys); +} + TlsCertificateConfigProviderSharedPtr SecretManagerImpl::findOrCreateTlsCertificateProvider( const envoy::api::v2::core::ConfigSource& sds_config_source, const std::string& config_name, Server::Configuration::TransportSocketFactoryContext& secret_provider_context) { @@ -84,7 +107,15 @@ SecretManagerImpl::findOrCreateCertificateValidationContextProvider( secret_provider_context); } -// We clear private key and password to avoid information leaking. +TlsSessionTicketKeysConfigProviderSharedPtr +SecretManagerImpl::findOrCreateTlsSessionTicketKeysContextProvider( + const envoy::api::v2::core::ConfigSource& sds_config_source, const std::string& config_name, + Server::Configuration::TransportSocketFactoryContext& secret_provider_context) { + return session_ticket_keys_providers_.findOrCreate(sds_config_source, config_name, + secret_provider_context); +} + +// We clear private key, password, and session ticket encryption keys to avoid information leaking. // TODO(incfly): switch to more generic scrubbing mechanism once // https://github.com/envoyproxy/envoy/issues/4757 is resolved. void redactSecret(::envoy::api::v2::auth::Secret* secret) { @@ -99,6 +130,13 @@ void redactSecret(::envoy::api::v2::auth::Secret* secret) { tls_certificate->mutable_password()->set_inline_string("[redacted]"); } } + if (secret && secret->type_case() == envoy::api::v2::auth::Secret::TypeCase::kSessionTicketKeys) { + for (auto& data_source : *secret->mutable_session_ticket_keys()->mutable_keys()) { + if (data_source.specifier_case() != envoy::api::v2::core::DataSource::kFilename) { + data_source.set_inline_string("[redacted]"); + } + } + } } ProtobufTypes::MessagePtr SecretManagerImpl::dumpSecretConfigs() { @@ -126,6 +164,20 @@ ProtobufTypes::MessagePtr SecretManagerImpl::dumpSecretConfigs() { dump_secret->mutable_validation_context()->MergeFrom(*validation_context->secret()); } + // Handle static session keys providers. + for (const auto& context_iter : static_session_ticket_keys_providers_) { + const auto& session_ticket_keys = context_iter.second; + auto static_secret = config_dump->mutable_static_secrets()->Add(); + static_secret->set_name(context_iter.first); + ASSERT(session_ticket_keys != nullptr); + auto dump_secret = static_secret->mutable_secret(); + dump_secret->set_name(context_iter.first); + for (const auto& key : session_ticket_keys->secret()->keys()) { + dump_secret->mutable_session_ticket_keys()->add_keys()->MergeFrom(key); + } + redactSecret(dump_secret); + } + // Handle dynamic tls_certificate providers. const auto providers = certificate_providers_.allSecretProviders(); for (const auto& cert_secrets : providers) { @@ -175,6 +227,32 @@ ProtobufTypes::MessagePtr SecretManagerImpl::dumpSecretConfigs() { secret->mutable_validation_context()->MergeFrom(*validation_context); } } + + // Handle dynamic session keys providers providers. + const auto stek_providers = session_ticket_keys_providers_.allSecretProviders(); + for (const auto& stek_secrets : stek_providers) { + const auto& secret_data = stek_secrets->secretData(); + const auto& tls_stek = stek_secrets->secret(); + ::envoy::admin::v2alpha::SecretsConfigDump_DynamicSecret* dump_secret; + const bool secret_ready = tls_stek != nullptr; + if (secret_ready) { + dump_secret = config_dump->mutable_dynamic_active_secrets()->Add(); + } else { + dump_secret = config_dump->mutable_dynamic_warming_secrets()->Add(); + } + dump_secret->set_name(secret_data.resource_name_); + auto secret = dump_secret->mutable_secret(); + secret->set_name(secret_data.resource_name_); + ProtobufWkt::Timestamp last_updated_ts; + TimestampUtil::systemClockToTimestamp(secret_data.last_updated_, last_updated_ts); + dump_secret->set_version_info(secret_data.version_info_); + *dump_secret->mutable_last_updated() = last_updated_ts; + secret->set_name(secret_data.resource_name_); + if (secret_ready) { + secret->mutable_session_ticket_keys()->MergeFrom(*tls_stek); + } + redactSecret(secret); + } return config_dump; } diff --git a/source/common/secret/secret_manager_impl.h b/source/common/secret/secret_manager_impl.h index 7e3da018ecc8..2237a49d2c4f 100644 --- a/source/common/secret/secret_manager_impl.h +++ b/source/common/secret/secret_manager_impl.h @@ -25,6 +25,9 @@ class SecretManagerImpl : public SecretManager { CertificateValidationContextConfigProviderSharedPtr findStaticCertificateValidationContextProvider(const std::string& name) const override; + TlsSessionTicketKeysConfigProviderSharedPtr + findStaticTlsSessionTicketKeysContextProvider(const std::string& name) const override; + TlsCertificateConfigProviderSharedPtr createInlineTlsCertificateProvider( const envoy::api::v2::auth::TlsCertificate& tls_certificate) override; @@ -33,6 +36,9 @@ class SecretManagerImpl : public SecretManager { const envoy::api::v2::auth::CertificateValidationContext& certificate_validation_context) override; + TlsSessionTicketKeysConfigProviderSharedPtr createInlineTlsSessionTicketKeysProvider( + const envoy::api::v2::auth::TlsSessionTicketKeys& tls_session_ticket_keys) override; + TlsCertificateConfigProviderSharedPtr findOrCreateTlsCertificateProvider( const envoy::api::v2::core::ConfigSource& config_source, const std::string& config_name, Server::Configuration::TransportSocketFactoryContext& secret_provider_context) override; @@ -42,6 +48,10 @@ class SecretManagerImpl : public SecretManager { const envoy::api::v2::core::ConfigSource& config_source, const std::string& config_name, Server::Configuration::TransportSocketFactoryContext& secret_provider_context) override; + TlsSessionTicketKeysConfigProviderSharedPtr findOrCreateTlsSessionTicketKeysContextProvider( + const envoy::api::v2::core::ConfigSource& config_source, const std::string& config_name, + Server::Configuration::TransportSocketFactoryContext& secret_provider_context) override; + private: ProtobufTypes::MessagePtr dumpSecretConfigs(); @@ -102,9 +112,13 @@ class SecretManagerImpl : public SecretManager { std::unordered_map static_certificate_validation_context_providers_; + std::unordered_map + static_session_ticket_keys_providers_; + // map hash code of SDS config source and SdsApi object. DynamicSecretProviders certificate_providers_; DynamicSecretProviders validation_context_providers_; + DynamicSecretProviders session_ticket_keys_providers_; Server::ConfigTracker::EntryOwnerPtr config_tracker_entry_; }; diff --git a/source/common/secret/secret_provider_impl.cc b/source/common/secret/secret_provider_impl.cc index a19843659cf8..dc0af8d2b645 100644 --- a/source/common/secret/secret_provider_impl.cc +++ b/source/common/secret/secret_provider_impl.cc @@ -17,5 +17,10 @@ CertificateValidationContextConfigProviderImpl::CertificateValidationContextConf std::make_unique( certificate_validation_context)) {} +TlsSessionTicketKeysConfigProviderImpl::TlsSessionTicketKeysConfigProviderImpl( + const envoy::api::v2::auth::TlsSessionTicketKeys& tls_session_ticket_keys) + : tls_session_ticket_keys_( + std::make_unique(tls_session_ticket_keys)) {} + } // namespace Secret } // namespace Envoy diff --git a/source/common/secret/secret_provider_impl.h b/source/common/secret/secret_provider_impl.h index c3f88ff37c1c..224def68cbcb 100644 --- a/source/common/secret/secret_provider_impl.h +++ b/source/common/secret/secret_provider_impl.h @@ -50,5 +50,25 @@ class CertificateValidationContextConfigProviderImpl Secret::CertificateValidationContextPtr certificate_validation_context_; }; +class TlsSessionTicketKeysConfigProviderImpl : public TlsSessionTicketKeysConfigProvider { +public: + TlsSessionTicketKeysConfigProviderImpl( + const envoy::api::v2::auth::TlsSessionTicketKeys& tls_session_ticket_keys); + + const envoy::api::v2::auth::TlsSessionTicketKeys* secret() const override { + return tls_session_ticket_keys_.get(); + } + + Common::CallbackHandle* addValidationCallback( + std::function) override { + return nullptr; + } + + Common::CallbackHandle* addUpdateCallback(std::function) override { return nullptr; } + +private: + Secret::TlsSessionTicketKeysPtr tls_session_ticket_keys_; +}; + } // namespace Secret } // namespace Envoy diff --git a/source/extensions/transport_sockets/tls/context_config_impl.cc b/source/extensions/transport_sockets/tls/context_config_impl.cc index 45dee440c0de..e7bcc9f6610b 100644 --- a/source/extensions/transport_sockets/tls/context_config_impl.cc +++ b/source/extensions/transport_sockets/tls/context_config_impl.cc @@ -104,6 +104,40 @@ getCertificateValidationContextConfigProvider( } } +Secret::TlsSessionTicketKeysConfigProviderSharedPtr getTlsSessionTicketKeysConfigProvider( + Server::Configuration::TransportSocketFactoryContext& factory_context, + const envoy::api::v2::auth::DownstreamTlsContext& config) { + + switch (config.session_ticket_keys_type_case()) { + case envoy::api::v2::auth::DownstreamTlsContext::kSessionTicketKeys: + return factory_context.secretManager().createInlineTlsSessionTicketKeysProvider( + config.session_ticket_keys()); + case envoy::api::v2::auth::DownstreamTlsContext::kSessionTicketKeysSdsSecretConfig: { + const auto& sds_secret_config = config.session_ticket_keys_sds_secret_config(); + if (sds_secret_config.has_sds_config()) { + // Fetch dynamic secret. + return factory_context.secretManager().findOrCreateTlsSessionTicketKeysContextProvider( + sds_secret_config.sds_config(), sds_secret_config.name(), factory_context); + } else { + // Load static secret. + auto secret_provider = + factory_context.secretManager().findStaticTlsSessionTicketKeysContextProvider( + sds_secret_config.name()); + if (!secret_provider) { + throw EnvoyException( + fmt::format("Unknown tls session ticket keys: {}", sds_secret_config.name())); + } + return secret_provider; + } + } + case envoy::api::v2::auth::DownstreamTlsContext::SESSION_TICKET_KEYS_TYPE_NOT_SET: + return nullptr; + default: + throw EnvoyException(fmt::format("Unexpected case for oneof session_ticket_keys: {}", + config.session_ticket_keys_type_case())); + } +} + } // namespace ContextConfigImpl::ContextConfigImpl( @@ -327,27 +361,22 @@ ServerContextConfigImpl::ServerContextConfigImpl( DEFAULT_CIPHER_SUITES, DEFAULT_CURVES, factory_context), require_client_certificate_( PROTOBUF_GET_WRAPPED_OR_DEFAULT(config, require_client_certificate, false)), - session_ticket_keys_([&config, &api = api_] { - std::vector ret; - - switch (config.session_ticket_keys_type_case()) { - case envoy::api::v2::auth::DownstreamTlsContext::kSessionTicketKeys: - for (const auto& datasource : config.session_ticket_keys().keys()) { - validateAndAppendKey(ret, Config::DataSource::read(datasource, false, api)); - } - break; - case envoy::api::v2::auth::DownstreamTlsContext::kSessionTicketKeysSdsSecretConfig: - throw EnvoyException("SDS not supported yet"); - break; - case envoy::api::v2::auth::DownstreamTlsContext::SESSION_TICKET_KEYS_TYPE_NOT_SET: - break; - default: - throw EnvoyException(fmt::format("Unexpected case for oneof session_ticket_keys: {}", - config.session_ticket_keys_type_case())); - } - - return ret; - }()) { + session_ticket_keys_provider_( + getTlsSessionTicketKeysConfigProvider(factory_context, config)) { + if (session_ticket_keys_provider_ != nullptr) { + // Validate tls session ticket keys early to reject bad sds updates. + stk_validation_callback_handle_ = session_ticket_keys_provider_->addValidationCallback( + [this](const envoy::api::v2::auth::TlsSessionTicketKeys& keys) { + getSessionTicketKeys(keys); + }); + } + + // Load inline or static secrets. + if (session_ticket_keys_provider_ != nullptr && + session_ticket_keys_provider_->secret() != nullptr) { + session_ticket_keys_ = getSessionTicketKeys(*session_ticket_keys_provider_->secret()); + } + if ((config.common_tls_context().tls_certificates().size() + config.common_tls_context().tls_certificate_sds_secret_configs().size()) == 0) { throw EnvoyException("No TLS certificates found for server context"); @@ -357,10 +386,45 @@ ServerContextConfigImpl::ServerContextConfigImpl( } } -// Append a SessionTicketKey to keys, initializing it with key_data. +ServerContextConfigImpl::~ServerContextConfigImpl() { + if (stk_update_callback_handle_ != nullptr) { + stk_update_callback_handle_->remove(); + } + if (stk_validation_callback_handle_ != nullptr) { + stk_validation_callback_handle_->remove(); + } +} + +void ServerContextConfigImpl::setSecretUpdateCallback(std::function callback) { + ContextConfigImpl::setSecretUpdateCallback(callback); + if (session_ticket_keys_provider_) { + if (stk_update_callback_handle_) { + stk_update_callback_handle_->remove(); + } + // Once session_ticket_keys_ receives new secret, this callback updates + // ContextConfigImpl::session_ticket_keys_ with new session ticket keys. + stk_update_callback_handle_ = + session_ticket_keys_provider_->addUpdateCallback([this, callback]() { + session_ticket_keys_ = getSessionTicketKeys(*session_ticket_keys_provider_->secret()); + callback(); + }); + } +} + +std::vector +ServerContextConfigImpl::getSessionTicketKeys( + const envoy::api::v2::auth::TlsSessionTicketKeys& keys) { + std::vector result; + for (const auto& datasource : keys.keys()) { + result.emplace_back(getSessionTicketKey(Config::DataSource::read(datasource, false, api_))); + } + return result; +} + +// Extracts a SessionTicketKey from raw binary data. // Throws if key_data is invalid. -void ServerContextConfigImpl::validateAndAppendKey( - std::vector& keys, const std::string& key_data) { +Ssl::ServerContextConfig::SessionTicketKey +ServerContextConfigImpl::getSessionTicketKey(const std::string& key_data) { // If this changes, need to figure out how to deal with key files // that previously worked. For now, just assert so we'll notice that // it changed if it does. @@ -372,8 +436,7 @@ void ServerContextConfigImpl::validateAndAppendKey( key_data.size(), sizeof(SessionTicketKey))); } - keys.emplace_back(); - SessionTicketKey& dst_key = keys.back(); + SessionTicketKey dst_key; std::copy_n(key_data.begin(), dst_key.name_.size(), dst_key.name_.begin()); size_t pos = dst_key.name_.size(); @@ -382,6 +445,8 @@ void ServerContextConfigImpl::validateAndAppendKey( std::copy_n(key_data.begin() + pos, dst_key.aes_key_.size(), dst_key.aes_key_.begin()); pos += dst_key.aes_key_.size(); ASSERT(key_data.begin() + pos == key_data.end()); + + return dst_key; } } // namespace Tls diff --git a/source/extensions/transport_sockets/tls/context_config_impl.h b/source/extensions/transport_sockets/tls/context_config_impl.h index fa48b9815f08..89eec432ce57 100644 --- a/source/extensions/transport_sockets/tls/context_config_impl.h +++ b/source/extensions/transport_sockets/tls/context_config_impl.h @@ -127,6 +127,7 @@ class ServerContextConfigImpl : public ContextConfigImpl, public Envoy::Ssl::Ser ServerContextConfigImpl( const envoy::api::v2::auth::DownstreamTlsContext& config, Server::Configuration::TransportSocketFactoryContext& secret_provider_context); + ~ServerContextConfigImpl() override; // Ssl::ServerContextConfig bool requireClientCertificate() const override { return require_client_certificate_; } @@ -134,6 +135,15 @@ class ServerContextConfigImpl : public ContextConfigImpl, public Envoy::Ssl::Ser return session_ticket_keys_; } + bool isReady() const override { + const bool parent_is_ready = ContextConfigImpl::isReady(); + const bool session_ticket_keys_are_ready = + (session_ticket_keys_provider_ == nullptr || !session_ticket_keys_.empty()); + return parent_is_ready && session_ticket_keys_are_ready; + } + + void setSecretUpdateCallback(std::function callback) override; + private: static const unsigned DEFAULT_MIN_VERSION; static const unsigned DEFAULT_MAX_VERSION; @@ -141,10 +151,14 @@ class ServerContextConfigImpl : public ContextConfigImpl, public Envoy::Ssl::Ser static const std::string DEFAULT_CURVES; const bool require_client_certificate_; - const std::vector session_ticket_keys_; - - static void validateAndAppendKey(std::vector& keys, - const std::string& key_data); + std::vector session_ticket_keys_; + const Secret::TlsSessionTicketKeysConfigProviderSharedPtr session_ticket_keys_provider_; + Common::CallbackHandle* stk_update_callback_handle_{}; + Common::CallbackHandle* stk_validation_callback_handle_{}; + + std::vector + getSessionTicketKeys(const envoy::api::v2::auth::TlsSessionTicketKeys& keys); + ServerContextConfig::SessionTicketKey getSessionTicketKey(const std::string& key_data); }; } // namespace Tls diff --git a/test/common/secret/secret_manager_impl_test.cc b/test/common/secret/secret_manager_impl_test.cc index 33e208be9f64..9c781b980716 100644 --- a/test/common/secret/secret_manager_impl_test.cc +++ b/test/common/secret/secret_manager_impl_test.cc @@ -146,9 +146,8 @@ TEST_F(SecretManagerImplTest, DuplicateStaticCertificateValidationContextSecret) "Duplicate static CertificateValidationContext secret name abc.com"); } -// Validate that secret manager throws an exception when adding static secret of a type that is not -// supported. -TEST_F(SecretManagerImplTest, NotImplementedException) { +// Validate that secret manager adds static STKs secret successfully. +TEST_F(SecretManagerImplTest, SessionTicketKeysLoadSuccess) { envoy::api::v2::auth::Secret secret_config; const std::string yaml = @@ -156,15 +155,47 @@ TEST_F(SecretManagerImplTest, NotImplementedException) { name: "abc.com" session_ticket_keys: keys: - - filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/selfsigned_cert.pem" + - filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/keys.bin" )EOF"; TestUtility::loadFromYaml(TestEnvironment::substitute(yaml), secret_config); std::unique_ptr secret_manager(new SecretManagerImpl(config_tracker_)); + secret_manager->addStaticSecret(secret_config); + + ASSERT_EQ(secret_manager->findStaticTlsSessionTicketKeysContextProvider("undefined"), nullptr); + ASSERT_NE(secret_manager->findStaticTlsSessionTicketKeysContextProvider("abc.com"), nullptr); + + const ::envoy::api::v2::auth::TlsSessionTicketKeys session_ticket_keys( + *secret_manager->findStaticTlsSessionTicketKeysContextProvider("abc.com")->secret()); + const std::string keys_path = + "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/keys.bin"; + EXPECT_EQ(session_ticket_keys.keys_size(), 1); + EXPECT_EQ(session_ticket_keys.keys()[0].filename(), TestEnvironment::substitute(keys_path)); +} + +// Validate that secret manager throws an exception when adding duplicated static STKs secret. +TEST_F(SecretManagerImplTest, DuplicateSessionTicketKeysSecret) { + envoy::api::v2::auth::Secret secret_config; + + const std::string yaml = + R"EOF( +name: "abc.com" +session_ticket_keys: + keys: + - filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/keys.bin" +)EOF"; + + TestUtility::loadFromYaml(TestEnvironment::substitute(yaml), secret_config); + + std::unique_ptr secret_manager(new SecretManagerImpl(config_tracker_)); + + secret_manager->addStaticSecret(secret_config); + + ASSERT_NE(secret_manager->findStaticTlsSessionTicketKeysContextProvider("abc.com"), nullptr); EXPECT_THROW_WITH_MESSAGE(secret_manager->addStaticSecret(secret_config), EnvoyException, - "Secret type not implemented"); + "Duplicate static TlsSessionTicketKeys secret name abc.com"); } // Validate that secret manager deduplicates dynamic TLS certificate secret provider. @@ -419,6 +450,66 @@ name: "abc.com.validation" inline_string: "DUMMY_INLINE_STRING_TRUSTED_CA" )EOF"; checkConfigDump(updated_config_dump); + + // Add a dynamic tls session ticket encryption keys context provider. + time_system_.setSystemTime(std::chrono::milliseconds(1234567899000)); + auto stek_secret_provider = secret_manager->findOrCreateTlsSessionTicketKeysContextProvider( + config_source, "abc.com.stek", secret_context); + const std::string stek_yaml = R"EOF( +name: "abc.com.stek" +session_ticket_keys: + keys: + - filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ticket_key_a" + - inline_string: "DUMMY_INLINE_STRING" + - inline_bytes: "RFVNTVlfSU5MSU5FX0JZVEVT" +)EOF"; + TestUtility::loadFromYaml(TestEnvironment::substitute(stek_yaml), typed_secret); + secret_resources.Clear(); + secret_resources.Add()->PackFrom(typed_secret); + + init_target_handle->initialize(init_watcher); + secret_context.cluster_manager_.subscription_factory_.callbacks_->onConfigUpdate( + secret_resources, "stek-context-v1"); + EXPECT_EQ(stek_secret_provider->secret()->keys()[1].inline_string(), "DUMMY_INLINE_STRING"); + + const std::string updated_once_more_config_dump = R"EOF( +dynamic_active_secrets: +- name: "abc.com" + version_info: "keycert-v1" + last_updated: + seconds: 1234567891 + nanos: 234000000 + secret: + name: "abc.com" + tls_certificate: + certificate_chain: + inline_string: "DUMMY_INLINE_BYTES_FOR_CERT_CHAIN" + private_key: + inline_string: "[redacted]" + password: + inline_string: "[redacted]" +- name: "abc.com.validation" + version_info: "validation-context-v1" + last_updated: + seconds: 1234567899 + secret: + name: "abc.com.validation" + validation_context: + trusted_ca: + inline_string: "DUMMY_INLINE_STRING_TRUSTED_CA" +- name: "abc.com.stek" + version_info: "stek-context-v1" + last_updated: + seconds: 1234567899 + secret: + name: "abc.com.stek" + session_ticket_keys: + keys: + - filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ticket_key_a" + - inline_string: "[redacted]" + - inline_string: "[redacted]" +)EOF"; + checkConfigDump(TestEnvironment::substitute(updated_once_more_config_dump)); } TEST_F(SecretManagerImplTest, ConfigDumpHandlerWarmingSecrets) { @@ -480,6 +571,34 @@ TEST_F(SecretManagerImplTest, ConfigDumpHandlerWarmingSecrets) { name: "abc.com.validation" )EOF"; checkConfigDump(updated_config_dump); + + time_system_.setSystemTime(std::chrono::milliseconds(1234567899000)); + auto stek_secret_provider = secret_manager->findOrCreateTlsSessionTicketKeysContextProvider( + config_source, "abc.com.stek", secret_context); + init_target_handle->initialize(init_watcher); + const std::string updated_once_more_config_dump = R"EOF( +dynamic_warming_secrets: +- name: "abc.com" + version_info: "uninitialized" + last_updated: + seconds: 1234567891 + nanos: 234000000 + secret: + name: "abc.com" +- name: "abc.com.validation" + version_info: "uninitialized" + last_updated: + seconds: 1234567899 + secret: + name: "abc.com.validation" +- name: "abc.com.stek" + version_info: "uninitialized" + last_updated: + seconds: 1234567899 + secret: + name: "abc.com.stek" +)EOF"; + checkConfigDump(updated_once_more_config_dump); } TEST_F(SecretManagerImplTest, ConfigDumpHandlerStaticSecrets) { @@ -649,6 +768,54 @@ name: "abc.com.validation" checkConfigDump(expected_config_dump); } +TEST_F(SecretManagerImplTest, ConfigDumpHandlerStaticSessionTicketsContext) { + Server::MockInstance server; + auto secret_manager = std::make_unique(config_tracker_); + time_system_.setSystemTime(std::chrono::milliseconds(1234567891234)); + NiceMock secret_context; + envoy::api::v2::core::ConfigSource config_source; + NiceMock local_info; + NiceMock dispatcher; + NiceMock random; + Stats::IsolatedStoreImpl stats; + NiceMock init_manager; + NiceMock init_watcher; + Init::TargetHandlePtr init_target_handle; + EXPECT_CALL(init_manager, add(_)) + .WillRepeatedly(Invoke([&init_target_handle](const Init::Target& target) { + init_target_handle = target.createHandle("test"); + })); + EXPECT_CALL(secret_context, stats()).WillRepeatedly(ReturnRef(stats)); + EXPECT_CALL(secret_context, initManager()).WillRepeatedly(Return(&init_manager)); + EXPECT_CALL(secret_context, dispatcher()).WillRepeatedly(ReturnRef(dispatcher)); + EXPECT_CALL(secret_context, localInfo()).WillRepeatedly(ReturnRef(local_info)); + + const std::string stek_context = + R"EOF( +name: "abc.com.stek" +session_ticket_keys: + keys: + - filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ticket_key_a" + - inline_string: "DUMMY_INLINE_STRING" + - inline_bytes: "RFVNTVlfSU5MSU5FX0JZVEVT" +)EOF"; + envoy::api::v2::auth::Secret stek_secret; + TestUtility::loadFromYaml(TestEnvironment::substitute(stek_context), stek_secret); + secret_manager->addStaticSecret(stek_secret); + const std::string expected_config_dump = R"EOF( +static_secrets: +- name: "abc.com.stek" + secret: + name: "abc.com.stek" + session_ticket_keys: + keys: + - filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ticket_key_a" + - inline_string: "[redacted]" + - inline_string: "[redacted]" +)EOF"; + checkConfigDump(TestEnvironment::substitute(expected_config_dump)); +} + } // namespace } // namespace Secret } // namespace Envoy diff --git a/test/extensions/transport_sockets/tls/context_impl_test.cc b/test/extensions/transport_sockets/tls/context_impl_test.cc index 6f12bc75cb4b..3b6b596b5f1b 100644 --- a/test/extensions/transport_sockets/tls/context_impl_test.cc +++ b/test/extensions/transport_sockets/tls/context_impl_test.cc @@ -456,10 +456,70 @@ TEST_F(SslServerContextImplTicketTest, TicketKeyInlineStringFailTooSmall) { EXPECT_THROW(loadConfigV2(cfg), EnvoyException); } -TEST_F(SslServerContextImplTicketTest, TicketKeySdsFail) { - envoy::api::v2::auth::DownstreamTlsContext cfg; - cfg.mutable_session_ticket_keys_sds_secret_config(); - EXPECT_THROW_WITH_MESSAGE(loadConfigV2(cfg), EnvoyException, "SDS not supported yet"); +TEST_F(SslServerContextImplTicketTest, TicketKeySdsNotReady) { + envoy::api::v2::auth::DownstreamTlsContext tls_context; + envoy::api::v2::auth::TlsCertificate* server_cert = + tls_context.mutable_common_tls_context()->add_tls_certificates(); + server_cert->mutable_certificate_chain()->set_filename(TestEnvironment::substitute( + "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/selfsigned_cert.pem")); + server_cert->mutable_private_key()->set_filename(TestEnvironment::substitute( + "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/selfsigned_key.pem")); + + NiceMock local_info; + NiceMock dispatcher; + NiceMock random; + Stats::IsolatedStoreImpl stats; + NiceMock cluster_manager; + NiceMock init_manager; + EXPECT_CALL(factory_context_, localInfo()).WillOnce(ReturnRef(local_info)); + EXPECT_CALL(factory_context_, dispatcher()).WillOnce(ReturnRef(dispatcher)); + // EXPECT_CALL(factory_context_, random()).WillOnce(ReturnRef(random)); + EXPECT_CALL(factory_context_, stats()).WillOnce(ReturnRef(stats)); + EXPECT_CALL(factory_context_, clusterManager()).WillOnce(ReturnRef(cluster_manager)); + EXPECT_CALL(factory_context_, initManager()).WillRepeatedly(Return(&init_manager)); + auto* sds_secret_configs = tls_context.mutable_session_ticket_keys_sds_secret_config(); + sds_secret_configs->set_name("abc.com"); + sds_secret_configs->mutable_sds_config(); + ServerContextConfigImpl server_context_config(tls_context, factory_context_); + // When sds secret is not downloaded, config is not ready. + EXPECT_FALSE(server_context_config.isReady()); + // Set various callbacks to config. + NiceMock secret_callback; + server_context_config.setSecretUpdateCallback( + [&secret_callback]() { secret_callback.onAddOrUpdateSecret(); }); + server_context_config.setSecretUpdateCallback([]() {}); +} + +// Validate that client context config with static TLS ticket encryption keys is created +// successfully. +TEST_F(SslServerContextImplTicketTest, StaticTickeyKey) { + envoy::api::v2::auth::Secret secret_config; + + const std::string yaml = R"EOF( +name: "abc.com" +session_ticket_keys: + keys: + - filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ticket_key_a" + - filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ticket_key_b" +)EOF"; + + TestUtility::loadFromYaml(TestEnvironment::substitute(yaml), secret_config); + factory_context_.secretManager().addStaticSecret(secret_config); + + envoy::api::v2::auth::DownstreamTlsContext tls_context; + envoy::api::v2::auth::TlsCertificate* server_cert = + tls_context.mutable_common_tls_context()->add_tls_certificates(); + server_cert->mutable_certificate_chain()->set_filename(TestEnvironment::substitute( + "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/selfsigned_cert.pem")); + server_cert->mutable_private_key()->set_filename(TestEnvironment::substitute( + "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/selfsigned_key.pem")); + + tls_context.mutable_session_ticket_keys_sds_secret_config()->set_name("abc.com"); + + ServerContextConfigImpl server_context_config(tls_context, factory_context_); + + EXPECT_TRUE(server_context_config.isReady()); + ASSERT_EQ(server_context_config.sessionTicketKeys().size(), 2); } TEST_F(SslServerContextImplTicketTest, CRLSuccess) { diff --git a/test/mocks/secret/mocks.h b/test/mocks/secret/mocks.h index 2f3f2c5f073e..93ba29fa5edc 100644 --- a/test/mocks/secret/mocks.h +++ b/test/mocks/secret/mocks.h @@ -21,6 +21,8 @@ class MockSecretManager : public SecretManager { TlsCertificateConfigProviderSharedPtr(const std::string& name)); MOCK_CONST_METHOD1(findStaticCertificateValidationContextProvider, CertificateValidationContextConfigProviderSharedPtr(const std::string& name)); + MOCK_CONST_METHOD1(findStaticTlsSessionTicketKeysContextProvider, + TlsSessionTicketKeysConfigProviderSharedPtr(const std::string& name)); MOCK_METHOD1(createInlineTlsCertificateProvider, TlsCertificateConfigProviderSharedPtr( const envoy::api::v2::auth::TlsCertificate& tls_certificate)); @@ -28,6 +30,9 @@ class MockSecretManager : public SecretManager { CertificateValidationContextConfigProviderSharedPtr( const envoy::api::v2::auth::CertificateValidationContext& certificate_validation_context)); + MOCK_METHOD1(createInlineTlsSessionTicketKeysProvider, + TlsSessionTicketKeysConfigProviderSharedPtr( + const envoy::api::v2::auth::TlsSessionTicketKeys& tls_session_ticket_keys)); MOCK_METHOD3(findOrCreateTlsCertificateProvider, TlsCertificateConfigProviderSharedPtr( const envoy::api::v2::core::ConfigSource&, const std::string&, @@ -37,6 +42,10 @@ class MockSecretManager : public SecretManager { const envoy::api::v2::core::ConfigSource& config_source, const std::string& config_name, Server::Configuration::TransportSocketFactoryContext& secret_provider_context)); + MOCK_METHOD3(findOrCreateTlsSessionTicketKeysContextProvider, + TlsSessionTicketKeysConfigProviderSharedPtr( + const envoy::api::v2::core::ConfigSource&, const std::string&, + Server::Configuration::TransportSocketFactoryContext&)); }; class MockSecretCallbacks : public SecretCallbacks {