diff --git a/Makefile b/Makefile index 10352457b9..fc29c31c08 100644 --- a/Makefile +++ b/Makefile @@ -243,6 +243,12 @@ pid = /tmp/stunnel.pid [redis] accept = 127.0.0.1:6390 connect = 127.0.0.1:6379 +[redis_3] +accept = 127.0.0.1:16381 +connect = 127.0.0.1:6381 +[redis_4] +accept = 127.0.0.1:16382 +connect = 127.0.0.1:6382 [redis_cluster_1] accept = 127.0.0.1:8379 connect = 127.0.0.1:7379 @@ -258,6 +264,18 @@ connect = 127.0.0.1:7382 [redis_cluster_5] accept = 127.0.0.1:8383 connect = 127.0.0.1:7383 +[redis_sentinel_1] +accept = 127.0.0.1:36379 +connect = 127.0.0.1:26379 +[redis_sentinel_2] +accept = 127.0.0.1:36380 +connect = 127.0.0.1:26380 +[redis_sentinel_3] +accept = 127.0.0.1:36381 +connect = 127.0.0.1:26381 +[redis_sentinel_4] +accept = 127.0.0.1:36382 +connect = 127.0.0.1:26382 endef export REDIS1_CONF diff --git a/src/main/java/redis/clients/jedis/JedisSentinelPool.java b/src/main/java/redis/clients/jedis/JedisSentinelPool.java index ef77f3e75c..7370bc47e6 100644 --- a/src/main/java/redis/clients/jedis/JedisSentinelPool.java +++ b/src/main/java/redis/clients/jedis/JedisSentinelPool.java @@ -6,6 +6,10 @@ import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.SSLParameters; +import javax.net.ssl.SSLSocketFactory; + import org.apache.commons.pool2.impl.GenericObjectPoolConfig; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -14,7 +18,7 @@ import redis.clients.jedis.exceptions.JedisException; public class JedisSentinelPool extends JedisPoolAbstract { - protected Logger log = LoggerFactory.getLogger(getClass().getName()); + protected final Logger log = LoggerFactory.getLogger(getClass().getName()); protected final GenericObjectPoolConfig poolConfig; @@ -26,12 +30,18 @@ public class JedisSentinelPool extends JedisPoolAbstract { protected final String password; protected final int database; protected final String clientName; + protected boolean isMasterSslEnabled; protected int sentinelConnectionTimeout; protected int sentinelSoTimeout; protected String sentinelUser; protected String sentinelPassword; protected String sentinelClientName; + protected boolean isSentinelSslEnabled; + + protected SSLSocketFactory sslSocketFactory; + protected SSLParameters sslParameters; + protected HostnameVerifier hostnameVerifier; protected final Set masterListeners = new HashSet<>(); @@ -155,6 +165,41 @@ public JedisSentinelPool(String masterName, Set sentinels, final String user, final String password, final int database, final String clientName, final int sentinelConnectionTimeout, final int sentinelSoTimeout, final String sentinelUser, final String sentinelPassword, final String sentinelClientName) { + this(masterName, sentinels, poolConfig, connectionTimeout, soTimeout, infiniteSoTimeout, user, + password, database, clientName, false, sentinelConnectionTimeout, sentinelSoTimeout, + sentinelUser, sentinelPassword, sentinelClientName, false, null, null, null); + } + + public JedisSentinelPool(String masterName, Set sentinels, + final GenericObjectPoolConfig poolConfig, final int connectionTimeout, final int soTimeout, + final String password, final int database, final String clientName, final boolean isMasterSslEnabled, + final int sentinelConnectionTimeout, final int sentinelSoTimeout, final String sentinelPassword, + final String sentinelClientName, final boolean isSentinelSslEnabled, final SSLSocketFactory sslSocketFactory, + final SSLParameters sslParameters, final HostnameVerifier hostnameVerifier) { + this(masterName, sentinels, poolConfig, connectionTimeout, soTimeout, 0, null, password, + database, clientName, isMasterSslEnabled, sentinelConnectionTimeout, sentinelSoTimeout, + null, sentinelPassword, sentinelClientName, isSentinelSslEnabled, sslSocketFactory, + sslParameters, hostnameVerifier); + } + + public JedisSentinelPool(String masterName, Set sentinels, final GenericObjectPoolConfig poolConfig, + final int connectionTimeout, final int soTimeout, final String user, final String password, + final int database, final String clientName, final boolean isMasterSslEnabled, + final int sentinelConnectionTimeout, final int sentinelSoTimeout, final String sentinelUser, + final String sentinelPassword, final String sentinelClientName, final boolean isSentinelSslEnabled, + final SSLSocketFactory sslSocketFactory, final SSLParameters sslParameters, final HostnameVerifier hostnameVerifier) { + this(masterName, sentinels, poolConfig, connectionTimeout, soTimeout, 0, user, password, + database, clientName, isMasterSslEnabled, sentinelConnectionTimeout, sentinelSoTimeout, + sentinelUser, sentinelPassword, sentinelClientName, isSentinelSslEnabled, sslSocketFactory, + sslParameters, hostnameVerifier); + } + + public JedisSentinelPool(String masterName, Set sentinels, final GenericObjectPoolConfig poolConfig, + final int connectionTimeout, final int soTimeout, final int infiniteSoTimeout, final String user, + final String password, final int database, final String clientName, final boolean isMasterSslEnabled, + final int sentinelConnectionTimeout, final int sentinelSoTimeout, final String sentinelUser, + final String sentinelPassword, final String sentinelClientName, final boolean isSentinelSslEnabled, + final SSLSocketFactory sslSocketFactory, final SSLParameters sslParameters, final HostnameVerifier hostnameVerifier) { this.poolConfig = poolConfig; this.connectionTimeout = connectionTimeout; @@ -164,11 +209,16 @@ public JedisSentinelPool(String masterName, Set sentinels, this.password = password; this.database = database; this.clientName = clientName; + this.isMasterSslEnabled = isMasterSslEnabled; this.sentinelConnectionTimeout = sentinelConnectionTimeout; this.sentinelSoTimeout = sentinelSoTimeout; this.sentinelUser = sentinelUser; this.sentinelPassword = sentinelPassword; this.sentinelClientName = sentinelClientName; + this.isSentinelSslEnabled = isSentinelSslEnabled; + this.sslSocketFactory = sslSocketFactory; + this.sslParameters = sslParameters; + this.hostnameVerifier = hostnameVerifier; HostAndPort master = initSentinels(sentinels, masterName); initPool(master); @@ -193,7 +243,8 @@ private void initPool(HostAndPort master) { currentHostMaster = master; if (factory == null) { factory = new JedisFactory(master.getHost(), master.getPort(), connectionTimeout, - soTimeout, infiniteSoTimeout, user, password, database, clientName); + soTimeout, infiniteSoTimeout, user, password, database, clientName, + isMasterSslEnabled, sslSocketFactory, sslParameters, hostnameVerifier); initPool(poolConfig, factory); } else { factory.setHostAndPort(currentHostMaster); @@ -217,13 +268,15 @@ private HostAndPort initSentinels(Set sentinels, final String masterName log.info("Trying to find master from available Sentinels..."); for (String sentinel : sentinels) { - final HostAndPort hap = HostAndPort.parseString(sentinel); + final HostAndPort hap = !isSentinelSslEnabled ? HostAndPort.parseString(sentinel) : HostAndPort.from(sentinel); + // TODO: find a sane solution. Leaving this code ugly for now. log.debug("Connecting to Sentinel {}", hap); Jedis jedis = null; try { - jedis = new Jedis(hap.getHost(), hap.getPort(), sentinelConnectionTimeout, sentinelSoTimeout); + jedis = new Jedis(hap.getHost(), hap.getPort(), sentinelConnectionTimeout, sentinelSoTimeout, + isSentinelSslEnabled, sslSocketFactory, sslParameters, hostnameVerifier); if (sentinelUser != null) { jedis.auth(sentinelUser, sentinelPassword); } else if (sentinelPassword != null) { @@ -273,7 +326,9 @@ private HostAndPort initSentinels(Set sentinels, final String masterName log.info("Redis master running at {}, starting Sentinel listeners...", master); for (String sentinel : sentinels) { - final HostAndPort hap = HostAndPort.parseString(sentinel); + final HostAndPort hap = !isSentinelSslEnabled ? HostAndPort.parseString(sentinel) : HostAndPort.from(sentinel); + // TODO: find a sane solution. Leaving this code ugly for now. + MasterListener masterListener = new MasterListener(masterName, hap.getHost(), hap.getPort()); // whether MasterListener threads are alive or not, process can be stopped masterListener.setDaemon(true); @@ -284,7 +339,9 @@ private HostAndPort initSentinels(Set sentinels, final String masterName return master; } - private HostAndPort toHostAndPort(List getMasterAddrByNameResult) { + // This should be private. The expected behavior can be achieved by host and port mapping. + @Deprecated + protected HostAndPort toHostAndPort(List getMasterAddrByNameResult) { String host = getMasterAddrByNameResult.get(0); int port = Integer.parseInt(getMasterAddrByNameResult.get(1)); @@ -369,7 +426,8 @@ public void run() { break; } - j = new Jedis(host, port, sentinelConnectionTimeout, sentinelSoTimeout); + j = new Jedis(host, port, sentinelConnectionTimeout, sentinelSoTimeout, + isSentinelSslEnabled, sslSocketFactory, sslParameters, hostnameVerifier); if (sentinelUser != null) { j.auth(sentinelUser, sentinelPassword); } else if (sentinelPassword != null) { diff --git a/src/test/java/redis/clients/jedis/tests/SSLJedisSentinelPoolTest.java b/src/test/java/redis/clients/jedis/tests/SSLJedisSentinelPoolTest.java new file mode 100644 index 0000000000..7acce2d7c9 --- /dev/null +++ b/src/test/java/redis/clients/jedis/tests/SSLJedisSentinelPoolTest.java @@ -0,0 +1,93 @@ +package redis.clients.jedis.tests; + +import org.apache.commons.pool2.impl.GenericObjectPoolConfig; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import redis.clients.jedis.HostAndPort; +import redis.clients.jedis.JedisSentinelPool; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.SSLParameters; +import javax.net.ssl.SSLSocketFactory; +import java.io.File; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import static org.junit.Assert.assertTrue; + +public class SSLJedisSentinelPoolTest { + private static final String MASTER_NAME = "mymaster"; + + protected Set sentinels = new HashSet(); + + @Before + public void setUp() throws Exception { + HostAndPort sentinel2 = HostAndPortUtil.getSentinelServers().get(1); + HostAndPort sentinel4 = HostAndPortUtil.getSentinelServers().get(3); + sentinels.add(sentinel2.getHost() + ":" + (sentinel2.getPort() + 10000)); + sentinels.add(sentinel4.getHost() + ":" + (sentinel4.getPort() + 10000)); + } + + @BeforeClass + public static void setupTrustStore() { + setJvmTrustStore("src/test/resources/truststore.jceks", "jceks"); + } + + private static void setJvmTrustStore(String trustStoreFilePath, String trustStoreType) { + assertTrue(String.format("Could not find trust store at '%s'.", trustStoreFilePath), new File( + trustStoreFilePath).exists()); + System.setProperty("javax.net.ssl.trustStore", trustStoreFilePath); + System.setProperty("javax.net.ssl.trustStoreType", trustStoreType); + } + + @Test + public void sentinelWithSslConnectsToRedisWithoutSsl() { + boolean masterSsl = false; + boolean sentinelSsl = true; + GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig(); + JedisSentinelPool pool = new JedisSentinelPool(MASTER_NAME, sentinels, poolConfig, 1000, 1000, + null, "foobared", 0, "clientName", masterSsl, 1000, 1000, null, null, "sentinelClientName", sentinelSsl, null, null, null); + pool.getResource().close(); + pool.destroy(); + } + + @Test + public void sentinelWithSslConnectsToRedisWithSsl() { + boolean masterSsl = true; + boolean sentinelSsl = true; + GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig(); + + class PortSwizzlingJedisSentinelPool extends JedisSentinelPool { + public PortSwizzlingJedisSentinelPool(String masterName, Set sentinels, + GenericObjectPoolConfig poolConfig, int connectionTimeout, int soTimeout, String password, + int database, String clientName, boolean isMasterSslEnabled, int sentinelConnectionTimeout, + int sentinelSoTimeout, String sentinelPassword, String sentinelClientName, + boolean isSentinelSslEnabled, SSLSocketFactory sslSocketFactory, + SSLParameters sslParameters, HostnameVerifier hostnameVerifier) { + super(masterName, sentinels, poolConfig, connectionTimeout, soTimeout, null, password, + database, clientName, isMasterSslEnabled, sentinelConnectionTimeout, sentinelSoTimeout, + null, sentinelPassword, sentinelClientName, isSentinelSslEnabled, sslSocketFactory, + sslParameters, hostnameVerifier); + } + + @Override + protected HostAndPort toHostAndPort(List getMasterAddrByNameResult) { + // Sentinel broadcasts the non-ssl port number of redis, this swizzles it to the ssl port. + HashMap portMapping = new HashMap(); + portMapping.put(6381, 16381); + portMapping.put(6382, 16382); + HostAndPort original = super.toHostAndPort(getMasterAddrByNameResult); + int swizzled = portMapping.get(original.getPort()); + return new HostAndPort(original.getHost(), swizzled); + } + } + JedisSentinelPool pool = new PortSwizzlingJedisSentinelPool(MASTER_NAME, sentinels, poolConfig, 1000, 1000, + "foobared", 0, "clientName", masterSsl, 1000, 1000, null, "sentinelClientName", sentinelSsl, null, null, null); + pool.getResource().close(); + pool.destroy(); + } + +}