diff --git a/api/envoy/api/v2/auth/cert.proto b/api/envoy/api/v2/auth/cert.proto index 880453e7e8a71..dca4c698895fa 100644 --- a/api/envoy/api/v2/auth/cert.proto +++ b/api/envoy/api/v2/auth/cert.proto @@ -232,14 +232,14 @@ message CommonTlsContext { // relaxed in the future. repeated TlsCertificate tls_certificates = 2 [(validate.rules).repeated .max_items = 1]; - // [#not-implemented-hide:] + // Configs for fetching TLS certificates via SDS API. repeated SdsSecretConfig tls_certificate_sds_secret_configs = 6; oneof validation_context_type { // How to validate peer certificates. CertificateValidationContext validation_context = 3; - // [#not-implemented-hide:] + // Config for fetching validation context via SDS API. SdsSecretConfig validation_context_sds_secret_config = 7; } @@ -302,7 +302,6 @@ message DownstreamTlsContext { } // [#proto-status: experimental] -// [#not-implemented-hide:] message SdsSecretConfig { // Name (FQDN, UUID, SPKI, SHA256, etc.) by which the secret can be uniquely referred to. // When both name and config are specified, then secret can be fetched and/or reloaded via SDS. @@ -312,7 +311,6 @@ message SdsSecretConfig { } // [#proto-status: experimental] -// [#not-implemented-hide:] message Secret { // Name (FQDN, UUID, SPKI, SHA256, etc.) by which the secret can be uniquely referred to. string name = 1; diff --git a/include/envoy/secret/secret_manager.h b/include/envoy/secret/secret_manager.h index ca02d639643ab..93205243f046e 100644 --- a/include/envoy/secret/secret_manager.h +++ b/include/envoy/secret/secret_manager.h @@ -75,6 +75,22 @@ class SecretManager { virtual TlsCertificateConfigProviderSharedPtr findOrCreateTlsCertificateProvider( 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 CertificateValidationContextConfigProviderSharedPtr the dynamic certificate validation + * context secret provider. + */ + virtual CertificateValidationContextConfigProviderSharedPtr + findOrCreateCertificateValidationContextProvider( + 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/source/common/secret/BUILD b/source/common/secret/BUILD index f45602e4c10c9..5a9f0f94ec7ce 100644 --- a/source/common/secret/BUILD +++ b/source/common/secret/BUILD @@ -46,12 +46,14 @@ envoy_cc_library( "//include/envoy/local_info:local_info_interface", "//include/envoy/runtime:runtime_interface", "//include/envoy/secret:secret_provider_interface", + "//include/envoy/server:transport_socket_config_interface", "//include/envoy/stats:stats_interface", "//source/common/common:callback_impl_lib", "//source/common/common:cleanup_lib", "//source/common/config:resources_lib", "//source/common/config:subscription_factory_lib", "//source/common/protobuf:utility_lib", + "//source/common/ssl:certificate_validation_context_config_impl_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 index 9f97a6857a247..3acbd12130a19 100644 --- a/source/common/secret/sds_api.cc +++ b/source/common/secret/sds_api.cc @@ -7,7 +7,6 @@ #include "common/config/resources.h" #include "common/config/subscription_factory.h" #include "common/protobuf/utility.h" -#include "common/ssl/tls_certificate_config_impl.h" namespace Envoy { namespace Secret { @@ -15,8 +14,8 @@ namespace Secret { SdsApi::SdsApi(const LocalInfo::LocalInfo& local_info, Event::Dispatcher& dispatcher, Runtime::RandomGenerator& random, Stats::Store& stats, Upstream::ClusterManager& cluster_manager, Init::Manager& init_manager, - const envoy::api::v2::core::ConfigSource& sds_config, std::string sds_config_name, - std::function destructor_cb) + const envoy::api::v2::core::ConfigSource& sds_config, + const std::string& sds_config_name, std::function destructor_cb) : local_info_(local_info), dispatcher_(dispatcher), random_(random), stats_(stats), cluster_manager_(cluster_manager), sds_config_(sds_config), sds_config_name_(sds_config_name), secret_hash_(0), clean_up_(destructor_cb) { @@ -60,12 +59,9 @@ void SdsApi::onConfigUpdate(const ResourceVector& resources, const std::string&) } const uint64_t new_hash = MessageUtil::hash(secret); - if (new_hash != secret_hash_ && - secret.type_case() == envoy::api::v2::auth::Secret::TypeCase::kTlsCertificate) { + if (new_hash != secret_hash_) { secret_hash_ = new_hash; - tls_certificate_secrets_ = - std::make_unique(secret.tls_certificate()); - + setSecret(secret); update_callback_manager_.runCallbacks(); } diff --git a/source/common/secret/sds_api.h b/source/common/secret/sds_api.h index f9211e3832b28..f6fe87ae0cf90 100644 --- a/source/common/secret/sds_api.h +++ b/source/common/secret/sds_api.h @@ -11,11 +11,14 @@ #include "envoy/runtime/runtime.h" #include "envoy/secret/secret_callbacks.h" #include "envoy/secret/secret_provider.h" +#include "envoy/server/transport_socket_config.h" #include "envoy/stats/stats.h" #include "envoy/upstream/cluster_manager.h" #include "common/common/callback_impl.h" #include "common/common/cleanup.h" +#include "common/ssl/certificate_validation_context_config_impl.h" +#include "common/ssl/tls_certificate_config_impl.h" namespace Envoy { namespace Secret { @@ -24,13 +27,12 @@ namespace Secret { * SDS API implementation that fetches secrets from SDS server via Subscription. */ class SdsApi : public Init::Target, - public TlsCertificateConfigProvider, public Config::SubscriptionCallbacks { public: SdsApi(const LocalInfo::LocalInfo& local_info, Event::Dispatcher& dispatcher, Runtime::RandomGenerator& random, Stats::Store& stats, Upstream::ClusterManager& cluster_manager, Init::Manager& init_manager, - const envoy::api::v2::core::ConfigSource& sds_config, std::string sds_config_name, + const envoy::api::v2::core::ConfigSource& sds_config, const std::string& sds_config_name, std::function destructor_cb); // Init::Target @@ -43,14 +45,10 @@ class SdsApi : public Init::Target, return MessageUtil::anyConvert(resource).name(); } - // SecretProvider - const Ssl::TlsCertificateConfig* secret() const override { - return tls_certificate_secrets_.get(); - } - - Common::CallbackHandle* addUpdateCallback(std::function callback) override { - return update_callback_manager_.add(callback); - } +protected: + // Creates new secrets. + virtual void setSecret(const envoy::api::v2::auth::Secret&) PURE; + Common::CallbackManager<> update_callback_manager_; private: void runInitializeCallbackIfAny(); @@ -68,11 +66,97 @@ class SdsApi : public Init::Target, uint64_t secret_hash_; Cleanup clean_up_; +}; + +typedef std::shared_ptr SdsApiSharedPtr; + +/** + * TlsCertificateSdsApi implementation maintains and updates dynamic TLS certificate secrets. + */ +class TlsCertificateSdsApi : public SdsApi, public TlsCertificateConfigProvider { +public: + static SdsApiSharedPtr + 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) { + return std::make_shared( + secret_provider_context.localInfo(), secret_provider_context.dispatcher(), + secret_provider_context.random(), secret_provider_context.stats(), + secret_provider_context.clusterManager(), *secret_provider_context.initManager(), + sds_config, sds_config_name, destructor_cb); + } + + TlsCertificateSdsApi(const LocalInfo::LocalInfo& local_info, Event::Dispatcher& dispatcher, + Runtime::RandomGenerator& random, Stats::Store& stats, + Upstream::ClusterManager& cluster_manager, Init::Manager& init_manager, + const envoy::api::v2::core::ConfigSource& sds_config, + const std::string& sds_config_name, std::function destructor_cb) + : SdsApi(local_info, dispatcher, random, stats, cluster_manager, init_manager, sds_config, + sds_config_name, destructor_cb) {} + + // SecretProvider + const Ssl::TlsCertificateConfig* secret() const override { + return tls_certificate_secrets_.get(); + } + Common::CallbackHandle* addUpdateCallback(std::function callback) override { + return update_callback_manager_.add(callback); + } + +protected: + void setSecret(const envoy::api::v2::auth::Secret& secret) override { + tls_certificate_secrets_ = + std::make_unique(secret.tls_certificate()); + } + +private: Ssl::TlsCertificateConfigPtr tls_certificate_secrets_; - Common::CallbackManager<> update_callback_manager_; }; -typedef std::unique_ptr SdsApiPtr; +/** + * CertificateValidationContextSdsApi implementation maintains and updates dynamic certificate + * validation context secrets. + */ +class CertificateValidationContextSdsApi : public SdsApi, + public CertificateValidationContextConfigProvider { +public: + static SdsApiSharedPtr + 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) { + return std::make_shared( + secret_provider_context.localInfo(), secret_provider_context.dispatcher(), + secret_provider_context.random(), secret_provider_context.stats(), + secret_provider_context.clusterManager(), *secret_provider_context.initManager(), + sds_config, sds_config_name, destructor_cb); + } + CertificateValidationContextSdsApi(const LocalInfo::LocalInfo& local_info, + Event::Dispatcher& dispatcher, + Runtime::RandomGenerator& random, Stats::Store& stats, + Upstream::ClusterManager& cluster_manager, + Init::Manager& init_manager, + const envoy::api::v2::core::ConfigSource& sds_config, + std::string sds_config_name, + std::function destructor_cb) + : SdsApi(local_info, dispatcher, random, stats, cluster_manager, init_manager, sds_config, + sds_config_name, destructor_cb) {} + + // SecretProvider + const Ssl::CertificateValidationContextConfig* secret() const override { + return certificate_validation_context_secrets_.get(); + } + Common::CallbackHandle* addUpdateCallback(std::function callback) override { + return update_callback_manager_.add(callback); + } + +protected: + void setSecret(const envoy::api::v2::auth::Secret& secret) override { + certificate_validation_context_secrets_ = + std::make_unique(secret.validation_context()); + } + +private: + Ssl::CertificateValidationContextConfigPtr certificate_validation_context_secrets_; +}; } // namespace Secret } // namespace Envoy diff --git a/source/common/secret/secret_manager_impl.cc b/source/common/secret/secret_manager_impl.cc index a310d59777ac9..a8a95a75e20b6 100644 --- a/source/common/secret/secret_manager_impl.cc +++ b/source/common/secret/secret_manager_impl.cc @@ -71,31 +71,53 @@ void SecretManagerImpl::removeDynamicSecretProvider(const std::string& map_key) ASSERT(num_deleted == 1, ""); } -TlsCertificateConfigProviderSharedPtr SecretManagerImpl::findOrCreateTlsCertificateProvider( +SdsApiSharedPtr SecretManagerImpl::findOrCreate( const envoy::api::v2::core::ConfigSource& sds_config_source, const std::string& config_name, - Server::Configuration::TransportSocketFactoryContext& secret_provider_context) { + std::function unregister_secret_provider)> create_fn) { const std::string map_key = sds_config_source.SerializeAsString() + config_name; - TlsCertificateConfigProviderSharedPtr secret_provider = dynamic_secret_providers_[map_key].lock(); + SdsApiSharedPtr secret_provider = dynamic_secret_providers_[map_key].lock(); if (!secret_provider) { - ASSERT(secret_provider_context.initManager() != nullptr); - // SdsApi is owned by ListenerImpl and ClusterInfo which are destroyed before // SecretManagerImpl. It is safe to invoke this callback at the destructor of SdsApi. std::function unregister_secret_provider = [map_key, this]() { removeDynamicSecretProvider(map_key); }; - secret_provider = std::make_shared( - secret_provider_context.localInfo(), secret_provider_context.dispatcher(), - secret_provider_context.random(), secret_provider_context.stats(), - secret_provider_context.clusterManager(), *secret_provider_context.initManager(), - sds_config_source, config_name, unregister_secret_provider); + secret_provider = create_fn(unregister_secret_provider); dynamic_secret_providers_[map_key] = secret_provider; } - return secret_provider; } +TlsCertificateConfigProviderSharedPtr SecretManagerImpl::findOrCreateTlsCertificateProvider( + const envoy::api::v2::core::ConfigSource& sds_config_source, const std::string& config_name, + Server::Configuration::TransportSocketFactoryContext& secret_provider_context) { + auto create_fn = [&secret_provider_context, &sds_config_source, &config_name]( + std::function unregister_secret_provider) -> SdsApiSharedPtr { + ASSERT(secret_provider_context.initManager() != nullptr); + return TlsCertificateSdsApi::create(secret_provider_context, sds_config_source, config_name, + unregister_secret_provider); + }; + SdsApiSharedPtr secret_provider = findOrCreate(sds_config_source, config_name, create_fn); + + return std::dynamic_pointer_cast(secret_provider); +} + +CertificateValidationContextConfigProviderSharedPtr +SecretManagerImpl::findOrCreateCertificateValidationContextProvider( + const envoy::api::v2::core::ConfigSource& sds_config_source, const std::string& config_name, + Server::Configuration::TransportSocketFactoryContext& secret_provider_context) { + auto create_fn = [&secret_provider_context, &sds_config_source, &config_name]( + std::function unregister_secret_provider) -> SdsApiSharedPtr { + ASSERT(secret_provider_context.initManager() != nullptr); + return CertificateValidationContextSdsApi::create(secret_provider_context, sds_config_source, + config_name, unregister_secret_provider); + }; + SdsApiSharedPtr secret_provider = findOrCreate(sds_config_source, config_name, create_fn); + + return std::dynamic_pointer_cast(secret_provider); +} + } // namespace Secret } // namespace Envoy diff --git a/source/common/secret/secret_manager_impl.h b/source/common/secret/secret_manager_impl.h index a6017ff8719c3..85c89cf861d8b 100644 --- a/source/common/secret/secret_manager_impl.h +++ b/source/common/secret/secret_manager_impl.h @@ -9,6 +9,7 @@ #include "envoy/ssl/tls_certificate_config.h" #include "common/common/logger.h" +#include "common/secret/sds_api.h" namespace Envoy { namespace Secret { @@ -35,9 +36,18 @@ class SecretManagerImpl : public SecretManager, Logger::Loggable unregister_secret_provider)> create_fn); // Manages pairs of secret name and TlsCertificateConfigProviderSharedPtr. std::unordered_map @@ -48,8 +58,7 @@ class SecretManagerImpl : public SecretManager, Logger::Loggable> - dynamic_secret_providers_; + std::unordered_map> dynamic_secret_providers_; }; } // namespace Secret diff --git a/source/common/ssl/certificate_validation_context_config_impl.cc b/source/common/ssl/certificate_validation_context_config_impl.cc index 8a79d224f046b..febdfbc964199 100644 --- a/source/common/ssl/certificate_validation_context_config_impl.cc +++ b/source/common/ssl/certificate_validation_context_config_impl.cc @@ -44,4 +44,4 @@ CertificateValidationContextConfigImpl::CertificateValidationContextConfigImpl( } } // namespace Ssl -} // namespace Envoy \ No newline at end of file +} // namespace Envoy diff --git a/source/common/ssl/context_config_impl.cc b/source/common/ssl/context_config_impl.cc index ea3d17ed38b56..8c80ecdb426c7 100644 --- a/source/common/ssl/context_config_impl.cc +++ b/source/common/ssl/context_config_impl.cc @@ -65,6 +65,9 @@ getCertificateValidationContextConfigProvider( sds_secret_config.name())); } return secret_provider; + } else { + return factory_context.secretManager().findOrCreateCertificateValidationContextProvider( + sds_secret_config.sds_config(), sds_secret_config.name(), factory_context); } } return nullptr; @@ -98,7 +101,6 @@ ContextConfigImpl::ContextConfigImpl( ecdh_curves_(StringUtil::nonEmptyStringOrDefault( RepeatedPtrUtil::join(config.tls_params().ecdh_curves(), ":"), DEFAULT_ECDH_CURVES)), tls_certficate_provider_(getTlsCertificateConfigProvider(config, factory_context)), - secret_update_callback_handle_(nullptr), certficate_validation_context_provider_( getCertificateValidationContextConfigProvider(config, factory_context)), min_protocol_version_( @@ -107,8 +109,11 @@ ContextConfigImpl::ContextConfigImpl( TLS1_2_VERSION)) {} ContextConfigImpl::~ContextConfigImpl() { - if (secret_update_callback_handle_) { - secret_update_callback_handle_->remove(); + if (tc_update_callback_handle_) { + tc_update_callback_handle_->remove(); + } + if (cvc_update_callback_handle_) { + cvc_update_callback_handle_->remove(); } } diff --git a/source/common/ssl/context_config_impl.h b/source/common/ssl/context_config_impl.h index 7e6443be9a090..7304108ff3aac 100644 --- a/source/common/ssl/context_config_impl.h +++ b/source/common/ssl/context_config_impl.h @@ -39,16 +39,27 @@ class ContextConfigImpl : public virtual Ssl::ContextConfig { bool isReady() const override { // Either tls_certficate_provider_ is nullptr or - // tls_certficate_provider_->secret() is NOT nullptr. - return !tls_certficate_provider_ || tls_certficate_provider_->secret() != nullptr; + // tls_certficate_provider_->secret() is NOT nullptr and + // either certficate_validation_context_provider_ is nullptr or + // certficate_validation_context_provider_->secret() is NOT nullptr. + return (!tls_certficate_provider_ || tls_certficate_provider_->secret() != nullptr) && + (!certficate_validation_context_provider_ || + certficate_validation_context_provider_->secret() != nullptr); } void setSecretUpdateCallback(std::function callback) override { if (tls_certficate_provider_) { - if (secret_update_callback_handle_) { - secret_update_callback_handle_->remove(); + if (tc_update_callback_handle_) { + tc_update_callback_handle_->remove(); } - secret_update_callback_handle_ = tls_certficate_provider_->addUpdateCallback(callback); + tc_update_callback_handle_ = tls_certficate_provider_->addUpdateCallback(callback); + } + if (certficate_validation_context_provider_) { + if (cvc_update_callback_handle_) { + cvc_update_callback_handle_->remove(); + } + cvc_update_callback_handle_ = + certficate_validation_context_provider_->addUpdateCallback(callback); } } @@ -69,9 +80,12 @@ class ContextConfigImpl : public virtual Ssl::ContextConfig { const std::string cipher_suites_; const std::string ecdh_curves_; Secret::TlsCertificateConfigProviderSharedPtr tls_certficate_provider_; - Common::CallbackHandle* secret_update_callback_handle_; + // Handle for TLS certificate dyanmic secret callback. + Common::CallbackHandle* tc_update_callback_handle_{}; Secret::CertificateValidationContextConfigProviderSharedPtr certficate_validation_context_provider_; + // Handle for certificate validation context dyanmic secret callback. + Common::CallbackHandle* cvc_update_callback_handle_{}; const unsigned min_protocol_version_; const unsigned max_protocol_version_; }; diff --git a/test/common/secret/sds_api_test.cc b/test/common/secret/sds_api_test.cc index 17435fe5c8d80..7a4432e4ff744 100644 --- a/test/common/secret/sds_api_test.cc +++ b/test/common/secret/sds_api_test.cc @@ -41,8 +41,9 @@ TEST_F(SdsApiTest, BasicTest) { auto google_grpc = grpc_service->mutable_google_grpc(); google_grpc->set_target_uri("fake_address"); google_grpc->set_stat_prefix("test"); - SdsApi sds_api(server.localInfo(), server.dispatcher(), server.random(), server.stats(), - server.clusterManager(), init_manager, config_source, "abc.com", []() {}); + TlsCertificateSdsApi sds_api(server.localInfo(), server.dispatcher(), server.random(), + server.stats(), server.clusterManager(), init_manager, config_source, + "abc.com", []() {}); NiceMock* grpc_client{new NiceMock()}; NiceMock* factory{new NiceMock()}; @@ -57,13 +58,15 @@ TEST_F(SdsApiTest, BasicTest) { init_manager.initialize(); } -// Validate that SdsApi updates secrets successfully if a good secret is passed to onConfigUpdate(). -TEST_F(SdsApiTest, SecretUpdateSuccess) { +// Validate that TlsCertificateSdsApi updates secrets successfully if a good secret +// is passed to onConfigUpdate(). +TEST_F(SdsApiTest, DynamicTlsCertificateUpdateSuccess) { NiceMock server; NiceMock init_manager; envoy::api::v2::core::ConfigSource config_source; - SdsApi sds_api(server.localInfo(), server.dispatcher(), server.random(), server.stats(), - server.clusterManager(), init_manager, config_source, "abc.com", []() {}); + TlsCertificateSdsApi sds_api(server.localInfo(), server.dispatcher(), server.random(), + server.stats(), server.clusterManager(), init_manager, config_source, + "abc.com", []() {}); NiceMock secret_callback; auto handle = @@ -96,13 +99,49 @@ TEST_F(SdsApiTest, SecretUpdateSuccess) { handle->remove(); } +// Validate that CertificateValidationContextSdsApi updates secrets successfully if +// a good secret is passed to onConfigUpdate(). +TEST_F(SdsApiTest, DynamicCertificateValidationContextUpdateSuccess) { + NiceMock server; + NiceMock init_manager; + envoy::api::v2::core::ConfigSource config_source; + CertificateValidationContextSdsApi sds_api( + server.localInfo(), server.dispatcher(), server.random(), server.stats(), + server.clusterManager(), init_manager, config_source, "abc.com", []() {}); + + NiceMock secret_callback; + auto handle = + sds_api.addUpdateCallback([&secret_callback]() { secret_callback.onAddOrUpdateSecret(); }); + + std::string yaml = + R"EOF( + name: "abc.com" + validation_context: + trusted_ca: { filename: "{{ test_rundir }}/test/common/ssl/test_data/ca_cert.pem" } + allow_expired_certificate: true + )EOF"; + + Protobuf::RepeatedPtrField secret_resources; + auto secret_config = secret_resources.Add(); + MessageUtil::loadFromYaml(TestEnvironment::substitute(yaml), *secret_config); + EXPECT_CALL(secret_callback, onAddOrUpdateSecret()); + sds_api.onConfigUpdate(secret_resources, ""); + + const std::string ca_cert = "{{ test_rundir }}/test/common/ssl/test_data/ca_cert.pem"; + EXPECT_EQ(TestEnvironment::readFileToStringForTest(TestEnvironment::substitute(ca_cert)), + sds_api.secret()->caCert()); + + handle->remove(); +} + // Validate that SdsApi throws exception if an empty secret is passed to onConfigUpdate(). TEST_F(SdsApiTest, EmptyResource) { NiceMock server; NiceMock init_manager; envoy::api::v2::core::ConfigSource config_source; - SdsApi sds_api(server.localInfo(), server.dispatcher(), server.random(), server.stats(), - server.clusterManager(), init_manager, config_source, "abc.com", []() {}); + TlsCertificateSdsApi sds_api(server.localInfo(), server.dispatcher(), server.random(), + server.stats(), server.clusterManager(), init_manager, config_source, + "abc.com", []() {}); Protobuf::RepeatedPtrField secret_resources; @@ -115,8 +154,9 @@ TEST_F(SdsApiTest, SecretUpdateWrongSize) { NiceMock server; NiceMock init_manager; envoy::api::v2::core::ConfigSource config_source; - SdsApi sds_api(server.localInfo(), server.dispatcher(), server.random(), server.stats(), - server.clusterManager(), init_manager, config_source, "abc.com", []() {}); + TlsCertificateSdsApi sds_api(server.localInfo(), server.dispatcher(), server.random(), + server.stats(), server.clusterManager(), init_manager, config_source, + "abc.com", []() {}); std::string yaml = R"EOF( @@ -144,8 +184,9 @@ TEST_F(SdsApiTest, SecretUpdateWrongSecretName) { NiceMock server; NiceMock init_manager; envoy::api::v2::core::ConfigSource config_source; - SdsApi sds_api(server.localInfo(), server.dispatcher(), server.random(), server.stats(), - server.clusterManager(), init_manager, config_source, "abc.com", []() {}); + TlsCertificateSdsApi sds_api(server.localInfo(), server.dispatcher(), server.random(), + server.stats(), server.clusterManager(), init_manager, config_source, + "abc.com", []() {}); std::string yaml = R"EOF( diff --git a/test/common/secret/secret_manager_impl_test.cc b/test/common/secret/secret_manager_impl_test.cc index c340e63342f47..4432d9e94e364 100644 --- a/test/common/secret/secret_manager_impl_test.cc +++ b/test/common/secret/secret_manager_impl_test.cc @@ -171,7 +171,7 @@ name: "abc.com" Protobuf::RepeatedPtrField secret_resources; auto secret_config = secret_resources.Add(); MessageUtil::loadFromYaml(TestEnvironment::substitute(yaml), *secret_config); - dynamic_cast(*secret_provider).onConfigUpdate(secret_resources, ""); + dynamic_cast(*secret_provider).onConfigUpdate(secret_resources, ""); const std::string cert_pem = "{{ test_rundir }}/test/common/ssl/test_data/selfsigned_cert.pem"; EXPECT_EQ(TestEnvironment::readFileToStringForTest(TestEnvironment::substitute(cert_pem)), secret_provider->secret()->certificateChain()); diff --git a/test/common/ssl/context_impl_test.cc b/test/common/ssl/context_impl_test.cc index 62bc116f23e40..ef9777055abf3 100644 --- a/test/common/ssl/context_impl_test.cc +++ b/test/common/ssl/context_impl_test.cc @@ -455,6 +455,48 @@ TEST(ClientContextConfigImplTest, SecretNotReady) { ClientContextConfigImpl client_context_config(tls_context, factory_context); // When sds secret is not downloaded, config is not ready. EXPECT_FALSE(client_context_config.isReady()); + // Set various callbacks to config. + NiceMock secret_callback; + client_context_config.setSecretUpdateCallback( + [&secret_callback]() { secret_callback.onAddOrUpdateSecret(); }); + client_context_config.setSecretUpdateCallback([]() {}); +} + +// Validate client context config supports SDS, and is marked as not ready if dynamic +// certificate validation context is not yet downloaded. +TEST(ClientContextConfigImplTest, ValidationContextNotReady) { + envoy::api::v2::auth::UpstreamTlsContext tls_context; + envoy::api::v2::auth::TlsCertificate* client_cert = + tls_context.mutable_common_tls_context()->add_tls_certificates(); + client_cert->mutable_certificate_chain()->set_filename(TestEnvironment::substitute( + "{{ test_rundir }}/test/common/ssl/test_data/selfsigned_cert.pem")); + client_cert->mutable_private_key()->set_filename(TestEnvironment::substitute( + "{{ test_rundir }}/test/common/ssl/test_data/selfsigned_key.pem")); + NiceMock local_info; + NiceMock dispatcher; + NiceMock random; + Stats::IsolatedStoreImpl stats; + NiceMock cluster_manager; + NiceMock init_manager; + NiceMock factory_context; + 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_common_tls_context()->mutable_validation_context_sds_secret_config(); + sds_secret_configs->set_name("abc.com"); + sds_secret_configs->mutable_sds_config(); + ClientContextConfigImpl client_context_config(tls_context, factory_context); + // When sds secret is not downloaded, config is not ready. + EXPECT_FALSE(client_context_config.isReady()); + // Set various callbacks to config. + NiceMock secret_callback; + client_context_config.setSecretUpdateCallback( + [&secret_callback]() { secret_callback.onAddOrUpdateSecret(); }); + client_context_config.setSecretUpdateCallback([]() {}); } // Validate that client context config with static TLS certificates is created successfully. @@ -653,6 +695,48 @@ TEST(ServerContextConfigImplTest, SecretNotReady) { 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 server context config supports SDS, and is marked as not ready if dynamic +// certificate validation context is not yet downloaded. +TEST(ServerContextConfigImplTest, ValidationContextNotReady) { + 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/common/ssl/test_data/selfsigned_cert.pem")); + server_cert->mutable_private_key()->set_filename(TestEnvironment::substitute( + "{{ test_rundir }}/test/common/ssl/test_data/selfsigned_key.pem")); + NiceMock local_info; + NiceMock dispatcher; + NiceMock random; + Stats::IsolatedStoreImpl stats; + NiceMock cluster_manager; + NiceMock init_manager; + NiceMock factory_context; + 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_common_tls_context()->mutable_validation_context_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([]() {}); } // TlsCertificate messages must have a cert for servers. diff --git a/test/integration/sds_dynamic_integration_test.cc b/test/integration/sds_dynamic_integration_test.cc index 3ec6d34a12a90..af0a34a6a20dd 100644 --- a/test/integration/sds_dynamic_integration_test.cc +++ b/test/integration/sds_dynamic_integration_test.cc @@ -44,7 +44,8 @@ class SdsDynamicIntegrationBaseTest : public HttpIntegrationTest, public: SdsDynamicIntegrationBaseTest() : HttpIntegrationTest(Http::CodecClient::Type::HTTP1, ipVersion()), - server_cert_("server_cert"), client_cert_("client_cert") {} + server_cert_("server_cert"), validation_secret_("validation_secret"), + client_cert_("client_cert") {} protected: void createSdsStream(FakeUpstream& upstream) { @@ -68,6 +69,18 @@ class SdsDynamicIntegrationBaseTest : public HttpIntegrationTest, return secret; } + envoy::api::v2::auth::Secret getCvcSecret() { + envoy::api::v2::auth::Secret secret; + secret.set_name(validation_secret_); + auto* validation_context = secret.mutable_validation_context(); + validation_context->mutable_trusted_ca()->set_filename( + TestEnvironment::runfilesPath("test/config/integration/certs/cacert.pem")); + validation_context->add_verify_certificate_hash( + "E0:F3:C8:CE:5E:2E:A3:05:F0:70:1F:F5:12:E3:6E:2E:" + "97:92:82:84:A2:28:BC:F7:73:32:D3:39:30:A1:B6:FD"); + return secret; + } + envoy::api::v2::auth::Secret getClientSecret() { envoy::api::v2::auth::Secret secret; secret.set_name(client_cert_); @@ -115,6 +128,7 @@ class SdsDynamicIntegrationBaseTest : public HttpIntegrationTest, } const std::string server_cert_; + const std::string validation_secret_; const std::string client_cert_; Runtime::MockLoader runtime_; Ssl::ContextManagerImpl context_manager_{runtime_}; @@ -184,7 +198,7 @@ class SdsDynamicDownstreamIntegrationTest : public SdsDynamicIntegrationBaseTest client_ssl_ctx_->createTransportSocket(), nullptr); } -private: +protected: Network::TransportSocketFactoryPtr client_ssl_ctx_; }; @@ -238,6 +252,62 @@ TEST_P(SdsDynamicDownstreamIntegrationTest, WrongSecretFirst) { testRouterHeaderOnlyRequestAndResponse(true, &creator); } +class SdsDynamicDownstreamCertValidationContextTest : public SdsDynamicDownstreamIntegrationTest { +public: + void initialize() override { + config_helper_.addConfigModifier([this](envoy::config::bootstrap::v2::Bootstrap& bootstrap) { + auto* common_tls_context = bootstrap.mutable_static_resources() + ->mutable_listeners(0) + ->mutable_filter_chains(0) + ->mutable_tls_context() + ->mutable_common_tls_context(); + common_tls_context->add_alpn_protocols("http/1.1"); + + auto* tls_certificate = common_tls_context->add_tls_certificates(); + tls_certificate->mutable_certificate_chain()->set_filename( + TestEnvironment::runfilesPath("/test/config/integration/certs/servercert.pem")); + tls_certificate->mutable_private_key()->set_filename( + TestEnvironment::runfilesPath("/test/config/integration/certs/serverkey.pem")); + + // Modify the listener certificate context validation to use SDS from sds_cluster + auto* secret_config = common_tls_context->mutable_validation_context_sds_secret_config(); + secret_config->set_name(validation_secret_); + auto* config_source = secret_config->mutable_sds_config(); + auto* api_config_source = config_source->mutable_api_config_source(); + api_config_source->set_api_type(envoy::api::v2::core::ApiConfigSource::GRPC); + auto* grpc_service = api_config_source->add_grpc_services(); + setGrpcService(*grpc_service, "sds_cluster", fake_upstreams_.back()->localAddress()); + + // Add a static sds cluster + auto* sds_cluster = bootstrap.mutable_static_resources()->add_clusters(); + sds_cluster->MergeFrom(bootstrap.static_resources().clusters()[0]); + sds_cluster->set_name("sds_cluster"); + sds_cluster->mutable_http2_protocol_options(); + }); + + HttpIntegrationTest::initialize(); + client_ssl_ctx_ = createClientSslTransportSocketFactory(false, false, context_manager_); + } +}; + +INSTANTIATE_TEST_CASE_P(IpVersionsClientType, SdsDynamicDownstreamCertValidationContextTest, + GRPC_CLIENT_INTEGRATION_PARAMS); + +// A test that SDS server send a good certificate validation context for a static listener. +// The first ssl request should be OK. +TEST_P(SdsDynamicDownstreamCertValidationContextTest, BasicSuccess) { + pre_worker_start_test_steps_ = [this]() { + createSdsStream(*(fake_upstreams_[1])); + sendSdsResponse(getCvcSecret()); + }; + initialize(); + + ConnectionCreationFunction creator = [&]() -> Network::ClientConnectionPtr { + return makeSslClientConnection(); + }; + testRouterHeaderOnlyRequestAndResponse(true, &creator); +} + // Upstream SDS integration test: a static cluster has ssl cert from SDS. class SdsDynamicUpstreamIntegrationTest : public SdsDynamicIntegrationBaseTest { public: diff --git a/test/integration/sds_static_integration_test.cc b/test/integration/sds_static_integration_test.cc index cc259d31ffe7f..f7551a751cd2e 100644 --- a/test/integration/sds_static_integration_test.cc +++ b/test/integration/sds_static_integration_test.cc @@ -47,16 +47,20 @@ class SdsStaticDownstreamIntegrationTest ->mutable_common_tls_context(); common_tls_context->add_alpn_protocols("http/1.1"); - auto* validation_context = common_tls_context->mutable_validation_context(); + common_tls_context->mutable_validation_context_sds_secret_config()->set_name( + "validation_context"); + common_tls_context->add_tls_certificate_sds_secret_configs()->set_name("server_cert"); + + auto* secret = bootstrap.mutable_static_resources()->add_secrets(); + secret->set_name("validation_context"); + auto* validation_context = secret->mutable_validation_context(); validation_context->mutable_trusted_ca()->set_filename( TestEnvironment::runfilesPath("test/config/integration/certs/cacert.pem")); validation_context->add_verify_certificate_hash( "E0:F3:C8:CE:5E:2E:A3:05:F0:70:1F:F5:12:E3:6E:2E:" "97:92:82:84:A2:28:BC:F7:73:32:D3:39:30:A1:B6:FD"); - common_tls_context->add_tls_certificate_sds_secret_configs()->set_name("server_cert"); - - auto* secret = bootstrap.mutable_static_resources()->add_secrets(); + secret = bootstrap.mutable_static_resources()->add_secrets(); secret->set_name("server_cert"); auto* tls_certificate = secret->mutable_tls_certificate(); tls_certificate->mutable_certificate_chain()->set_filename( diff --git a/test/mocks/secret/mocks.h b/test/mocks/secret/mocks.h index 2637d6d850325..428f1ec28faad 100644 --- a/test/mocks/secret/mocks.h +++ b/test/mocks/secret/mocks.h @@ -32,6 +32,11 @@ class MockSecretManager : public SecretManager { TlsCertificateConfigProviderSharedPtr( const envoy::api::v2::core::ConfigSource&, const std::string&, Server::Configuration::TransportSocketFactoryContext&)); + MOCK_METHOD3(findOrCreateCertificateValidationContextProvider, + CertificateValidationContextConfigProviderSharedPtr( + const envoy::api::v2::core::ConfigSource& config_source, + const std::string& config_name, + Server::Configuration::TransportSocketFactoryContext& secret_provider_context)); }; class MockSecretCallbacks : public SecretCallbacks {