Skip to content

DATAREDIS-1145, DATAREDIS-1046 - Add ACL support and Sentinel authentication for Jedis #558

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

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 46 additions & 5 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.

REDIS_VERSION:=5.0.5
REDIS_VERSION:=6.0.7
SPRING_PROFILE?=ci
SHELL=/bin/bash -euo pipefail

Expand All @@ -36,6 +36,24 @@ work/redis-%.conf:
echo save \"\" >> $@
echo slaveof 127.0.0.1 6379 >> $@

# Handled separately because it's a node with authentication. User: spring, password: data. Default password: foobared
work/redis-6382.conf:
@mkdir -p $(@D)

echo port 6382 >> $@
echo daemonize yes >> $@
echo protected-mode no >> $@
echo bind 0.0.0.0 >> $@
echo notify-keyspace-events Ex >> $@
echo pidfile $(shell pwd)/work/redis-6382.pid >> $@
echo logfile $(shell pwd)/work/redis-6382.log >> $@
echo unixsocket $(shell pwd)/work/redis-6382.sock >> $@
echo unixsocketperm 755 >> $@
echo "requirepass foobared" >> $@
echo "user default on #1b58ee375b42e41f0e48ef2ff27d10a5b1f6924a9acdcdba7cae868e7adce6bf ~* +@all" >> $@
echo "user spring on #3a6eb0790f39ac87c94f3856b2dd2c5d110e6811602261a9a923d3bb23adc8b7 +@all" >> $@
echo save \"\" >> $@

# Handled separately because it's the master and all others are slaves
work/redis-6379.conf:
@mkdir -p $(@D)
Expand All @@ -54,9 +72,9 @@ work/redis-6379.conf:
work/redis-%.pid: work/redis-%.conf work/redis/bin/redis-server
work/redis/bin/redis-server $<

redis-start: work/redis-6379.pid work/redis-6380.pid work/redis-6381.pid
redis-start: work/redis-6379.pid work/redis-6380.pid work/redis-6381.pid work/redis-6382.pid

redis-stop: stop-6379 stop-6380 stop-6381
redis-stop: stop-6379 stop-6380 stop-6381 stop-6382

##########
# Sentinel
Expand All @@ -75,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


#########
Expand Down Expand Up @@ -150,6 +185,12 @@ start: redis-start sentinel-start cluster-init
stop-%: work/redis/bin/redis-cli
-work/redis/bin/redis-cli -p $* shutdown

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:
Expand Down
4 changes: 2 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<version>2.4.0-SNAPSHOT</version>
<version>2.4.0-DATAREDIS-1046-SNAPSHOT</version>

<name>Spring Data Redis</name>

Expand All @@ -22,7 +22,7 @@
<beanutils>1.9.2</beanutils>
<xstream>1.4.12</xstream>
<pool>2.7.0</pool>
<lettuce>5.3.3.RELEASE</lettuce>
<lettuce>6.0.0.RC1</lettuce>
<jedis>3.3.0</jedis>
<multithreadedtc>1.01</multithreadedtc>
<netty>4.1.51.Final</netty>
Expand Down
3 changes: 2 additions & 1 deletion src/main/asciidoc/new-features.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ This section briefly covers items that are new and noteworthy in the latest rele
== New in Spring Data Redis 2.4

* `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

* Template API Method Refinements for `Duration` and `Instant`.
Expand Down
5 changes: 0 additions & 5 deletions src/main/asciidoc/reference/redis.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Optional;
import java.util.Set;

import org.springframework.core.env.MapPropertySource;
Expand All @@ -49,6 +50,7 @@ public class RedisClusterConfiguration implements RedisConfiguration, ClusterCon

private Set<RedisNode> clusterNodes;
private @Nullable Integer maxRedirects;
private Optional<String> username = Optional.empty();
private RedisPassword password = RedisPassword.none();

/**
Expand Down Expand Up @@ -182,6 +184,24 @@ private void appendClusterNodes(Set<String> hostAndPorts) {
}
}

/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.RedisConfiguration.WithAuthentication#setUsername(String)
*/
@Override
public void setUsername(String username) {
this.username = Optional.of(username);
}

/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.RedisConfiguration.WithAuthentication#getUsername()
*/
@Override
public Optional<String> getUsername() {
return this.username;
}

/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.RedisConfiguration.WithPassword#getPassword()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.function.IntSupplier;
import java.util.function.Supplier;
Expand Down Expand Up @@ -51,11 +52,11 @@ default Integer getDatabaseOrElse(Supplier<Integer> other) {

/**
* Get the configured {@link RedisPassword} if the current {@link RedisConfiguration} is
* {@link #isPasswordAware(RedisConfiguration) password aware} or evaluate and return the value of the given
* {@link #isAuthenticationAware(RedisConfiguration) password aware} or evaluate and return the value of the given
* {@link Supplier}.
*
* @param other a {@code Supplier} whose result is returned if given {@link RedisConfiguration} is not
* {@link #isPasswordAware(RedisConfiguration) password aware}.
* {@link #isAuthenticationAware(RedisConfiguration) password aware}.
* @return never {@literal null}.
* @throws IllegalArgumentException if {@code other} is {@literal null}.
*/
Expand All @@ -67,8 +68,8 @@ default RedisPassword getPasswordOrElse(Supplier<RedisPassword> other) {
* @param configuration can be {@literal null}.
* @return {@code true} if given {@link RedisConfiguration} is instance of {@link WithPassword}.
*/
static boolean isPasswordAware(@Nullable RedisConfiguration configuration) {
return configuration instanceof WithPassword;
static boolean isAuthenticationAware(@Nullable RedisConfiguration configuration) {
return configuration instanceof WithAuthentication;
}

/**
Expand Down Expand Up @@ -136,14 +137,28 @@ static Integer getDatabaseOrElse(@Nullable RedisConfiguration configuration, Sup
/**
* @param configuration can be {@literal null}.
* @param other a {@code Supplier} whose result is returned if given {@link RedisConfiguration} is not
* {@link #isPasswordAware(RedisConfiguration) password aware}.
* {@link #isAuthenticationAware(RedisConfiguration) password aware}.
* @return never {@literal null}.
* @throws IllegalArgumentException if {@code other} is {@literal null}.
*/
static Optional<String> getUsernameOrElse(@Nullable RedisConfiguration configuration,
Supplier<Optional<String>> other) {

Assert.notNull(other, "Other must not be null!");
return isAuthenticationAware(configuration) ? ((WithAuthentication) configuration).getUsername() : other.get();
}

/**
* @param configuration can be {@literal null}.
* @param other a {@code Supplier} whose result is returned if given {@link RedisConfiguration} is not
* {@link #isAuthenticationAware(RedisConfiguration) password aware}.
* @return never {@literal null}.
* @throws IllegalArgumentException if {@code other} is {@literal null}.
*/
static RedisPassword getPasswordOrElse(@Nullable RedisConfiguration configuration, Supplier<RedisPassword> other) {

Assert.notNull(other, "Other must not be null!");
return isPasswordAware(configuration) ? ((WithPassword) configuration).getPassword() : other.get();
return isAuthenticationAware(configuration) ? ((WithAuthentication) configuration).getPassword() : other.get();
}

/**
Expand Down Expand Up @@ -178,9 +193,17 @@ static String getHostOrElse(@Nullable RedisConfiguration configuration, Supplier
* {@link RedisConfiguration} part suitable for configurations that may use authentication when connecting.
*
* @author Christoph Strobl
* @since 2.1
* @author Mark Paluch
* @since 2.4
*/
interface WithPassword {
interface WithAuthentication {

/**
* Create and set a username with the given {@link String}. Requires Redis 6 or newer.
*
* @param username the username.
*/
void setUsername(String username);

/**
* Create and set a {@link RedisPassword} for given {@link String}.
Expand All @@ -207,6 +230,13 @@ default void setPassword(@Nullable char[] password) {
*/
void setPassword(RedisPassword password);

/**
* Get the username to use when connecting.
*
* @return {@link Optional#empty()} if none set.
*/
Optional<String> getUsername();

/**
* Get the RedisPassword to use when connecting.
*
Expand All @@ -215,6 +245,16 @@ default void setPassword(@Nullable char[] password) {
RedisPassword getPassword();
}

/**
* {@link RedisConfiguration} part suitable for configurations that may use authentication when connecting.
*
* @author Christoph Strobl
* @since 2.1
*/
interface WithPassword extends WithAuthentication {

}

/**
* {@link RedisConfiguration} part suitable for configurations that use a specific database.
*
Expand Down Expand Up @@ -339,7 +379,17 @@ default void setMaster(final String name) {
Set<RedisNode> getSentinels();

/**
* Get the {@link RedisPassword} used when authenticating with a Redis Server..
* Get the username used when authenticating with a Redis Server.
*
* @return never {@literal null}.
* @since 2.4
*/
default Optional<String> getDataNodeUsername() {
return getUsername();
}

/**
* Get the {@link RedisPassword} used when authenticating with a Redis Server.
*
* @return never {@literal null}.
* @since 2.2.2
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Optional;
import java.util.Set;

import org.springframework.core.env.MapPropertySource;
Expand Down Expand Up @@ -50,6 +51,7 @@ public class RedisSentinelConfiguration implements RedisConfiguration, SentinelC
private Set<RedisNode> sentinels;
private int database;

private Optional<String> dataNodeUsername = Optional.empty();
private RedisPassword dataNodePassword = RedisPassword.none();
private RedisPassword sentinelPassword = RedisPassword.none();

Expand Down Expand Up @@ -229,6 +231,24 @@ public void setDatabase(int index) {
this.database = index;
}

/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.RedisConfiguration.WithAuthentication#setUsername(String)
*/
@Override
public void setUsername(String username) {
this.dataNodeUsername = Optional.of(username);
}

/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.RedisConfiguration.WithAuthentication#getUsername()
*/
@Override
public Optional<String> getUsername() {
return this.dataNodeUsername;
}

/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.RedisConfiguration.WithPassword#getPassword()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
*/
package org.springframework.data.redis.connection;

import java.util.Optional;

import org.springframework.data.redis.connection.RedisConfiguration.DomainSocketConfiguration;
import org.springframework.util.Assert;

Expand All @@ -32,6 +34,7 @@ public class RedisSocketConfiguration implements RedisConfiguration, DomainSocke

private String socket = DEFAULT_SOCKET;
private int database;
private Optional<String> username = Optional.empty();
private RedisPassword password = RedisPassword.none();

/**
Expand Down Expand Up @@ -92,6 +95,24 @@ public void setDatabase(int index) {
this.database = index;
}

/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.RedisConfiguration.WithAuthentication#setUsername(String)
*/
@Override
public void setUsername(String username) {
this.username = Optional.of(username);
}

/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.RedisConfiguration.WithAuthentication#getUsername()
*/
@Override
public Optional<String> getUsername() {
return this.username;
}

/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.RedisConfiguration.WithPassword#getPassword()
Expand Down
Loading