package com.lambdaworks;
import static com.lambdaworks.redis.TestSettings.host;
import static com.lambdaworks.redis.TestSettings.sslPort;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.fail;
import static org.junit.Assume.assumeTrue;
import java.io.File;
import java.io.IOException;
import java.security.cert.CertificateException;
import java.util.List;
import java.util.concurrent.ExecutionException;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.Test;
import com.lambdaworks.redis.*;
import com.lambdaworks.redis.api.StatefulRedisConnection;
import com.lambdaworks.redis.api.sync.RedisCommands;
import com.lambdaworks.redis.pubsub.api.async.RedisPubSubAsyncCommands;
import com.lambdaworks.redis.pubsub.api.sync.RedisPubSubCommands;
import io.netty.handler.codec.DecoderException;
/**
* @author Mark Paluch
*/
public class SslTest extends AbstractTest {
private static final String KEYSTORE = "work/keystore.jks";
private static final String LOCALHOST_KEYSTORE = "work/keystore-localhost.jks";
private static final RedisClient redisClient = RedisClient.create();
private static final RedisURI URI_NO_VERIFY = RedisURI.Builder.redis(host(), sslPort()) //
.withSsl(true) //
.withVerifyPeer(false) //
.build();
private static final RedisURI URI_VERIFY = RedisURI.Builder.redis(host(), sslPort(1)) //
.withSsl(true) //
.withVerifyPeer(true) //
.build();
@Before
public void before() throws Exception {
assumeTrue("Assume that stunnel runs on port 6443", Sockets.isOpen(host(), sslPort()));
assertThat(new File(KEYSTORE)).exists();
System.setProperty("javax.net.ssl.trustStore", KEYSTORE);
redisClient.setOptions(ClientOptions.create());
}
@AfterClass
public static void afterClass() {
FastShutdown.shutdown(redisClient);
}
@Test
public void standaloneWithSsl() throws Exception {
RedisCommands<String, String> connection = redisClient.connect(URI_NO_VERIFY).sync();
connection.set("key", "value");
assertThat(connection.get("key")).isEqualTo("value");
connection.close();
}
@Test
public void standaloneWithJdkSsl() throws Exception {
SslOptions sslOptions = SslOptions.builder() //
.jdkSslProvider() //
.truststore(new File(LOCALHOST_KEYSTORE)) //
.build();
setOptions(sslOptions);
verifyConnection(URI_VERIFY);
}
@Test
public void standaloneWithJdkSslUsingTruststoreUrl() throws Exception {
SslOptions sslOptions = SslOptions.builder() //
.jdkSslProvider() //
.truststore(new File(LOCALHOST_KEYSTORE).toURI().toURL()) //
.build();
setOptions(sslOptions);
verifyConnection(URI_VERIFY);
}
@Test(expected = RedisConnectionException.class)
public void standaloneWithJdkSslUsingTruststoreUrlWithWrongPassword() throws Exception {
SslOptions sslOptions = SslOptions.builder() //
.jdkSslProvider() //
.truststore(new File(LOCALHOST_KEYSTORE).toURI().toURL(), "knödel") //
.build();
setOptions(sslOptions);
verifyConnection(URI_VERIFY);
}
@Test(expected = RedisConnectionException.class)
public void standaloneWithJdkSslFailsWithWrongTruststore() throws Exception {
SslOptions sslOptions = SslOptions.builder() //
.jdkSslProvider() //
.build();
setOptions(sslOptions);
verifyConnection(URI_VERIFY);
}
@Test
public void standaloneWithOpenSsl() throws Exception {
SslOptions sslOptions = SslOptions.builder() //
.openSslProvider() //
.truststore(new File(LOCALHOST_KEYSTORE)) //
.build();
setOptions(sslOptions);
verifyConnection(URI_VERIFY);
}
@Test(expected = RedisConnectionException.class)
public void standaloneWithOpenSslFailsWithWrongTruststore() throws Exception {
SslOptions sslOptions = SslOptions.builder() //
.openSslProvider() //
.build();
setOptions(sslOptions);
verifyConnection(URI_VERIFY);
}
@Test
public void pingBeforeActivate() throws Exception {
redisClient.setOptions(ClientOptions.builder().pingBeforeActivateConnection(true).build());
verifyConnection(URI_NO_VERIFY);
}
@Test
public void regularSslWithReconnect() throws Exception {
RedisCommands<String, String> connection = redisClient.connect(URI_NO_VERIFY).sync();
connection.quit();
Thread.sleep(200);
assertThat(connection.ping()).isEqualTo("PONG");
connection.close();
}
@Test(expected = RedisConnectionException.class)
public void sslWithVerificationWillFail() throws Exception {
RedisURI redisUri = RedisURI.create("rediss://" + host() + ":" + sslPort());
redisClient.connect(redisUri).sync();
}
@Test
public void pubSubSsl() throws Exception {
RedisPubSubCommands<String, String> connection = redisClient.connectPubSub(URI_NO_VERIFY).sync();
connection.subscribe("c1");
connection.subscribe("c2");
Thread.sleep(200);
RedisPubSubCommands<String, String> connection2 = redisClient.connectPubSub(URI_NO_VERIFY).sync();
assertThat(connection2.pubsubChannels()).contains("c1", "c2");
connection.quit();
Thread.sleep(200);
Wait.untilTrue(connection::isOpen).waitOrTimeout();
Wait.untilEquals(2, () -> connection2.pubsubChannels().size()).waitOrTimeout();
assertThat(connection2.pubsubChannels()).contains("c1", "c2");
connection.close();
connection2.close();
}
@Test
public void pubSubSslAndBreakConnection() throws Exception {
RedisURI redisURI = RedisURI.Builder.redis(host(), sslPort()).withSsl(true).withVerifyPeer(false)
.build();
redisClient.setOptions(ClientOptions.builder().suspendReconnectOnProtocolFailure(true).build());
RedisPubSubAsyncCommands<String, String> connection = redisClient.connectPubSub(redisURI).async();
connection.subscribe("c1").get();
connection.subscribe("c2").get();
Thread.sleep(200);
RedisPubSubAsyncCommands<String, String> connection2 = redisClient.connectPubSub(redisURI).async();
assertThat(connection2.pubsubChannels().get()).contains("c1", "c2");
redisURI.setVerifyPeer(true);
connection.quit();
Thread.sleep(500);
RedisFuture<List<String>> future = connection2.pubsubChannels();
assertThat(future.get()).doesNotContain("c1", "c2");
assertThat(future.isDone()).isEqualTo(true);
RedisFuture<List<String>> defectFuture = connection.pubsubChannels();
try {
assertThat(defectFuture.get()).doesNotContain("c1", "c2");
fail("Missing ExecutionException with nested SSLHandshakeException");
} catch (InterruptedException e) {
fail("Missing ExecutionException with nested SSLHandshakeException");
} catch (ExecutionException e) {
assertThat(e).hasCauseInstanceOf(DecoderException.class);
assertThat(e).hasRootCauseInstanceOf(CertificateException.class);
}
assertThat(defectFuture.isDone()).isEqualTo(true);
connection.close();
connection2.close();
}
private void setOptions(SslOptions sslOptions) {
ClientOptions clientOptions = ClientOptions.builder().sslOptions(sslOptions).build();
redisClient.setOptions(clientOptions);
}
private void verifyConnection(RedisURI redisUri) {
StatefulRedisConnection<String, String> connection = redisClient.connect(redisUri);
connection.sync().ping();
connection.close();
}
}