diff --git a/boms/geode-all-bom/src/test/resources/expected-pom.xml b/boms/geode-all-bom/src/test/resources/expected-pom.xml index 7df23cd5261f..f8c6696bccd7 100644 --- a/boms/geode-all-bom/src/test/resources/expected-pom.xml +++ b/boms/geode-all-bom/src/test/resources/expected-pom.xml @@ -472,7 +472,7 @@ redis.clients jedis - 2.9.0 + 3.2.0 compile diff --git a/buildSrc/src/main/groovy/org/apache/geode/gradle/plugins/DependencyConstraints.groovy b/buildSrc/src/main/groovy/org/apache/geode/gradle/plugins/DependencyConstraints.groovy index 2c72c75b6f53..152430b6d74a 100644 --- a/buildSrc/src/main/groovy/org/apache/geode/gradle/plugins/DependencyConstraints.groovy +++ b/buildSrc/src/main/groovy/org/apache/geode/gradle/plugins/DependencyConstraints.groovy @@ -156,7 +156,7 @@ class DependencyConstraints implements Plugin { api(group: 'org.springframework.ldap', name: 'spring-ldap-core', version: '2.3.2.RELEASE') api(group: 'org.springframework.shell', name: 'spring-shell', version: '1.2.0.RELEASE') api(group: 'pl.pragmatists', name: 'JUnitParams', version: '1.1.0') - api(group: 'redis.clients', name: 'jedis', version: '2.9.0') + api(group: 'redis.clients', name: 'jedis', version: '3.2.0') api(group: 'xerces', name: 'xercesImpl', version: '2.12.0') api(group: 'com.arakelian', name: 'java-jq', version: '0.10.1') api(group: 'com.fasterxml.jackson.datatype', name: 'jackson-datatype-joda', version: '2.9.8') diff --git a/geode-redis/build.gradle b/geode-redis/build.gradle index b107fe9b7f3c..740142545b81 100644 --- a/geode-redis/build.gradle +++ b/geode-redis/build.gradle @@ -32,6 +32,7 @@ dependencies { testCompile(project(':geode-junit')) testCompile('org.mockito:mockito-core') + integrationTestCompile(project(':geode-dunit')) integrationTestCompile(project(':geode-junit')) integrationTestCompile('redis.clients:jedis') diff --git a/geode-redis/src/integrationTest/java/org/apache/geode/redis/GeoJUnitTest.java b/geode-redis/src/integrationTest/java/org/apache/geode/redis/GeoJUnitTest.java index d8d29fd4aa72..221055f33eb9 100755 --- a/geode-redis/src/integrationTest/java/org/apache/geode/redis/GeoJUnitTest.java +++ b/geode-redis/src/integrationTest/java/org/apache/geode/redis/GeoJUnitTest.java @@ -37,7 +37,7 @@ import redis.clients.jedis.GeoRadiusResponse; import redis.clients.jedis.GeoUnit; import redis.clients.jedis.Jedis; -import redis.clients.jedis.params.geo.GeoRadiusParam; +import redis.clients.jedis.params.GeoRadiusParam; import org.apache.geode.cache.CacheFactory; import org.apache.geode.cache.GemFireCache; diff --git a/geode-redis/src/integrationTest/java/org/apache/geode/redis/SSLTest.java b/geode-redis/src/integrationTest/java/org/apache/geode/redis/SSLTest.java new file mode 100644 index 000000000000..13fa4f011052 --- /dev/null +++ b/geode-redis/src/integrationTest/java/org/apache/geode/redis/SSLTest.java @@ -0,0 +1,79 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license + * agreements. See the NOTICE file distributed with this work for additional information regarding + * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. You may obtain a + * copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package org.apache.geode.redis; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.junit.contrib.java.lang.system.RestoreSystemProperties; +import org.junit.experimental.categories.Category; +import redis.clients.jedis.Jedis; + +import org.apache.geode.distributed.internal.InternalDistributedSystem; +import org.apache.geode.internal.admin.SSLConfig; +import org.apache.geode.internal.net.SSLConfigurationFactory; +import org.apache.geode.internal.security.SecurableCommunicationChannel; +import org.apache.geode.test.junit.categories.RedisTest; +import org.apache.geode.test.junit.rules.ServerStarterRule; + +@Category({RedisTest.class}) +public class SSLTest { + + @Rule + public RestoreSystemProperties restoreSystemProperties = new RestoreSystemProperties(); + + @ClassRule + public static ServerStarterRule sslEnabledServer = new ServerStarterRule() + .withSSL("server", true, true) + .withProperty("redis-bind-address", "localhost") + .withProperty("redis-port", "11211") + .withAutoStart(); + + + @Test + public void canConnectOverSSL() { + SSLConfig sslConfigForComponent = SSLConfigurationFactory.getSSLConfigForComponent( + ((InternalDistributedSystem) sslEnabledServer.getCache().getDistributedSystem()) + .getConfig(), + SecurableCommunicationChannel.SERVER); + + System.setProperty("javax.net.ssl.trustStore", sslConfigForComponent.getTruststore()); + System.setProperty("javax.net.ssl.trustStoreType", "JKS"); + + Jedis localhost = new Jedis("localhost", 11211, true); + + assertThat(localhost.ping()).isEqualTo("PONG"); + } + + @Test + public void cannotConnectOverCleartext() { + SSLConfig sslConfigForComponent = SSLConfigurationFactory.getSSLConfigForComponent( + ((InternalDistributedSystem) sslEnabledServer.getCache().getDistributedSystem()) + .getConfig(), + SecurableCommunicationChannel.ALL); + + System.setProperty("javax.net.ssl.trustStore", sslConfigForComponent.getTruststore()); + System.setProperty("javax.net.ssl.trustStoreType", "JKS"); + + Jedis localhost = new Jedis("localhost", 11211, false); + + assertThatThrownBy(() -> localhost.ping()); + } + +} diff --git a/geode-redis/src/main/java/org/apache/geode/redis/GeodeRedisServer.java b/geode-redis/src/main/java/org/apache/geode/redis/GeodeRedisServer.java index 27748deadebe..da8e0e51a1fd 100644 --- a/geode-redis/src/main/java/org/apache/geode/redis/GeodeRedisServer.java +++ b/geode-redis/src/main/java/org/apache/geode/redis/GeodeRedisServer.java @@ -16,10 +16,12 @@ import static org.apache.geode.distributed.ConfigurationProperties.LOG_LEVEL; +import java.io.FileInputStream; import java.io.IOException; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.UnknownHostException; +import java.security.KeyStore; import java.util.Collection; import java.util.Map.Entry; import java.util.concurrent.ConcurrentHashMap; @@ -30,6 +32,8 @@ import java.util.concurrent.ThreadFactory; import java.util.concurrent.atomic.AtomicInteger; +import javax.net.ssl.KeyManagerFactory; + import io.netty.bootstrap.ServerBootstrap; import io.netty.buffer.PooledByteBufAllocator; import io.netty.channel.Channel; @@ -44,6 +48,8 @@ import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.channel.socket.oio.OioServerSocketChannel; +import io.netty.handler.ssl.SslContext; +import io.netty.handler.ssl.SslContextBuilder; import io.netty.util.concurrent.Future; import org.apache.geode.LogWriter; @@ -59,11 +65,14 @@ import org.apache.geode.cache.RegionShortcut; import org.apache.geode.cache.util.CacheListenerAdapter; import org.apache.geode.distributed.internal.InternalDistributedSystem; +import org.apache.geode.internal.admin.SSLConfig; import org.apache.geode.internal.cache.GemFireCacheImpl; import org.apache.geode.internal.cache.InternalCache; import org.apache.geode.internal.cache.InternalRegionFactory; import org.apache.geode.internal.hll.HyperLogLogPlus; import org.apache.geode.internal.inet.LocalHostUtil; +import org.apache.geode.internal.net.SSLConfigurationFactory; +import org.apache.geode.internal.security.SecurableCommunicationChannel; import org.apache.geode.redis.internal.ByteArrayWrapper; import org.apache.geode.redis.internal.ByteToCommandDecoder; import org.apache.geode.redis.internal.Coder; @@ -513,6 +522,7 @@ public Thread newThread(Runnable r) { String pwd = system.getConfig().getRedisPassword(); final byte[] pwdB = Coder.stringToBytes(pwd); ServerBootstrap b = new ServerBootstrap(); + b.group(bossGroup, workerGroup).channel(socketClass) .childHandler(new ChannelInitializer() { @Override @@ -521,6 +531,7 @@ public void initChannel(SocketChannel ch) throws Exception { logger.fine("GeodeRedisServer-Connection established with " + ch.remoteAddress()); } ChannelPipeline p = ch.pipeline(); + addSSLIfEnabled(ch, p); p.addLast(ByteToCommandDecoder.class.getSimpleName(), new ByteToCommandDecoder()); p.addLast(ExecutionHandlerContext.class.getSimpleName(), new ExecutionHandlerContext(ch, cache, regionCache, GeodeRedisServer.this, pwdB, @@ -546,6 +557,37 @@ public void initChannel(SocketChannel ch) throws Exception { this.serverChannel = f.channel(); } + private void addSSLIfEnabled(SocketChannel ch, ChannelPipeline p) { + + SSLConfig sslConfigForComponent = + SSLConfigurationFactory.getSSLConfigForComponent( + ((InternalDistributedSystem) cache.getDistributedSystem()).getConfig(), + SecurableCommunicationChannel.SERVER); + + if (!sslConfigForComponent.isEnabled()) { + return; + } + + SslContext sslContext; + try { + KeyStore ks = KeyStore.getInstance("JKS"); + ks.load(new FileInputStream(sslConfigForComponent.getKeystore()), + sslConfigForComponent.getKeystorePassword().toCharArray()/**/); + + // Set up key manager factory to use our key store + KeyManagerFactory kmf = + KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); + kmf.init(ks, sslConfigForComponent.getKeystorePassword().toCharArray()); + + SslContextBuilder sslContextBuilder = SslContextBuilder.forServer(kmf); + sslContext = sslContextBuilder.build(); + + } catch (Exception e) { + throw new RuntimeException(e); + } + p.addLast(sslContext.newHandler(ch.alloc())); + } + /** * Takes an entry event and processes it. If the entry denotes that a {@link * RedisDataType#REDIS_LIST} or {@link RedisDataType#REDIS_SORTEDSET} was created then this