Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SDS: reduce cost of secret update #19971

Merged
merged 12 commits into from
Feb 17, 2022
13 changes: 8 additions & 5 deletions envoy/ssl/context_manager.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,15 @@ class ContextManager {
/**
* Builds a ClientContext from a ClientContextConfig.
*/
virtual ClientContextSharedPtr
createSslClientContext(Stats::Scope& scope, const ClientContextConfig& config,
Envoy::Ssl::ClientContextSharedPtr old_context) PURE;
virtual ClientContextSharedPtr createSslClientContext(Stats::Scope& scope,
const ClientContextConfig& config) PURE;

/**
* Builds a ServerContext from a ServerContextConfig.
*/
virtual ServerContextSharedPtr
createSslServerContext(Stats::Scope& scope, const ServerContextConfig& config,
const std::vector<std::string>& server_names,
Envoy::Ssl::ServerContextSharedPtr old_context) PURE;
const std::vector<std::string>& server_names) PURE;

/**
* @return the number of days until the next certificate being managed will expire.
Expand All @@ -55,6 +53,11 @@ class ContextManager {
* expire, or `absl::nullopt` if no OCSP responses exist.
*/
virtual absl::optional<uint64_t> secondsUntilFirstOcspResponseExpires() const PURE;

/**
* Remove an existing ssl context.
*/
virtual void removeContext(const std::shared_ptr<Envoy::Ssl::Context>& old_context) PURE;
lambdai marked this conversation as resolved.
Show resolved Hide resolved
};

using ContextManagerPtr = std::unique_ptr<ContextManager>;
Expand Down
53 changes: 19 additions & 34 deletions source/extensions/transport_sockets/tls/context_manager_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -14,62 +14,43 @@ namespace Extensions {
namespace TransportSockets {
namespace Tls {

ContextManagerImpl::ContextManagerImpl(TimeSource& time_source) : time_source_(time_source) {}

ContextManagerImpl::~ContextManagerImpl() {
removeEmptyContexts();
KNOWN_ISSUE_ASSERT(contexts_.empty(), "https://github.com/envoyproxy/envoy/issues/10030");
}

void ContextManagerImpl::removeEmptyContexts() {
contexts_.remove_if([](const std::weak_ptr<Envoy::Ssl::Context>& n) { return n.expired(); });
}

void ContextManagerImpl::removeOldContext(std::shared_ptr<Envoy::Ssl::Context> old_context) {
if (old_context) {
contexts_.remove_if([old_context](const std::weak_ptr<Envoy::Ssl::Context>& n) {
std::shared_ptr<Envoy::Ssl::Context> sp = n.lock();
if (sp) {
return old_context == sp;
}
return false;
});
}
}

Envoy::Ssl::ClientContextSharedPtr
ContextManagerImpl::createSslClientContext(Stats::Scope& scope,
const Envoy::Ssl::ClientContextConfig& config,
Envoy::Ssl::ClientContextSharedPtr old_context) {
const Envoy::Ssl::ClientContextConfig& config) {
if (!config.isReady()) {
return nullptr;
}

Envoy::Ssl::ClientContextSharedPtr context =
std::make_shared<ClientContextImpl>(scope, config, time_source_);
removeOldContext(old_context);
removeEmptyContexts();
contexts_.emplace_back(context);
ASSERT(contexts_.insert(context).second);
lambdai marked this conversation as resolved.
Show resolved Hide resolved
return context;
}

Envoy::Ssl::ServerContextSharedPtr ContextManagerImpl::createSslServerContext(
Stats::Scope& scope, const Envoy::Ssl::ServerContextConfig& config,
const std::vector<std::string>& server_names, Envoy::Ssl::ServerContextSharedPtr old_context) {
Envoy::Ssl::ServerContextSharedPtr
ContextManagerImpl::createSslServerContext(Stats::Scope& scope,
const Envoy::Ssl::ServerContextConfig& config,
const std::vector<std::string>& server_names) {
if (!config.isReady()) {
return nullptr;
}

Envoy::Ssl::ServerContextSharedPtr context =
std::make_shared<ServerContextImpl>(scope, config, server_names, time_source_);
removeOldContext(old_context);
removeEmptyContexts();
contexts_.emplace_back(context);
// Insert new context should never fail.
ASSERT(contexts_.insert(context).second);
return context;
}

size_t ContextManagerImpl::daysUntilFirstCertExpires() const {
size_t ret = std::numeric_limits<int>::max();
for (const auto& ctx_weak_ptr : contexts_) {
Envoy::Ssl::ContextSharedPtr context = ctx_weak_ptr.lock();
for (const auto& context : contexts_) {
if (context) {
ret = std::min<size_t>(context->daysUntilFirstCertExpires(), ret);
}
Expand All @@ -79,8 +60,7 @@ size_t ContextManagerImpl::daysUntilFirstCertExpires() const {

absl::optional<uint64_t> ContextManagerImpl::secondsUntilFirstOcspResponseExpires() const {
absl::optional<uint64_t> ret;
for (const auto& ctx_weak_ptr : contexts_) {
Envoy::Ssl::ContextSharedPtr context = ctx_weak_ptr.lock();
for (const auto& context : contexts_) {
if (context) {
auto next_expiration = context->secondsUntilFirstOcspResponseExpires();
if (next_expiration) {
Expand All @@ -93,14 +73,19 @@ absl::optional<uint64_t> ContextManagerImpl::secondsUntilFirstOcspResponseExpire
}

void ContextManagerImpl::iterateContexts(std::function<void(const Envoy::Ssl::Context&)> callback) {
for (const auto& ctx_weak_ptr : contexts_) {
Envoy::Ssl::ContextSharedPtr context = ctx_weak_ptr.lock();
for (const auto& context : contexts_) {
if (context) {
callback(*context);
}
}
}

void ContextManagerImpl::removeContext(const std::shared_ptr<Envoy::Ssl::Context>& old_context) {
lambdai marked this conversation as resolved.
Show resolved Hide resolved
if (old_context != nullptr) {
ASSERT(contexts_.erase(old_context) == 1);
}
}

} // namespace Tls
} // namespace TransportSockets
} // namespace Extensions
Expand Down
14 changes: 6 additions & 8 deletions source/extensions/transport_sockets/tls/context_manager_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,29 +24,27 @@ namespace Tls {
*/
class ContextManagerImpl final : public Envoy::Ssl::ContextManager {
public:
ContextManagerImpl(TimeSource& time_source) : time_source_(time_source) {}
explicit ContextManagerImpl(TimeSource& time_source);
~ContextManagerImpl() override;

// Ssl::ContextManager
Ssl::ClientContextSharedPtr
createSslClientContext(Stats::Scope& scope, const Envoy::Ssl::ClientContextConfig& config,
Envoy::Ssl::ClientContextSharedPtr old_context) override;
createSslClientContext(Stats::Scope& scope,
const Envoy::Ssl::ClientContextConfig& config) override;
Ssl::ServerContextSharedPtr
createSslServerContext(Stats::Scope& scope, const Envoy::Ssl::ServerContextConfig& config,
const std::vector<std::string>& server_names,
Envoy::Ssl::ServerContextSharedPtr old_context) override;
const std::vector<std::string>& server_names) override;
size_t daysUntilFirstCertExpires() const override;
absl::optional<uint64_t> secondsUntilFirstOcspResponseExpires() const override;
void iterateContexts(std::function<void(const Envoy::Ssl::Context&)> callback) override;
Ssl::PrivateKeyMethodManager& privateKeyMethodManager() override {
return private_key_method_manager_;
};
void removeContext(const std::shared_ptr<Envoy::Ssl::Context>& old_context) override;

private:
void removeEmptyContexts();
void removeOldContext(std::shared_ptr<Envoy::Ssl::Context> old_context);
TimeSource& time_source_;
std::list<std::weak_ptr<Envoy::Ssl::Context>> contexts_;
absl::flat_hash_set<std::shared_ptr<Envoy::Ssl::Context>> contexts_;
PrivateKeyMethodManagerImpl private_key_method_manager_{};
};

Expand Down
17 changes: 13 additions & 4 deletions source/extensions/transport_sockets/tls/ssl_socket.cc
Original file line number Diff line number Diff line change
Expand Up @@ -359,10 +359,12 @@ ClientSslSocketFactory::ClientSslSocketFactory(Envoy::Ssl::ClientContextConfigPt
Stats::Scope& stats_scope)
: manager_(manager), stats_scope_(stats_scope), stats_(generateStats("client", stats_scope)),
config_(std::move(config)),
ssl_ctx_(manager_.createSslClientContext(stats_scope_, *config_, nullptr)) {
ssl_ctx_(manager_.createSslClientContext(stats_scope_, *config_)) {
config_->setSecretUpdateCallback([this]() { onAddOrUpdateSecret(); });
}

ClientSslSocketFactory::~ClientSslSocketFactory() { manager_.removeContext(ssl_ctx_); }
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

manager_ could be a dangling reference when Envoy shuts down

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you create a test that fails in ASAN for this?


Network::TransportSocketPtr ClientSslSocketFactory::createTransportSocket(
Network::TransportSocketOptionsConstSharedPtr transport_socket_options) const {
// onAddOrUpdateSecret() could be invoked in the middle of checking the existence of ssl_ctx and
Expand All @@ -387,10 +389,12 @@ bool ClientSslSocketFactory::implementsSecureTransport() const { return true; }

void ClientSslSocketFactory::onAddOrUpdateSecret() {
ENVOY_LOG(debug, "Secret is updated.");
auto ctx = manager_.createSslClientContext(stats_scope_, *config_);
{
absl::WriterMutexLock l(&ssl_ctx_mu_);
ssl_ctx_ = manager_.createSslClientContext(stats_scope_, *config_, ssl_ctx_);
std::swap(ctx, ssl_ctx_);
}
manager_.removeContext(ctx);
stats_.ssl_context_update_by_sds_.inc();
}

Expand All @@ -400,10 +404,12 @@ ServerSslSocketFactory::ServerSslSocketFactory(Envoy::Ssl::ServerContextConfigPt
const std::vector<std::string>& server_names)
: manager_(manager), stats_scope_(stats_scope), stats_(generateStats("server", stats_scope)),
config_(std::move(config)), server_names_(server_names),
ssl_ctx_(manager_.createSslServerContext(stats_scope_, *config_, server_names_, nullptr)) {
ssl_ctx_(manager_.createSslServerContext(stats_scope_, *config_, server_names_)) {
config_->setSecretUpdateCallback([this]() { onAddOrUpdateSecret(); });
}

ServerSslSocketFactory::~ServerSslSocketFactory() { manager_.removeContext(ssl_ctx_); }

Envoy::Ssl::ClientContextSharedPtr ClientSslSocketFactory::sslCtx() {
absl::ReaderMutexLock l(&ssl_ctx_mu_);
return ssl_ctx_;
Expand Down Expand Up @@ -433,10 +439,13 @@ bool ServerSslSocketFactory::implementsSecureTransport() const { return true; }

void ServerSslSocketFactory::onAddOrUpdateSecret() {
ENVOY_LOG(debug, "Secret is updated.");
auto ctx = manager_.createSslServerContext(stats_scope_, *config_, server_names_);
{
absl::WriterMutexLock l(&ssl_ctx_mu_);
ssl_ctx_ = manager_.createSslServerContext(stats_scope_, *config_, server_names_, ssl_ctx_);
std::swap(ctx, ssl_ctx_);
}
manager_.removeContext(ctx);

stats_.ssl_context_update_by_sds_.inc();
}

Expand Down
4 changes: 4 additions & 0 deletions source/extensions/transport_sockets/tls/ssl_socket.h
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,8 @@ class ClientSslSocketFactory : public Network::TransportSocketFactory,
ClientSslSocketFactory(Envoy::Ssl::ClientContextConfigPtr config,
Envoy::Ssl::ContextManager& manager, Stats::Scope& stats_scope);

~ClientSslSocketFactory() override;

Network::TransportSocketPtr
createTransportSocket(Network::TransportSocketOptionsConstSharedPtr options) const override;
bool implementsSecureTransport() const override;
Expand Down Expand Up @@ -133,6 +135,8 @@ class ServerSslSocketFactory : public Network::TransportSocketFactory,
Envoy::Ssl::ContextManager& manager, Stats::Scope& stats_scope,
const std::vector<std::string>& server_names);

~ServerSslSocketFactory() override;

Network::TransportSocketPtr
createTransportSocket(Network::TransportSocketOptionsConstSharedPtr options) const override;
bool implementsSecureTransport() const override;
Expand Down
12 changes: 8 additions & 4 deletions source/server/ssl_context_manager.cc
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,14 @@ namespace Server {
class SslContextManagerNoTlsStub final : public Envoy::Ssl::ContextManager {
Ssl::ClientContextSharedPtr
createSslClientContext(Stats::Scope& /* scope */,
const Envoy::Ssl::ClientContextConfig& /* config */,
Envoy::Ssl::ClientContextSharedPtr /* old_context */) override {
const Envoy::Ssl::ClientContextConfig& /* config */) override {
throwException();
}

Ssl::ServerContextSharedPtr
createSslServerContext(Stats::Scope& /* scope */,
const Envoy::Ssl::ServerContextConfig& /* config */,
const std::vector<std::string>& /* server_names */,
Envoy::Ssl::ServerContextSharedPtr /* old_context */) override {
const std::vector<std::string>& /* server_names */) override {
throwException();
}

Expand All @@ -36,6 +34,12 @@ class SslContextManagerNoTlsStub final : public Envoy::Ssl::ContextManager {

Ssl::PrivateKeyMethodManager& privateKeyMethodManager() override { throwException(); }

void removeContext(const std::shared_ptr<Envoy::Ssl::Context>& old_context) override {
if (old_context) {
throw EnvoyException("SSL is not supported in this configuration");
}
}

private:
[[noreturn]] void throwException() {
throw EnvoyException("SSL is not supported in this configuration");
Expand Down
2 changes: 1 addition & 1 deletion test/common/http/conn_pool_grid_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -828,7 +828,7 @@ TEST_F(ConnectivityGridTest, ConnectionCloseDuringCreation) {
Envoy::Ssl::ClientContextConfigPtr config(new NiceMock<Ssl::MockClientContextConfig>());
NiceMock<Server::Configuration::MockTransportSocketFactoryContext> factory_context;
Ssl::ClientContextSharedPtr ssl_context(new Ssl::MockClientContext());
EXPECT_CALL(factory_context.context_manager_, createSslClientContext(_, _, _))
EXPECT_CALL(factory_context.context_manager_, createSslClientContext(_, _))
.WillOnce(Return(ssl_context));
auto factory =
std::make_unique<Quic::QuicClientTransportSocketFactory>(std::move(config), factory_context);
Expand Down
2 changes: 1 addition & 1 deletion test/common/http/http3/conn_pool_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ class MockPoolConnectResultCallback : public PoolConnectResultCallback {
class Http3ConnPoolImplTest : public Event::TestUsingSimulatedTime, public testing::Test {
public:
Http3ConnPoolImplTest() {
EXPECT_CALL(context_.context_manager_, createSslClientContext(_, _, _))
EXPECT_CALL(context_.context_manager_, createSslClientContext(_, _))
.WillRepeatedly(Return(ssl_context_));
factory_.emplace(std::unique_ptr<Envoy::Ssl::ClientContextConfig>(
new NiceMock<Ssl::MockClientContextConfig>),
Expand Down
3 changes: 1 addition & 2 deletions test/common/quic/client_connection_factory_impl_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,7 @@ class QuicNetworkConnectionTest : public Event::TestUsingSimulatedTime,
test_address_ = Network::Utility::resolveUrl(
absl::StrCat("tcp://", Network::Test::getLoopbackAddressUrlString(GetParam()), ":30"));
Ssl::ClientContextSharedPtr context{new Ssl::MockClientContext()};
EXPECT_CALL(context_.context_manager_, createSslClientContext(_, _, _))
.WillOnce(Return(context));
EXPECT_CALL(context_.context_manager_, createSslClientContext(_, _)).WillOnce(Return(context));
factory_ = std::make_unique<Quic::QuicClientTransportSocketFactory>(
std::unique_ptr<Envoy::Ssl::ClientContextConfig>(
new NiceMock<Ssl::MockClientContextConfig>),
Expand Down
1 change: 1 addition & 0 deletions test/common/quic/envoy_quic_proof_source_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ class SignatureVerifier {
NiceMock<Ssl::MockClientContextConfig> client_context_config_;
NiceMock<Ssl::MockCertificateValidationContextConfig> cert_validation_ctx_config_;
std::unique_ptr<EnvoyQuicProofVerifier> verifier_;
NiceMock<Ssl::MockContextManager> tls_context_manager_;
};

class TestSignatureCallback : public quic::ProofSource::SignatureCallback {
Expand Down
1 change: 1 addition & 0 deletions test/common/quic/envoy_quic_proof_verifier_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ class EnvoyQuicProofVerifierTest : public testing::Test {
NiceMock<Ssl::MockClientContextConfig> client_context_config_;
Ssl::MockCertificateValidationContextConfig cert_validation_ctx_config_;
std::unique_ptr<EnvoyQuicProofVerifier> verifier_;
NiceMock<Ssl::MockContextManager> tls_context_manager_;
};

TEST_F(EnvoyQuicProofVerifierTest, VerifyCertChainSuccess) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ class ClusterTest : public testing::Test,
admin_, ssl_context_manager_, *scope, cm_, local_info_, dispatcher_, stats_store_,
singleton_manager_, tls_, validation_visitor_, *api_, options_);
if (uses_tls) {
EXPECT_CALL(ssl_context_manager_, createSslClientContext(_, _, _));
EXPECT_CALL(ssl_context_manager_, createSslClientContext(_, _));
}
EXPECT_CALL(*dns_cache_manager_, getCache(_));
// Below we return a nullptr handle which has no effect on the code under test but isn't
Expand Down
Loading