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

Implementation of SDS #4176

Closed
wants to merge 20 commits into from
Closed
Show file tree
Hide file tree
Changes from 15 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions include/envoy/secret/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,16 @@ load(

envoy_package()

envoy_cc_library(
name = "secret_callbacks_interface",
hdrs = ["secret_callbacks.h"],
)

envoy_cc_library(
name = "secret_provider_interface",
hdrs = ["secret_provider.h"],
deps = [
":secret_callbacks_interface",
"//include/envoy/ssl:tls_certificate_config_interface",
],
)
Expand All @@ -22,5 +28,6 @@ envoy_cc_library(
deps = [
":secret_provider_interface",
"@envoy_api//envoy/api/v2/auth:cert_cc",
"@envoy_api//envoy/api/v2/core:config_source_cc",
],
)
19 changes: 19 additions & 0 deletions include/envoy/secret/secret_callbacks.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#pragma once

#include "envoy/common/pure.h"

namespace Envoy {
namespace Secret {

/**
* Callbacks invoked by a dynamic secret provider.
*/
class SecretCallbacks {
public:
virtual ~SecretCallbacks() {}

virtual void onAddOrUpdateSecret() PURE;
};

} // namespace Secret
} // namespace Envoy
25 changes: 22 additions & 3 deletions include/envoy/secret/secret_manager.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,17 @@
#include "envoy/secret/secret_provider.h"

namespace Envoy {

namespace Server {
namespace Configuration {
class TransportSocketFactoryContext;
Copy link
Member

Choose a reason for hiding this comment

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

nit: It would probably be better to just move this interface into the secret or SSL/TLS namespace but not a big deal.

Copy link
Member Author

Choose a reason for hiding this comment

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

Thanks, I will keep this.

} // namespace Configuration
} // namespace Server

namespace Secret {

/**
* A manager for static secrets.
*
* TODO(jaebong) Support dynamic secrets.
* A manager for static and dynamic secrets.
*/
class SecretManager {
public:
Expand All @@ -37,6 +42,20 @@ class SecretManager {
*/
virtual TlsCertificateConfigProviderSharedPtr createInlineTlsCertificateProvider(
const envoy::api::v2::auth::TlsCertificate& tls_certificate) 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 contains SDS config source.
Copy link
Member

Choose a reason for hiding this comment

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

Nit: s/contains/containing a/

Copy link
Member Author

Choose a reason for hiding this comment

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

Done. Thanks.

* @param config_name a name that uniquely refers to the SDS config source
Copy link
Member

Choose a reason for hiding this comment

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

Nit: full stop at end of sentence.

Copy link
Member Author

Choose a reason for hiding this comment

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

Done. Thanks.

* @param secret_provider_context context that provides components for creating and initializing
* secret provider.
* @return the dynamic TLS secret provider.
Copy link
Member

Choose a reason for hiding this comment

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

Nit: @return TlsCertificateConfigProviderSharedPtr the dynamic TLS secret provider.

Copy link
Member Author

Choose a reason for hiding this comment

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

Done. Thanks.

*/
virtual TlsCertificateConfigProviderSharedPtr findOrCreateDynamicSecretProvider(
const envoy::api::v2::core::ConfigSource& config_source, const std::string& config_name,
Server::Configuration::TransportSocketFactoryContext& secret_provider_context) PURE;
};

} // namespace Secret
Expand Down
13 changes: 12 additions & 1 deletion include/envoy/secret/secret_provider.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#pragma once

#include "envoy/common/pure.h"
#include "envoy/secret/secret_callbacks.h"
#include "envoy/ssl/tls_certificate_config.h"

namespace Envoy {
Expand All @@ -18,7 +19,17 @@ template <class SecretType> class SecretProvider {
*/
virtual const SecretType* secret() const PURE;

// TODO(lizan): Add more methods for dynamic secret provider.
/**
* Add secret callback into secret provider.
Copy link
Member

Choose a reason for hiding this comment

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

Can you add some comments on thread safety? E.g. from which threads is it safe to call this, on which thread will the callback be invoked?

Copy link
Member Author

Choose a reason for hiding this comment

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

Comments are added. Thanks.

* @param callback callback that is executed by secret provider.
*/
virtual void addUpdateCallback(SecretCallbacks& callback) PURE;

/**
* Remove secret callback from secret provider.
* @param callback callback that is executed by secret provider.
*/
virtual void removeUpdateCallback(SecretCallbacks& callback) PURE;
};

typedef SecretProvider<Ssl::TlsCertificateConfig> TlsCertificateConfigProvider;
Expand Down
1 change: 1 addition & 0 deletions include/envoy/server/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ envoy_cc_library(
hdrs = ["transport_socket_config.h"],
deps = [
"//include/envoy/event:dispatcher_interface",
"//include/envoy/init:init_interface",
"//include/envoy/local_info:local_info_interface",
"//include/envoy/network:transport_socket_interface",
"//include/envoy/runtime:runtime_interface",
Expand Down
13 changes: 13 additions & 0 deletions include/envoy/server/transport_socket_config.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include <string>

#include "envoy/event/dispatcher.h"
#include "envoy/init/init.h"
#include "envoy/local_info/local_info.h"
#include "envoy/network/transport_socket.h"
#include "envoy/runtime/runtime.h"
Expand Down Expand Up @@ -63,6 +64,18 @@ class TransportSocketFactoryContext {
* @return the server-wide stats store.
*/
virtual Stats::Store& stats() PURE;

/**
* Pass an init manager to register dynamic secret provider.
* @param init_manager instance of init manager.
*/
virtual void setInitManager(Init::Manager& init_manager) PURE;
Copy link
Member

Choose a reason for hiding this comment

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

It's not optimal that this interface has a setter and a getter for the init manager. Is there any way to simplify this so that the init manager is known ahead of time and we only need a getter?

Copy link
Member Author

Choose a reason for hiding this comment

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

Ideally the init manager should exist when we create TransportSocketFactoryContext, so that we only need a getter. But we create TransportSocketFactoryContext at ClusterManagerFactory and then create init manager per cluster, we have to add it into factory context. @qiwzhang proposed #3831, I think once we are working on that, we can get rid of this setter.

Copy link
Member

Choose a reason for hiding this comment

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

Shouldn't the init manager then be part of the factory context?

Copy link
Member Author

Choose a reason for hiding this comment

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

For now, init manager is per cluster. At the time of the construction of TransportSocketFactoryContext, per cluster init manager is not available. We have to set it when init manager is available.


/**
* @return a pointer pointing to the instance of an init manager, or nullptr
* if not set.
*/
virtual Init::Manager* initManager() PURE;
};

class TransportSocketConfigFactory {
Expand Down
2 changes: 1 addition & 1 deletion include/envoy/ssl/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ envoy_cc_library(
name = "context_config_interface",
hdrs = ["context_config.h"],
deps = [
":tls_certificate_config_interface",
"//include/envoy/secret:secret_provider_interface",
],
)

Expand Down
16 changes: 15 additions & 1 deletion include/envoy/ssl/context_config.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
#include <vector>

#include "envoy/common/pure.h"
#include "envoy/ssl/tls_certificate_config.h"
#include "envoy/secret/secret_callbacks.h"
#include "envoy/secret/secret_provider.h"

namespace Envoy {
namespace Ssl {
Expand Down Expand Up @@ -95,6 +96,19 @@ class ContextConfig {
* @return The maximum TLS protocol version to negotiate.
*/
virtual unsigned maxProtocolVersion() const PURE;

/**
* @return true if the ContextConfig is able to provide secrets to create SSL context,
* and false if dynamic secrets are expected but are not downloaded from SDS server yet.
*/
virtual bool isReady() const PURE;
Copy link
Member

Choose a reason for hiding this comment

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

Why do we need an explicit isReady()? Can't we just set the callback below and take an immediate invocation if ready?

Copy link
Member Author

Choose a reason for hiding this comment

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

isReady() is used only when SDS sever sends bad response (i.e. no good secrets are received since Envoy starts), and the listenerImpl/ClusterImpl object has been marked as active state. We use isReady() to tell the context config is not ready.


/**
* Add secret callback into context config. When dynamic secrets are in use and new secrets
* are downloaded from SDS server, this callback is invoked to update SSL context.
* @param callback callback that is executed by context config.
Copy link
Member

Choose a reason for hiding this comment

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

Can you add more information about when callbacks will be invoked? It's not clear at the interface level why/when this happens.

Copy link
Member Author

Choose a reason for hiding this comment

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

More information is added into comment. Thanks.

*/
virtual void setSecretUpdateCallback(Secret::SecretCallbacks& callback) PURE;
};

class ClientContextConfig : public virtual ContextConfig {
Expand Down
1 change: 1 addition & 0 deletions source/common/common/logger.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ namespace Logger {
FUNCTION(router) \
FUNCTION(runtime) \
FUNCTION(stats) \
FUNCTION(secret) \
FUNCTION(testing) \
FUNCTION(thrift) \
FUNCTION(tracing) \
Expand Down
1 change: 1 addition & 0 deletions source/common/config/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,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",
],
)
Expand Down
2 changes: 2 additions & 0 deletions source/common/config/protobuf_link_hacks.h
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -9,4 +10,5 @@ namespace Envoy {
// This file should be included ONLY if this hack is required.
const envoy::service::discovery::v2::AdsDummy _ads_dummy;
const envoy::service::ratelimit::v2::RateLimitRequest _rls_dummy;
const envoy::service::discovery::v2::SdsDummy _sds_dummy;
} // namespace Envoy
1 change: 1 addition & 0 deletions source/common/config/resources.h
Original file line number Diff line number Diff line change
Expand Up @@ -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"};
};

Expand Down
22 changes: 22 additions & 0 deletions source/common/secret/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,11 @@ envoy_cc_library(
srcs = ["secret_manager_impl.cc"],
hdrs = ["secret_manager_impl.h"],
deps = [
":sds_api_lib",
":secret_provider_impl_lib",
"//include/envoy/secret:secret_manager_interface",
"//include/envoy/server:transport_socket_config_interface",
"//source/common/common:assert_lib",
"//source/common/common:minimal_logger_lib",
"@envoy_api//envoy/api/v2/auth:cert_cc",
],
Expand All @@ -30,3 +33,22 @@ envoy_cc_library(
"@envoy_api//envoy/api/v2/auth:cert_cc",
],
)

envoy_cc_library(
name = "sds_api_lib",
srcs = ["sds_api.cc"],
hdrs = ["sds_api.h"],
deps = [
"//include/envoy/config:subscription_interface",
"//include/envoy/event:dispatcher_interface",
"//include/envoy/init:init_interface",
"//include/envoy/local_info:local_info_interface",
"//include/envoy/runtime:runtime_interface",
"//include/envoy/secret:secret_provider_interface",
"//include/envoy/stats:stats_interface",
"//source/common/config:resources_lib",
"//source/common/config:subscription_factory_lib",
"//source/common/protobuf:utility_lib",
"//source/common/ssl:tls_certificate_config_impl_lib",
],
)
89 changes: 89 additions & 0 deletions source/common/secret/sds_api.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
#include "common/secret/sds_api.h"

#include <unordered_map>

#include "envoy/api/v2/auth/cert.pb.validate.h"

#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 {

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<void()> unregister_secret_provider)
: 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), unregister_secret_provider_cb_(unregister_secret_provider) {
// TODO(JimmyCYJ): Implement chained_init_manager, so that multiple init_manager
// can be chained together to behave as one init_manager. In that way, we let
// two listeners which share same SdsApi to register at separate init managers, and
// each init manager has a chance to initialize its targets.
Copy link
Member

Choose a reason for hiding this comment

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

I'm not sure I fully understand the implication here. If two listeners point to the same secret today, what happens? Does one listener just immediately initialize?

Copy link
Contributor

Choose a reason for hiding this comment

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

In the current implementation, yes. The second listener will not wait, its initialization will finish right away since its init_manager is not used. Only the first one wait for the sds server response.
The second listener will be ready prematurely, and start to listen on a port. The connection will be reset before the SDS get the secret.

I have a rough idea of how to solve this: design a InitManagerChain; it can register multiple init_managers, but behaves like one init_manager. Each indivicual init_manager's callbacks will be called when the combined_init_manager callback is called.

That is what this TODO about. like to implement it as a separate PR.

Copy link
Member

Choose a reason for hiding this comment

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

Can you also file an issue? This seems a major issue, since you will not have warming on SDS work correctly otherwise.

Copy link
Member Author

Choose a reason for hiding this comment

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

I have filed an issue here #4224. Please take a look and let me know if any modification is required. Thanks.

init_manager.registerTarget(*this);
}

SdsApi::~SdsApi() { unregister_secret_provider_cb_(); }

void SdsApi::initialize(std::function<void()> callback) {
initialize_callback_ = callback;

subscription_ = Envoy::Config::SubscriptionFactory::subscriptionFromConfigSource<
envoy::api::v2::auth::Secret>(
sds_config_, local_info_, dispatcher_, cluster_manager_, random_, stats_,
/* rest_legacy_constructor */ nullptr,
"envoy.service.discovery.v2.SecretDiscoveryService.FetchSecrets",
"envoy.service.discovery.v2.SecretDiscoveryService.StreamSecrets");
Config::Utility::checkLocalInfo("sds", local_info_);
Copy link
Contributor

Choose a reason for hiding this comment

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

Can move subcription_ creation into the constructor so we don't need to store so many object references.

Only call start in the initialize() function

Copy link
Member Author

Choose a reason for hiding this comment

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

We cannot move subcription_ into constructor. Envoy::Config::SubscriptionFactory::subscriptionFromConfigSourceenvoy::api::v2::auth::Secre will access members of those objects, which are not ready when SdsApi constructor is called. That causes segmentation fault in test runs.


subscription_->start({sds_config_name_}, *this);
}

void SdsApi::onConfigUpdate(const ResourceVector& resources, const std::string&) {
if (resources.empty()) {
throw EnvoyException(
fmt::format("Missing SDS resources for {} in onConfigUpdate()", sds_config_name_));
}
if (resources.size() != 1) {
throw EnvoyException(fmt::format("Unexpected SDS secrets length: {}", resources.size()));
}
const auto& secret = resources[0];
MessageUtil::validate(secret);
if (!(secret.name() == sds_config_name_)) {
Copy link
Member

Choose a reason for hiding this comment

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

Nit: !=

Copy link
Member Author

Choose a reason for hiding this comment

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

Done.

throw EnvoyException(
fmt::format("Unexpected SDS secret (expecting {}): {}", sds_config_name_, secret.name()));
}

const uint64_t new_hash = MessageUtil::hash(secret);
if (new_hash != secret_hash_ &&
secret.type_case() == envoy::api::v2::auth::Secret::TypeCase::kTlsCertificate) {
secret_hash_ = new_hash;
tls_certificate_secrets_ =
std::make_unique<Ssl::TlsCertificateConfigImpl>(secret.tls_certificate());

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
Loading