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

Redis health check is not sending the auth command on it's connection #8166

Merged
merged 13 commits into from
Sep 11, 2019
1 change: 1 addition & 0 deletions docs/root/intro/version_history.rst
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ Version history
* rbac: added support for DNS SAN as :ref:`principal_name <envoy_api_field_config.rbac.v2.Principal.Authenticated.principal_name>`.
* redis: added :ref:`enable_command_stats <envoy_api_field_config.filter.network.redis_proxy.v2.RedisProxy.ConnPoolSettings.enable_command_stats>` to enable :ref:`per command statistics <arch_overview_redis_cluster_command_stats>` for upstream clusters.
* redis: added :ref:`read_policy <envoy_api_field_config.filter.network.redis_proxy.v2.RedisProxy.ConnPoolSettings.read_policy>` to allow reading from redis replicas for Redis Cluster deployments.
* redis: fix a bug where the redis health checker ignored the upstream auth password.
* regex: introduce new :ref:`RegexMatcher <envoy_api_msg_type.matcher.RegexMatcher>` type that
provides a safe regex implementation for untrusted user input. This type is now used in all
configuration that processes user provided input. See :ref:`deprecated configuration details
Expand Down
5 changes: 5 additions & 0 deletions include/envoy/server/health_checker_config.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@ class HealthCheckerFactoryContext {
* messages.
*/
virtual ProtobufMessage::ValidationVisitor& messageValidationVisitor() PURE;

/**
* @return Api::Api& the API used by the server.
*/
virtual Api::Api& api() PURE;
};

/**
Expand Down
3 changes: 2 additions & 1 deletion source/common/upstream/cluster_factory_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,8 @@ ClusterFactoryImplBase::create(const envoy::api::v2::Cluster& cluster,
} else {
new_cluster_pair.first->setHealthChecker(HealthCheckerFactory::create(
cluster.health_checks()[0], *new_cluster_pair.first, context.runtime(), context.random(),
context.dispatcher(), context.logManager(), context.messageValidationVisitor()));
context.dispatcher(), context.logManager(), context.messageValidationVisitor(),
context.api()));
}
}

Expand Down
21 changes: 12 additions & 9 deletions source/common/upstream/health_checker_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,11 @@ class HealthCheckerFactoryContextImpl : public Server::Configuration::HealthChec
Envoy::Runtime::RandomGenerator& random,
Event::Dispatcher& dispatcher,
HealthCheckEventLoggerPtr&& event_logger,
ProtobufMessage::ValidationVisitor& validation_visitor)
ProtobufMessage::ValidationVisitor& validation_visitor,
Api::Api& api)
: cluster_(cluster), runtime_(runtime), random_(random), dispatcher_(dispatcher),
event_logger_(std::move(event_logger)), validation_visitor_(validation_visitor) {}
event_logger_(std::move(event_logger)), validation_visitor_(validation_visitor), api_(api) {
}
Upstream::Cluster& cluster() override { return cluster_; }
Envoy::Runtime::Loader& runtime() override { return runtime_; }
Envoy::Runtime::RandomGenerator& random() override { return random_; }
Expand All @@ -40,6 +42,7 @@ class HealthCheckerFactoryContextImpl : public Server::Configuration::HealthChec
ProtobufMessage::ValidationVisitor& messageValidationVisitor() override {
return validation_visitor_;
}
Api::Api& api() override { return api_; }

private:
Upstream::Cluster& cluster_;
Expand All @@ -48,14 +51,14 @@ class HealthCheckerFactoryContextImpl : public Server::Configuration::HealthChec
Event::Dispatcher& dispatcher_;
HealthCheckEventLoggerPtr event_logger_;
ProtobufMessage::ValidationVisitor& validation_visitor_;
Api::Api& api_;
};

HealthCheckerSharedPtr
HealthCheckerFactory::create(const envoy::api::v2::core::HealthCheck& health_check_config,
Upstream::Cluster& cluster, Runtime::Loader& runtime,
Runtime::RandomGenerator& random, Event::Dispatcher& dispatcher,
AccessLog::AccessLogManager& log_manager,
ProtobufMessage::ValidationVisitor& validation_visitor) {
HealthCheckerSharedPtr HealthCheckerFactory::create(
const envoy::api::v2::core::HealthCheck& health_check_config, Upstream::Cluster& cluster,
Runtime::Loader& runtime, Runtime::RandomGenerator& random, Event::Dispatcher& dispatcher,
AccessLog::AccessLogManager& log_manager,
ProtobufMessage::ValidationVisitor& validation_visitor, Api::Api& api) {
HealthCheckEventLoggerPtr event_logger;
if (!health_check_config.event_log_path().empty()) {
event_logger = std::make_unique<HealthCheckEventLoggerImpl>(
Expand All @@ -81,7 +84,7 @@ HealthCheckerFactory::create(const envoy::api::v2::core::HealthCheck& health_che
health_check_config.custom_health_check().name());
std::unique_ptr<Server::Configuration::HealthCheckerFactoryContext> context(
new HealthCheckerFactoryContextImpl(cluster, runtime, random, dispatcher,
std::move(event_logger), validation_visitor));
std::move(event_logger), validation_visitor, api));
return factory.createCustomHealthChecker(health_check_config, *context);
}
default:
Expand Down
12 changes: 6 additions & 6 deletions source/common/upstream/health_checker_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include "common/stream_info/stream_info_impl.h"
#include "common/upstream/health_checker_base_impl.h"

#include "include/envoy/api/_virtual_includes/api_interface/envoy/api/api.h"
#include "src/proto/grpc/health/v1/health.pb.h"

namespace Envoy {
Expand All @@ -32,12 +33,11 @@ class HealthCheckerFactory : public Logger::Loggable<Logger::Id::health_checker>
* @param validation_visitor message validation visitor instance.
* @return a health checker.
*/
static HealthCheckerSharedPtr create(const envoy::api::v2::core::HealthCheck& health_check_config,
Upstream::Cluster& cluster, Runtime::Loader& runtime,
Runtime::RandomGenerator& random,
Event::Dispatcher& dispatcher,
AccessLog::AccessLogManager& log_manager,
ProtobufMessage::ValidationVisitor& validation_visitor);
static HealthCheckerSharedPtr
create(const envoy::api::v2::core::HealthCheck& health_check_config, Upstream::Cluster& cluster,
Runtime::Loader& runtime, Runtime::RandomGenerator& random, Event::Dispatcher& dispatcher,
AccessLog::AccessLogManager& log_manager,
ProtobufMessage::ValidationVisitor& validation_visitor, Api::Api& api);
};

/**
Expand Down
10 changes: 6 additions & 4 deletions source/common/upstream/health_discovery_service.cc
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,8 @@ void HdsDelegate::processMessage(
info_factory_, cm_, local_info_, dispatcher_, random_,
singleton_manager_, tls_, validation_visitor_, api_));

hds_clusters_.back()->startHealthchecks(access_log_manager_, runtime_, random_, dispatcher_);
hds_clusters_.back()->startHealthchecks(access_log_manager_, runtime_, random_, dispatcher_,
api_);
}
}

Expand Down Expand Up @@ -243,10 +244,11 @@ ProdClusterInfoFactory::createClusterInfo(const CreateClusterInfoParams& params)

void HdsCluster::startHealthchecks(AccessLog::AccessLogManager& access_log_manager,
Runtime::Loader& runtime, Runtime::RandomGenerator& random,
Event::Dispatcher& dispatcher) {
Event::Dispatcher& dispatcher, Api::Api& api) {
for (auto& health_check : cluster_.health_checks()) {
health_checkers_.push_back(Upstream::HealthCheckerFactory::create(
health_check, *this, runtime, random, dispatcher, access_log_manager, validation_visitor_));
health_checkers_.push_back(
Upstream::HealthCheckerFactory::create(health_check, *this, runtime, random, dispatcher,
access_log_manager, validation_visitor_, api));
health_checkers_.back()->start();
}
}
Expand Down
3 changes: 2 additions & 1 deletion source/common/upstream/health_discovery_service.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,8 @@ class HdsCluster : public Cluster, Logger::Loggable<Logger::Id::upstream> {

// Creates and starts healthcheckers to its endpoints
void startHealthchecks(AccessLog::AccessLogManager& access_log_manager, Runtime::Loader& runtime,
Runtime::RandomGenerator& random, Event::Dispatcher& dispatcher);
Runtime::RandomGenerator& random, Event::Dispatcher& dispatcher,
Api::Api& api);

std::vector<Upstream::HealthCheckerSharedPtr> healthCheckers() { return health_checkers_; };

Expand Down
20 changes: 3 additions & 17 deletions source/extensions/clusters/redis/redis_cluster.cc
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ RedisCluster::RedisCluster(
: Config::Utility::translateClusterHosts(cluster.hosts())),
local_info_(factory_context.localInfo()), random_(factory_context.random()),
redis_discovery_session_(*this, redis_client_factory), lb_factory_(std::move(lb_factory)),
api_(api) {
auth_password_(
NetworkFilters::RedisProxy::ProtocolOptionsConfigImpl::auth_password(info(), api)) {
const auto& locality_lb_endpoints = load_assignment_.endpoints();
for (const auto& locality_lb_endpoint : locality_lb_endpoints) {
for (const auto& lb_endpoint : locality_lb_endpoint.lb_endpoints()) {
Expand All @@ -43,13 +44,6 @@ RedisCluster::RedisCluster(
*this, host.socket_address().address(), host.socket_address().port_value()));
}
}

auto options =
info()->extensionProtocolOptionsTyped<NetworkFilters::RedisProxy::ProtocolOptionsConfigImpl>(
NetworkFilters::NetworkFilterNames::get().RedisProxy);
if (options) {
auth_password_datasource_ = options->auth_password_datasource();
}
}

void RedisCluster::startPreInit() {
Expand Down Expand Up @@ -253,16 +247,8 @@ void RedisCluster::RedisDiscoverySession::startResolveRedis() {
client = std::make_unique<RedisDiscoveryClient>(*this);
client->host_ = current_host_address_;
client->client_ = client_factory_.create(host, dispatcher_, *this, redis_command_stats_,
parent_.info()->statsScope());
parent_.info()->statsScope(), parent_.auth_password_);
client->client_->addConnectionCallbacks(*client);
std::string auth_password =
Envoy::Config::DataSource::read(parent_.auth_password_datasource_, true, parent_.api_);
if (!auth_password.empty()) {
// Send an AUTH command to the upstream server.
client->client_->makeRequest(
Extensions::NetworkFilters::Common::Redis::Utility::makeAuthCommand(auth_password),
null_pool_callbacks);
}
}

current_request_ = client->client_->makeRequest(ClusterSlotsRequest::instance_, *this);
Expand Down
11 changes: 6 additions & 5 deletions source/extensions/clusters/redis/redis_cluster.h
Original file line number Diff line number Diff line change
Expand Up @@ -215,10 +215,12 @@ class RedisCluster : public Upstream::BaseDynamicClusterImpl {
std::chrono::milliseconds bufferFlushTimeoutInMs() const override { return buffer_timeout_; }
uint32_t maxUpstreamUnknownConnections() const override { return 0; }
bool enableCommandStats() const override { return false; }
// This is effectively not in used for making the "Cluster Slots" calls.
// since we call cluster slots on both the master and slaves, ANY is more appropriate here.
// For any readPolicy other than Master, the RedisClientFactory will send a READONLY command
// when establishing a new connection. Since we're only using this for making the "cluster
// slots" commands, the READONLY command is not relevant in this context. We're setting it to
// Master to avoid the additional READONLY command.
Extensions::NetworkFilters::Common::Redis::Client::ReadPolicy readPolicy() const override {
return Extensions::NetworkFilters::Common::Redis::Client::ReadPolicy::Any;
return Extensions::NetworkFilters::Common::Redis::Client::ReadPolicy::Master;
}

// Extensions::NetworkFilters::Common::Redis::Client::PoolCallbacks
Expand Down Expand Up @@ -261,8 +263,7 @@ class RedisCluster : public Upstream::BaseDynamicClusterImpl {
Upstream::HostVector hosts_;
Upstream::HostMap all_hosts_;

envoy::api::v2::core::DataSource auth_password_datasource_;
Api::Api& api_;
const std::string auth_password_;
};

class RedisClusterFactory : public Upstream::ConfigurableClusterFactoryBase<
Expand Down
1 change: 1 addition & 0 deletions source/extensions/filters/network/common/redis/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ envoy_cc_library(
deps = [
":client_interface",
":codec_lib",
":utility_lib",
"//include/envoy/router:router_interface",
"//include/envoy/stats:timespan",
"//include/envoy/thread_local:thread_local_interface",
Expand Down
11 changes: 10 additions & 1 deletion source/extensions/filters/network/common/redis/client.h
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,12 @@ class Client : public Event::DeferredDeletable {
* for some reason.
*/
virtual PoolRequest* makeRequest(const RespValue& request, PoolCallbacks& callbacks) PURE;

/**
* Initialize the connection. Issue the auth command and readonly command as needed.
* @param auth password for upstream host.
*/
virtual void initialize(const std::string& auth_password) PURE;
};

using ClientPtr = std::unique_ptr<Client>;
Expand Down Expand Up @@ -187,12 +193,15 @@ class ClientFactory {
* @param host supplies the upstream host.
* @param dispatcher supplies the owning thread's dispatcher.
* @param config supplies the connection pool configuration.
* @param redis_command_stats supplies the redis command stats.
* @param scope supplies the stats scope.
* @param auth password for upstream host.
* @return ClientPtr a new connection pool client.
*/
virtual ClientPtr create(Upstream::HostConstSharedPtr host, Event::Dispatcher& dispatcher,
const Config& config,
const RedisCommandStatsSharedPtr& redis_command_stats,
Stats::Scope& scope) PURE;
Stats::Scope& scope, const std::string& auth_password) PURE;
};

} // namespace Client
Expand Down
24 changes: 21 additions & 3 deletions source/extensions/filters/network/common/redis/client_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ namespace NetworkFilters {
namespace Common {
namespace Redis {
namespace Client {
namespace {
Common::Redis::Client::DoNothingPoolCallbacks null_pool_callbacks;
} // namespace

ConfigImpl::ConfigImpl(
const envoy::config::filter::network::redis_proxy::v2::RedisProxy::ConnPoolSettings& config)
Expand Down Expand Up @@ -275,14 +278,29 @@ void ClientImpl::PendingRequest::cancel() {
canceled_ = true;
}

void ClientImpl::initialize(const std::string& auth_password) {
if (!auth_password.empty()) {
// Send an AUTH command to the upstream server.
makeRequest(Utility::makeAuthCommand(auth_password), null_pool_callbacks);
}
// Any connection to replica requires the READONLY command in order to perform read.
// Also the READONLY command is a no-opt for the master.
// We only need to send the READONLY command iff it's possible that the host is a replica.
if (config_.readPolicy() != Common::Redis::Client::ReadPolicy::Master) {
makeRequest(Utility::ReadOnlyRequest::instance(), null_pool_callbacks);
}
}

ClientFactoryImpl ClientFactoryImpl::instance_;

ClientPtr ClientFactoryImpl::create(Upstream::HostConstSharedPtr host,
Event::Dispatcher& dispatcher, const Config& config,
const RedisCommandStatsSharedPtr& redis_command_stats,
Stats::Scope& scope) {
return ClientImpl::create(host, dispatcher, EncoderPtr{new EncoderImpl()}, decoder_factory_,
config, redis_command_stats, scope);
Stats::Scope& scope, const std::string& auth_password) {
ClientPtr client = ClientImpl::create(host, dispatcher, EncoderPtr{new EncoderImpl()},
decoder_factory_, config, redis_command_stats, scope);
client->initialize(auth_password);
Copy link
Member

Choose a reason for hiding this comment

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

Is there any reason to have an explicit initialize function vs. doing this from within the context of the create/constructor call?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Having the explicit initialize function makes it easier to test and adhere to the single responsibility principal better. The small perf hit of a function call is probably not a big issue given we don't create that many connections. I'm open to change this if you think it's better to put it in the create call.

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 fine if you need it for testing, but generally I try to avoid extra functions unless its really beneficial.

return client;
}

} // namespace Client
Expand Down
4 changes: 3 additions & 1 deletion source/extensions/filters/network/common/redis/client_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#include "common/upstream/upstream_impl.h"

#include "extensions/filters/network/common/redis/client.h"
#include "extensions/filters/network/common/redis/utility.h"

namespace Envoy {
namespace Extensions {
Expand Down Expand Up @@ -85,6 +86,7 @@ class ClientImpl : public Client, public DecoderCallbacks, public Network::Conne
PoolRequest* makeRequest(const RespValue& request, PoolCallbacks& callbacks) override;
bool active() override { return !pending_requests_.empty(); }
void flushBufferAndResetTimer();
void initialize(const std::string& auth_password) override;

private:
friend class RedisClientImplTest;
Expand Down Expand Up @@ -148,7 +150,7 @@ class ClientFactoryImpl : public ClientFactory {
// RedisProxy::ConnPool::ClientFactoryImpl
ClientPtr create(Upstream::HostConstSharedPtr host, Event::Dispatcher& dispatcher,
const Config& config, const RedisCommandStatsSharedPtr& redis_command_stats,
Stats::Scope& scope) override;
Stats::Scope& scope, const std::string& auth_password) override;

static ClientFactoryImpl instance_;

Expand Down
1 change: 1 addition & 0 deletions source/extensions/filters/network/redis_proxy/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ envoy_cc_library(
hdrs = ["config.h"],
deps = [
"//include/envoy/registry",
"//include/envoy/upstream:upstream_interface",
"//source/common/config:filter_json_lib",
"//source/extensions/filters/network:well_known_names",
"//source/extensions/filters/network/common:factory_base_lib",
Expand Down
13 changes: 13 additions & 0 deletions source/extensions/filters/network/redis_proxy/config.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@

#include <string>

#include "envoy/api/api.h"
#include "envoy/config/filter/network/redis_proxy/v2/redis_proxy.pb.h"
#include "envoy/config/filter/network/redis_proxy/v2/redis_proxy.pb.validate.h"
#include "envoy/upstream/upstream.h"

#include "common/common/empty_string.h"
#include "common/config/datasource.h"

#include "extensions/filters/network/common/factory_base.h"
Expand All @@ -29,6 +32,16 @@ class ProtocolOptionsConfigImpl : public Upstream::ProtocolOptionsConfig {
return auth_password_;
}

static const std::string auth_password(const Upstream::ClusterInfoConstSharedPtr info,
Api::Api& api) {
auto options = info->extensionProtocolOptionsTyped<ProtocolOptionsConfigImpl>(
NetworkFilterNames::get().RedisProxy);
if (options) {
return options->auth_password(api);
}
return EMPTY_STRING;
}

private:
envoy::api::v2::core::DataSource auth_password_;
};
Expand Down
Loading