diff --git a/api/envoy/extensions/filters/network/redis_proxy/v3/redis_proxy.proto b/api/envoy/extensions/filters/network/redis_proxy/v3/redis_proxy.proto index 143bd4da65e12..658ac1c16b8ca 100644 --- a/api/envoy/extensions/filters/network/redis_proxy/v3/redis_proxy.proto +++ b/api/envoy/extensions/filters/network/redis_proxy/v3/redis_proxy.proto @@ -22,7 +22,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // Redis Proxy :ref:`configuration overview `. // [#extension: envoy.filters.network.redis_proxy] -// [#next-free-field: 7] +// [#next-free-field: 8] message RedisProxy { option (udpa.annotations.versioning).previous_message_type = "envoy.config.filter.network.redis_proxy.v2.RedisProxy"; @@ -234,6 +234,18 @@ message RedisProxy { // client. If an AUTH command is received when the password is not set, then an "ERR Client sent // AUTH, but no password is set" error will be returned. config.core.v3.DataSource downstream_auth_password = 6 [(udpa.annotations.sensitive) = true]; + + // If a username is provided an ACL style AUTH command will be required with a username and password. + // Authenticate Redis client connections locally by forcing downstream clients to issue a `Redis + // AUTH command `_ with this username and the *downstream_auth_password* + // before enabling any other command. If an AUTH command's username and password matches this username + // and the *downstream_auth_password* , an "OK" response will be returned to the client. If the AUTH + // command username or password does not match this username or the *downstream_auth_password*, then an + // "WRONGPASS invalid username-password pair" error will be returned. If any other command is received before AUTH when this + // password is set, then a "NOAUTH Authentication required." error response will be sent to the + // client. If an AUTH command is received when the password is not set, then an "ERR Client sent + // AUTH, but no ACL is set" error will be returned. + config.core.v3.DataSource downstream_auth_username = 7 [(udpa.annotations.sensitive) = true]; } // RedisProtocolOptions specifies Redis upstream protocol options. This object is used in @@ -246,4 +258,8 @@ message RedisProtocolOptions { // Upstream server password as defined by the `requirepass` directive // `_ in the server's configuration file. config.core.v3.DataSource auth_password = 1 [(udpa.annotations.sensitive) = true]; + + // Upstream server username as defined by the `user` directive + // `_ in the server's configuration file. + config.core.v3.DataSource auth_username = 2 [(udpa.annotations.sensitive) = true]; } diff --git a/docs/root/version_history/current.rst b/docs/root/version_history/current.rst index 56ea33449ca2c..08ccc6e673f53 100644 --- a/docs/root/version_history/current.rst +++ b/docs/root/version_history/current.rst @@ -98,6 +98,7 @@ New Features * network filters: added a :ref:`postgres proxy filter `. * network filters: added a :ref:`rocketmq proxy filter `. * ratelimit: added :ref:`API version ` to explicitly set the version of gRPC service endpoint and message to be used. +* redis: added acl support :ref:`downstream_auth_username ` for downstream client ACL authentication, and :ref:`auth_username ` to configure authentication usernames for upstream Redis 6+ server clusters with ACL enabled. * request_id: added to :ref:`always_set_request_id_in_response setting ` to set :ref:`x-request-id ` header in response even if tracing is not forced. diff --git a/generated_api_shadow/envoy/extensions/filters/network/redis_proxy/v3/redis_proxy.proto b/generated_api_shadow/envoy/extensions/filters/network/redis_proxy/v3/redis_proxy.proto index b9ca387f4ca55..098f5f4a2ea97 100644 --- a/generated_api_shadow/envoy/extensions/filters/network/redis_proxy/v3/redis_proxy.proto +++ b/generated_api_shadow/envoy/extensions/filters/network/redis_proxy/v3/redis_proxy.proto @@ -22,7 +22,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // Redis Proxy :ref:`configuration overview `. // [#extension: envoy.filters.network.redis_proxy] -// [#next-free-field: 7] +// [#next-free-field: 8] message RedisProxy { option (udpa.annotations.versioning).previous_message_type = "envoy.config.filter.network.redis_proxy.v2.RedisProxy"; @@ -230,6 +230,18 @@ message RedisProxy { // AUTH, but no password is set" error will be returned. config.core.v3.DataSource downstream_auth_password = 6 [(udpa.annotations.sensitive) = true]; + // If a username is provided an ACL style AUTH command will be required with a username and password. + // Authenticate Redis client connections locally by forcing downstream clients to issue a `Redis + // AUTH command `_ with this username and the *downstream_auth_password* + // before enabling any other command. If an AUTH command's username and password matches this username + // and the *downstream_auth_password* , an "OK" response will be returned to the client. If the AUTH + // command username or password does not match this username or the *downstream_auth_password*, then an + // "WRONGPASS invalid username-password pair" error will be returned. If any other command is received before AUTH when this + // password is set, then a "NOAUTH Authentication required." error response will be sent to the + // client. If an AUTH command is received when the password is not set, then an "ERR Client sent + // AUTH, but no ACL is set" error will be returned. + config.core.v3.DataSource downstream_auth_username = 7 [(udpa.annotations.sensitive) = true]; + string hidden_envoy_deprecated_cluster = 2 [deprecated = true, (envoy.annotations.disallowed_by_default) = true]; } @@ -244,4 +256,8 @@ message RedisProtocolOptions { // Upstream server password as defined by the `requirepass` directive // `_ in the server's configuration file. config.core.v3.DataSource auth_password = 1 [(udpa.annotations.sensitive) = true]; + + // Upstream server username as defined by the `user` directive + // `_ in the server's configuration file. + config.core.v3.DataSource auth_username = 2 [(udpa.annotations.sensitive) = true]; } diff --git a/source/extensions/clusters/redis/redis_cluster.cc b/source/extensions/clusters/redis/redis_cluster.cc index 6c0e04bdfbc1c..38dbccc60e6af 100644 --- a/source/extensions/clusters/redis/redis_cluster.cc +++ b/source/extensions/clusters/redis/redis_cluster.cc @@ -45,6 +45,8 @@ RedisCluster::RedisCluster( : Config::Utility::translateClusterHosts(cluster.hidden_envoy_deprecated_hosts())), local_info_(factory_context.localInfo()), random_(factory_context.random()), redis_discovery_session_(*this, redis_client_factory), lb_factory_(std::move(lb_factory)), + auth_username_( + NetworkFilters::RedisProxy::ProtocolOptionsConfigImpl::authUsername(info(), api)), auth_password_( NetworkFilters::RedisProxy::ProtocolOptionsConfigImpl::authPassword(info(), api)), cluster_name_(cluster.name()), @@ -278,7 +280,8 @@ void RedisCluster::RedisDiscoverySession::startResolveRedis() { client = std::make_unique(*this); client->host_ = current_host_address_; client->client_ = client_factory_.create(host, dispatcher_, *this, redis_command_stats_, - parent_.info()->statsScope(), parent_.auth_password_); + parent_.info()->statsScope(), parent_.auth_username_, + parent_.auth_password_); client->client_->addConnectionCallbacks(*client); } diff --git a/source/extensions/clusters/redis/redis_cluster.h b/source/extensions/clusters/redis/redis_cluster.h index f716960385c8a..ce0c32dd800e2 100644 --- a/source/extensions/clusters/redis/redis_cluster.h +++ b/source/extensions/clusters/redis/redis_cluster.h @@ -276,6 +276,7 @@ class RedisCluster : public Upstream::BaseDynamicClusterImpl { Upstream::HostVector hosts_; Upstream::HostMap all_hosts_; + const std::string auth_username_; const std::string auth_password_; const std::string cluster_name_; const Common::Redis::ClusterRefreshManagerSharedPtr refresh_manager_; diff --git a/source/extensions/filters/network/common/redis/client.h b/source/extensions/filters/network/common/redis/client.h index b420438ac55f2..0c8a15cb65fc5 100644 --- a/source/extensions/filters/network/common/redis/client.h +++ b/source/extensions/filters/network/common/redis/client.h @@ -106,7 +106,7 @@ class Client : public Event::DeferredDeletable { * 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; + virtual void initialize(const std::string& auth_username, const std::string& auth_password) PURE; }; using ClientPtr = std::unique_ptr; @@ -206,7 +206,8 @@ class ClientFactory { virtual ClientPtr create(Upstream::HostConstSharedPtr host, Event::Dispatcher& dispatcher, const Config& config, const RedisCommandStatsSharedPtr& redis_command_stats, - Stats::Scope& scope, const std::string& auth_password) PURE; + Stats::Scope& scope, const std::string& auth_username, + const std::string& auth_password) PURE; }; } // namespace Client diff --git a/source/extensions/filters/network/common/redis/client_impl.cc b/source/extensions/filters/network/common/redis/client_impl.cc index 9643b725a0111..cadfa95a13716 100644 --- a/source/extensions/filters/network/common/redis/client_impl.cc +++ b/source/extensions/filters/network/common/redis/client_impl.cc @@ -285,8 +285,12 @@ void ClientImpl::PendingRequest::cancel() { canceled_ = true; } -void ClientImpl::initialize(const std::string& auth_password) { - if (!auth_password.empty()) { +void ClientImpl::initialize(const std::string& auth_username, const std::string& auth_password) { + if (!auth_username.empty()) { + // Send an AUTH command to the upstream server with username and password. + Utility::AuthRequest auth_request(auth_username, auth_password); + makeRequest(auth_request, null_pool_callbacks); + } else if (!auth_password.empty()) { // Send an AUTH command to the upstream server. Utility::AuthRequest auth_request(auth_password); makeRequest(auth_request, null_pool_callbacks); @@ -304,10 +308,11 @@ ClientFactoryImpl ClientFactoryImpl::instance_; ClientPtr ClientFactoryImpl::create(Upstream::HostConstSharedPtr host, Event::Dispatcher& dispatcher, const Config& config, const RedisCommandStatsSharedPtr& redis_command_stats, - Stats::Scope& scope, const std::string& auth_password) { + Stats::Scope& scope, const std::string& auth_username, + 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); + client->initialize(auth_username, auth_password); return client; } diff --git a/source/extensions/filters/network/common/redis/client_impl.h b/source/extensions/filters/network/common/redis/client_impl.h index 5d7bcb182ea87..ad5b6231ffb79 100644 --- a/source/extensions/filters/network/common/redis/client_impl.h +++ b/source/extensions/filters/network/common/redis/client_impl.h @@ -87,7 +87,7 @@ class ClientImpl : public Client, public DecoderCallbacks, public Network::Conne PoolRequest* makeRequest(const RespValue& request, ClientCallbacks& callbacks) override; bool active() override { return !pending_requests_.empty(); } void flushBufferAndResetTimer(); - void initialize(const std::string& auth_password) override; + void initialize(const std::string& auth_username, const std::string& auth_password) override; private: friend class RedisClientImplTest; @@ -151,7 +151,8 @@ 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, const std::string& auth_password) override; + Stats::Scope& scope, const std::string& auth_username, + const std::string& auth_password) override; static ClientFactoryImpl instance_; diff --git a/source/extensions/filters/network/common/redis/utility.cc b/source/extensions/filters/network/common/redis/utility.cc index c652addb3e12d..773196dd70e21 100644 --- a/source/extensions/filters/network/common/redis/utility.cc +++ b/source/extensions/filters/network/common/redis/utility.cc @@ -19,6 +19,18 @@ AuthRequest::AuthRequest(const std::string& password) { asArray().swap(values); } +AuthRequest::AuthRequest(const std::string& username, const std::string& password) { + std::vector values(3); + values[0].type(RespType::BulkString); + values[0].asString() = "auth"; + values[1].type(RespType::BulkString); + values[1].asString() = username; + values[2].type(RespType::BulkString); + values[2].asString() = password; + type(RespType::Array); + asArray().swap(values); +} + RespValuePtr makeError(const std::string& error) { Common::Redis::RespValuePtr response(new RespValue()); response->type(Common::Redis::RespType::Error); diff --git a/source/extensions/filters/network/common/redis/utility.h b/source/extensions/filters/network/common/redis/utility.h index b2e77b8e94ab4..ca5774d2d3a6c 100644 --- a/source/extensions/filters/network/common/redis/utility.h +++ b/source/extensions/filters/network/common/redis/utility.h @@ -13,6 +13,7 @@ namespace Utility { class AuthRequest : public Redis::RespValue { public: + AuthRequest(const std::string& username, const std::string& password); AuthRequest(const std::string& password); }; diff --git a/source/extensions/filters/network/redis_proxy/command_splitter.h b/source/extensions/filters/network/redis_proxy/command_splitter.h index 5e1248d3b5001..e03d0a92e1377 100644 --- a/source/extensions/filters/network/redis_proxy/command_splitter.h +++ b/source/extensions/filters/network/redis_proxy/command_splitter.h @@ -41,11 +41,18 @@ class SplitCallbacks { virtual bool connectionAllowed() PURE; /** - * Called when an authentication command has been received. + * Called when an authentication command has been received with a password. * @param password supplies the AUTH password provided by the downstream client. */ virtual void onAuth(const std::string& password) PURE; + /** + * Called when an authentication command has been received with a username and password. + * @param username supplies the AUTH username provided by the downstream client. + * @param password supplies the AUTH password provided by the downstream client. + */ + virtual void onAuth(const std::string& username, const std::string& password) PURE; + /** * Called when the response is ready. * @param value supplies the response which is now owned by the callee. diff --git a/source/extensions/filters/network/redis_proxy/command_splitter_impl.cc b/source/extensions/filters/network/redis_proxy/command_splitter_impl.cc index adfbf7ff9fbe1..a5bd89588f51a 100644 --- a/source/extensions/filters/network/redis_proxy/command_splitter_impl.cc +++ b/source/extensions/filters/network/redis_proxy/command_splitter_impl.cc @@ -439,7 +439,12 @@ SplitRequestPtr InstanceImpl::makeRequest(Common::Redis::RespValuePtr&& request, onInvalidRequest(callbacks); return nullptr; } - callbacks.onAuth(request->asArray()[1].asString()); + if (request->asArray().size() == 3) { + callbacks.onAuth(request->asArray()[1].asString(), request->asArray()[2].asString()); + } else { + callbacks.onAuth(request->asArray()[1].asString()); + } + return nullptr; } diff --git a/source/extensions/filters/network/redis_proxy/config.h b/source/extensions/filters/network/redis_proxy/config.h index e13d0cda331ef..cbb1866018f49 100644 --- a/source/extensions/filters/network/redis_proxy/config.h +++ b/source/extensions/filters/network/redis_proxy/config.h @@ -24,12 +24,27 @@ class ProtocolOptionsConfigImpl : public Upstream::ProtocolOptionsConfig { ProtocolOptionsConfigImpl( const envoy::extensions::filters::network::redis_proxy::v3::RedisProtocolOptions& proto_config) - : auth_password_(proto_config.auth_password()) {} + : auth_username_(proto_config.auth_username()), auth_password_(proto_config.auth_password()) { + } + + std::string authUsername(Api::Api& api) const { + return Config::DataSource::read(auth_username_, true, api); + } std::string authPassword(Api::Api& api) const { return Config::DataSource::read(auth_password_, true, api); } + static const std::string authUsername(const Upstream::ClusterInfoConstSharedPtr info, + Api::Api& api) { + auto options = info->extensionProtocolOptionsTyped( + NetworkFilterNames::get().RedisProxy); + if (options) { + return options->authUsername(api); + } + return EMPTY_STRING; + } + static const std::string authPassword(const Upstream::ClusterInfoConstSharedPtr info, Api::Api& api) { auto options = info->extensionProtocolOptionsTyped( @@ -41,6 +56,7 @@ class ProtocolOptionsConfigImpl : public Upstream::ProtocolOptionsConfig { } private: + envoy::config::core::v3::DataSource auth_username_; envoy::config::core::v3::DataSource auth_password_; }; diff --git a/source/extensions/filters/network/redis_proxy/conn_pool_impl.cc b/source/extensions/filters/network/redis_proxy/conn_pool_impl.cc index 9bc15b0f14a6d..b446901c60720 100644 --- a/source/extensions/filters/network/redis_proxy/conn_pool_impl.cc +++ b/source/extensions/filters/network/redis_proxy/conn_pool_impl.cc @@ -74,6 +74,7 @@ InstanceImpl::ThreadLocalPool::ThreadLocalPool(InstanceImpl& parent, Event::Disp cluster_update_handle_ = parent_.cm_.addThreadLocalClusterUpdateCallbacks(*this); Upstream::ThreadLocalCluster* cluster = parent_.cm_.get(cluster_name_); if (cluster != nullptr) { + auth_username_ = ProtocolOptionsConfigImpl::authUsername(cluster->info(), parent_.api_); auth_password_ = ProtocolOptionsConfigImpl::authPassword(cluster->info(), parent_.api_); onClusterAddOrUpdateNonVirtual(*cluster); } @@ -214,9 +215,9 @@ InstanceImpl::ThreadLocalPool::threadLocalActiveClient(Upstream::HostConstShared if (!client) { client = std::make_unique(*this); client->host_ = host; - client->redis_client_ = parent_.client_factory_.create(host, dispatcher_, parent_.config_, - parent_.redis_command_stats_, - *parent_.stats_scope_, auth_password_); + client->redis_client_ = parent_.client_factory_.create( + host, dispatcher_, parent_.config_, parent_.redis_command_stats_, *parent_.stats_scope_, + auth_username_, auth_password_); client->redis_client_->addConnectionCallbacks(*client); } return client; diff --git a/source/extensions/filters/network/redis_proxy/conn_pool_impl.h b/source/extensions/filters/network/redis_proxy/conn_pool_impl.h index 6dcb695efac80..6fa31f2ca0ea8 100644 --- a/source/extensions/filters/network/redis_proxy/conn_pool_impl.h +++ b/source/extensions/filters/network/redis_proxy/conn_pool_impl.h @@ -157,6 +157,7 @@ class InstanceImpl : public Instance { std::unordered_map client_map_; Envoy::Common::CallbackHandle* host_set_member_update_cb_handle_{}; std::unordered_map host_address_map_; + std::string auth_username_; std::string auth_password_; std::list created_via_redirect_hosts_; std::list clients_to_drain_; diff --git a/source/extensions/filters/network/redis_proxy/proxy_filter.cc b/source/extensions/filters/network/redis_proxy/proxy_filter.cc index 7782485d5ec9a..aa2f558cc51af 100644 --- a/source/extensions/filters/network/redis_proxy/proxy_filter.cc +++ b/source/extensions/filters/network/redis_proxy/proxy_filter.cc @@ -23,6 +23,8 @@ ProxyFilterConfig::ProxyFilterConfig( : drain_decision_(drain_decision), runtime_(runtime), stat_prefix_(fmt::format("redis.{}.", config.stat_prefix())), stats_(generateStats(stat_prefix_, scope)), + downstream_auth_username_( + Config::DataSource::read(config.downstream_auth_username(), true, api)), downstream_auth_password_( Config::DataSource::read(config.downstream_auth_password(), true, api)) {} @@ -38,7 +40,8 @@ ProxyFilter::ProxyFilter(Common::Redis::DecoderFactory& factory, config_(config) { config_->stats_.downstream_cx_total_.inc(); config_->stats_.downstream_cx_active_.inc(); - connection_allowed_ = config_->downstream_auth_password_.empty(); + connection_allowed_ = + config_->downstream_auth_username_.empty() && config_->downstream_auth_password_.empty(); } ProxyFilter::~ProxyFilter() { @@ -96,6 +99,31 @@ void ProxyFilter::onAuth(PendingRequest& request, const std::string& password) { request.onResponse(std::move(response)); } +void ProxyFilter::onAuth(PendingRequest& request, const std::string& username, + const std::string& password) { + Common::Redis::RespValuePtr response{new Common::Redis::RespValue()}; + if (config_->downstream_auth_username_.empty() && config_->downstream_auth_password_.empty()) { + response->type(Common::Redis::RespType::Error); + response->asString() = "ERR Client sent AUTH, but no username-password pair is set"; + } else if (config_->downstream_auth_username_.empty() && username == "default" && + password == config_->downstream_auth_password_) { + // empty username and "default" are synonymous in Redis 6 ACLs + response->type(Common::Redis::RespType::SimpleString); + response->asString() = "OK"; + connection_allowed_ = true; + } else if (username == config_->downstream_auth_username_ && + password == config_->downstream_auth_password_) { + response->type(Common::Redis::RespType::SimpleString); + response->asString() = "OK"; + connection_allowed_ = true; + } else { + response->type(Common::Redis::RespType::Error); + response->asString() = "WRONGPASS invalid username-password pair"; + connection_allowed_ = false; + } + request.onResponse(std::move(response)); +} + void ProxyFilter::onResponse(PendingRequest& request, Common::Redis::RespValuePtr&& value) { ASSERT(!pending_requests_.empty()); request.pending_response_ = std::move(value); diff --git a/source/extensions/filters/network/redis_proxy/proxy_filter.h b/source/extensions/filters/network/redis_proxy/proxy_filter.h index 23ebd3e0f039b..1694a2a0640e9 100644 --- a/source/extensions/filters/network/redis_proxy/proxy_filter.h +++ b/source/extensions/filters/network/redis_proxy/proxy_filter.h @@ -57,6 +57,7 @@ class ProxyFilterConfig { const std::string stat_prefix_; const std::string redis_drain_close_runtime_key_{"redis.drain_close_enabled"}; ProxyStats stats_; + const std::string downstream_auth_username_; const std::string downstream_auth_password_; private: @@ -100,6 +101,9 @@ class ProxyFilter : public Network::ReadFilter, // RedisProxy::CommandSplitter::SplitCallbacks bool connectionAllowed() override { return parent_.connectionAllowed(); } void onAuth(const std::string& password) override { parent_.onAuth(*this, password); } + void onAuth(const std::string& username, const std::string& password) override { + parent_.onAuth(*this, username, password); + } void onResponse(Common::Redis::RespValuePtr&& value) override { parent_.onResponse(*this, std::move(value)); } @@ -110,6 +114,7 @@ class ProxyFilter : public Network::ReadFilter, }; void onAuth(PendingRequest& request, const std::string& password); + void onAuth(PendingRequest& request, const std::string& username, const std::string& password); void onResponse(PendingRequest& request, Common::Redis::RespValuePtr&& value); Common::Redis::DecoderPtr decoder_; diff --git a/source/extensions/health_checkers/redis/redis.cc b/source/extensions/health_checkers/redis/redis.cc index d508193445da8..2f403b9742e58 100644 --- a/source/extensions/health_checkers/redis/redis.cc +++ b/source/extensions/health_checkers/redis/redis.cc @@ -65,7 +65,7 @@ void RedisHealthChecker::RedisActiveHealthCheckSession::onInterval() { if (!client_) { client_ = parent_.client_factory_.create( host_, parent_.dispatcher_, *this, redis_command_stats_, - parent_.cluster_.info()->statsScope(), parent_.auth_password_); + parent_.cluster_.info()->statsScope(), "", parent_.auth_password_); client_->addConnectionCallbacks(*this); } diff --git a/test/extensions/clusters/redis/redis_cluster_integration_test.cc b/test/extensions/clusters/redis/redis_cluster_integration_test.cc index 1bcb951423091..ed83eab698d74 100644 --- a/test/extensions/clusters/redis/redis_cluster_integration_test.cc +++ b/test/extensions/clusters/redis/redis_cluster_integration_test.cc @@ -194,7 +194,7 @@ class RedisClusterIntegrationTest : public testing::TestWithParamwaitForData(auth_command.size() + request.size(), &proxy_to_server)); // The original request should be the same as the data received by the server. @@ -239,13 +242,14 @@ class RedisClusterIntegrationTest : public testing::TestWithParamclose(); EXPECT_TRUE(fake_upstream_connection->close()); } void expectCallClusterSlot(int stream_index, std::string& response, + const std::string& auth_username = "", const std::string& auth_password = "") { std::string cluster_slot_request = makeBulkStringArray({"CLUSTER", "SLOTS"}); @@ -259,10 +263,18 @@ class RedisClusterIntegrationTest : public testing::TestWithParamwaitForData(cluster_slot_request.size(), &proxied_cluster_slot_request)); EXPECT_EQ(cluster_slot_request, proxied_cluster_slot_request); - } else { + } else if (auth_username.empty()) { std::string auth_request = makeBulkStringArray({"auth", auth_password}); std::string ok = "+OK\r\n"; + EXPECT_TRUE(fake_upstream_connection_->waitForData( + auth_request.size() + cluster_slot_request.size(), &proxied_cluster_slot_request)); + EXPECT_EQ(auth_request + cluster_slot_request, proxied_cluster_slot_request); + EXPECT_TRUE(fake_upstream_connection_->write(ok)); + } else { + std::string auth_request = makeBulkStringArray({"auth", auth_username, auth_password}); + std::string ok = "+OK\r\n"; + EXPECT_TRUE(fake_upstream_connection_->waitForData( auth_request.size() + cluster_slot_request.size(), &proxied_cluster_slot_request)); EXPECT_EQ(auth_request + cluster_slot_request, proxied_cluster_slot_request); @@ -525,7 +537,7 @@ TEST_P(RedisClusterWithAuthIntegrationTest, SingleSlotMasterReplica) { on_server_init_function_ = [this]() { std::string cluster_slot_response = singleSlotMasterReplica( fake_upstreams_[0]->localAddress()->ip(), fake_upstreams_[1]->localAddress()->ip()); - expectCallClusterSlot(0, cluster_slot_response, "somepassword"); + expectCallClusterSlot(0, cluster_slot_response, "", "somepassword"); }; initialize(); @@ -534,7 +546,8 @@ TEST_P(RedisClusterWithAuthIntegrationTest, SingleSlotMasterReplica) { FakeRawConnectionPtr fake_upstream_connection; roundtripToUpstreamStep(fake_upstreams_[random_index_], makeBulkStringArray({"get", "foo"}), - "$3\r\nbar\r\n", redis_client, fake_upstream_connection, "somepassword"); + "$3\r\nbar\r\n", redis_client, fake_upstream_connection, "", + "somepassword"); redis_client->close(); EXPECT_TRUE(fake_upstream_connection->close()); diff --git a/test/extensions/clusters/redis/redis_cluster_test.cc b/test/extensions/clusters/redis/redis_cluster_test.cc index 6b9a87ab778a3..5936773382ad9 100644 --- a/test/extensions/clusters/redis/redis_cluster_test.cc +++ b/test/extensions/clusters/redis/redis_cluster_test.cc @@ -65,7 +65,7 @@ class RedisClusterTest : public testing::Test, create(Upstream::HostConstSharedPtr host, Event::Dispatcher&, const Extensions::NetworkFilters::Common::Redis::Client::Config&, const Extensions::NetworkFilters::Common::Redis::RedisCommandStatsSharedPtr&, - Stats::Scope&, const std::string&) override { + Stats::Scope&, const std::string&, const std::string&) override { EXPECT_EQ(22120, host->address()->ip()->port()); return Extensions::NetworkFilters::Common::Redis::Client::ClientPtr{ create_(host->address()->asString())}; diff --git a/test/extensions/filters/network/common/redis/client_impl_test.cc b/test/extensions/filters/network/common/redis/client_impl_test.cc index c9028a1da42ac..d5402aeaa6dbf 100644 --- a/test/extensions/filters/network/common/redis/client_impl_test.cc +++ b/test/extensions/filters/network/common/redis/client_impl_test.cc @@ -116,7 +116,7 @@ class RedisClientImplTest : public testing::Test, Common::Redis::RespValue readonly_request = Utility::ReadOnlyRequest::instance(); EXPECT_CALL(*encoder_, encode(Eq(readonly_request), _)); EXPECT_CALL(*flush_timer_, enabled()).WillOnce(Return(false)); - client_->initialize(auth_password_); + client_->initialize(auth_username_, auth_password_); EXPECT_EQ(1UL, host_->cluster_.stats_.upstream_rq_total_.value()); EXPECT_EQ(1UL, host_->cluster_.stats_.upstream_rq_active_.value()); @@ -143,6 +143,7 @@ class RedisClientImplTest : public testing::Test, NiceMock stats_; Stats::ScopePtr stats_scope_; Common::Redis::RedisCommandStatsSharedPtr redis_command_stats_; + std::string auth_username_; std::string auth_password_; }; @@ -290,7 +291,7 @@ TEST_F(RedisClientImplTest, Basic) { setup(); - client_->initialize(auth_password_); + client_->initialize(auth_username_, auth_password_); Common::Redis::RespValue request1; MockClientCallbacks callbacks1; @@ -370,7 +371,7 @@ TEST_F(RedisClientImplTest, CommandStatsDisabledSingleRequest) { setup(); - client_->initialize(auth_password_); + client_->initialize(auth_username_, auth_password_); std::string get_command = "get"; @@ -426,7 +427,7 @@ TEST_F(RedisClientImplTest, CommandStatsEnabledTwoRequests) { setup(std::make_unique()); - client_->initialize(auth_password_); + client_->initialize(auth_username_, auth_password_); std::string get_command = "get"; @@ -511,7 +512,29 @@ TEST_F(RedisClientImplTest, InitializedWithAuthPassword) { Utility::AuthRequest auth_request(auth_password_); EXPECT_CALL(*encoder_, encode(Eq(auth_request), _)); EXPECT_CALL(*flush_timer_, enabled()).WillOnce(Return(false)); - client_->initialize(auth_password_); + client_->initialize(auth_username_, auth_password_); + + EXPECT_EQ(1UL, host_->cluster_.stats_.upstream_rq_total_.value()); + EXPECT_EQ(1UL, host_->cluster_.stats_.upstream_rq_active_.value()); + EXPECT_EQ(1UL, host_->stats_.rq_total_.value()); + EXPECT_EQ(1UL, host_->stats_.rq_active_.value()); + + EXPECT_CALL(*upstream_connection_, close(Network::ConnectionCloseType::NoFlush)); + EXPECT_CALL(*connect_or_op_timer_, disableTimer()); + client_->close(); +} + +TEST_F(RedisClientImplTest, InitializedWithAuthAcl) { + InSequence s; + + setup(); + + auth_username_ = "testing username"; + auth_password_ = "testing password"; + Utility::AuthRequest auth_request(auth_username_, auth_password_); + EXPECT_CALL(*encoder_, encode(Eq(auth_request), _)); + EXPECT_CALL(*flush_timer_, enabled()).WillOnce(Return(false)); + client_->initialize(auth_username_, auth_password_); EXPECT_EQ(1UL, host_->cluster_.stats_.upstream_rq_total_.value()); EXPECT_EQ(1UL, host_->cluster_.stats_.upstream_rq_active_.value()); @@ -1188,9 +1211,10 @@ TEST(RedisClientFactoryImplTest, Basic) { Stats::IsolatedStoreImpl stats_; auto redis_command_stats = Common::Redis::RedisCommandStats::createRedisCommandStats(stats_.symbolTable()); + const std::string auth_username; const std::string auth_password; - ClientPtr client = - factory.create(host, dispatcher, config, redis_command_stats, stats_, auth_password); + ClientPtr client = factory.create(host, dispatcher, config, redis_command_stats, stats_, + auth_username, auth_password); client->close(); } } // namespace Client diff --git a/test/extensions/filters/network/common/redis/mocks.h b/test/extensions/filters/network/common/redis/mocks.h index 0561c7bb57e0a..4f8f11bdaa4b5 100644 --- a/test/extensions/filters/network/common/redis/mocks.h +++ b/test/extensions/filters/network/common/redis/mocks.h @@ -87,7 +87,7 @@ class MockClient : public Client { MOCK_METHOD(void, close, ()); MOCK_METHOD(PoolRequest*, makeRequest_, (const Common::Redis::RespValue& request, ClientCallbacks& callbacks)); - MOCK_METHOD(void, initialize, (const std::string& password)); + MOCK_METHOD(void, initialize, (const std::string& username, const std::string& password)); std::list callbacks_; std::list client_callbacks_; diff --git a/test/extensions/filters/network/redis_proxy/command_lookup_speed_test.cc b/test/extensions/filters/network/redis_proxy/command_lookup_speed_test.cc index a6ccc4f1b59d2..edf29c9730921 100644 --- a/test/extensions/filters/network/redis_proxy/command_lookup_speed_test.cc +++ b/test/extensions/filters/network/redis_proxy/command_lookup_speed_test.cc @@ -28,6 +28,7 @@ class NoOpSplitCallbacks : public CommandSplitter::SplitCallbacks { bool connectionAllowed() override { return true; } void onAuth(const std::string&) override {} + void onAuth(const std::string&, const std::string&) override {} void onResponse(Common::Redis::RespValuePtr&&) override {} }; diff --git a/test/extensions/filters/network/redis_proxy/conn_pool_impl_test.cc b/test/extensions/filters/network/redis_proxy/conn_pool_impl_test.cc index 639a9b8313c88..f3350db7cd5f8 100644 --- a/test/extensions/filters/network/redis_proxy/conn_pool_impl_test.cc +++ b/test/extensions/filters/network/redis_proxy/conn_pool_impl_test.cc @@ -84,6 +84,7 @@ class RedisConnPoolImplTest : public testing::Test, public Common::Redis::Client read_policy_), api_, std::move(store), redis_command_stats, cluster_refresh_manager_); // Set the authentication password for this connection pool. + conn_pool_impl->tls_->getTyped().auth_username_ = auth_username_; conn_pool_impl->tls_->getTyped().auth_password_ = auth_password_; conn_pool_ = std::move(conn_pool_impl); test_address_ = Network::Utility::resolveUrl("tcp://127.0.0.1:3000"); @@ -199,7 +200,9 @@ class RedisConnPoolImplTest : public testing::Test, public Common::Redis::Client Common::Redis::Client::ClientPtr create(Upstream::HostConstSharedPtr host, Event::Dispatcher&, const Common::Redis::Client::Config&, const Common::Redis::RedisCommandStatsSharedPtr&, - Stats::Scope&, const std::string& password) override { + Stats::Scope&, const std::string& username, + const std::string& password) override { + EXPECT_EQ(auth_username_, username); EXPECT_EQ(auth_password_, password); return Common::Redis::Client::ClientPtr{create_(host)}; } @@ -273,6 +276,7 @@ class RedisConnPoolImplTest : public testing::Test, public Common::Redis::Client Upstream::ClusterUpdateCallbacks* update_callbacks_{}; Common::Redis::Client::MockClient* client_{}; Network::Address::InstanceConstSharedPtr test_address_; + std::string auth_username_; std::string auth_password_; NiceMock api_; envoy::extensions::filters::network::redis_proxy::v3::RedisProxy::ConnPoolSettings::ReadPolicy diff --git a/test/extensions/filters/network/redis_proxy/mocks.h b/test/extensions/filters/network/redis_proxy/mocks.h index 5bb208bfa9019..b093ad35b9b94 100644 --- a/test/extensions/filters/network/redis_proxy/mocks.h +++ b/test/extensions/filters/network/redis_proxy/mocks.h @@ -101,6 +101,7 @@ class MockSplitCallbacks : public SplitCallbacks { MOCK_METHOD(bool, connectionAllowed, ()); MOCK_METHOD(void, onAuth, (const std::string& password)); + MOCK_METHOD(void, onAuth, (const std::string& username, const std::string& password)); MOCK_METHOD(void, onResponse_, (Common::Redis::RespValuePtr & value)); }; diff --git a/test/extensions/filters/network/redis_proxy/proxy_filter_test.cc b/test/extensions/filters/network/redis_proxy/proxy_filter_test.cc index 72cebf97fcd28..f094c02b665a0 100644 --- a/test/extensions/filters/network/redis_proxy/proxy_filter_test.cc +++ b/test/extensions/filters/network/redis_proxy/proxy_filter_test.cc @@ -63,6 +63,7 @@ TEST_F(RedisProxyFilterConfigTest, Normal) { parseProtoFromYaml(yaml_string); ProxyFilterConfig config(proto_config, store_, drain_decision_, runtime_, api_); EXPECT_EQ("redis.foo.", config.stat_prefix_); + EXPECT_TRUE(config.downstream_auth_username_.empty()); EXPECT_TRUE(config.downstream_auth_password_.empty()); } @@ -93,6 +94,27 @@ TEST_F(RedisProxyFilterConfigTest, DownstreamAuthPasswordSet) { EXPECT_EQ(config.downstream_auth_password_, "somepassword"); } +TEST_F(RedisProxyFilterConfigTest, DownstreamAuthAclSet) { + const std::string yaml_string = R"EOF( + prefix_routes: + catch_all_route: + cluster: fake_cluster + stat_prefix: foo + settings: + op_timeout: 0.01s + downstream_auth_username: + inline_string: someusername + downstream_auth_password: + inline_string: somepassword + )EOF"; + + envoy::extensions::filters::network::redis_proxy::v3::RedisProxy proto_config = + parseProtoFromYaml(yaml_string); + ProxyFilterConfig config(proto_config, store_, drain_decision_, runtime_, api_); + EXPECT_EQ(config.downstream_auth_username_, "someusername"); + EXPECT_EQ(config.downstream_auth_password_, "somepassword"); +} + class RedisProxyFilterTest : public testing::Test, public Common::Redis::DecoderFactory { public: static constexpr const char* DefaultConfig = R"EOF( @@ -310,6 +332,33 @@ TEST_F(RedisProxyFilterTest, AuthWhenNotRequired) { EXPECT_EQ(Network::FilterStatus::Continue, filter_->onData(fake_data, false)); } +TEST_F(RedisProxyFilterTest, AuthAclWhenNotRequired) { + InSequence s; + + Buffer::OwnedImpl fake_data; + Common::Redis::RespValuePtr request(new Common::Redis::RespValue()); + EXPECT_CALL(*decoder_, decode(Ref(fake_data))).WillOnce(Invoke([&](Buffer::Instance&) -> void { + decoder_callbacks_->onRespValue(std::move(request)); + })); + EXPECT_CALL(splitter_, makeRequest_(Ref(*request), _)) + .WillOnce( + Invoke([&](const Common::Redis::RespValue&, + CommandSplitter::SplitCallbacks& callbacks) -> CommandSplitter::SplitRequest* { + EXPECT_TRUE(callbacks.connectionAllowed()); + Common::Redis::RespValuePtr error(new Common::Redis::RespValue()); + error->type(Common::Redis::RespType::Error); + error->asString() = "ERR Client sent AUTH, but no username-password pair is set"; + EXPECT_CALL(*encoder_, encode(Eq(ByRef(*error)), _)); + EXPECT_CALL(filter_callbacks_.connection_, write(_, _)); + callbacks.onAuth("foo", "bar"); + // callbacks cannot be accessed now. + EXPECT_TRUE(filter_->connectionAllowed()); + return nullptr; + })); + + EXPECT_EQ(Network::FilterStatus::Continue, filter_->onData(fake_data, false)); +} + const std::string downstream_auth_password_config = R"EOF( prefix_routes: catch_all_route: @@ -380,6 +429,105 @@ TEST_F(RedisProxyFilterWithAuthPasswordTest, AuthPasswordIncorrect) { EXPECT_EQ(Network::FilterStatus::Continue, filter_->onData(fake_data, false)); } +const std::string downstream_auth_acl_config = R"EOF( +prefix_routes: + catch_all_route: + cluster: fake_cluster +stat_prefix: foo +settings: + op_timeout: 0.01s +downstream_auth_username: + inline_string: someusername +downstream_auth_password: + inline_string: somepassword +)EOF"; + +class RedisProxyFilterWithAuthAclTest : public RedisProxyFilterTest { +public: + RedisProxyFilterWithAuthAclTest() : RedisProxyFilterTest(downstream_auth_acl_config) {} +}; + +TEST_F(RedisProxyFilterWithAuthAclTest, AuthAclCorrect) { + InSequence s; + + Buffer::OwnedImpl fake_data; + Common::Redis::RespValuePtr request(new Common::Redis::RespValue()); + EXPECT_CALL(*decoder_, decode(Ref(fake_data))).WillOnce(Invoke([&](Buffer::Instance&) -> void { + decoder_callbacks_->onRespValue(std::move(request)); + })); + EXPECT_CALL(splitter_, makeRequest_(Ref(*request), _)) + .WillOnce( + Invoke([&](const Common::Redis::RespValue&, + CommandSplitter::SplitCallbacks& callbacks) -> CommandSplitter::SplitRequest* { + EXPECT_FALSE(callbacks.connectionAllowed()); + Common::Redis::RespValuePtr reply(new Common::Redis::RespValue()); + reply->type(Common::Redis::RespType::SimpleString); + reply->asString() = "OK"; + EXPECT_CALL(*encoder_, encode(Eq(ByRef(*reply)), _)); + EXPECT_CALL(filter_callbacks_.connection_, write(_, _)); + callbacks.onAuth("someusername", "somepassword"); + // callbacks cannot be accessed now. + EXPECT_TRUE(filter_->connectionAllowed()); + return nullptr; + })); + + EXPECT_EQ(Network::FilterStatus::Continue, filter_->onData(fake_data, false)); +} + +TEST_F(RedisProxyFilterWithAuthAclTest, AuthAclUsernameIncorrect) { + InSequence s; + + Buffer::OwnedImpl fake_data; + Common::Redis::RespValuePtr request(new Common::Redis::RespValue()); + EXPECT_CALL(*decoder_, decode(Ref(fake_data))).WillOnce(Invoke([&](Buffer::Instance&) -> void { + decoder_callbacks_->onRespValue(std::move(request)); + })); + EXPECT_CALL(splitter_, makeRequest_(Ref(*request), _)) + .WillOnce( + Invoke([&](const Common::Redis::RespValue&, + CommandSplitter::SplitCallbacks& callbacks) -> CommandSplitter::SplitRequest* { + EXPECT_FALSE(callbacks.connectionAllowed()); + Common::Redis::RespValuePtr reply(new Common::Redis::RespValue()); + reply->type(Common::Redis::RespType::Error); + reply->asString() = "WRONGPASS invalid username-password pair"; + EXPECT_CALL(*encoder_, encode(Eq(ByRef(*reply)), _)); + EXPECT_CALL(filter_callbacks_.connection_, write(_, _)); + callbacks.onAuth("wrongusername", "somepassword"); + // callbacks cannot be accessed now. + EXPECT_FALSE(filter_->connectionAllowed()); + return nullptr; + })); + + EXPECT_EQ(Network::FilterStatus::Continue, filter_->onData(fake_data, false)); +} + +TEST_F(RedisProxyFilterWithAuthAclTest, AuthAclPasswordIncorrect) { + InSequence s; + + Buffer::OwnedImpl fake_data; + Common::Redis::RespValuePtr request(new Common::Redis::RespValue()); + EXPECT_CALL(*decoder_, decode(Ref(fake_data))).WillOnce(Invoke([&](Buffer::Instance&) -> void { + decoder_callbacks_->onRespValue(std::move(request)); + })); + EXPECT_CALL(splitter_, makeRequest_(Ref(*request), _)) + .WillOnce( + Invoke([&](const Common::Redis::RespValue&, + CommandSplitter::SplitCallbacks& callbacks) -> CommandSplitter::SplitRequest* { + EXPECT_FALSE(callbacks.connectionAllowed()); + Common::Redis::RespValuePtr reply(new Common::Redis::RespValue()); + reply->type(Common::Redis::RespType::Error); + reply->asString() = "WRONGPASS invalid username-password pair"; + EXPECT_CALL(*encoder_, encode(Eq(ByRef(*reply)), _)); + EXPECT_CALL(filter_callbacks_.connection_, write(_, _)); + callbacks.onAuth("someusername", "wrongpassword"); + // callbacks cannot be accessed now. + EXPECT_FALSE(filter_->connectionAllowed()); + return nullptr; + })); + + EXPECT_EQ(Network::FilterStatus::Continue, filter_->onData(fake_data, false)); +} + } // namespace RedisProxy } // namespace NetworkFilters } // namespace Extensions diff --git a/test/extensions/filters/network/redis_proxy/redis_proxy_integration_test.cc b/test/extensions/filters/network/redis_proxy/redis_proxy_integration_test.cc index 7625f406b1adc..f44f10b9d1de4 100644 --- a/test/extensions/filters/network/redis_proxy/redis_proxy_integration_test.cc +++ b/test/extensions/filters/network/redis_proxy/redis_proxy_integration_test.cc @@ -355,12 +355,13 @@ class RedisProxyIntegrationTest : public testing::TestWithParamclearData(); redis_client->write(request); expectUpstreamRequestResponse(upstream, request, response, fake_upstream_connection, - auth_password); + auth_username, auth_password); redis_client->waitForData(response); // The original response should be received by the fake Redis client. @@ -489,7 +492,8 @@ void RedisProxyIntegrationTest::roundtripToUpstreamStep( void RedisProxyIntegrationTest::expectUpstreamRequestResponse( FakeUpstreamPtr& upstream, const std::string& request, const std::string& response, - FakeRawConnectionPtr& fake_upstream_connection, const std::string& auth_password) { + FakeRawConnectionPtr& fake_upstream_connection, const std::string& auth_username, + const std::string& auth_password) { std::string proxy_to_server; bool expect_auth_command = false; std::string ok = "+OK\r\n"; @@ -499,7 +503,9 @@ void RedisProxyIntegrationTest::expectUpstreamRequestResponse( EXPECT_TRUE(upstream->waitForRawConnection(fake_upstream_connection)); } if (expect_auth_command) { - std::string auth_command = makeBulkStringArray({"auth", auth_password}); + std::string auth_command = (auth_username.empty()) + ? makeBulkStringArray({"auth", auth_password}) + : makeBulkStringArray({"auth", auth_username, auth_password}); EXPECT_TRUE(fake_upstream_connection->waitForData(auth_command.size() + request.size(), &proxy_to_server)); // The original request should be the same as the data received by the server. @@ -522,7 +528,8 @@ void RedisProxyIntegrationTest::simpleRoundtripToUpstream(FakeUpstreamPtr& upstr IntegrationTcpClientPtr redis_client = makeTcpConnection(lookupPort("redis_proxy")); FakeRawConnectionPtr fake_upstream_connection; - roundtripToUpstreamStep(upstream, request, response, redis_client, fake_upstream_connection, ""); + roundtripToUpstreamStep(upstream, request, response, redis_client, fake_upstream_connection, "", + ""); EXPECT_TRUE(fake_upstream_connection->close()); redis_client->close(); @@ -621,10 +628,11 @@ TEST_P(RedisProxyWithCommandStatsIntegrationTest, MGETRequestAndResponse) { // Make GET request to upstream (MGET is turned into GETs for upstream) FakeUpstreamPtr& upstream = fake_upstreams_[0]; FakeRawConnectionPtr fake_upstream_connection; + std::string auth_username = ""; std::string auth_password = ""; std::string upstream_request = makeBulkStringArray({"get", "foo"}); expectUpstreamRequestResponse(upstream, upstream_request, upstream_response, - fake_upstream_connection, auth_password); + fake_upstream_connection, auth_username, auth_password); // Downstream response for MGET redis_client->waitForData(downstream_response); @@ -910,7 +918,7 @@ TEST_P(RedisProxyWithDownstreamAuthIntegrationTest, ErrorsUntilCorrectPasswordSe proxyResponseStep(makeBulkStringArray({"auth", "somepassword"}), "+OK\r\n", redis_client); roundtripToUpstreamStep(fake_upstreams_[0], makeBulkStringArray({"get", "foo"}), "$3\r\nbar\r\n", - redis_client, fake_upstream_connection, ""); + redis_client, fake_upstream_connection, "", ""); EXPECT_TRUE(fake_upstream_connection->close()); redis_client->close(); @@ -927,16 +935,16 @@ TEST_P(RedisProxyWithRoutesAndAuthPasswordsIntegrationTest, TransparentAuthentic // roundtrip to cluster_0 (catch_all route) roundtripToUpstreamStep(fake_upstreams_[0], makeBulkStringArray({"get", "toto"}), "$3\r\nbar\r\n", - redis_client, fake_upstream_connection[0], "cluster_0_password"); + redis_client, fake_upstream_connection[0], "", "cluster_0_password"); // roundtrip to cluster_1 (prefix "foo:" route) roundtripToUpstreamStep(fake_upstreams_[1], makeBulkStringArray({"get", "foo:123"}), - "$3\r\nbar\r\n", redis_client, fake_upstream_connection[1], + "$3\r\nbar\r\n", redis_client, fake_upstream_connection[1], "", "cluster_1_password"); // roundtrip to cluster_2 (prefix "baz:" route) roundtripToUpstreamStep(fake_upstreams_[2], makeBulkStringArray({"get", "baz:123"}), - "$3\r\nbar\r\n", redis_client, fake_upstream_connection[2], + "$3\r\nbar\r\n", redis_client, fake_upstream_connection[2], "", "cluster_2_password"); EXPECT_TRUE(fake_upstream_connection[0]->close()); diff --git a/test/extensions/health_checkers/redis/redis_test.cc b/test/extensions/health_checkers/redis/redis_test.cc index 9d413879998ca..7b1b850f0033f 100644 --- a/test/extensions/health_checkers/redis/redis_test.cc +++ b/test/extensions/health_checkers/redis/redis_test.cc @@ -157,7 +157,7 @@ class RedisHealthCheckerTest create(Upstream::HostConstSharedPtr, Event::Dispatcher&, const Extensions::NetworkFilters::Common::Redis::Client::Config&, const Extensions::NetworkFilters::Common::Redis::RedisCommandStatsSharedPtr&, - Stats::Scope&, const std::string&) override { + Stats::Scope&, const std::string&, const std::string&) override { return Extensions::NetworkFilters::Common::Redis::Client::ClientPtr{create_()}; } diff --git a/tools/spelling/spelling_dictionary.txt b/tools/spelling/spelling_dictionary.txt index a9c2b9af21efd..acdc35b82743a 100644 --- a/tools/spelling/spelling_dictionary.txt +++ b/tools/spelling/spelling_dictionary.txt @@ -4,6 +4,7 @@ # are allowed for any otherwise correctly spelled word. ABI ACK +ACL AES AFAICT ALPN @@ -337,6 +338,7 @@ WASM WAVM WIP WKT +WRONGPASS WRR WS WSA