diff --git a/Makefile b/Makefile index 2455e6998a..42dc289a9f 100644 --- a/Makefile +++ b/Makefile @@ -218,6 +218,21 @@ pid = /tmp/stunnel.pid [redis] accept = 127.0.0.1:6390 connect = 127.0.0.1:6379 +[redis_cluster_1] +accept = 127.0.0.1:8379 +connect = 127.0.0.1:7379 +[redis_cluster_2] +accept = 127.0.0.1:8380 +connect = 127.0.001:7380 +[redis_cluster_3] +accept = 127.0.0.1:8381 +connect = 127.0.001:7381 +[redis_cluster_4] +accept = 127.0.0.1:8382 +connect = 127.0.0.1:7382 +[redis_cluster_5] +accept = 127.0.0.1:8383 +connect = 127.0.0.1:7383 endef export REDIS1_CONF diff --git a/src/main/java/redis/clients/jedis/BinaryJedisCluster.java b/src/main/java/redis/clients/jedis/BinaryJedisCluster.java index d40d17eea7..e8f243939a 100644 --- a/src/main/java/redis/clients/jedis/BinaryJedisCluster.java +++ b/src/main/java/redis/clients/jedis/BinaryJedisCluster.java @@ -16,6 +16,10 @@ import java.util.Set; import redis.clients.util.JedisClusterHashTagUtil; +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.SSLParameters; +import javax.net.ssl.SSLSocketFactory; + public class BinaryJedisCluster implements BasicCommands, BinaryJedisClusterCommands, MultiKeyBinaryJedisClusterCommands, JedisClusterBinaryScriptingCommands, Closeable { @@ -60,6 +64,19 @@ public BinaryJedisCluster(Set jedisClusterNode, int connectionTimeo connectionTimeout, soTimeout, password, clientName); this.maxAttempts = maxAttempts; } + + public BinaryJedisCluster(Set jedisClusterNode, int connectionTimeout, int soTimeout, int maxAttempts, String password, String clientName, GenericObjectPoolConfig poolConfig, + boolean ssl) { + this(jedisClusterNode, connectionTimeout, soTimeout, maxAttempts, password, clientName, poolConfig, ssl, null, null, null, null); + } + + public BinaryJedisCluster(Set jedisClusterNode, int connectionTimeout, int soTimeout, int maxAttempts, String password, String clientName, GenericObjectPoolConfig poolConfig, + boolean ssl, SSLSocketFactory sslSocketFactory, SSLParameters sslParameters, + HostnameVerifier hostnameVerifier, JedisClusterHostAndPortMap hostAndPortMap) { + this.connectionHandler = new JedisSlotBasedConnectionHandler(jedisClusterNode, poolConfig, + connectionTimeout, soTimeout, password, clientName, ssl, sslSocketFactory, sslParameters, hostnameVerifier, hostAndPortMap); + this.maxAttempts = maxAttempts; + } @Override public void close() throws IOException { diff --git a/src/main/java/redis/clients/jedis/JedisCluster.java b/src/main/java/redis/clients/jedis/JedisCluster.java index e4a43de1e9..441e6d74cc 100644 --- a/src/main/java/redis/clients/jedis/JedisCluster.java +++ b/src/main/java/redis/clients/jedis/JedisCluster.java @@ -12,6 +12,10 @@ import java.util.Map.Entry; import java.util.Set; +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.SSLParameters; +import javax.net.ssl.SSLSocketFactory; + import org.apache.commons.pool2.impl.GenericObjectPoolConfig; import redis.clients.util.JedisClusterHashTagUtil; @@ -64,6 +68,20 @@ public JedisCluster(HostAndPort node, int connectionTimeout, int soTimeout, int maxAttempts, String password, String clientName, final GenericObjectPoolConfig poolConfig) { this(Collections.singleton(node), connectionTimeout, soTimeout, maxAttempts, password, clientName, poolConfig); } + + public JedisCluster(HostAndPort node, int connectionTimeout, int soTimeout, + int maxAttempts, String password, String clientName, final GenericObjectPoolConfig poolConfig, + boolean ssl) { + super(Collections.singleton(node), connectionTimeout, soTimeout, maxAttempts, password, clientName, poolConfig, ssl); + } + + public JedisCluster(HostAndPort node, int connectionTimeout, int soTimeout, + int maxAttempts, String password, String clientName, final GenericObjectPoolConfig poolConfig, + boolean ssl, SSLSocketFactory sslSocketFactory, SSLParameters sslParameters, + HostnameVerifier hostnameVerifier, JedisClusterHostAndPortMap hostAndPortMap) { + super(Collections.singleton(node), connectionTimeout, soTimeout, maxAttempts, password, clientName, poolConfig, + ssl, sslSocketFactory, sslParameters, hostnameVerifier, hostAndPortMap); + } public JedisCluster(Set nodes) { this(nodes, DEFAULT_TIMEOUT); @@ -103,7 +121,21 @@ public JedisCluster(Set jedisClusterNode, int connectionTimeout, in public JedisCluster(Set jedisClusterNode, int connectionTimeout, int soTimeout, int maxAttempts, String password, String clientName, final GenericObjectPoolConfig poolConfig) { super(jedisClusterNode, connectionTimeout, soTimeout, maxAttempts, password, clientName, poolConfig); -} + } + + public JedisCluster(Set jedisClusterNode, int connectionTimeout, int soTimeout, + int maxAttempts, String password, String clientName, final GenericObjectPoolConfig poolConfig, + boolean ssl) { + super(jedisClusterNode, connectionTimeout, soTimeout, maxAttempts, password, clientName, poolConfig, ssl); + } + + public JedisCluster(Set jedisClusterNode, int connectionTimeout, int soTimeout, + int maxAttempts, String password, String clientName, final GenericObjectPoolConfig poolConfig, + boolean ssl, SSLSocketFactory sslSocketFactory, SSLParameters sslParameters, + HostnameVerifier hostnameVerifier, JedisClusterHostAndPortMap hostAndPortMap) { + super(jedisClusterNode, connectionTimeout, soTimeout, maxAttempts, password, clientName, poolConfig, + ssl, sslSocketFactory, sslParameters, hostnameVerifier, hostAndPortMap); + } @Override public String set(final String key, final String value) { diff --git a/src/main/java/redis/clients/jedis/JedisClusterConnectionHandler.java b/src/main/java/redis/clients/jedis/JedisClusterConnectionHandler.java index 270f4013f1..3bd6f13325 100644 --- a/src/main/java/redis/clients/jedis/JedisClusterConnectionHandler.java +++ b/src/main/java/redis/clients/jedis/JedisClusterConnectionHandler.java @@ -4,6 +4,10 @@ import java.util.Map; import java.util.Set; +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.SSLParameters; +import javax.net.ssl.SSLSocketFactory; + import org.apache.commons.pool2.impl.GenericObjectPoolConfig; import redis.clients.jedis.exceptions.JedisConnectionException; @@ -18,9 +22,17 @@ public JedisClusterConnectionHandler(Set nodes, public JedisClusterConnectionHandler(Set nodes, final GenericObjectPoolConfig poolConfig, int connectionTimeout, int soTimeout, String password, String clientName) { - this.cache = new JedisClusterInfoCache(poolConfig, connectionTimeout, soTimeout, password, clientName); - initializeSlotsCache(nodes, poolConfig, connectionTimeout, soTimeout, password, clientName); -} + this(nodes, poolConfig, connectionTimeout, soTimeout, password, clientName, false, null, null, null, null); + } + + public JedisClusterConnectionHandler(Set nodes, + final GenericObjectPoolConfig poolConfig, int connectionTimeout, int soTimeout, String password, String clientName, + boolean ssl, SSLSocketFactory sslSocketFactory, SSLParameters sslParameters, + HostnameVerifier hostnameVerifier, JedisClusterHostAndPortMap portMap) { + this.cache = new JedisClusterInfoCache(poolConfig, connectionTimeout, soTimeout, password, clientName, + ssl, sslSocketFactory, sslParameters, hostnameVerifier, portMap); + initializeSlotsCache(nodes, poolConfig, connectionTimeout, soTimeout, password, clientName, ssl, sslSocketFactory, sslParameters, hostnameVerifier); + } abstract Jedis getConnection(); @@ -34,12 +46,12 @@ public Map getNodes() { return cache.getNodes(); } - private void initializeSlotsCache(Set startNodes, GenericObjectPoolConfig poolConfig, - int connectionTimeout, int soTimeout, String password, String clientName) { + private void initializeSlotsCache(Set startNodes, GenericObjectPoolConfig poolConfig, int connectionTimeout, int soTimeout, String password, String clientName, + boolean ssl, SSLSocketFactory sslSocketFactory, SSLParameters sslParameters, HostnameVerifier hostnameVerifier) { for (HostAndPort hostAndPort : startNodes) { Jedis jedis = null; try { - jedis = new Jedis(hostAndPort.getHost(), hostAndPort.getPort(), connectionTimeout, soTimeout); + jedis = new Jedis(hostAndPort.getHost(), hostAndPort.getPort(), connectionTimeout, soTimeout, ssl, sslSocketFactory, sslParameters, hostnameVerifier); if (password != null) { jedis.auth(password); } diff --git a/src/main/java/redis/clients/jedis/JedisClusterHostAndPortMap.java b/src/main/java/redis/clients/jedis/JedisClusterHostAndPortMap.java new file mode 100644 index 0000000000..9a5a5029cb --- /dev/null +++ b/src/main/java/redis/clients/jedis/JedisClusterHostAndPortMap.java @@ -0,0 +1,5 @@ +package redis.clients.jedis; + +public interface JedisClusterHostAndPortMap { + HostAndPort getSSLHostAndPort(String host, int port); +} diff --git a/src/main/java/redis/clients/jedis/JedisClusterInfoCache.java b/src/main/java/redis/clients/jedis/JedisClusterInfoCache.java index 3d931738e9..7b8ecd6280 100644 --- a/src/main/java/redis/clients/jedis/JedisClusterInfoCache.java +++ b/src/main/java/redis/clients/jedis/JedisClusterInfoCache.java @@ -33,19 +33,37 @@ public class JedisClusterInfoCache { private String password; private String clientName; + private boolean ssl; + private SSLSocketFactory sslSocketFactory; + private SSLParameters sslParameters; + private HostnameVerifier hostnameVerifier; + private JedisClusterHostAndPortMap hostAndPortMap; + private static final int MASTER_NODE_INDEX = 2; public JedisClusterInfoCache(final GenericObjectPoolConfig poolConfig, int timeout) { this(poolConfig, timeout, timeout, null, null); } - + public JedisClusterInfoCache(final GenericObjectPoolConfig poolConfig, final int connectionTimeout, final int soTimeout, final String password, final String clientName) { + this(poolConfig, connectionTimeout, soTimeout, password, clientName, false, null, null, null, null); + } + + public JedisClusterInfoCache(final GenericObjectPoolConfig poolConfig, + final int connectionTimeout, final int soTimeout, final String password, final String clientName, + boolean ssl, SSLSocketFactory sslSocketFactory, SSLParameters sslParameters, + HostnameVerifier hostnameVerifier, JedisClusterHostAndPortMap hostAndPortMap) { this.poolConfig = poolConfig; this.connectionTimeout = connectionTimeout; this.soTimeout = soTimeout; this.password = password; this.clientName = clientName; + this.ssl = ssl; + this.sslSocketFactory = sslSocketFactory; + this.sslParameters = sslParameters; + this.hostnameVerifier = hostnameVerifier; + this.hostAndPortMap = hostAndPortMap; } public void discoverClusterNodesAndSlots(Jedis jedis) { @@ -147,8 +165,15 @@ private void discoverClusterSlots(Jedis jedis) { } private HostAndPort generateHostAndPort(List hostInfos) { - return new HostAndPort(SafeEncoder.encode((byte[]) hostInfos.get(0)), - ((Long) hostInfos.get(1)).intValue()); + String host = SafeEncoder.encode((byte[]) hostInfos.get(0)); + int port = ((Long) hostInfos.get(1)).intValue(); + if (ssl && hostAndPortMap != null) { + HostAndPort hostAndPort = hostAndPortMap.getSSLHostAndPort(host, port); + if (hostAndPortMap != null) { + return hostAndPort; + } + } + return new HostAndPort(host, port); } public JedisPool setupNodeIfNotExist(HostAndPort node) { @@ -159,7 +184,8 @@ public JedisPool setupNodeIfNotExist(HostAndPort node) { if (existingPool != null) return existingPool; JedisPool nodePool = new JedisPool(poolConfig, node.getHost(), node.getPort(), - connectionTimeout, soTimeout, password, 0, clientName, false, null, null, null); + connectionTimeout, soTimeout, password, 0, clientName, + ssl, sslSocketFactory, sslParameters, hostnameVerifier); nodes.put(nodeKey, nodePool); return nodePool; } finally { diff --git a/src/main/java/redis/clients/jedis/JedisSlotBasedConnectionHandler.java b/src/main/java/redis/clients/jedis/JedisSlotBasedConnectionHandler.java index fe10bc7aaf..ca5c289086 100644 --- a/src/main/java/redis/clients/jedis/JedisSlotBasedConnectionHandler.java +++ b/src/main/java/redis/clients/jedis/JedisSlotBasedConnectionHandler.java @@ -5,6 +5,10 @@ import org.apache.commons.pool2.impl.GenericObjectPoolConfig; +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.SSLParameters; +import javax.net.ssl.SSLSocketFactory; + import redis.clients.jedis.exceptions.JedisException; import redis.clients.jedis.exceptions.JedisNoReachableClusterNodeException; @@ -27,6 +31,11 @@ public JedisSlotBasedConnectionHandler(Set nodes, GenericObjectPool public JedisSlotBasedConnectionHandler(Set nodes, GenericObjectPoolConfig poolConfig, int connectionTimeout, int soTimeout, String password, String clientName) { super(nodes, poolConfig, connectionTimeout, soTimeout, password, clientName); } + + public JedisSlotBasedConnectionHandler(Set nodes, GenericObjectPoolConfig poolConfig, int connectionTimeout, int soTimeout, String password, String clientName, + boolean ssl, SSLSocketFactory sslSocketFactory, SSLParameters sslParameters, HostnameVerifier hostnameVerifier, JedisClusterHostAndPortMap portMap) { + super(nodes, poolConfig, connectionTimeout, soTimeout, password, clientName, ssl, sslSocketFactory, sslParameters, hostnameVerifier, portMap); + } @Override public Jedis getConnection() { diff --git a/src/test/java/redis/clients/jedis/tests/SSLJedisClusterTest.java b/src/test/java/redis/clients/jedis/tests/SSLJedisClusterTest.java new file mode 100644 index 0000000000..5bd895acda --- /dev/null +++ b/src/test/java/redis/clients/jedis/tests/SSLJedisClusterTest.java @@ -0,0 +1,302 @@ +package redis.clients.jedis.tests; + + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.io.IOException; +import java.security.InvalidAlgorithmParameterException; +import java.security.cert.CertificateException; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.SSLException; +import javax.net.ssl.SSLHandshakeException; +import javax.net.ssl.SSLParameters; +import javax.net.ssl.SSLSession; +import javax.net.ssl.SSLSocketFactory; + +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import redis.clients.jedis.HostAndPort; +import redis.clients.jedis.JedisCluster; +import redis.clients.jedis.JedisClusterHostAndPortMap; +import redis.clients.jedis.JedisPool; +import redis.clients.jedis.JedisPoolConfig; +import redis.clients.jedis.exceptions.*; +import redis.clients.jedis.tests.SSLJedisTest.BasicHostnameVerifier; + +public class SSLJedisClusterTest extends JedisClusterTest { + private static final int DEFAULT_TIMEOUT = 2000; + private static final int DEFAULT_REDIRECTIONS = 5; + private static final JedisPoolConfig DEFAULT_CONFIG = new JedisPoolConfig(); + + private JedisClusterHostAndPortMap hostAndPortMap = new JedisClusterHostAndPortMap() { + public HostAndPort getSSLHostAndPort(String host, int port) { + host = host.equalsIgnoreCase("127.0.0.1") ? "localhost" : host; + return new HostAndPort(host, port + 1000); + } + }; + + //don't map IP addresses so that we try to connect with host 127.0.0.1 + private JedisClusterHostAndPortMap portMap = new JedisClusterHostAndPortMap() { + public HostAndPort getSSLHostAndPort(String host, int port) { + return new HostAndPort(host, port + 1000); + } + }; + + @Before + public void setUp() throws InterruptedException { + super.setUp(); + + SSLJedisTest.setupTrustStore(); // set up trust store for SSL tests + } + + @AfterClass + public static void cleanUp() { + JedisClusterTest.cleanUp(); + } + + @After + public void tearDown() throws InterruptedException { + cleanUp(); + } + + @Test + public void testSSLDiscoverNodesAutomatically() throws IOException { + Set jedisClusterNode = new HashSet(); + jedisClusterNode.add(new HostAndPort("localhost", 8379)); + JedisCluster jc = new JedisCluster(jedisClusterNode, DEFAULT_TIMEOUT, DEFAULT_TIMEOUT, DEFAULT_REDIRECTIONS, + "cluster", null, DEFAULT_CONFIG, true, null, null, null, hostAndPortMap); + Map clusterNodes = jc.getClusterNodes(); + assertEquals(3, clusterNodes.size()); + assertTrue(clusterNodes.containsKey("localhost:8379")); + assertTrue(clusterNodes.containsKey("localhost:8380")); + assertTrue(clusterNodes.containsKey("localhost:8381")); + + jc.get("foo"); + jc.close(); + + JedisCluster jc2 = new JedisCluster(new HostAndPort("localhost", 8379), DEFAULT_TIMEOUT, DEFAULT_TIMEOUT, + DEFAULT_REDIRECTIONS, "cluster", null, DEFAULT_CONFIG, true, null, null, null, hostAndPortMap); + clusterNodes = jc2.getClusterNodes(); + assertEquals(3, clusterNodes.size()); + assertTrue(clusterNodes.containsKey("localhost:8379")); + assertTrue(clusterNodes.containsKey("localhost:8380")); + assertTrue(clusterNodes.containsKey("localhost:8381")); + jc2.get("foo"); + jc2.close(); + } + + @Test + public void testSSLWithoutPortMap() throws IOException { + Set jedisClusterNode = new HashSet(); + jedisClusterNode.add(new HostAndPort("localhost", 8379)); + JedisCluster jc = new JedisCluster(jedisClusterNode, DEFAULT_TIMEOUT, DEFAULT_TIMEOUT, DEFAULT_REDIRECTIONS, + "cluster", null, DEFAULT_CONFIG, true, null, null, null, null); + + Map clusterNodes = jc.getClusterNodes(); + assertEquals(3, clusterNodes.size()); + assertTrue(clusterNodes.containsKey("127.0.0.1:7379")); + assertTrue(clusterNodes.containsKey("127.0.0.1:7380")); + assertTrue(clusterNodes.containsKey("127.0.0.1:7381")); + jc.close(); + } + + @Test + public void connectByIpAddress() throws IOException { + JedisCluster jc = new JedisCluster(new HostAndPort("127.0.0.1", 8379), DEFAULT_TIMEOUT, DEFAULT_TIMEOUT, + DEFAULT_REDIRECTIONS, "cluster", null, DEFAULT_CONFIG, true, + null, null, null, hostAndPortMap); + jc.get("foo"); + jc.close(); + } + + @Test + public void connectToNodesFailsWithSSLParametersAndNoHostMapping() throws IOException { + final SSLParameters sslParameters = new SSLParameters(); + sslParameters.setEndpointIdentificationAlgorithm("HTTPS"); + + JedisCluster jc = new JedisCluster(new HostAndPort("localhost", 8379), DEFAULT_TIMEOUT, DEFAULT_TIMEOUT, + DEFAULT_REDIRECTIONS, "cluster", null, DEFAULT_CONFIG, true, + null, sslParameters, null, portMap); + + try { + jc.get("foo"); + Assert.fail("The code did not throw the expected JedisConnectionException."); + } catch (JedisConnectionException e) { + // initial connection to localhost works, but subsequent connections to nodes use 127.0.0.1 + // and fail hostname verification + Assert.assertEquals("The JedisConnectionException does not contain the expected message.", + "Could not get a resource from the pool", e.getMessage()); + } + jc.close(); + } + + @Test + public void connectToNodesSucceedsWithSSLParametersAndHostMapping() throws IOException { + final SSLParameters sslParameters = new SSLParameters(); + sslParameters.setEndpointIdentificationAlgorithm("HTTPS"); + + JedisCluster jc = new JedisCluster(new HostAndPort("localhost", 8379), DEFAULT_TIMEOUT, DEFAULT_TIMEOUT, + DEFAULT_REDIRECTIONS, "cluster", null, DEFAULT_CONFIG, true, + null, sslParameters, null, hostAndPortMap); + jc.get("foo"); + jc.close(); + } + + @Test + public void connectByIpAddressFailsWithSSLParameters() throws IOException { + final SSLParameters sslParameters = new SSLParameters(); + sslParameters.setEndpointIdentificationAlgorithm("HTTPS"); + + JedisCluster jc = null; + try { + jc = new JedisCluster(new HostAndPort("127.0.0.1", 8379), DEFAULT_TIMEOUT, DEFAULT_TIMEOUT, + DEFAULT_REDIRECTIONS, "cluster", null, DEFAULT_CONFIG, true, + null, sslParameters, null, hostAndPortMap); + Assert.fail("The code did not throw the expected JedisConnectionException."); + } catch (JedisConnectionException e) { + Assert.assertEquals(SSLException.class, e.getCause().getClass()); + Assert.assertEquals(SSLHandshakeException.class, e.getCause().getCause().getClass()); + Assert.assertEquals(CertificateException.class, e.getCause().getCause().getCause().getClass()); + } finally { + if (jc != null) { + jc.close(); + } + } + } + + @Test + public void connectWithCustomHostNameVerifier() throws IOException { + HostnameVerifier hostnameVerifier = new BasicHostnameVerifier(); + HostnameVerifier localhostVerifier = new LocalhostVerifier(); + + JedisCluster jc = new JedisCluster(new HostAndPort("localhost", 8379), DEFAULT_TIMEOUT, DEFAULT_TIMEOUT, + DEFAULT_REDIRECTIONS, "cluster", null, DEFAULT_CONFIG, true, + null, null, hostnameVerifier, portMap);; + try { + // initial connection made with 'localhost' but subsequent connections to nodes use 127.0.0.1 + // which causes custom hostname verification to fail + jc.get("foo"); + Assert.fail("The code did not throw the expected JedisConnectionException."); + } catch (JedisConnectionException e) { + Assert.assertEquals("The JedisConnectionException does not contain the expected message.", + "Could not get a resource from the pool", e.getMessage()); + } + jc.close(); + + JedisCluster jc2 = null; + try { + jc2 = new JedisCluster(new HostAndPort("127.0.0.1", 8379), DEFAULT_TIMEOUT, DEFAULT_TIMEOUT, + DEFAULT_REDIRECTIONS, "cluster", null, DEFAULT_CONFIG, true, + null, null, hostnameVerifier, portMap); + Assert.fail("The code did not throw the expected NullPointerException."); + } catch (NullPointerException e) { + // Null pointer exception occurs from closing Jedis object that did not connect due to custom + // hostname validation. When closing this Jedis object, the RedisOutputStream in the underlying + // Connection object is null and causes this exception + } finally { + if (jc2 != null) { + jc2.close(); + } + } + + JedisCluster jc3 = new JedisCluster(new HostAndPort("localhost", 8379), DEFAULT_TIMEOUT, DEFAULT_TIMEOUT, + DEFAULT_REDIRECTIONS, "cluster", null, DEFAULT_CONFIG, true, + null, null, localhostVerifier, portMap);; + jc3.get("foo"); + jc3.close(); + } + + @Test + public void connectWithCustomSocketFactory() throws Exception { + final SSLSocketFactory sslSocketFactory = SSLJedisTest.createTrustStoreSslSocketFactory(); + + JedisCluster jc = new JedisCluster(new HostAndPort("localhost", 8379), DEFAULT_TIMEOUT, DEFAULT_TIMEOUT, + DEFAULT_REDIRECTIONS, "cluster", null, DEFAULT_CONFIG, true, + sslSocketFactory, null, null, portMap); + assertEquals(3, jc.getClusterNodes().size()); + jc.close(); + } + + @Test + public void connectWithEmptyTrustStore() throws Exception { + final SSLSocketFactory sslSocketFactory = SSLJedisTest.createTrustNoOneSslSocketFactory(); + + JedisCluster jc = null; + try { + jc = new JedisCluster(new HostAndPort("localhost", 8379), DEFAULT_TIMEOUT, DEFAULT_TIMEOUT, + DEFAULT_REDIRECTIONS, "cluster", null, DEFAULT_CONFIG, true, + sslSocketFactory, null, null, null); + Assert.fail("The code did not throw the expected JedisConnectionException."); + } catch (JedisConnectionException e) { + Assert.assertEquals("Unexpected first inner exception.", + SSLException.class, e.getCause().getClass()); + Assert.assertEquals("Unexpected second inner exception.", + SSLException.class, e.getCause().getCause().getClass()); + Assert.assertEquals("Unexpected third inner exception", + RuntimeException.class, e.getCause().getCause().getCause().getClass()); + Assert.assertEquals("Unexpected fourth inner exception.", + InvalidAlgorithmParameterException.class, e.getCause().getCause().getCause().getCause().getClass()); + } finally { + if (jc != null) { + jc.close(); + } + } + } + + @Test + public void hostAndPortMapIgnoredIfSSLFalse() throws IOException { + JedisClusterHostAndPortMap hostAndPortMap = new JedisClusterHostAndPortMap() { + public HostAndPort getSSLHostAndPort(String host, int port) { + return new HostAndPort(host, port + 2000); + } + }; + + JedisCluster jc = new JedisCluster(new HostAndPort("localhost", 7379), DEFAULT_TIMEOUT, DEFAULT_TIMEOUT, + DEFAULT_REDIRECTIONS, "cluster", null, DEFAULT_CONFIG, false, + null, null, null, hostAndPortMap); + + Map nodes = jc.getClusterNodes(); + assertTrue(nodes.containsKey("127.0.0.1:7379")); + assertFalse(nodes.containsKey("127.0.0.1:9739")); + jc.close(); + } + + @Test + public void defaultHostAndPortUsedIfMapReturnsNull() throws IOException { + JedisClusterHostAndPortMap hostAndPortMap = new JedisClusterHostAndPortMap() { + public HostAndPort getSSLHostAndPort(String host, int port) { + return null; + } + }; + + JedisCluster jc = new JedisCluster(new HostAndPort("localhost", 7379), DEFAULT_TIMEOUT, DEFAULT_TIMEOUT, + DEFAULT_REDIRECTIONS, "cluster", null, DEFAULT_CONFIG, false, + null, null, null, hostAndPortMap); + + Map clusterNodes = jc.getClusterNodes(); + assertEquals(3, clusterNodes.size()); + assertTrue(clusterNodes.containsKey("127.0.0.1:7379")); + assertTrue(clusterNodes.containsKey("127.0.0.1:7380")); + assertTrue(clusterNodes.containsKey("127.0.0.1:7381")); + jc.close(); + } + + public class LocalhostVerifier extends BasicHostnameVerifier { + @Override + public boolean verify(String hostname, SSLSession session) { + if (hostname.equals("127.0.0.1")) { + hostname = "localhost"; + } + return super.verify(hostname, session); + } + } +} diff --git a/src/test/java/redis/clients/jedis/tests/SSLJedisTest.java b/src/test/java/redis/clients/jedis/tests/SSLJedisTest.java index fe20b1b2e7..610329145a 100644 --- a/src/test/java/redis/clients/jedis/tests/SSLJedisTest.java +++ b/src/test/java/redis/clients/jedis/tests/SSLJedisTest.java @@ -251,7 +251,7 @@ public void connectWithShardInfoAndEmptyTrustStore() throws Exception { * Creates an SSLSocketFactory that trusts all certificates in * truststore.jceks. */ - private static SSLSocketFactory createTrustStoreSslSocketFactory() throws Exception { + static SSLSocketFactory createTrustStoreSslSocketFactory() throws Exception { KeyStore trustStore = KeyStore.getInstance("jceks"); InputStream inputStream = null; @@ -275,7 +275,7 @@ private static SSLSocketFactory createTrustStoreSslSocketFactory() throws Except * Creates an SSLSocketFactory with a trust manager that does not trust any * certificates. */ - private static SSLSocketFactory createTrustNoOneSslSocketFactory() throws Exception { + static SSLSocketFactory createTrustNoOneSslSocketFactory() throws Exception { TrustManager[] unTrustManagers = new TrustManager[] { new X509TrustManager() { public X509Certificate[] getAcceptedIssuers() { @@ -301,7 +301,7 @@ public void checkServerTrusted(X509Certificate[] chain, String authType) { * for production. * */ - private static class BasicHostnameVerifier implements HostnameVerifier { + static class BasicHostnameVerifier implements HostnameVerifier { private static final String COMMON_NAME_RDN_PREFIX = "CN=";