I’ve an Azure Cache for Redis – Premium and Cluster enabled. I’ve been trying to connect to that Redis using spring-boot-starter-data-redis
(spring boot version: 2.3.4.RELEASE
, Java version: 11
) and using lettuce client but Lettuce is throwing the following SSL exception when I am treating my Redis as a Redis Cluster but connects just fine when using it as a Standalone Redis server.
My pom.xml
dependencies are:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
Java code:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.*;
import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.StringRedisTemplate;
@Configuration
class LettuceConfig {
@Bean
StringRedisTemplate getStringRedisTemplate(final RedisProperties redisProperties) {
return new StringRedisTemplate(getRedisConnectionFactory(redisProperties));
}
@Bean
RedisConnectionFactory getRedisConnectionFactory(final RedisProperties redisProperties) {
final RedisNode redisNode = RedisNode.newRedisNode()
.listeningAt(redisProperties.getHost(), redisProperties.getPort())
.build();
// Connecting as a Redis Cluster
final RedisClusterConfiguration redisClusterConfiguration = new RedisClusterConfiguration();
redisClusterConfiguration.addClusterNode(redisNode);
redisClusterConfiguration.setPassword(RedisPassword.of(redisProperties.getPassword()));
// Connecting as a Standalone Redis server
final RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration();
redisStandaloneConfiguration.setHostName(redisProperties.getHost());
redisStandaloneConfiguration.setPort(redisProperties.getPort());
redisStandaloneConfiguration.setPassword(RedisPassword.of(redisProperties.getPassword()));
final LettuceClientConfiguration.LettuceClientConfigurationBuilder lettuceClientConfigurationBuilder =
LettuceClientConfiguration.builder()
.clientName(redisProperties.getClientName())
.commandTimeout(redisProperties.getTimeout());
if (redisProperties.isSsl()) {
lettuceClientConfigurationBuilder.useSsl();
}
final LettuceClientConfiguration lettuceClientConfiguration = lettuceClientConfigurationBuilder.build();
return new LettuceConnectionFactory(redisClusterConfiguration, lettuceClientConfiguration);
}
}
@SpringBootApplication
public class LettuceClusterApplication implements CommandLineRunner {
private final StringRedisTemplate stringRedisTemplate;
@Autowired
public LettuceClusterApplication(final StringRedisTemplate stringRedisTemplate) {
this.stringRedisTemplate = stringRedisTemplate;
}
public static void main(String[] args) {
SpringApplication.run(LettuceClusterApplication.class, args);
}
@Override
public void run(String... args) throws Exception {
System.out.println(stringRedisTemplate.hasKey("abc"));
}
}
When using redisStandaloneConfiguration
in new LettuceConnectionFactory(..., ...)
, the code works just fine, but if I use redisClusterConfiguration
, the code fails with the following exception:
java.lang.IllegalStateException: Failed to execute CommandLineRunner
at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:798) ~[spring-boot-2.3.4.RELEASE.jar:2.3.4.RELEASE]
...
Caused by: org.springframework.data.redis.RedisConnectionFailureException: Redis connection failed; nested exception is io.lettuce.core.RedisConnectionException: Unable to connect to [RedisURI [host='<redacted>.redis.cache.windows.net', port=6380]]
at org.springframework.data.redis.connection.lettuce.LettuceExceptionConverter.convert(LettuceExceptionConverter.java:66) ~[spring-data-redis-2.3.4.RELEASE.jar:2.3.4.RELEASE]
...
Caused by: io.lettuce.core.RedisConnectionException: Unable to connect to [RedisURI [host='<redacted>.redis.cache.windows.net', port=6380]]
at io.lettuce.core.RedisConnectionException.create(RedisConnectionException.java:78) ~[lettuce-core-5.3.4.RELEASE.jar:5.3.4.RELEASE]
...
Caused by: javax.net.ssl.SSLHandshakeException: No subject alternative names matching IP address <redacted> found
...
Caused by: java.security.cert.CertificateException: No subject alternative names matching IP address <redacted> found
at java.base/sun.security.util.HostnameChecker.matchIP(HostnameChecker.java:165) ~[na:na]
...
My application.properties
file:
spring.redis.host = <redacted>.redis.cache.windows.net
spring.redis.port = 6380
spring.redis.password = <redacted>
spring.redis.ssl = true
spring.redis.clientName = ${HOSTNAME}
spring.redis.timeout = 100000
Update: Found a similar issue in Github: https://github.com/lettuce-io/lettuce-core/issues/246 but it says that it should work with lettuce versions > 4.2
and my lettuce-core version (bundled under spring-boot-starter-data-redis
) is 5.3.4.RELEASE
.
Also worth checking out is the documentation which states the same: https://lettuce.io/core/release/reference/#ssl
Lettuce supports SSL connections since version 3.1 on Redis Standalone connections and since version 4.2 on Redis Cluster
Raised GitHub issue as well: https://github.com/lettuce-io/lettuce-core/issues/1454
2
Answers
If all of your nodes have the same IP address as the hostname (as is the case in Azure Cache for Redis, I think), then this is one way you could configure your client to map unresolved IP addresses back to the hostname listed in the certificate.
As best I understand it, this is the most preferable of the solutions listed on the Github issue until Microsoft fixes things from their end.
Unlike connecting in standalone mode, connecting to Azure redis in cluster mode is a two step process:
<hostname:6380>
, authenticate, and fetch the cluster endpoint details<ip address:port>
that you got in the cluster endpoint details, authenticate again, and then send commands to the particular cluster shard your key is onThe reason you get
No subject alternative names matching IP address <redacted> found
is that Azure redis gives you an IP address + port number in the cluster endpoint details, and then Lettuce tries to validate your SSL connection against the IP address – instead of the hostname, but fails because its trying to verify the the SSL cert subject or SANsomething.redis.cache.windows.net
against the server endpoint that you are currently connecting to<ip address>:<port>
.You can get around this in most client libraries by configuring or overriding the SSL certificate validation to validate the server cert against your particular redis cache’s hostname.
E.g. in .Net StackExchange.redis there’s a config setting called ‘sslhost’ useful for this purpose.
Hopefully Lettuce has an equivalent.