diff --git a/include/envoy/secret/BUILD b/include/envoy/secret/BUILD index c4dcf8404fd6f..66a2516882057 100644 --- a/include/envoy/secret/BUILD +++ b/include/envoy/secret/BUILD @@ -8,11 +8,26 @@ load( envoy_package() +envoy_cc_library( + name = "secret_callbacks_interface", + hdrs = ["secret_callbacks.h"], +) + +envoy_cc_library( + name = "dynamic_secret_provider_interface", + hdrs = ["dynamic_secret_provider.h"], + deps = [ + "secret_callbacks_interface", + "//include/envoy/ssl:tls_certificate_config_interface", + ], +) + envoy_cc_library( name = "secret_manager_interface", hdrs = ["secret_manager.h"], deps = [ - "//include/envoy/ssl:tls_certificate_config_interface", + ":dynamic_secret_provider_interface", "@envoy_api//envoy/api/v2/auth:cert_cc", + "@envoy_api//envoy/api/v2/core:config_source_cc", ], ) diff --git a/include/envoy/secret/dynamic_secret_provider.h b/include/envoy/secret/dynamic_secret_provider.h new file mode 100644 index 0000000000000..8016ddfe1e58c --- /dev/null +++ b/include/envoy/secret/dynamic_secret_provider.h @@ -0,0 +1,30 @@ +#pragma once + +#include + +#include "envoy/secret/secret_callbacks.h" +#include "envoy/ssl/tls_certificate_config.h" + +namespace Envoy { +namespace Secret { + +/** + * An interface to fetch dynamic secret. + */ +class DynamicSecretProvider { +public: + virtual ~DynamicSecretProvider() {} + + /** + * @return the TlsCertificate secret. Returns nullptr if the secret is not found. + */ + virtual const Ssl::TlsCertificateConfigSharedPtr secret() const PURE; + + virtual void addUpdateCallback(SecretCallbacks& callback) PURE; + virtual void removeUpdateCallback(SecretCallbacks& callback) PURE; +}; + +typedef std::shared_ptr DynamicSecretProviderSharedPtr; + +} // namespace Secret +} // namespace Envoy diff --git a/include/envoy/secret/secret_callbacks.h b/include/envoy/secret/secret_callbacks.h new file mode 100644 index 0000000000000..fd90b4bd636d7 --- /dev/null +++ b/include/envoy/secret/secret_callbacks.h @@ -0,0 +1,22 @@ +#pragma once + +#include +#include + +#include "envoy/common/pure.h" + +namespace Envoy { +namespace Secret { + +/** + * Callbacks invoked by a secret manager. + */ +class SecretCallbacks { +public: + virtual ~SecretCallbacks() {} + + virtual void onAddOrUpdateSecret() PURE; +}; + +} // namespace Secret +} // namespace Envoy diff --git a/include/envoy/secret/secret_manager.h b/include/envoy/secret/secret_manager.h index d7f9788741213..549e7829a7276 100644 --- a/include/envoy/secret/secret_manager.h +++ b/include/envoy/secret/secret_manager.h @@ -3,31 +3,46 @@ #include #include "envoy/api/v2/auth/cert.pb.h" +#include "envoy/secret/dynamic_secret_provider.h" #include "envoy/ssl/tls_certificate_config.h" namespace Envoy { namespace Secret { /** - * A manager for static secrets. - * - * TODO(jaebong) Support dynamic secrets. + * A manager for static and dynamic secrets. */ class SecretManager { public: virtual ~SecretManager() {} /** + * @param config_source_hash a hash string of normalized config source. If it is empty string, + * find secret from the static secrets. * @param secret a protobuf message of envoy::api::v2::auth::Secret. * @throw an EnvoyException if the secret is invalid or not supported. */ - virtual void addOrUpdateSecret(const envoy::api::v2::auth::Secret& secret) PURE; + virtual void addStaticSecret(const envoy::api::v2::auth::Secret& secret) PURE; /** + * @param sds_config_source_hash hash string of normalized config source. * @param name a name of the Ssl::TlsCertificateConfig. * @return the TlsCertificate secret. Returns nullptr if the secret is not found. */ - virtual const Ssl::TlsCertificateConfig* findTlsCertificate(const std::string& name) const PURE; + virtual const Ssl::TlsCertificateConfigSharedPtr + findStaticTlsCertificate(const std::string& name) const PURE; + + /** + * Add or update SDS config source. SecretManager starts downloading secrets from registered + * config source. + * + * @param sdsConfigSource a protobuf message object contains SDS config source. + * @param config_name a name that uniquely refers to the SDS config source. + * @return a hash string of normalized config source. + */ + virtual DynamicSecretProviderSharedPtr + createDynamicSecretProvider(const envoy::api::v2::core::ConfigSource& config_source, + std::string config_name) PURE; }; } // namespace Secret diff --git a/include/envoy/ssl/context.h b/include/envoy/ssl/context.h index 5af2fa804fb8a..3bf9000658838 100644 --- a/include/envoy/ssl/context.h +++ b/include/envoy/ssl/context.h @@ -32,12 +32,15 @@ class Context { */ virtual std::string getCertChainInformation() const PURE; }; +typedef std::shared_ptr ContextSharedPtr; class ClientContext : public virtual Context {}; typedef std::unique_ptr ClientContextPtr; +typedef std::shared_ptr ClientContextSharedPtr; class ServerContext : public virtual Context {}; typedef std::unique_ptr ServerContextPtr; +typedef std::shared_ptr ServerContextSharedPtr; } // namespace Ssl } // namespace Envoy diff --git a/include/envoy/ssl/context_config.h b/include/envoy/ssl/context_config.h index a56a89e903e6b..f3c2be3b2a6d5 100644 --- a/include/envoy/ssl/context_config.h +++ b/include/envoy/ssl/context_config.h @@ -5,6 +5,7 @@ #include #include "envoy/common/pure.h" +#include "envoy/secret/dynamic_secret_provider.h" namespace Envoy { namespace Ssl { @@ -111,6 +112,16 @@ class ContextConfig { * @return The maximum TLS protocol version to negotiate. */ virtual unsigned maxProtocolVersion() const PURE; + + /** + * @return true of the config is valid. + */ + virtual bool isValid() const PURE; + + /** + * @return the DynamicSecretProvider object. + */ + virtual Secret::DynamicSecretProvider* getDynamicSecretProvider() const PURE; }; class ClientContextConfig : public virtual ContextConfig { @@ -127,6 +138,8 @@ class ClientContextConfig : public virtual ContextConfig { virtual bool allowRenegotiation() const PURE; }; +typedef std::unique_ptr ClientContextConfigPtr; + class ServerContextConfig : public virtual ContextConfig { public: struct SessionTicketKey { @@ -148,5 +161,7 @@ class ServerContextConfig : public virtual ContextConfig { virtual const std::vector& sessionTicketKeys() const PURE; }; +typedef std::unique_ptr ServerContextConfigPtr; + } // namespace Ssl } // namespace Envoy diff --git a/include/envoy/ssl/context_manager.h b/include/envoy/ssl/context_manager.h index 7489800c99caf..25a684833a13b 100644 --- a/include/envoy/ssl/context_manager.h +++ b/include/envoy/ssl/context_manager.h @@ -19,16 +19,21 @@ class ContextManager { /** * Builds a ClientContext from a ClientContextConfig. */ - virtual ClientContextPtr createSslClientContext(Stats::Scope& scope, - const ClientContextConfig& config) PURE; + virtual ClientContextSharedPtr createSslClientContext(Stats::Scope& scope, + const ClientContextConfig& config) PURE; /** * Builds a ServerContext from a ServerContextConfig. */ - virtual ServerContextPtr + virtual ServerContextSharedPtr createSslServerContext(Stats::Scope& scope, const ServerContextConfig& config, const std::vector& server_names) PURE; + /** + * Remove a context. + */ + virtual void releaseContext(Context* context) PURE; + /** * @return the number of days until the next certificate being managed will expire. */ diff --git a/include/envoy/ssl/tls_certificate_config.h b/include/envoy/ssl/tls_certificate_config.h index 6a5c8c842a060..c500663afe306 100644 --- a/include/envoy/ssl/tls_certificate_config.h +++ b/include/envoy/ssl/tls_certificate_config.h @@ -23,7 +23,7 @@ class TlsCertificateConfig { virtual const std::string& privateKey() const PURE; }; -typedef std::unique_ptr TlsCertificateConfigPtr; +typedef std::shared_ptr TlsCertificateConfigSharedPtr; } // namespace Ssl } // namespace Envoy diff --git a/source/common/config/BUILD b/source/common/config/BUILD index 83067aa8806eb..ba821c967bd73 100644 --- a/source/common/config/BUILD +++ b/source/common/config/BUILD @@ -237,6 +237,7 @@ envoy_cc_library( hdrs = ["protobuf_link_hacks.h"], deps = [ "@envoy_api//envoy/service/discovery/v2:ads_cc", + "@envoy_api//envoy/service/discovery/v2:sds_cc", "@envoy_api//envoy/service/ratelimit/v2:rls_cc", ], ) diff --git a/source/common/config/protobuf_link_hacks.h b/source/common/config/protobuf_link_hacks.h index 6792f3e797c1e..243d9ecf69a20 100644 --- a/source/common/config/protobuf_link_hacks.h +++ b/source/common/config/protobuf_link_hacks.h @@ -1,6 +1,7 @@ #pragma once #include "envoy/service/discovery/v2/ads.pb.h" +#include "envoy/service/discovery/v2/sds.pb.h" #include "envoy/service/ratelimit/v2/rls.pb.h" namespace Envoy { @@ -8,5 +9,6 @@ namespace Envoy { // Hack to force linking of the service: https://github.com/google/protobuf/issues/4221. // This file should be included ONLY if this hack is required. const envoy::service::discovery::v2::AdsDummy _ads_dummy; +const envoy::service::discovery::v2::SdsDummy _sds_dummy; const envoy::service::ratelimit::v2::RateLimitRequest _rls_dummy; } // namespace Envoy diff --git a/source/common/config/resources.h b/source/common/config/resources.h index 03f0c9a2efd8b..69ed2d91a46dc 100644 --- a/source/common/config/resources.h +++ b/source/common/config/resources.h @@ -15,6 +15,7 @@ class TypeUrlValues { const std::string Listener{"type.googleapis.com/envoy.api.v2.Listener"}; const std::string Cluster{"type.googleapis.com/envoy.api.v2.Cluster"}; const std::string ClusterLoadAssignment{"type.googleapis.com/envoy.api.v2.ClusterLoadAssignment"}; + const std::string Secret{"type.googleapis.com/envoy.api.v2.auth.Secret"}; const std::string RouteConfiguration{"type.googleapis.com/envoy.api.v2.RouteConfiguration"}; }; diff --git a/source/common/secret/BUILD b/source/common/secret/BUILD index 4f1eff746d6d9..ca440ca9416b3 100644 --- a/source/common/secret/BUILD +++ b/source/common/secret/BUILD @@ -13,9 +13,34 @@ envoy_cc_library( srcs = ["secret_manager_impl.cc"], hdrs = ["secret_manager_impl.h"], deps = [ + ":sds_api_lib", + ":secret_manager_util", "//include/envoy/secret:secret_manager_interface", + "//include/envoy/server:instance_interface", "//source/common/common:minimal_logger_lib", - "//source/common/ssl:tls_certificate_config_impl_lib", "@envoy_api//envoy/api/v2/auth:cert_cc", ], ) + +envoy_cc_library( + name = "secret_manager_util", + hdrs = ["secret_manager_util.h"], + deps = [ + "//source/common/json:json_loader_lib", + "@envoy_api//envoy/api/v2/core:config_source_cc", + ], +) + +envoy_cc_library( + name = "sds_api_lib", + srcs = ["sds_api.cc"], + hdrs = ["sds_api.h"], + deps = [ + ":secret_manager_util", + "//include/envoy/config:subscription_interface", + "//include/envoy/server:instance_interface", + "//source/common/config:resources_lib", + "//source/common/config:subscription_factory_lib", + "//source/common/ssl:tls_certificate_config_impl_lib", + ], +) diff --git a/source/common/secret/sds_api.cc b/source/common/secret/sds_api.cc new file mode 100644 index 0000000000000..88f2cca344aa7 --- /dev/null +++ b/source/common/secret/sds_api.cc @@ -0,0 +1,82 @@ +#include "common/secret/sds_api.h" + +#include + +#include "envoy/api/v2/auth/cert.pb.validate.h" + +#include "common/config/resources.h" +#include "common/config/subscription_factory.h" +#include "common/secret/secret_manager_util.h" +#include "common/ssl/tls_certificate_config_impl.h" + +namespace Envoy { +namespace Secret { + +SdsApi::SdsApi(Server::Instance& server, const envoy::api::v2::core::ConfigSource& sds_config, + std::string sds_config_name) + : server_(server), sds_config_(sds_config), sds_config_name_(sds_config_name) { + server_.initManager().registerTarget(*this); +} + +void SdsApi::initialize(std::function callback) { + initialize_callback_ = callback; + subscription_ = Envoy::Config::SubscriptionFactory::subscriptionFromConfigSource< + envoy::api::v2::auth::Secret>( + sds_config_, server_.localInfo().node(), server_.dispatcher(), server_.clusterManager(), + server_.random(), server_.stats(), /* rest_legacy_constructor */ nullptr, + "envoy.service.discovery.v2.SecretDiscoveryService.FetchSecrets", + // TODO(jaebong): replace next line with + // "envoy.service.discovery.v2.SecretDiscoveryService.StreamSecrets" to support streaming + // service + "envoy.service.discovery.v2.SecretDiscoveryService.FetchSecrets"); + + Config::Utility::checkLocalInfo("sds", server_.localInfo()); + + subscription_->start({sds_config_name_}, *this); +} + +void SdsApi::onConfigUpdate(const ResourceVector& resources, const std::string&) { + if (resources.empty()) { + runInitializeCallbackIfAny(); + return; + } + if (resources.size() != 1) { + throw EnvoyException(fmt::format("Unexpected RDS resource length: {}", resources.size())); + } + const auto& secret = resources[0]; + MessageUtil::validate(secret); + if (!(secret.name() == sds_config_name_)) { + throw EnvoyException(fmt::format("Unexpected RDS configuration (expecting {}): {}", + sds_config_name_, secret.name())); + } + + const uint64_t new_hash = MessageUtil::hash(secret); + if (new_hash != secret_hash_) { + if (secret.type_case() == envoy::api::v2::auth::Secret::TypeCase::kTlsCertificate) { + tls_certificate_secrets_ = + std::make_shared(secret.tls_certificate()); + secret_hash_ = new_hash; + + for (auto cb : update_callbacks_) { + cb->onAddOrUpdateSecret(); + } + } + } + + runInitializeCallbackIfAny(); +} + +void SdsApi::onConfigUpdateFailed(const EnvoyException*) { + // We need to allow server startup to continue, even if we have a bad config. + runInitializeCallbackIfAny(); +} + +void SdsApi::runInitializeCallbackIfAny() { + if (initialize_callback_) { + initialize_callback_(); + initialize_callback_ = nullptr; + } +} + +} // namespace Secret +} // namespace Envoy diff --git a/source/common/secret/sds_api.h b/source/common/secret/sds_api.h new file mode 100644 index 0000000000000..69d8d7cb94317 --- /dev/null +++ b/source/common/secret/sds_api.h @@ -0,0 +1,61 @@ +#pragma once + +#include + +#include "envoy/api/v2/auth/cert.pb.h" +#include "envoy/api/v2/core/config_source.pb.h" +#include "envoy/config/subscription.h" +#include "envoy/server/instance.h" + +namespace Envoy { +namespace Secret { + +/** + * SDS API implementation that fetches secrets from SDS server via Subscription. + */ +class SdsApi : public Init::Target, + public DynamicSecretProvider, + public Config::SubscriptionCallbacks { +public: + SdsApi(Server::Instance& server, const envoy::api::v2::core::ConfigSource& sds_config, + std::string sds_config_name); + + // Init::Target + void initialize(std::function callback) override; + + // Config::SubscriptionCallbacks + void onConfigUpdate(const ResourceVector& resources, const std::string& version_info) override; + void onConfigUpdateFailed(const EnvoyException* e) override; + std::string resourceName(const ProtobufWkt::Any& resource) override { + return MessageUtil::anyConvert(resource).name(); + } + + // DynamicSecretProvider + const Ssl::TlsCertificateConfigSharedPtr secret() const override { + return tls_certificate_secrets_; + } + void addUpdateCallback(SecretCallbacks& callback) override { + update_callbacks_.push_back(&callback); + } + void removeUpdateCallback(SecretCallbacks& callback) override { + update_callbacks_.remove(&callback); + } + +private: + void runInitializeCallbackIfAny(); + + Server::Instance& server_; + const envoy::api::v2::core::ConfigSource sds_config_; + std::unique_ptr> subscription_; + std::function initialize_callback_; + std::string sds_config_name_; + + std::size_t secret_hash_; + Ssl::TlsCertificateConfigSharedPtr tls_certificate_secrets_; + std::list update_callbacks_; +}; + +typedef std::unique_ptr SdsApiPtr; + +} // namespace Secret +} // namespace Envoy diff --git a/source/common/secret/secret_manager_impl.cc b/source/common/secret/secret_manager_impl.cc index 3e6689a369da4..0fe9814779691 100644 --- a/source/common/secret/secret_manager_impl.cc +++ b/source/common/secret/secret_manager_impl.cc @@ -2,26 +2,45 @@ #include "envoy/common/exception.h" +#include "common/secret/secret_manager_util.h" #include "common/ssl/tls_certificate_config_impl.h" namespace Envoy { namespace Secret { -void SecretManagerImpl::addOrUpdateSecret(const envoy::api::v2::auth::Secret& secret) { +void SecretManagerImpl::addStaticSecret(const envoy::api::v2::auth::Secret& secret) { switch (secret.type_case()) { - case envoy::api::v2::auth::Secret::TypeCase::kTlsCertificate: - tls_certificate_secrets_[secret.name()] = - std::make_unique(secret.tls_certificate()); - break; + case envoy::api::v2::auth::Secret::TypeCase::kTlsCertificate: { + auto tls_certificate_secret = + std::make_shared(secret.tls_certificate()); + std::unique_lock lhs(static_tls_certificate_secrets_mutex_); + static_tls_certificate_secrets_[secret.name()] = tls_certificate_secret; + } break; default: throw EnvoyException("Secret type not implemented"); } } -const Ssl::TlsCertificateConfig* -SecretManagerImpl::findTlsCertificate(const std::string& name) const { - auto secret = tls_certificate_secrets_.find(name); - return (secret != tls_certificate_secrets_.end()) ? secret->second.get() : nullptr; +const Ssl::TlsCertificateConfigSharedPtr +SecretManagerImpl::findStaticTlsCertificate(const std::string& name) const { + std::shared_lock lhs(static_tls_certificate_secrets_mutex_); + auto secret = static_tls_certificate_secrets_.find(name); + return (secret != static_tls_certificate_secrets_.end()) ? secret->second : nullptr; +} + +DynamicSecretProviderSharedPtr SecretManagerImpl::createDynamicSecretProvider( + const envoy::api::v2::core::ConfigSource& config_source, std::string config_name) { + auto hash = SecretManagerUtil::configSourceHash(config_source); + std::string map_key = hash + config_name; + + std::unique_lock lhs(dynamic_secret_providers_mutex_); + auto dynamic_secret_provider = dynamic_secret_providers_[map_key].lock(); + if (!dynamic_secret_provider) { + dynamic_secret_provider = std::make_shared(server_, config_source, config_name); + dynamic_secret_providers_[map_key] = dynamic_secret_provider; + } + + return dynamic_secret_provider; } } // namespace Secret diff --git a/source/common/secret/secret_manager_impl.h b/source/common/secret/secret_manager_impl.h index b9406754a8c45..bfb7c32b48e6a 100644 --- a/source/common/secret/secret_manager_impl.h +++ b/source/common/secret/secret_manager_impl.h @@ -1,22 +1,43 @@ #pragma once +#include #include #include "envoy/secret/secret_manager.h" +#include "envoy/server/instance.h" #include "envoy/ssl/tls_certificate_config.h" #include "common/common/logger.h" +#include "common/secret/sds_api.h" namespace Envoy { namespace Secret { class SecretManagerImpl : public SecretManager, Logger::Loggable { public: - void addOrUpdateSecret(const envoy::api::v2::auth::Secret& secret) override; - const Ssl::TlsCertificateConfig* findTlsCertificate(const std::string& name) const override; + SecretManagerImpl(Server::Instance& server) : server_(server) {} + + void addStaticSecret(const envoy::api::v2::auth::Secret& secret) override; + + const Ssl::TlsCertificateConfigSharedPtr + findStaticTlsCertificate(const std::string& name) const override; + + virtual DynamicSecretProviderSharedPtr + createDynamicSecretProvider(const envoy::api::v2::core::ConfigSource& config_source, + std::string config_name) override; private: - std::unordered_map tls_certificate_secrets_; + Server::Instance& server_; + + // Manages pairs of name and Ssl::TlsCertificateConfig grouped by SDS config source hash. + // If SDS config source hash is empty, it is a static secret. + std::unordered_map + static_tls_certificate_secrets_; + mutable std::shared_timed_mutex static_tls_certificate_secrets_mutex_; + + // map hash code of SDS config source and SdsApi object. + std::unordered_map> dynamic_secret_providers_; + mutable std::shared_timed_mutex dynamic_secret_providers_mutex_; }; } // namespace Secret diff --git a/source/common/secret/secret_manager_util.h b/source/common/secret/secret_manager_util.h new file mode 100644 index 0000000000000..5571e28943867 --- /dev/null +++ b/source/common/secret/secret_manager_util.h @@ -0,0 +1,37 @@ +#pragma once + +#include "envoy/api/v2/core/config_source.pb.h" + +#include "common/common/fmt.h" +#include "common/json/json_loader.h" +#include "common/protobuf/protobuf.h" + +namespace Envoy { +namespace Secret { + +class SecretManagerUtil { +public: + virtual ~SecretManagerUtil() {} + + /** + * Calculate hash code of ConfigSource. To identify the same ConfigSource, calculate the hash + * code from the ConfigSource. + * + * @param config_source envoy::api::v2::core::ConfigSource. + * @return hash code. + */ + static std::string configSourceHash(const envoy::api::v2::core::ConfigSource& config_source) { + std::string jsonstr; + if (Protobuf::util::MessageToJsonString(config_source, &jsonstr).ok()) { + auto obj = Json::Factory::loadFromString(jsonstr); + if (obj.get() != nullptr) { + return std::to_string(obj->hash()); + } + } + throw EnvoyException( + fmt::format("Invalid ConfigSource message: {}", config_source.DebugString())); + } +}; + +} // namespace Secret +} // namespace Envoy \ No newline at end of file diff --git a/source/common/ssl/BUILD b/source/common/ssl/BUILD index 486d0da347dca..2b4b9ea8150b0 100644 --- a/source/common/ssl/BUILD +++ b/source/common/ssl/BUILD @@ -18,6 +18,7 @@ envoy_cc_library( ":context_lib", "//include/envoy/network:connection_interface", "//include/envoy/network:transport_socket_interface", + "//include/envoy/secret:secret_manager_interface", "//source/common/common:assert_lib", "//source/common/common:empty_string", "//source/common/common:minimal_logger_lib", @@ -58,6 +59,7 @@ envoy_cc_library( external_deps = ["ssl"], deps = [ "//include/envoy/runtime:runtime_interface", + "//include/envoy/secret:secret_manager_interface", "//include/envoy/ssl:context_config_interface", "//include/envoy/ssl:context_interface", "//include/envoy/ssl:context_manager_interface", diff --git a/source/common/ssl/context_config_impl.cc b/source/common/ssl/context_config_impl.cc index 374cd2945b50a..a76101216b1d9 100644 --- a/source/common/ssl/context_config_impl.cc +++ b/source/common/ssl/context_config_impl.cc @@ -3,6 +3,8 @@ #include #include +#include "envoy/ssl/tls_certificate_config.h" + #include "common/common/assert.h" #include "common/common/empty_string.h" #include "common/config/datasource.h" @@ -14,29 +16,6 @@ namespace Envoy { namespace Ssl { -namespace { - -std::string readConfig( - const envoy::api::v2::auth::CommonTlsContext& config, Secret::SecretManager& secret_manager, - const std::function& - read_inline_config, - const std::function& read_secret) { - if (!config.tls_certificates().empty()) { - return read_inline_config(config.tls_certificates()[0]); - } else if (!config.tls_certificate_sds_secret_configs().empty()) { - auto name = config.tls_certificate_sds_secret_configs()[0].name(); - const Ssl::TlsCertificateConfig* secret = secret_manager.findTlsCertificate(name); - if (!secret) { - throw EnvoyException(fmt::format("Static secret is not defined: {}", name)); - } - return read_secret(*secret); - } else { - return EMPTY_STRING; - } -} - -} // namespace - const std::string ContextConfigImpl::DEFAULT_CIPHER_SUITES = "[ECDHE-ECDSA-AES128-GCM-SHA256|ECDHE-ECDSA-CHACHA20-POLY1305]:" "[ECDHE-RSA-AES128-GCM-SHA256|ECDHE-RSA-CHACHA20-POLY1305]:" @@ -55,7 +34,8 @@ const std::string ContextConfigImpl::DEFAULT_ECDH_CURVES = "X25519:P-256"; ContextConfigImpl::ContextConfigImpl(const envoy::api::v2::auth::CommonTlsContext& config, Secret::SecretManager& secret_manager) - : alpn_protocols_(RepeatedPtrUtil::join(config.alpn_protocols(), ",")), + : secret_manager_(secret_manager), + alpn_protocols_(RepeatedPtrUtil::join(config.alpn_protocols(), ",")), alt_alpn_protocols_(config.deprecated_v1().alt_alpn_protocols()), cipher_suites_(StringUtil::nonEmptyStringOrDefault( RepeatedPtrUtil::join(config.tls_params().cipher_suites(), ":"), DEFAULT_CIPHER_SUITES)), @@ -67,26 +47,10 @@ ContextConfigImpl::ContextConfigImpl(const envoy::api::v2::auth::CommonTlsContex Config::DataSource::read(config.validation_context().crl(), true)), certificate_revocation_list_path_( Config::DataSource::getPath(config.validation_context().crl())), - cert_chain_(readConfig( - config, secret_manager, - [](const envoy::api::v2::auth::TlsCertificate& tls_certificate) -> std::string { - return Config::DataSource::read(tls_certificate.certificate_chain(), true); - }, - [](const Ssl::TlsCertificateConfig& secret) -> std::string { - return secret.certificateChain(); - })), cert_chain_path_( config.tls_certificates().empty() ? "" : Config::DataSource::getPath(config.tls_certificates()[0].certificate_chain())), - private_key_(readConfig( - config, secret_manager, - [](const envoy::api::v2::auth::TlsCertificate& tls_certificate) -> std::string { - return Config::DataSource::read(tls_certificate.private_key(), true); - }, - [](const Ssl::TlsCertificateConfig& secret) -> std::string { - return secret.privateKey(); - })), private_key_path_( config.tls_certificates().empty() ? "" @@ -102,6 +66,9 @@ ContextConfigImpl::ContextConfigImpl(const envoy::api::v2::auth::CommonTlsContex tlsVersionFromProto(config.tls_params().tls_minimum_protocol_version(), TLS1_VERSION)), max_protocol_version_( tlsVersionFromProto(config.tls_params().tls_maximum_protocol_version(), TLS1_2_VERSION)) { + + readCertificates(config); + if (ca_cert_.empty()) { if (!certificate_revocation_list_.empty()) { throw EnvoyException(fmt::format("Failed to load CRL from {} without trusted CA", @@ -118,6 +85,36 @@ ContextConfigImpl::ContextConfigImpl(const envoy::api::v2::auth::CommonTlsContex } } +void ContextConfigImpl::readCertificates(const envoy::api::v2::auth::CommonTlsContext& config) { + if (!config.tls_certificates().empty()) { + // TODO(qiwzhang): only use the first secret_configs for now. + const auto& cert_config = config.tls_certificates()[0]; + cert_chain_ = Config::DataSource::read(cert_config.certificate_chain(), true); + private_key_ = Config::DataSource::read(cert_config.private_key(), true); + return; + } + if (!config.tls_certificate_sds_secret_configs().empty()) { + // TODO(qiwzhang): only use the first secret_configs for now. + const auto& secret_config = config.tls_certificate_sds_secret_configs()[0]; + const auto& secret_name = secret_config.name(); + if (!secret_config.has_sds_config()) { + // static secret + const auto& secret = secret_manager_.findStaticTlsCertificate(secret_name); + if (secret) { + cert_chain_ = secret->certificateChain(); + private_key_ = secret->privateKey(); + return; + } else { + throw EnvoyException(fmt::format("Unknown static secret: {}", secret_name)); + } + } else { + secret_provider_ = + secret_manager_.createDynamicSecretProvider(secret_config.sds_config(), secret_name); + return; + } + } +} + unsigned ContextConfigImpl::tlsVersionFromProto( const envoy::api::v2::auth::TlsParameters_TlsProtocol& version, unsigned default_version) { switch (version) { @@ -138,6 +135,20 @@ unsigned ContextConfigImpl::tlsVersionFromProto( NOT_REACHED; } +const std::string& ContextConfigImpl::certChain() const { + if (secret_provider_ && secret_provider_->secret()) { + return secret_provider_->secret()->certificateChain(); + } + return cert_chain_; +} + +const std::string& ContextConfigImpl::privateKey() const { + if (secret_provider_ && secret_provider_->secret()) { + return secret_provider_->secret()->privateKey(); + } + return private_key_; +} + ClientContextConfigImpl::ClientContextConfigImpl( const envoy::api::v2::auth::UpstreamTlsContext& config, Secret::SecretManager& secret_manager) : ContextConfigImpl(config.common_tls_context(), secret_manager), diff --git a/source/common/ssl/context_config_impl.h b/source/common/ssl/context_config_impl.h index 2628f39b2e00c..3fa039fb74d34 100644 --- a/source/common/ssl/context_config_impl.h +++ b/source/common/ssl/context_config_impl.h @@ -33,11 +33,11 @@ class ContextConfigImpl : public virtual Ssl::ContextConfig { ? INLINE_STRING : certificate_revocation_list_path_; } - const std::string& certChain() const override { return cert_chain_; } + const std::string& certChain() const override; const std::string& certChainPath() const override { return (cert_chain_path_.empty() && !cert_chain_.empty()) ? INLINE_STRING : cert_chain_path_; } - const std::string& privateKey() const override { return private_key_; } + const std::string& privateKey() const override; const std::string& privateKeyPath() const override { return (private_key_path_.empty() && !private_key_.empty()) ? INLINE_STRING : private_key_path_; } @@ -54,6 +54,15 @@ class ContextConfigImpl : public virtual Ssl::ContextConfig { unsigned minProtocolVersion() const override { return min_protocol_version_; }; unsigned maxProtocolVersion() const override { return max_protocol_version_; }; + bool isValid() const override { + // either secret_provider_ is nullptr or secret_provider_->secret() is NOT nullptr. + return !secret_provider_ || secret_provider_->secret(); + } + + Secret::DynamicSecretProvider* getDynamicSecretProvider() const override { + return secret_provider_.get(); + } + protected: ContextConfigImpl(const envoy::api::v2::auth::CommonTlsContext& config, Secret::SecretManager& secret_manager); @@ -62,10 +71,12 @@ class ContextConfigImpl : public virtual Ssl::ContextConfig { static unsigned tlsVersionFromProto(const envoy::api::v2::auth::TlsParameters_TlsProtocol& version, unsigned default_version); + void readCertificates(const envoy::api::v2::auth::CommonTlsContext& config); static const std::string DEFAULT_CIPHER_SUITES; static const std::string DEFAULT_ECDH_CURVES; + Secret::SecretManager& secret_manager_; const std::string alpn_protocols_; const std::string alt_alpn_protocols_; const std::string cipher_suites_; @@ -74,9 +85,7 @@ class ContextConfigImpl : public virtual Ssl::ContextConfig { const std::string ca_cert_path_; const std::string certificate_revocation_list_; const std::string certificate_revocation_list_path_; - const std::string cert_chain_; const std::string cert_chain_path_; - const std::string private_key_; const std::string private_key_path_; const std::vector verify_subject_alt_name_list_; const std::vector verify_certificate_hash_list_; @@ -84,6 +93,10 @@ class ContextConfigImpl : public virtual Ssl::ContextConfig { const bool allow_expired_certificate_; const unsigned min_protocol_version_; const unsigned max_protocol_version_; + + Secret::DynamicSecretProviderSharedPtr secret_provider_; + std::string cert_chain_; + std::string private_key_; }; class ClientContextConfigImpl : public ContextConfigImpl, public ClientContextConfig { diff --git a/source/common/ssl/context_manager_impl.cc b/source/common/ssl/context_manager_impl.cc index 1c54c2f018656..7be3dbe4f2f63 100644 --- a/source/common/ssl/context_manager_impl.cc +++ b/source/common/ssl/context_manager_impl.cc @@ -20,18 +20,27 @@ void ContextManagerImpl::releaseContext(Context* context) { contexts_.remove(context); } -ClientContextPtr ContextManagerImpl::createSslClientContext(Stats::Scope& scope, - const ClientContextConfig& config) { - ClientContextPtr context(new ClientContextImpl(*this, scope, config)); +ClientContextSharedPtr +ContextManagerImpl::createSslClientContext(Stats::Scope& scope, const ClientContextConfig& config) { + if (!config.isValid()) { + return nullptr; + } + + ClientContextSharedPtr context(new ClientContextImpl(*this, scope, config)); std::unique_lock lock(contexts_lock_); contexts_.emplace_back(context.get()); return context; } -ServerContextPtr +ServerContextSharedPtr ContextManagerImpl::createSslServerContext(Stats::Scope& scope, const ServerContextConfig& config, const std::vector& server_names) { - ServerContextPtr context(new ServerContextImpl(*this, scope, config, server_names, runtime_)); + if (!config.isValid()) { + return nullptr; + } + + ServerContextSharedPtr context( + new ServerContextImpl(*this, scope, config, server_names, runtime_)); std::unique_lock lock(contexts_lock_); contexts_.emplace_back(context.get()); return context; diff --git a/source/common/ssl/context_manager_impl.h b/source/common/ssl/context_manager_impl.h index bd31db4f22008..d5f96cd47ac38 100644 --- a/source/common/ssl/context_manager_impl.h +++ b/source/common/ssl/context_manager_impl.h @@ -27,12 +27,12 @@ class ContextManagerImpl final : public ContextManager { * admin purposes. When a caller frees a context it will tell us to release it also from the list * of contexts. */ - void releaseContext(Context* context); + void releaseContext(Context* context) override; // Ssl::ContextManager - Ssl::ClientContextPtr createSslClientContext(Stats::Scope& scope, - const ClientContextConfig& config) override; - Ssl::ServerContextPtr + Ssl::ClientContextSharedPtr createSslClientContext(Stats::Scope& scope, + const ClientContextConfig& config) override; + Ssl::ServerContextSharedPtr createSslServerContext(Stats::Scope& scope, const ServerContextConfig& config, const std::vector& server_names) override; size_t daysUntilFirstCertExpires() const override; diff --git a/source/common/ssl/ssl_socket.cc b/source/common/ssl/ssl_socket.cc index b433358f16532..8f877fed168ff 100644 --- a/source/common/ssl/ssl_socket.cc +++ b/source/common/ssl/ssl_socket.cc @@ -14,8 +14,8 @@ using Envoy::Network::PostIoAction; namespace Envoy { namespace Ssl { -SslSocket::SslSocket(Context& ctx, InitialState state) - : ctx_(dynamic_cast(ctx)), ssl_(ctx_.newSsl()) { +SslSocket::SslSocket(ContextSharedPtr ctx, InitialState state) + : ctx_owner_(ctx), ctx_(dynamic_cast(*ctx)), ssl_(ctx_.newSsl()) { if (state == InitialState::Client) { SSL_set_connect_state(ssl_.get()); } else { @@ -373,28 +373,67 @@ std::string SslSocket::subjectLocalCertificate() const { return getSubjectFromCertificate(cert); } -ClientSslSocketFactory::ClientSslSocketFactory(const ClientContextConfig& config, +ClientSslSocketFactory::ClientSslSocketFactory(ClientContextConfigPtr config, Ssl::ContextManager& manager, Stats::Scope& stats_scope) - : ssl_ctx_(manager.createSslClientContext(stats_scope, config)) {} + : manager_(manager), stats_scope_(stats_scope), config_(std::move(config)), + ssl_ctx_(manager.createSslClientContext(stats_scope, *config)) { + if (config_->getDynamicSecretProvider()) { + config_->getDynamicSecretProvider()->addUpdateCallback(*this); + } +} + +ClientSslSocketFactory::~ClientSslSocketFactory() { + if (config_->getDynamicSecretProvider()) { + config_->getDynamicSecretProvider()->removeUpdateCallback(*this); + } +} Network::TransportSocketPtr ClientSslSocketFactory::createTransportSocket() const { - return std::make_unique(*ssl_ctx_, Ssl::InitialState::Client); + return std::make_unique(ssl_ctx_, Ssl::InitialState::Client); } bool ClientSslSocketFactory::implementsSecureTransport() const { return true; } -ServerSslSocketFactory::ServerSslSocketFactory(const ServerContextConfig& config, +void ClientSslSocketFactory::onAddOrUpdateSecret() { + if (ssl_ctx_) { + ENVOY_LOG(debug, "listener socket updated"); + manager_.releaseContext(ssl_ctx_.get()); + } + ssl_ctx_ = manager_.createSslClientContext(stats_scope_, *config_); +} + +ServerSslSocketFactory::ServerSslSocketFactory(ServerContextConfigPtr config, Ssl::ContextManager& manager, Stats::Scope& stats_scope, const std::vector& server_names) - : ssl_ctx_(manager.createSslServerContext(stats_scope, config, server_names)) {} + : manager_(manager), stats_scope_(stats_scope), config_(std::move(config)), + ssl_ctx_(manager.createSslServerContext(stats_scope, *config, server_names)), + server_names_(server_names) { + if (config_->getDynamicSecretProvider()) { + config_->getDynamicSecretProvider()->addUpdateCallback(*this); + } +} + +ServerSslSocketFactory::~ServerSslSocketFactory() { + if (config_->getDynamicSecretProvider()) { + config_->getDynamicSecretProvider()->removeUpdateCallback(*this); + } +} Network::TransportSocketPtr ServerSslSocketFactory::createTransportSocket() const { - return std::make_unique(*ssl_ctx_, Ssl::InitialState::Server); + return std::make_unique(ssl_ctx_, Ssl::InitialState::Server); } bool ServerSslSocketFactory::implementsSecureTransport() const { return true; } +void ServerSslSocketFactory::onAddOrUpdateSecret() { + if (ssl_ctx_) { + ENVOY_LOG(debug, "listener socket updated"); + manager_.releaseContext(ssl_ctx_.get()); + } + ssl_ctx_ = manager_.createSslServerContext(stats_scope_, *config_, server_names_); +} + } // namespace Ssl } // namespace Envoy diff --git a/source/common/ssl/ssl_socket.h b/source/common/ssl/ssl_socket.h index 6bb040edcd7ef..80fa1d9a08451 100644 --- a/source/common/ssl/ssl_socket.h +++ b/source/common/ssl/ssl_socket.h @@ -5,6 +5,7 @@ #include "envoy/network/connection.h" #include "envoy/network/transport_socket.h" +#include "envoy/secret/secret_callbacks.h" #include "common/common/logger.h" #include "common/ssl/context_impl.h" @@ -20,7 +21,7 @@ class SslSocket : public Network::TransportSocket, public Connection, protected Logger::Loggable { public: - SslSocket(Context& ctx, InitialState state); + SslSocket(ContextSharedPtr ctx, InitialState state); // Ssl::Connection bool peerCertificatePresented() const override; @@ -55,6 +56,7 @@ class SslSocket : public Network::TransportSocket, std::vector getDnsSansFromCertificate(X509* cert); Network::TransportSocketCallbacks* callbacks_{}; + ContextSharedPtr ctx_owner_; ContextImpl& ctx_; bssl::UniquePtr ssl_; bool handshake_complete_{}; @@ -64,27 +66,52 @@ class SslSocket : public Network::TransportSocket, mutable std::string cached_url_encoded_pem_encoded_peer_certificate_; }; -class ClientSslSocketFactory : public Network::TransportSocketFactory { +class ClientSslSocketFactory : public Network::TransportSocketFactory, + public Secret::SecretCallbacks, + Logger::Loggable { public: - ClientSslSocketFactory(const ClientContextConfig& config, Ssl::ContextManager& manager, + ClientSslSocketFactory(ClientContextConfigPtr config, Ssl::ContextManager& manager, Stats::Scope& stats_scope); + virtual ~ClientSslSocketFactory(); + Network::TransportSocketPtr createTransportSocket() const override; bool implementsSecureTransport() const override; + // Secret::SecretCallbacks + void onAddOrUpdateSecret() override; + private: - const ClientContextPtr ssl_ctx_; + Ssl::ContextManager& manager_; + Stats::Scope& stats_scope_; + ClientContextConfigPtr config_; + ClientContextSharedPtr ssl_ctx_; }; -class ServerSslSocketFactory : public Network::TransportSocketFactory { +typedef std::unique_ptr ClientContextConfigPtr; + +class ServerSslSocketFactory : public Network::TransportSocketFactory, + public Secret::SecretCallbacks, + Logger::Loggable { public: - ServerSslSocketFactory(const ServerContextConfig& config, Ssl::ContextManager& manager, + ServerSslSocketFactory(ServerContextConfigPtr config, Ssl::ContextManager& manager, Stats::Scope& stats_scope, const std::vector& server_names); + virtual ~ServerSslSocketFactory(); + Network::TransportSocketPtr createTransportSocket() const override; bool implementsSecureTransport() const override; + // Secret::SecretCallbacks + void onAddOrUpdateSecret() override; + private: - const ServerContextPtr ssl_ctx_; + Ssl::ContextManager& manager_; + Stats::Scope& stats_scope_; + ServerContextConfigPtr config_; + ServerContextSharedPtr ssl_ctx_; + const std::vector server_names_; }; +typedef std::unique_ptr ServerContextConfigPtr; + } // namespace Ssl } // namespace Envoy diff --git a/source/common/ssl/tls_certificate_config_impl.cc b/source/common/ssl/tls_certificate_config_impl.cc index 4f0afeb49733f..dabe97a1e4fc6 100644 --- a/source/common/ssl/tls_certificate_config_impl.cc +++ b/source/common/ssl/tls_certificate_config_impl.cc @@ -1,5 +1,7 @@ #include "common/ssl/tls_certificate_config_impl.h" +#include + #include "envoy/common/exception.h" #include "common/config/datasource.h" diff --git a/source/common/upstream/upstream_impl.cc b/source/common/upstream/upstream_impl.cc index ab14e685f0b23..c4f49fb85b06b 100644 --- a/source/common/upstream/upstream_impl.cc +++ b/source/common/upstream/upstream_impl.cc @@ -141,9 +141,13 @@ HostImpl::createConnection(Event::Dispatcher& dispatcher, const ClusterInfo& clu connection_options = options; } + auto transport_socket = cluster.transportSocketFactory().createTransportSocket(); + if (!transport_socket) { + return nullptr; + } + Network::ClientConnectionPtr connection = dispatcher.createClientConnection( - address, cluster.sourceAddress(), cluster.transportSocketFactory().createTransportSocket(), - connection_options); + address, cluster.sourceAddress(), std::move(transport_socket), connection_options); connection->setBufferLimits(cluster.perConnectionBufferLimitBytes()); return connection; } diff --git a/source/extensions/transport_sockets/ssl/config.cc b/source/extensions/transport_sockets/ssl/config.cc index c0c0feec1970a..d665eb45f20db 100644 --- a/source/extensions/transport_sockets/ssl/config.cc +++ b/source/extensions/transport_sockets/ssl/config.cc @@ -16,12 +16,14 @@ namespace SslTransport { Network::TransportSocketFactoryPtr UpstreamSslSocketFactory::createTransportSocketFactory( const Protobuf::Message& message, Server::Configuration::TransportSocketFactoryContext& context) { - return std::make_unique( - Ssl::ClientContextConfigImpl( + std::unique_ptr upstream_config = + std::make_unique( MessageUtil::downcastAndValidate( message), - context.secretManager()), - context.sslContextManager(), context.statsScope()); + context.secretManager()); + + return std::make_unique( + std::move(upstream_config), context.sslContextManager(), context.statsScope()); } ProtobufTypes::MessagePtr UpstreamSslSocketFactory::createEmptyConfigProto() { @@ -35,12 +37,15 @@ static Registry::RegisterFactory& server_names) { - return std::make_unique( - Ssl::ServerContextConfigImpl( + std::unique_ptr downstream_config = + std::make_unique( MessageUtil::downcastAndValidate( message), - context.secretManager()), - context.sslContextManager(), context.statsScope(), server_names); + context.secretManager()); + + return std::make_unique(std::move(downstream_config), + context.sslContextManager(), + context.statsScope(), server_names); } ProtobufTypes::MessagePtr DownstreamSslSocketFactory::createEmptyConfigProto() { diff --git a/source/server/config_validation/server.cc b/source/server/config_validation/server.cc index 8dc49dc87edcd..8b2ea04633788 100644 --- a/source/server/config_validation/server.cc +++ b/source/server/config_validation/server.cc @@ -79,7 +79,7 @@ void ValidationInstance::initialize(Options& options, Configuration::InitialImpl initial_config(bootstrap); thread_local_.registerThread(*dispatcher_, true); runtime_loader_ = component_factory.createRuntime(*this, initial_config); - secret_manager_.reset(new Secret::SecretManagerImpl()); + secret_manager_.reset(new Secret::SecretManagerImpl(*this)); ssl_context_manager_.reset(new Ssl::ContextManagerImpl(*runtime_loader_)); cluster_manager_factory_.reset(new Upstream::ValidationClusterManagerFactory( runtime(), stats(), threadLocal(), random(), dnsResolver(), sslContextManager(), dispatcher(), diff --git a/source/server/configuration_impl.cc b/source/server/configuration_impl.cc index 0746d48d9d6d0..7f80a2c33f0bb 100644 --- a/source/server/configuration_impl.cc +++ b/source/server/configuration_impl.cc @@ -13,6 +13,7 @@ #include "envoy/ssl/context_manager.h" #include "common/common/assert.h" +#include "common/common/empty_string.h" #include "common/common/utility.h" #include "common/config/lds_json.h" #include "common/config/utility.h" @@ -50,7 +51,7 @@ void MainImpl::initialize(const envoy::config::bootstrap::v2::Bootstrap& bootstr ENVOY_LOG(info, "loading {} static secret(s)", secrets.size()); for (ssize_t i = 0; i < secrets.size(); i++) { ENVOY_LOG(debug, "static secret #{}: {}", i, secrets[i].name()); - server.secretManager().addOrUpdateSecret(secrets[i]); + server.secretManager().addStaticSecret(secrets[i]); } cluster_manager_ = cluster_manager_factory.clusterManagerFromProto( diff --git a/source/server/connection_handler_impl.cc b/source/server/connection_handler_impl.cc index 854180cd4914d..e2dd241c35fcc 100644 --- a/source/server/connection_handler_impl.cc +++ b/source/server/connection_handler_impl.cc @@ -196,6 +196,14 @@ void ConnectionHandlerImpl::ActiveListener::newConnection(Network::ConnectionSoc } auto transport_socket = filter_chain->transportSocketFactory().createTransportSocket(); + if (!transport_socket) { + ENVOY_LOG_TO_LOGGER(parent_.logger_, debug, + "closing connection: transport socket was not created yet"); + // TODO(jaebong) update stats + socket->close(); + return; + } + Network::ConnectionPtr new_connection = parent_.dispatcher_.createServerConnection(std::move(socket), std::move(transport_socket)); new_connection->setBufferLimits(config_.perConnectionBufferLimitBytes()); diff --git a/source/server/server.cc b/source/server/server.cc index 7c682ceed809f..bca746bb8c7f2 100644 --- a/source/server/server.cc +++ b/source/server/server.cc @@ -55,7 +55,7 @@ InstanceImpl::InstanceImpl(Options& options, Network::Address::InstanceConstShar handler_(new ConnectionHandlerImpl(ENVOY_LOGGER(), *dispatcher_)), random_generator_(std::move(random_generator)), listener_component_factory_(*this), worker_factory_(thread_local_, *api_, hooks), - secret_manager_(new Secret::SecretManagerImpl()), + secret_manager_(new Secret::SecretManagerImpl(*this)), dns_resolver_(dispatcher_->createDnsResolver({})), access_log_manager_(*api_, *dispatcher_, access_log_lock, store), terminated_(false) { @@ -247,6 +247,9 @@ void InstanceImpl::initialize(Options& options, loadServerFlags(initial_config.flagsPath()); + // Shared storage of secrets from SDS + secret_manager_.reset(new Secret::SecretManagerImpl(*this)); + // Workers get created first so they register for thread local updates. listener_manager_.reset(new ListenerManagerImpl( *this, listener_component_factory_, worker_factory_, ProdSystemTimeSource::instance_));