Skip to content

Commit ab7c371

Browse files
author
Taishi Kasuga
committed
Support single endpoint architecture with SSL/TLS in cluster mode
1 parent 610c783 commit ab7c371

File tree

4 files changed

+30
-2
lines changed

4 files changed

+30
-2
lines changed

README.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,21 @@ redis.mget('{key}1', '{key}2')
155155
* The client support permanent node failures, and will reroute requests to promoted slaves.
156156
* The client supports `MOVED` and `ASK` redirections transparently.
157157

158+
## Cluster mode with SSL/TLS
159+
Since Redis can return FQDN of nodes in reply to client since `7.*` with CLUSTER commands, we can use cluster feature with SSL/TLS connection like this:
160+
161+
```ruby
162+
Redis.new(cluster: %w[rediss://foo.example.com:6379])
163+
```
164+
165+
On the other hand, in Redis versions prior to `6.*`, you can specify options like the following if cluster mode is enabled and client has to connect to nodes via single endpoint with SSL/TLS.
166+
167+
```ruby
168+
Redis.new(cluster: %w[rediss://foo-endpoint.example.com:6379], fixed_hostname: 'foo-endpoint.example.com')
169+
```
170+
171+
In case of the above architecture, if you don't pass the `fixed_hostname` option to the client and servers return IP addresses of nodes, the client may fail to verify certificates.
172+
158173
## Storing objects
159174

160175
Redis "string" types can be used to store serialized Ruby objects, for

lib/redis.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,8 @@ def current=(redis)
7474
# @option options [Symbol] :role (:master) Role to fetch via Sentinel, either `:master` or `:slave`
7575
# @option options [Array<String, Hash{Symbol => String, Integer}>] :cluster List of cluster nodes to contact
7676
# @option options [Boolean] :replica Whether to use readonly replica nodes in Redis Cluster or not
77+
# @option options [String] :fixed_hostname Specify a FQDN if cluster mode enabled and
78+
# client has to connect nodes via single endpoint with SSL/TLS
7779
# @option options [Class] :connector Class of custom connector
7880
#
7981
# @return [Redis] a new client instance

lib/redis/cluster/option.rb

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,20 @@ def initialize(options)
1717
node_addrs = options.delete(:cluster)
1818
@node_opts = build_node_options(node_addrs)
1919
@replica = options.delete(:replica) == true
20+
@fixed_hostname = options.delete(:fixed_hostname)
2021
add_common_node_option_if_needed(options, @node_opts, :scheme)
2122
add_common_node_option_if_needed(options, @node_opts, :username)
2223
add_common_node_option_if_needed(options, @node_opts, :password)
2324
@options = options
2425
end
2526

2627
def per_node_key
27-
@node_opts.map { |opt| [NodeKey.build_from_host_port(opt[:host], opt[:port]), @options.merge(opt)] }
28-
.to_h
28+
@node_opts.map do |opt|
29+
node_key = NodeKey.build_from_host_port(opt[:host], opt[:port])
30+
options = @options.merge(opt)
31+
options = options.merge(host: @fixed_hostname) if @fixed_hostname && !@fixed_hostname.empty?
32+
[node_key, options]
33+
end.to_h
2934
end
3035

3136
def use_replica?

test/cluster_client_options_test.rb

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,12 @@ def test_option_class
4444
option = Redis::Cluster::Option.new(cluster: [{ host: '127.0.0.1', port: 7000 }])
4545
assert_equal({ '127.0.0.1:7000' => { host: '127.0.0.1', port: 7000 } }, option.per_node_key)
4646

47+
option = Redis::Cluster::Option.new(cluster: %w[redis://127.0.0.1:7000], fixed_hostname: 'foo-endpoint.example.com')
48+
assert_equal({ '127.0.0.1:7000' => { scheme: 'redis', host: 'foo-endpoint.example.com', port: 7000 } }, option.per_node_key)
49+
50+
option = Redis::Cluster::Option.new(cluster: %w[redis://127.0.0.1:7000], fixed_hostname: '')
51+
assert_equal({ '127.0.0.1:7000' => { scheme: 'redis', host: '127.0.0.1', port: 7000 } }, option.per_node_key)
52+
4753
assert_raises(Redis::InvalidClientOptionError) do
4854
Redis::Cluster::Option.new(cluster: nil)
4955
end

0 commit comments

Comments
 (0)