diff --git a/Makefile b/Makefile index b49e962507..e34a66e489 100644 --- a/Makefile +++ b/Makefile @@ -242,6 +242,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 @@ -257,6 +263,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 5e18124c13..e3c373e9bf 100644 --- a/src/main/java/redis/clients/jedis/JedisSentinelPool.java +++ b/src/main/java/redis/clients/jedis/JedisSentinelPool.java @@ -13,6 +13,10 @@ import redis.clients.jedis.exceptions.JedisConnectionException; import redis.clients.jedis.exceptions.JedisException; +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.SSLParameters; +import javax.net.ssl.SSLSocketFactory; + public class JedisSentinelPool extends JedisPoolAbstract { protected GenericObjectPoolConfig poolConfig; @@ -30,6 +34,12 @@ public class JedisSentinelPool extends JedisPoolAbstract { protected String sentinelPassword; protected String sentinelClientName; + protected boolean isMasterSslEnabled; + protected boolean isSentinelSslEnabled; + protected SSLSocketFactory sslSocketFactory; + protected SSLParameters sslParameters; + protected HostnameVerifier hostnameVerifier; + protected final Set masterListeners = new HashSet<>(); protected final Logger log = LoggerFactory.getLogger(getClass().getName()); @@ -137,6 +147,18 @@ 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, user, password, database, clientName, + sentinelConnectionTimeout, sentinelSoTimeout, sentinelUser, sentinelPassword, sentinelClientName, + false, false, null, null, null); + } + + 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 int sentinelConnectionTimeout, final int sentinelSoTimeout, final String sentinelUser, + final String sentinelPassword, final String sentinelClientName, final boolean isMasterSslEnabled, + final boolean isSentinelSslEnabled, final SSLSocketFactory sslSocketFactory, + final SSLParameters sslParameters, final HostnameVerifier hostnameVerifier) { this.poolConfig = poolConfig; this.connectionTimeout = connectionTimeout; @@ -150,6 +172,11 @@ public JedisSentinelPool(String masterName, Set sentinels, this.sentinelUser = sentinelUser; this.sentinelPassword = sentinelPassword; this.sentinelClientName = sentinelClientName; + this.isMasterSslEnabled = isMasterSslEnabled; + this.isSentinelSslEnabled = isSentinelSslEnabled; + this.sslSocketFactory = sslSocketFactory; + this.sslParameters = sslParameters; + this.hostnameVerifier = hostnameVerifier; HostAndPort master = initSentinels(sentinels, masterName); initPool(master); @@ -174,7 +201,8 @@ private void initPool(HostAndPort master) { currentHostMaster = master; if (factory == null) { factory = new JedisFactory(master.getHost(), master.getPort(), connectionTimeout, - soTimeout, user, password, database, clientName); + soTimeout, user, password, database, clientName, isMasterSslEnabled, + sslSocketFactory, sslParameters, hostnameVerifier); initPool(poolConfig, factory); } else { factory.setHostAndPort(currentHostMaster); @@ -204,7 +232,8 @@ private HostAndPort initSentinels(Set sentinels, final String masterName 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) { @@ -255,7 +284,8 @@ private HostAndPort initSentinels(Set sentinels, final String masterName for (String sentinel : sentinels) { final HostAndPort hap = HostAndPort.parseString(sentinel); - MasterListener masterListener = new MasterListener(masterName, hap.getHost(), hap.getPort()); + MasterListener masterListener = new MasterListener(masterName, hap.getHost(), hap.getPort(), isSentinelSslEnabled, + sslSocketFactory, sslParameters, hostnameVerifier); // whether MasterListener threads are alive or not, process can be stopped masterListener.setDaemon(true); masterListeners.add(masterListener); @@ -317,6 +347,10 @@ protected class MasterListener extends Thread { protected String masterName; protected String host; protected int port; + protected boolean isSslEnabled; + protected SSLSocketFactory sslSocketFactory; + protected SSLParameters sslParameters; + protected HostnameVerifier hostnameVerifier; protected long subscribeRetryWaitTimeMillis = 5000; protected volatile Jedis j; protected AtomicBoolean running = new AtomicBoolean(false); @@ -324,16 +358,28 @@ protected class MasterListener extends Thread { protected MasterListener() { } - public MasterListener(String masterName, String host, int port) { + public MasterListener(String masterName, String host, int port, boolean isSslEnabled, + SSLSocketFactory sslSocketFactory, SSLParameters sslParameters, HostnameVerifier hostnameVerifier) { super(String.format("MasterListener-%s-[%s:%d]", masterName, host, port)); this.masterName = masterName; this.host = host; this.port = port; + this.isSslEnabled = isSslEnabled; + this.sslSocketFactory = sslSocketFactory; + this.sslParameters = sslParameters; + this.hostnameVerifier = hostnameVerifier; + } + + public MasterListener(String masterName, String host, int port, + long subscribeRetryWaitTimeMillis, boolean isSslEnabled, SSLSocketFactory sslSocketFactory, + SSLParameters sslParameters, HostnameVerifier hostnameVerifier) { + this(masterName, host, port, isSslEnabled, sslSocketFactory, sslParameters, hostnameVerifier); + this.subscribeRetryWaitTimeMillis = subscribeRetryWaitTimeMillis; } public MasterListener(String masterName, String host, int port, long subscribeRetryWaitTimeMillis) { - this(masterName, host, port); + this(masterName, host, port, false, null, null, null); this.subscribeRetryWaitTimeMillis = subscribeRetryWaitTimeMillis; } @@ -350,7 +396,8 @@ public void run() { break; } - j = new Jedis(host, port, sentinelConnectionTimeout, sentinelSoTimeout); + j = new Jedis(host, port, sentinelConnectionTimeout, sentinelSoTimeout, + isSslEnabled, sslSocketFactory, sslParameters, hostnameVerifier); if (sentinelUser != null) { j.auth(sentinelUser, sentinelPassword); } else if (sentinelPassword != null) { @@ -427,4 +474,4 @@ public void shutdown() { } } } -} +} \ No newline at end of file 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..126bc34193 --- /dev/null +++ b/src/test/java/redis/clients/jedis/tests/SSLJedisSentinelPoolTest.java @@ -0,0 +1,91 @@ +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 redisSsl = false; + boolean sentinelSsl = true; + GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig(); + JedisSentinelPool pool = new JedisSentinelPool(MASTER_NAME, sentinels, poolConfig, 1000, 1000, + "foobared", 0, "clientName", 1000, 1000, null, "sentinelClientName", redisSsl, sentinelSsl, null, null, null); + pool.getResource().close(); + pool.destroy(); + } + + @Test + public void sentinelWithSslConnectsToRedisWithSsl() { + boolean redisSsl = 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, int sentinelConnectionTimeout, int sentinelSoTimeout, String sentinelPassword, + String sentinelClientName, boolean isRedisSslEnabled, boolean isSentinelSslEnabled, + SSLSocketFactory sslSocketFactory, SSLParameters sslParameters, HostnameVerifier hostnameVerifier) { + super(masterName, sentinels, poolConfig, connectionTimeout, soTimeout, password, database, clientName, + sentinelConnectionTimeout, sentinelSoTimeout, sentinelPassword, sentinelClientName, isRedisSslEnabled, + 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", 1000, 1000, null, "sentinelClientName", redisSsl, sentinelSsl, null, null, null); + pool.getResource().close(); + pool.destroy(); + } + +}