RedisClusterClientIT.java

package redis.clients.jedis.tls;

import static org.junit.jupiter.api.Assertions.*;
import static redis.clients.jedis.util.TlsUtil.*;

import java.util.Collections;
import java.util.Map;
import java.util.stream.Stream;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLParameters;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;

import redis.clients.jedis.*;
import redis.clients.jedis.util.TlsUtil;
import redis.clients.jedis.exceptions.JedisClusterOperationException;

public class RedisClusterClientIT extends RedisClusterTestBase {

  private static final int DEFAULT_REDIRECTIONS = 5;
  private static final ConnectionPoolConfig DEFAULT_POOL_CONFIG = new ConnectionPoolConfig();

  /**
   * Provides different SslOptions configurations for parametrized tests.
   */
  protected static Stream<Arguments> sslOptionsProvider() {
    return Stream.of(Arguments.of("truststore", createSslOptions()),
      Arguments.of("insecure", SslOptions.builder().sslVerifyMode(SslVerifyMode.INSECURE).build()),
      Arguments.of("ssl-protocol",
        SslOptions.builder().sslProtocol("SSL").truststore(trustStorePath.toFile())
            .trustStoreType("jceks").sslVerifyMode(SslVerifyMode.CA).build()));
  }

  /**
   * Tests SSL discover nodes with various SSL configurations.
   */
  @ParameterizedTest(name = "testSSLDiscoverNodesAutomatically_{0}")
  @MethodSource("sslOptionsProvider")
  void testSSLDiscoverNodesAutomatically(String testName, SslOptions ssl) {
    try (RedisClusterClient jc = RedisClusterClient.builder()
        .nodes(Collections.singleton(tlsEndpoint.getHostAndPort()))
        .clientConfig(DefaultJedisClientConfig.builder().password(tlsEndpoint.getPassword())
            .sslOptions(ssl).build())
        .maxAttempts(DEFAULT_REDIRECTIONS).poolConfig(DEFAULT_POOL_CONFIG).build()) {
      Map<String, ?> clusterNodes = jc.getClusterNodes();
      assertTrue(clusterNodes.containsKey(tlsEndpoint.getHostAndPort(0).toString()));
      assertTrue(clusterNodes.containsKey(tlsEndpoint.getHostAndPort(1).toString()));
      assertTrue(clusterNodes.containsKey(tlsEndpoint.getHostAndPort(2).toString()));
      assertEquals("PONG", jc.ping());
    }
  }

  /**
   * Tests that connecting with ssl=true flag (system truststore) works.
   */
  @Test
  void connectWithSslFlag() {
    try (RedisClusterClient jc = RedisClusterClient.builder()
        .nodes(Collections.singleton(tlsEndpoint.getHostAndPort()))
        .clientConfig(
          DefaultJedisClientConfig.builder().password(tlsEndpoint.getPassword()).ssl(true).build())
        .maxAttempts(DEFAULT_REDIRECTIONS).poolConfig(DEFAULT_POOL_CONFIG).build()) {
      assertEquals("PONG", jc.ping());
    }
  }

  /**
   * Tests that connecting to nodes succeeds with SSL parameters and hostname verification.
   */
  @ParameterizedTest(name = "connectToNodesSucceedsWithSSLParametersAndHostMapping_{0}")
  @MethodSource("sslOptionsProvider")
  void connectToNodesSucceedsWithSSLParametersAndHostMapping(String testName, SslOptions ssl) {
    final SSLParameters sslParameters = new SSLParameters();
    sslParameters.setEndpointIdentificationAlgorithm("HTTPS");

    try (RedisClusterClient jc = RedisClusterClient.builder()
        .nodes(Collections.singleton(tlsEndpoint.getHostAndPort()))
        .clientConfig(DefaultJedisClientConfig.builder().password(tlsEndpoint.getPassword())
            .sslOptions(ssl).sslParameters(sslParameters).build())
        .maxAttempts(DEFAULT_REDIRECTIONS).poolConfig(DEFAULT_POOL_CONFIG).build()) {
      assertEquals("PONG", jc.ping());
    }
  }

  /**
   * Tests connecting with custom hostname verifier and SslOptions (from
   * SSLOptionsRedisClusterClientIT).
   */
  @Test
  public void connectWithCustomHostNameVerifierAndSslOptions() {
    HostnameVerifier hostnameVerifier = new TlsUtil.BasicHostnameVerifier();

    SslOptions sslOptions = SslOptions.builder().truststore(trustStorePath.toFile())
        .trustStoreType("jceks").sslVerifyMode(SslVerifyMode.CA).build();

    try (RedisClusterClient jc = RedisClusterClient.builder()
        .nodes(Collections.singleton(tlsEndpoint.getHostAndPort()))
        .clientConfig(DefaultJedisClientConfig.builder().password(tlsEndpoint.getPassword())
            .sslOptions(sslOptions).hostnameVerifier(hostnameVerifier).build())
        .maxAttempts(DEFAULT_REDIRECTIONS).poolConfig(DEFAULT_POOL_CONFIG).build()) {
      assertEquals("PONG", jc.ping());
    }
  }

  /**
   * Tests connecting with custom SSL socket factory.
   */
  @Test
  void connectWithCustomSocketFactory() {
    try (RedisClusterClient jc = RedisClusterClient.builder()
        .nodes(Collections.singleton(tlsEndpoint.getHostAndPort()))
        .clientConfig(
          DefaultJedisClientConfig.builder().password(tlsEndpoint.getPassword()).ssl(true)
              .sslSocketFactory(sslSocketFactoryForEnv(tlsEndpoint.getCertificatesLocation()))
              .build())
        .maxAttempts(DEFAULT_REDIRECTIONS).poolConfig(DEFAULT_POOL_CONFIG).build()) {
      assertEquals("PONG", jc.ping());
    }
  }

  /**
   * Tests that connecting with an empty trust store fails.
   */
  @Test
  void connectWithEmptyTrustStore() throws Exception {
    try (RedisClusterClient jc = RedisClusterClient.builder()
        .nodes(Collections.singleton(tlsEndpoint.getHostAndPort()))
        .clientConfig(DefaultJedisClientConfig.builder().password(tlsEndpoint.getPassword())
            .ssl(true).sslSocketFactory(createTrustNoOneSslSocketFactory()).build())
        .maxAttempts(DEFAULT_REDIRECTIONS).poolConfig(DEFAULT_POOL_CONFIG).build()) {
      jc.get("foo");
      fail("Should have thrown an exception");
    } catch (JedisClusterOperationException e) {
      assertEquals("Could not initialize cluster slots cache.", e.getMessage());
    }
  }

}