-
Notifications
You must be signed in to change notification settings - Fork 4.8k
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
Implementation of SDS #4176
Changes from 15 commits
733372f
08da5ee
5fa9936
f301307
1f6fe1b
09ba59f
1e45df7
dee24cb
ce1714f
a20a70a
99eb93f
2f678cd
1aaaa12
d6eb302
aa8053b
2c4b1eb
aa58e90
a58b95a
7c59756
a589e2f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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 |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,12 +6,17 @@ | |
#include "envoy/secret/secret_provider.h" | ||
|
||
namespace Envoy { | ||
|
||
namespace Server { | ||
namespace Configuration { | ||
class TransportSocketFactoryContext; | ||
} // 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: | ||
|
@@ -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. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: s/contains/containing a/ There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: full stop at end of sentence. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
|
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 { | ||
|
@@ -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. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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" | ||
|
@@ -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; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shouldn't the init manager then be part of the factory context? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 { | ||
|
@@ -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; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why do we need an explicit There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 { | ||
|
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. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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_); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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_)) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.