Skip to content

Own RedisCredentialsProvider causes issue with protocol handshake on Redis 5 #2234

@jiantosca

Description

@jiantosca

Bug Report

Current Behavior

Howdy - I get a RedisConnectionException when using a RedisCredentialsProvider trying to connect over SSL. If I use an ImmediateRedisCredentialsProvider instead, the ssl connection works fine.

Also, the same RedisCredentialsProvider instance that fails with ssl connection, works fine for non-ssl connection.

I included some simple code that i used to recreate the issue.

Stack trace
// your stack trace here;
io.lettuce.core.RedisConnectionException:  my.redis.server/<unresolved>:443
     at io.lettuce.core.RedisConnectionException.create(RedisConnectionException.java:78)
     at io.lettuce.core.RedisConnectionException.create(RedisConnectionException.java:56)
     at io.lettuce.core.AbstractRedisClient.getConnection(AbstractRedisClient.java:350)
     at io.lettuce.core.RedisClient.connect(RedisClient.java:216)
     at io.lettuce.core.RedisClient.connect(RedisClient.java:201)
     at RedisCredentialsProviderTest.testIt(RedisCredentialsProviderTest.java:49)
     at RedisCredentialsProviderTest.main(RedisCredentialsProviderTest.java:27)
Caused by: io.lettuce.core.RedisCommandExecutionException: ERR unknown command 'HELLO'
     at io.lettuce.core.internal.ExceptionFactory.createExecutionException(ExceptionFactory.java:147)
     at io.lettuce.core.internal.ExceptionFactory.createExecutionException(ExceptionFactory.java:116)
     at io.lettuce.core.protocol.AsyncCommand.completeResult(AsyncCommand.java:120)
     at io.lettuce.core.protocol.AsyncCommand.complete(AsyncCommand.java:111)
     at io.lettuce.core.protocol.CommandHandler.complete(CommandHandler.java:747)
     at io.lettuce.core.protocol.CommandHandler.decode(CommandHandler.java:682)
     at io.lettuce.core.protocol.CommandHandler.channelRead(CommandHandler.java:599)
     at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:442)
     at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420)
     at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412)
     at io.netty.handler.ssl.SslHandler.unwrap(SslHandler.java:1373)
     at io.netty.handler.ssl.SslHandler.decodeJdkCompatible(SslHandler.java:1236)
     at io.netty.handler.ssl.SslHandler.decode(SslHandler.java:1285)
     at io.netty.handler.codec.ByteToMessageDecoder.decodeRemovalReentryProtection(ByteToMessageDecoder.java:519)
     at io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:458)
     at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:280)
     at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:444)
     at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420)
     at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412)
     at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1410)
     at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:440)
     at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420)
     at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:919)
     at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:166)
     at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:788)
     at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:724)
     at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:650)
     at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:562)
     at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:997)
     at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
     at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
     at java.base/java.lang.Thread.run(Thread.java:833)

Input Code

Input Code
import io.lettuce.core.RedisClient;
import io.lettuce.core.RedisCredentials;
import io.lettuce.core.RedisCredentialsProvider;
import io.lettuce.core.RedisCredentialsProvider.ImmediateRedisCredentialsProvider;
import io.lettuce.core.RedisURI;
import reactor.core.publisher.Mono;

public class RedisCredentialsProviderTest {

	public static void main(String[] args) {

		var sslServer = "ssl server";
		var sslPort = 443;
		
		var notSslServer = "no ssl server";
		var notSslPort = 6379;
		
		var provider = new MyRedisCredentialsProvider("username", "password");
		var immediateProvider = new MyImmediateRedisCredentialsProvider("username", "password");
		
                // this first one fails
                testIt(sslServer, sslPort, true, provider, "SSL with MyRedisCredentialsProvider");
		testIt(sslServer, sslPort, true, immediateProvider, "SSL with MyImmediateRedisCredentialsProvider");

		testIt(notSslServer, notSslPort, false, provider, "No SSL with MyRedisCredentialsProvider");
		testIt(notSslServer, notSslPort, false, immediateProvider, "No SSL with MyImmediateRedisCredentialsProvider");
	}
	
	static void testIt(String server, int port, boolean withSsl, RedisCredentialsProvider provider, String testMsg) {
		
		var redisUri = RedisURI.Builder
				.redis(server, port)
				.withAuthentication(provider)
				.withSsl(withSsl)
				.build();
		
		try(var client = RedisClient.create(redisUri)) {
			var commands = client.connect().sync();
			System.out.println(String.format("%s - Ping: %s", testMsg, commands.ping()));
		} catch (Exception e) {
			System.out.println(testMsg + " - Ping failed:");
			e.printStackTrace();
		}
	}
	
	static class MyRedisCredentialsProvider implements RedisCredentialsProvider {
		
                String username;
	        String password;
		
                public MyRedisCredentialsProvider(String username, String password) {
			this.username = username;
			this.password = password;
		}

		@Override
		public Mono<RedisCredentials> resolveCredentials() {
			return Mono.just(RedisCredentials.just(username, password));
		}
	}
	
	static class MyImmediateRedisCredentialsProvider implements ImmediateRedisCredentialsProvider {
		
                String username;
		String password;

		public MyImmediateRedisCredentialsProvider(String username, String password) {
			this.username = username;
			this.password = password;
		}
		
		@Override
		public RedisCredentials resolveCredentialsNow() {
			return RedisCredentials.just(username, password);
		}
	}		
}

Expected behavior/code

I would expect connectivity to work equally well whether I use a RedisCredentialsProvider that returns a Mono<RedisCredentials>, or an ImmediateRedisCredentialsProvider that return a RedisCredentials

Environment

  • Lettuce version(s): 6.2.1.RELEASE
  • Redis version: 6.2.5

Possible Solution

Additional context

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions