Skip to content

Commit

Permalink
DATAREDIS-1060 - Redis password should not automatically be applied t…
Browse files Browse the repository at this point in the history
…o Sentinel.

We now expose two separate properties in RedisSentinelConfiguration to configure the data password individually from the sentinel password.

Original pull request: #495.
  • Loading branch information
christophstrobl authored and mp911de committed Nov 12, 2019
1 parent df9c57a commit 1955074
Show file tree
Hide file tree
Showing 7 changed files with 292 additions and 31 deletions.
3 changes: 2 additions & 1 deletion src/main/asciidoc/reference/redis.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -174,13 +174,14 @@ public RedisConnectionFactory lettuceConnectionFactory() {
.Configuration Properties
* `spring.redis.sentinel.master`: name of the master node.
* `spring.redis.sentinel.nodes`: Comma delimited list of host:port pairs.
* `spring.redis.sentinel.password`: The password to apply when authenticating with Redis Sentinel
====

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]
====
Configuration options like password, timeouts, SSL... also get applied to Redis Sentinel when using https://lettuce.io/[Lettuce].
Sentinel authentication is only available using https://lettuce.io/[Lettuce].
====

[[redis:template]]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,79 @@ default void setMaster(final String name) {
* @return {@link Set} of sentinels. Never {@literal null}.
*/
Set<RedisNode> getSentinels();

/**
* Get the {@link RedisPassword} used when authenticating with a Redis Server.
*
* @return never {@literal null}.
*/
default RedisPassword getDataNodePassword() {
return getPassword();
}

/**
* Create and set a {@link RedisPassword} to be used when authenticating with Sentinel from the given {@link String}
*
* @param password can be {@literal null}.
* @throws IllegalStateException if the {@link #useDataNodeAuthenticationForSentinel(boolean) Data Node Password}
* should be used for authenticating with Redis Sentinel.
* @since 2.2.2
*/
default void setSentinelPassword(@Nullable String password) {
setSentinelPassword(RedisPassword.of(password));
}

/**
* Create and set a {@link RedisPassword} to be used when authenticating with Sentinel from the given
* {@link Character} sequence.
*
* @param password can be {@literal null}.
* @throws IllegalStateException if the {@link #useDataNodeAuthenticationForSentinel(boolean) Data Node Password}
* should be used for authenticating with Redis Sentinel.
* @since 2.2.2
*/
default void setSentinelPassword(@Nullable char[] password) {
setSentinelPassword(RedisPassword.of(password));
}

/**
* Set a {@link RedisPassword} to be used when authenticating with Sentinel.
*
* @param password must not be {@literal null} use {@link RedisPassword#none()} instead.
* @throws IllegalStateException if the {@link #useDataNodeAuthenticationForSentinel(boolean) Data Node Password}
* should be used for authenticating with Redis Sentinel.
* @since 2.2.2
*/
void setSentinelPassword(RedisPassword password);

/**
* Get the {@link RedisPassword} to use when connecting to a Redis Sentinel. <br />
* This can be the password explicitly set via {@link #setSentinelPassword(RedisPassword)}, or the
* {@link #getDataNodePassword() Data Node password} if it should be also used for
* {@link #getUseDataNodeAuthenticationForSentinel() sentinel}, or {@link RedisPassword#none()} if no password has
* been set.
*
* @return the {@link RedisPassword} for authenticating with Sentinel.
* @since 2.2.2
*/
RedisPassword getSentinelPassword();

/**
* Use the {@link #getDataNodePassword() RedisPassword} also for authentication with Redis Sentinel.
*
* @param useDataNodeAuthenticationForSentinel
* @throws IllegalStateException if a {@link #getSentinelPassword() Sentinel Password} is already set.
* @since 2.2.2
*/
void useDataNodeAuthenticationForSentinel(boolean useDataNodeAuthenticationForSentinel);

/**
* Use the {@link #getDataNodePassword() RedisPassword} also for authentication with Redis Sentinel.
*
* @return
* @since 2.2.2
*/
boolean getUseDataNodeAuthenticationForSentinel();
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@
*/
package org.springframework.data.redis.connection;

import static org.springframework.util.Assert.*;
import static org.springframework.util.Assert.hasText;
import static org.springframework.util.StringUtils.*;

import java.util.Collections;
Expand Down Expand Up @@ -46,11 +44,15 @@ public class RedisSentinelConfiguration implements RedisConfiguration, SentinelC

private static final String REDIS_SENTINEL_MASTER_CONFIG_PROPERTY = "spring.redis.sentinel.master";
private static final String REDIS_SENTINEL_NODES_CONFIG_PROPERTY = "spring.redis.sentinel.nodes";
private static final String REDIS_SENTINEL_PASSWORD_CONFIG_PROPERTY = "spring.redis.sentinel.password";

private @Nullable NamedNode master;
private Set<RedisNode> sentinels;
private int database;
private RedisPassword password = RedisPassword.none();

private RedisPassword dataNodePassword = RedisPassword.none();
private RedisPassword sentinelPassword = RedisPassword.none();
private boolean useDataNodePasswordForSentinel = false;

/**
* Creates new {@link RedisSentinelConfiguration}.
Expand Down Expand Up @@ -89,7 +91,7 @@ public RedisSentinelConfiguration(String master, Set<String> sentinelHostAndPort
*/
public RedisSentinelConfiguration(PropertySource<?> propertySource) {

notNull(propertySource, "PropertySource must not be null!");
Assert.notNull(propertySource, "PropertySource must not be null!");

this.sentinels = new LinkedHashSet<>();

Expand All @@ -101,6 +103,10 @@ public RedisSentinelConfiguration(PropertySource<?> propertySource) {
appendSentinels(
commaDelimitedListToSet(propertySource.getProperty(REDIS_SENTINEL_NODES_CONFIG_PROPERTY).toString()));
}

if (propertySource.containsProperty(REDIS_SENTINEL_PASSWORD_CONFIG_PROPERTY)) {
this.setSentinelPassword(propertySource.getProperty(REDIS_SENTINEL_PASSWORD_CONFIG_PROPERTY).toString());
}
}

/**
Expand All @@ -110,7 +116,7 @@ public RedisSentinelConfiguration(PropertySource<?> propertySource) {
*/
public void setSentinels(Iterable<RedisNode> sentinels) {

notNull(sentinels, "Cannot set sentinels to 'null'.");
Assert.notNull(sentinels, "Cannot set sentinels to 'null'.");

this.sentinels.clear();

Expand All @@ -134,7 +140,7 @@ public Set<RedisNode> getSentinels() {
*/
public void addSentinel(RedisNode sentinel) {

notNull(sentinel, "Sentinel must not be 'null'.");
Assert.notNull(sentinel, "Sentinel must not be 'null'.");
this.sentinels.add(sentinel);
}

Expand All @@ -144,7 +150,7 @@ public void addSentinel(RedisNode sentinel) {
*/
public void setMaster(NamedNode master) {

notNull(master, "Sentinel master node must not be 'null'.");
Assert.notNull(master, "Sentinel master node must not be 'null'.");
this.master = master;
}

Expand Down Expand Up @@ -230,7 +236,7 @@ public void setDatabase(int index) {
*/
@Override
public RedisPassword getPassword() {
return password;
return dataNodePassword;
}

/*
Expand All @@ -242,15 +248,60 @@ public void setPassword(RedisPassword password) {

Assert.notNull(password, "RedisPassword must not be null!");

this.password = password;
this.dataNodePassword = password;
}

/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.RedisConfiguration.WithPassword#setSentinelPassword(org.springframework.data.redis.connection.RedisPassword)
*/
public void setSentinelPassword(RedisPassword sentinelPassword) {

Assert.state(!useDataNodePasswordForSentinel,
"Configuration uses Redis Data Node password for authenticating with Sentinel. Please set 'RedisSentinelConfiguration.useDataNodeAuthenticationForSentinel(false)' before using this option.");
Assert.notNull(sentinelPassword, "SentinelPassword must not be null!");
this.sentinelPassword = sentinelPassword;
}

/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.RedisConfiguration.WithPassword#setSentinelPassword()
*/
@Override
public RedisPassword getSentinelPassword() {
return getUseDataNodeAuthenticationForSentinel() ? this.dataNodePassword : sentinelPassword;
}

/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.RedisConfiguration.WithPassword#useDataNodeAuthenticationForSentinel(boolean)
*/
@Override
public void useDataNodeAuthenticationForSentinel(boolean useDataNodeAuthenticationForSentinel) {

if (useDataNodeAuthenticationForSentinel) {
Assert.state(!this.sentinelPassword.isPresent(),
"Configuration already defines a password for authenticating with Sentinel. Please use 'RedisSentinelConfiguration.setSentinelPassword(RedisPassword.none())' remove the password.");
}

this.useDataNodePasswordForSentinel = useDataNodeAuthenticationForSentinel;
}

/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.RedisConfiguration.WithPassword#getUseDataNodeAuthenticationForSentinel()
*/
@Override
public boolean getUseDataNodeAuthenticationForSentinel() {
return this.useDataNodePasswordForSentinel;
}

private RedisNode readHostAndPortFromString(String hostAndPort) {

String[] args = split(hostAndPort, ":");

notNull(args, "HostAndPort need to be seperated by ':'.");
isTrue(args.length == 2, "Host and Port String needs to specified as host:port");
Assert.notNull(args, "HostAndPort need to be seperated by ':'.");
Assert.isTrue(args.length == 2, "Host and Port String needs to specified as host:port");
return new RedisNode(args[0], Integer.valueOf(args[1]).intValue());
}

Expand All @@ -261,8 +312,8 @@ private RedisNode readHostAndPortFromString(String hostAndPort) {
*/
private static Map<String, Object> asMap(String master, Set<String> sentinelHostAndPorts) {

hasText(master, "Master address must not be null or empty!");
notNull(sentinelHostAndPorts, "SentinelHostAndPorts must not be null!");
Assert.hasText(master, "Master address must not be null or empty!");
Assert.notNull(sentinelHostAndPorts, "SentinelHostAndPorts must not be null!");

Map<String, Object> map = new HashMap<>();
map.put(REDIS_SENTINEL_MASTER_CONFIG_PROPERTY, master);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1080,7 +1080,6 @@ private RedisURI getSentinelRedisURI() {

applyToAll(redisUri, it -> {

getRedisPassword().toOptional().ifPresent(it::setPassword);
clientConfiguration.getClientName().ifPresent(it::setClientName);

it.setSsl(clientConfiguration.isUseSsl());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
import org.springframework.data.redis.connection.RedisListCommands.Position;
import org.springframework.data.redis.connection.RedisNode;
import org.springframework.data.redis.connection.RedisNode.NodeType;
import org.springframework.data.redis.connection.RedisPassword;
import org.springframework.data.redis.connection.RedisSentinelConfiguration;
import org.springframework.data.redis.connection.RedisServer;
import org.springframework.data.redis.connection.RedisStringCommands.SetOption;
Expand Down Expand Up @@ -198,7 +199,8 @@ abstract public class LettuceConverters extends Converters {
};

SCORED_VALUE_TO_TUPLE = source -> source != null
? new DefaultTuple(source.getValue(), Double.valueOf(source.getScore())) : null;
? new DefaultTuple(source.getValue(), Double.valueOf(source.getScore()))
: null;

BYTES_LIST_TO_TUPLE_LIST_CONVERTER = source -> {

Expand Down Expand Up @@ -297,7 +299,8 @@ private Set<Flag> parseFlags(Set<NodeFlag> source) {
};

GEO_COORDINATE_TO_POINT_CONVERTER = geoCoordinate -> geoCoordinate != null
? new Point(geoCoordinate.getX().doubleValue(), geoCoordinate.getY().doubleValue()) : null;
? new Point(geoCoordinate.getX().doubleValue(), geoCoordinate.getY().doubleValue())
: null;
GEO_COORDINATE_LIST_TO_POINT_LIST_CONVERTER = new ListConverter<>(GEO_COORDINATE_TO_POINT_CONVERTER);

KEY_VALUE_UNWRAPPER = source -> source.getValueOrElse(null);
Expand Down Expand Up @@ -637,15 +640,21 @@ public static RedisURI sentinelConfigurationToRedisURI(RedisSentinelConfiguratio
Assert.notNull(sentinelConfiguration, "RedisSentinelConfiguration is required");

Set<RedisNode> sentinels = sentinelConfiguration.getSentinels();
RedisURI.Builder builder = null;
RedisURI.Builder builder = RedisURI.builder();
for (RedisNode sentinel : sentinels) {

if (builder == null) {
builder = RedisURI.Builder.sentinel(sentinel.getHost(), sentinel.getPort(),
sentinelConfiguration.getMaster().getName());
} else {
builder.withSentinel(sentinel.getHost(), sentinel.getPort());
RedisURI.Builder uri = RedisURI.Builder.sentinel(sentinel.getHost(), sentinel.getPort(),
sentinelConfiguration.getMaster().getName());

if (sentinelConfiguration.getSentinelPassword().isPresent()) {
uri.withPassword(sentinelConfiguration.getSentinelPassword().get());
}
builder.withSentinel(uri.build());
}

RedisPassword password = sentinelConfiguration.getPassword();
if (password.isPresent()) {
builder.withPassword(password.get());
}

return builder.build();
Expand Down

0 comments on commit 1955074

Please sign in to comment.