diff --git a/Makefile b/Makefile index 3eb57118d..e23a1519f 100644 --- a/Makefile +++ b/Makefile @@ -93,12 +93,29 @@ work/sentinel-%.conf: echo save \"\" >> $@ echo sentinel monitor mymaster 127.0.0.1 6379 2 >> $@ +# Password-protected Sentinel +work/sentinel-26382.conf: + @mkdir -p $(@D) + + echo port 26382 >> $@ + echo daemonize yes >> $@ + echo protected-mode no >> $@ + echo bind 0.0.0.0 >> $@ + echo pidfile $(shell pwd)/work/sentinel-26382.pid >> $@ + echo logfile $(shell pwd)/work/sentinel-26382.log >> $@ + echo save \"\" >> $@ + echo "requirepass foobared" >> $@ + echo "user default on #1b58ee375b42e41f0e48ef2ff27d10a5b1f6924a9acdcdba7cae868e7adce6bf ~* +@all" >> $@ + echo "user spring on #3a6eb0790f39ac87c94f3856b2dd2c5d110e6811602261a9a923d3bb23adc8b7 +@all" >> $@ + echo sentinel monitor mymaster 127.0.0.1 6382 2 >> $@ + echo sentinel auth-pass mymaster foobared >> $@ + work/sentinel-%.pid: work/sentinel-%.conf work/redis-6379.pid work/redis/bin/redis-server work/redis/bin/redis-server $< --sentinel -sentinel-start: work/sentinel-26379.pid work/sentinel-26380.pid work/sentinel-26381.pid +sentinel-start: work/sentinel-26379.pid work/sentinel-26380.pid work/sentinel-26381.pid work/sentinel-26382.pid -sentinel-stop: stop-26379 stop-26380 stop-26381 +sentinel-stop: stop-26379 stop-26380 stop-26381 stop-26382 ######### @@ -171,6 +188,9 @@ stop-%: work/redis/bin/redis-cli stop-6382: work/redis/bin/redis-cli -work/redis/bin/redis-cli -a foobared -p 6382 shutdown +stop-26382: work/redis/bin/redis-cli + -work/redis/bin/redis-cli -a foobared -p 26382 shutdown + stop: redis-stop sentinel-stop cluster-stop test: diff --git a/src/main/asciidoc/new-features.adoc b/src/main/asciidoc/new-features.adoc index f0f19afa2..414264c33 100644 --- a/src/main/asciidoc/new-features.adoc +++ b/src/main/asciidoc/new-features.adoc @@ -8,6 +8,7 @@ This section briefly covers items that are new and noteworthy in the latest rele * `RedisCache` now exposes `CacheStatistics`. * ACL authentication support for Redis Standalone, Redis Cluster and Master/Replica. +* Password support for Redis Sentinel using Jedis. [[new-in-2.3.0]] == New in Spring Data Redis 2.3 diff --git a/src/main/asciidoc/reference/redis.adoc b/src/main/asciidoc/reference/redis.adoc index 2ea34a792..5a719c387 100644 --- a/src/main/asciidoc/reference/redis.adoc +++ b/src/main/asciidoc/reference/redis.adoc @@ -324,11 +324,6 @@ public RedisConnectionFactory lettuceConnectionFactory() { Sometimes, direct interaction with one of the Sentinels is required. Using `RedisConnectionFactory.getSentinelConnection()` or `RedisConnection.getSentinelCommands()` gives you access to the first active Sentinel configured. -[NOTE] -==== -Sentinel authentication is only available using https://lettuce.io/[Lettuce]. -==== - [[redis:template]] == Working with Objects through RedisTemplate diff --git a/src/main/java/org/springframework/data/redis/connection/jedis/JedisConnectionFactory.java b/src/main/java/org/springframework/data/redis/connection/jedis/JedisConnectionFactory.java index 998a35cf0..8cb3fbb6c 100644 --- a/src/main/java/org/springframework/data/redis/connection/jedis/JedisConnectionFactory.java +++ b/src/main/java/org/springframework/data/redis/connection/jedis/JedisConnectionFactory.java @@ -371,10 +371,12 @@ public class JedisConnectionFactory implements InitializingBean, DisposableBean, protected Pool createRedisSentinelPool(RedisSentinelConfiguration config) { GenericObjectPoolConfig poolConfig = getPoolConfig() != null ? getPoolConfig() : new JedisPoolConfig(); + String sentinelUser = null; + String sentinelPassword = config.getSentinelPassword().toOptional().map(String::new).orElse(null); return new JedisSentinelPool(config.getMaster().getName(), convertToJedisSentinelSet(config.getSentinels()), poolConfig, getConnectTimeout(), getReadTimeout(), getUsername(), getPassword(), getDatabase(), - getClientName()); + getClientName(), getConnectTimeout(), getReadTimeout(), sentinelUser, sentinelPassword, getClientName()); } /** @@ -868,10 +870,12 @@ public class JedisConnectionFactory implements InitializingBean, DisposableBean, private Jedis getActiveSentinel() { Assert.isTrue(RedisConfiguration.isSentinelConfiguration(configuration), "SentinelConfig must not be null!"); + SentinelConfiguration sentinelConfiguration = (SentinelConfiguration) configuration; - for (RedisNode node : ((SentinelConfiguration) configuration).getSentinels()) { + for (RedisNode node : sentinelConfiguration.getSentinels()) { Jedis jedis = new Jedis(node.getHost(), node.getPort(), getConnectTimeout(), getReadTimeout()); + sentinelConfiguration.getSentinelPassword().toOptional().map(String::new).ifPresent(jedis::auth); try { if (jedis.ping().equalsIgnoreCase("pong")) { diff --git a/src/test/java/org/springframework/data/redis/connection/jedis/JedisAclIntegrationTests.java b/src/test/java/org/springframework/data/redis/connection/jedis/JedisAclIntegrationTests.java index 6fe790687..4d52f83dc 100644 --- a/src/test/java/org/springframework/data/redis/connection/jedis/JedisAclIntegrationTests.java +++ b/src/test/java/org/springframework/data/redis/connection/jedis/JedisAclIntegrationTests.java @@ -18,11 +18,16 @@ package org.springframework.data.redis.connection.jedis; import static org.assertj.core.api.Assertions.*; import static org.junit.Assume.*; +import java.io.IOException; +import java.util.Collections; + import org.junit.Before; import org.junit.Test; import org.springframework.data.redis.RedisTestProfileValueSource; import org.springframework.data.redis.connection.RedisConnection; +import org.springframework.data.redis.connection.RedisSentinelConfiguration; +import org.springframework.data.redis.connection.RedisSentinelConnection; import org.springframework.data.redis.connection.RedisStandaloneConfiguration; /** @@ -72,6 +77,26 @@ public class JedisAclIntegrationTests { connectionFactory.destroy(); } + @Test // DATAREDIS-1145 + public void shouldConnectSentinelWithAclAuthentication() throws IOException { + + // Note: As per https://github.com/redis/redis/issues/7708, Sentinel does not support ACL authentication yet. + + RedisSentinelConfiguration sentinelConfiguration = new RedisSentinelConfiguration("mymaster", + Collections.singleton("localhost:26382")); + sentinelConfiguration.setSentinelPassword("foobared"); + + JedisConnectionFactory connectionFactory = new JedisConnectionFactory(sentinelConfiguration); + connectionFactory.afterPropertiesSet(); + + RedisSentinelConnection connection = connectionFactory.getSentinelConnection(); + + assertThat(connection.masters()).isNotEmpty(); + connection.close(); + + connectionFactory.destroy(); + } + @Test // DATAREDIS-1046 public void shouldConnectStandaloneWithAclAuthenticationAndPooling() { diff --git a/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceAclIntegrationTests.java b/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceAclIntegrationTests.java index e14e58e34..7a1a056fd 100644 --- a/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceAclIntegrationTests.java +++ b/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceAclIntegrationTests.java @@ -18,12 +18,20 @@ package org.springframework.data.redis.connection.lettuce; import static org.assertj.core.api.Assertions.*; import static org.junit.Assume.*; +import io.lettuce.core.ClientOptions; +import io.lettuce.core.protocol.ProtocolVersion; + +import java.io.IOException; +import java.util.Collections; + import org.junit.Before; import org.junit.Ignore; import org.junit.Test; import org.springframework.data.redis.RedisTestProfileValueSource; import org.springframework.data.redis.connection.RedisConnection; +import org.springframework.data.redis.connection.RedisSentinelConfiguration; +import org.springframework.data.redis.connection.RedisSentinelConnection; import org.springframework.data.redis.connection.RedisStandaloneConfiguration; import org.springframework.data.redis.connection.RedisStaticMasterReplicaConfiguration; @@ -76,6 +84,30 @@ public class LettuceAclIntegrationTests { connectionFactory.destroy(); } + @Test // DATAREDIS-1145 + public void shouldConnectSentinelWithAuthentication() throws IOException { + + // Note: As per https://github.com/redis/redis/issues/7708, Sentinel does not support ACL authentication yet. + + LettuceClientConfiguration configuration = LettuceClientConfiguration.builder() + .clientResources(LettuceTestClientResources.getSharedClientResources()) + .clientOptions(ClientOptions.builder().protocolVersion(ProtocolVersion.RESP2).build()).build(); + + RedisSentinelConfiguration sentinelConfiguration = new RedisSentinelConfiguration("mymaster", + Collections.singleton("localhost:26382")); + sentinelConfiguration.setSentinelPassword("foobared"); + + LettuceConnectionFactory connectionFactory = new LettuceConnectionFactory(sentinelConfiguration, configuration); + connectionFactory.afterPropertiesSet(); + + RedisSentinelConnection connection = connectionFactory.getSentinelConnection(); + + assertThat(connection.masters()).isNotEmpty(); + connection.close(); + + connectionFactory.destroy(); + } + @Test // DATAREDIS-1046 @Ignore("https://github.com/lettuce-io/lettuce-core/issues/1406") public void shouldConnectMasterReplicaWithAclAuthentication() {