diff --git a/folsom-elasticache/src/main/java/com/spotify/folsom/elasticache/ElastiCacheResolver.java b/folsom-elasticache/src/main/java/com/spotify/folsom/elasticache/ElastiCacheResolver.java index ceff37da..8b66f632 100644 --- a/folsom-elasticache/src/main/java/com/spotify/folsom/elasticache/ElastiCacheResolver.java +++ b/folsom-elasticache/src/main/java/com/spotify/folsom/elasticache/ElastiCacheResolver.java @@ -27,6 +27,7 @@ import java.net.Socket; import java.util.List; import java.util.concurrent.atomic.AtomicReference; +import javax.net.ssl.SSLSocketFactory; /** * Implement support for AWS ElastiCache node auto-discovery netty-transport ${netty.version} + + io.netty + netty-handler + ${netty.version} + io.netty netty-codec diff --git a/folsom/src/main/java/com/spotify/folsom/MemcacheClientBuilder.java b/folsom/src/main/java/com/spotify/folsom/MemcacheClientBuilder.java index 6f74ef4f..7d0f705c 100644 --- a/folsom/src/main/java/com/spotify/folsom/MemcacheClientBuilder.java +++ b/folsom/src/main/java/com/spotify/folsom/MemcacheClientBuilder.java @@ -37,6 +37,7 @@ import com.spotify.folsom.client.NoopTracer; import com.spotify.folsom.client.ascii.DefaultAsciiMemcacheClient; import com.spotify.folsom.client.binary.DefaultBinaryMemcacheClient; +import com.spotify.folsom.client.tls.SSLEngineFactory; import com.spotify.folsom.guava.HostAndPort; import com.spotify.folsom.ketama.AddressAndClient; import com.spotify.folsom.ketama.KetamaMemcacheClient; @@ -125,6 +126,8 @@ public class MemcacheClientBuilder { private final List passwords = new ArrayList<>(); private boolean skipAuth = false; + private SSLEngineFactory sslEngineFactory = null; + /** * Create a client builder for byte array values. * @@ -579,6 +582,11 @@ MemcacheClientBuilder withoutAuthenticationValidation() { return this; } + public MemcacheClientBuilder withSSLEngineFactory(final SSLEngineFactory sslEngineFactory) { + this.sslEngineFactory = sslEngineFactory; + return this; + } + /** * Create a client that uses the binary memcache protocol. * @@ -732,6 +740,7 @@ private RawMemcacheClient createReconnectingClient( metrics, maxSetLength, eventLoopGroup, - channelClass); + channelClass, + sslEngineFactory); } } diff --git a/folsom/src/main/java/com/spotify/folsom/client/DefaultRawMemcacheClient.java b/folsom/src/main/java/com/spotify/folsom/client/DefaultRawMemcacheClient.java index 9c82ad40..60f2b989 100644 --- a/folsom/src/main/java/com/spotify/folsom/client/DefaultRawMemcacheClient.java +++ b/folsom/src/main/java/com/spotify/folsom/client/DefaultRawMemcacheClient.java @@ -30,6 +30,7 @@ import com.spotify.folsom.RawMemcacheClient; import com.spotify.folsom.client.ascii.AsciiMemcacheDecoder; import com.spotify.folsom.client.binary.BinaryMemcacheDecoder; +import com.spotify.folsom.client.tls.SSLEngineFactory; import com.spotify.folsom.guava.HostAndPort; import com.spotify.folsom.ketama.AddressAndClient; import com.spotify.futures.CompletableFutures; @@ -43,6 +44,7 @@ import io.netty.channel.ChannelInboundHandler; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOption; +import io.netty.channel.ChannelPipeline; import io.netty.channel.ChannelPromise; import io.netty.channel.DefaultChannelPromise; import io.netty.channel.EventLoopGroup; @@ -52,6 +54,7 @@ import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.nio.NioSocketChannel; import io.netty.handler.codec.DecoderException; +import io.netty.handler.ssl.SslHandler; import io.netty.util.concurrent.DefaultThreadFactory; import java.io.IOException; import java.net.ConnectException; @@ -66,6 +69,7 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import java.util.stream.Stream; +import javax.net.ssl.SSLEngine; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -112,7 +116,8 @@ public static CompletionStage connect( final Metrics metrics, final int maxSetLength, final EventLoopGroup eventLoopGroup, - final Class channelClass) { + final Class channelClass, + final SSLEngineFactory sslEngineFactory) { final ChannelInboundHandler decoder; if (binary) { @@ -124,14 +129,21 @@ public static CompletionStage connect( final ChannelHandler initializer = new ChannelInitializer() { @Override - protected void initChannel(final Channel ch) throws Exception { - ch.pipeline() - .addLast( - new TcpTuningHandler(), - decoder, - - // Downstream - new MemcacheEncoder()); + protected void initChannel(final Channel ch) { + final ChannelPipeline channelPipeline = ch.pipeline(); + channelPipeline.addLast(new TcpTuningHandler()); + + if (sslEngineFactory != null) { + final SSLEngine sslEngine = + sslEngineFactory.createSSLEngine(address.getHostText(), address.getPort()); + SslHandler sslHandler = new SslHandler(sslEngine); + // Disable SSL data aggregation + // it doesn't play well with memcached protocol and causes connection hangs + sslHandler.setWrapDataSize(0); + channelPipeline.addLast(sslHandler); + } + + channelPipeline.addLast(decoder, new MemcacheEncoder()); } }; diff --git a/folsom/src/main/java/com/spotify/folsom/client/tls/DefaultSSLEngineFactory.java b/folsom/src/main/java/com/spotify/folsom/client/tls/DefaultSSLEngineFactory.java new file mode 100644 index 00000000..61c7b827 --- /dev/null +++ b/folsom/src/main/java/com/spotify/folsom/client/tls/DefaultSSLEngineFactory.java @@ -0,0 +1,33 @@ +package com.spotify.folsom.client.tls; + +import java.security.NoSuchAlgorithmException; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLEngine; + +public class DefaultSSLEngineFactory implements SSLEngineFactory { + private final SSLContext sslContext; + private final boolean reuseSession; + + public DefaultSSLEngineFactory(final boolean reuseSession) throws NoSuchAlgorithmException { + this(SSLContext.getDefault(), reuseSession); + } + + public DefaultSSLEngineFactory(final SSLContext sslContext, final boolean reuseSession) { + this.sslContext = sslContext; + this.reuseSession = reuseSession; + } + + @Override + public SSLEngine createSSLEngine(final String hostname, final int port) { + final SSLEngine sslEngine; + + if (reuseSession) { + sslEngine = sslContext.createSSLEngine(hostname, port); + } else { + sslEngine = sslContext.createSSLEngine(); + } + + sslEngine.setUseClientMode(true); + return sslEngine; + } +} diff --git a/folsom/src/main/java/com/spotify/folsom/client/tls/SSLEngineFactory.java b/folsom/src/main/java/com/spotify/folsom/client/tls/SSLEngineFactory.java new file mode 100644 index 00000000..982c944e --- /dev/null +++ b/folsom/src/main/java/com/spotify/folsom/client/tls/SSLEngineFactory.java @@ -0,0 +1,7 @@ +package com.spotify.folsom.client.tls; + +import javax.net.ssl.SSLEngine; + +public interface SSLEngineFactory { + SSLEngine createSSLEngine(String hostname, int port); +} diff --git a/folsom/src/main/java/com/spotify/folsom/reconnect/ReconnectingClient.java b/folsom/src/main/java/com/spotify/folsom/reconnect/ReconnectingClient.java index 10d74749..96747f21 100644 --- a/folsom/src/main/java/com/spotify/folsom/reconnect/ReconnectingClient.java +++ b/folsom/src/main/java/com/spotify/folsom/reconnect/ReconnectingClient.java @@ -26,6 +26,7 @@ import com.spotify.folsom.client.DefaultRawMemcacheClient; import com.spotify.folsom.client.NotConnectedClient; import com.spotify.folsom.client.Request; +import com.spotify.folsom.client.tls.SSLEngineFactory; import com.spotify.folsom.guava.HostAndPort; import com.spotify.folsom.ketama.AddressAndClient; import io.netty.channel.Channel; @@ -77,7 +78,8 @@ public ReconnectingClient( final Metrics metrics, final int maxSetLength, final EventLoopGroup eventLoopGroup, - final Class channelClass) { + final Class channelClass, + final SSLEngineFactory sslEngineFactory) { this( backoffFunction, scheduledExecutorService, @@ -93,7 +95,8 @@ public ReconnectingClient( metrics, maxSetLength, eventLoopGroup, - channelClass), + channelClass, + sslEngineFactory), authenticator, address, reconnectionListener); @@ -113,7 +116,8 @@ public ReconnectingClient( final Metrics metrics, final int maxSetLength, final EventLoopGroup eventLoopGroup, - final Class channelClass) { + final Class channelClass, + final SSLEngineFactory sslEngineFactory) { this( backoffFunction, scheduledExecutorService, @@ -129,7 +133,8 @@ public ReconnectingClient( metrics, maxSetLength, eventLoopGroup, - channelClass), + channelClass, + sslEngineFactory), authenticator, address, new StandardReconnectionListener()); diff --git a/folsom/src/test/java/com/spotify/folsom/IntegrationTest.java b/folsom/src/test/java/com/spotify/folsom/IntegrationTest.java index 6771d678..64157f4a 100644 --- a/folsom/src/test/java/com/spotify/folsom/IntegrationTest.java +++ b/folsom/src/test/java/com/spotify/folsom/IntegrationTest.java @@ -29,6 +29,7 @@ import com.google.common.collect.Sets; import com.spotify.folsom.client.NoopMetrics; import com.spotify.folsom.client.Utils; +import com.spotify.folsom.client.tls.DefaultSSLEngineFactory; import com.spotify.futures.CompletableFutures; import java.time.Duration; import java.util.ArrayList; @@ -53,18 +54,24 @@ public class IntegrationTest { private static MemcachedServer server; + private static MemcachedServer tlsServer; - @Parameterized.Parameters(name = "{0}") + @Parameterized.Parameters(name = "{0},useTls={1}") public static Collection data() throws Exception { ArrayList res = new ArrayList<>(); - res.add(new Object[] {"ascii"}); - res.add(new Object[] {"binary"}); + res.add(new Object[] {"ascii", false}); + res.add(new Object[] {"ascii", true}); + res.add(new Object[] {"binary", false}); + res.add(new Object[] {"binary", true}); return res; } @Parameterized.Parameter(0) public String protocol; + @Parameterized.Parameter(1) + public Boolean useTls; + private MemcacheClient client; private AsciiMemcacheClient asciiClient; private BinaryMemcacheClient binaryClient; @@ -73,6 +80,19 @@ public static Collection data() throws Exception { @BeforeClass public static void setUpClass() throws Exception { server = MemcachedServer.SIMPLE_INSTANCE.get(); + + // Use self-signed test certs + String currentDirectory = System.getProperty("user.dir"); + System.setProperty( + "javax.net.ssl.keyStore", currentDirectory + "/src/test/resources/pki/test.p12"); + System.setProperty("javax.net.ssl.keyStoreType", "pkcs12"); + System.setProperty("javax.net.ssl.keyStorePassword", "changeit"); + System.setProperty( + "javax.net.ssl.trustStore", currentDirectory + "/src/test/resources/pki/test.p12"); + System.setProperty("javax.net.ssl.trustStoreType", "pkcs12"); + System.setProperty("javax.net.ssl.trustStorePassword", "changeit"); + + tlsServer = new MemcachedServer(true); } @Before @@ -89,13 +109,17 @@ public void setUp() throws Exception { } MemcacheClientBuilder builder = MemcacheClientBuilder.newStringClient() - .withAddress(server.getHost(), server.getPort()) + .withAddress(server().getHost(), server().getPort()) .withConnections(1) .withMaxOutstandingRequests(1000) .withMetrics(NoopMetrics.INSTANCE) .withRetry(false) .withRequestTimeoutMillis(100); + if (useTls) { + builder.withSSLEngineFactory(new DefaultSSLEngineFactory(true)); + } + if (ascii) { asciiClient = builder.connectAscii(); binaryClient = null; @@ -110,7 +134,7 @@ public void setUp() throws Exception { } private void cleanup() { - server.flush(); + server().flush(); } @After @@ -614,7 +638,7 @@ public void testDeleteWithCasWrongCas() throws Exception { public void testGetAllNodes() { final Map> nodes = client.getAllNodes(); assertEquals(1, nodes.size()); - final MemcacheClient client2 = nodes.get(server.getHost() + ":" + server.getPort()); + final MemcacheClient client2 = nodes.get(server().getHost() + ":" + server().getPort()); assertNotNull(client2); assertEquals(MemcacheStatus.OK, client.set(KEY1, VALUE1, 0).toCompletableFuture().join()); @@ -642,6 +666,14 @@ private String createValue(int size) { return sb.toString(); } + private MemcachedServer server() { + if (useTls) { + return tlsServer; + } else { + return server; + } + } + private void assumeAscii() { assumeTrue(isAscii()); } diff --git a/folsom/src/test/java/com/spotify/folsom/MemcachedServer.java b/folsom/src/test/java/com/spotify/folsom/MemcachedServer.java index 79d0303e..0dc08d43 100644 --- a/folsom/src/test/java/com/spotify/folsom/MemcachedServer.java +++ b/folsom/src/test/java/com/spotify/folsom/MemcachedServer.java @@ -16,6 +16,8 @@ package com.spotify.folsom; import com.google.common.base.Suppliers; +import com.spotify.folsom.client.tls.DefaultSSLEngineFactory; +import java.security.NoSuchAlgorithmException; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; @@ -38,31 +40,63 @@ public enum AuthenticationMode { Suppliers.memoize(MemcachedServer::new)::get; public static int DEFAULT_PORT = 11211; + + private static final String MEMCACHED_VERSION = "1.6.22"; private final String username; private final String password; + private final boolean useTLS; public MemcachedServer() { this(null, null); } public MemcachedServer(String username, String password) { - this(username, password, DEFAULT_PORT, AuthenticationMode.NONE); + this(username, password, DEFAULT_PORT, AuthenticationMode.NONE, false); } public MemcachedServer(String username, String password, AuthenticationMode authenticationMode) { - this(username, password, DEFAULT_PORT, authenticationMode); + this(username, password, DEFAULT_PORT, authenticationMode, false); + } + + public MemcachedServer(boolean useTLS) { + this(null, null, DEFAULT_PORT, AuthenticationMode.NONE, useTLS); } public MemcachedServer( - String username, String password, int port, AuthenticationMode authenticationMode) { + String username, + String password, + int port, + AuthenticationMode authenticationMode, + boolean useTLS) { this.username = username; this.password = password; - container = new FixedHostPortGenericContainer("bitnami/memcached:1.6.21"); + this.useTLS = useTLS; + + if (useTLS) { + if (authenticationMode != AuthenticationMode.NONE) { + throw new RuntimeException( + "Authentication is not currently supported with the TLS-enabled container"); + } + + container = setupTLSContainer(); + } else { + container = setupContainer(username, password, authenticationMode); + } + if (port != DEFAULT_PORT) { container.withFixedExposedPort(DEFAULT_PORT, port); } else { container.withExposedPorts(DEFAULT_PORT); } + + start(authenticationMode); + } + + private FixedHostPortGenericContainer setupContainer( + String username, String password, AuthenticationMode authenticationMode) { + final FixedHostPortGenericContainer container = + new FixedHostPortGenericContainer("bitnami/memcached:" + MEMCACHED_VERSION); + switch (authenticationMode) { case NONE: break; @@ -78,7 +112,30 @@ public MemcachedServer( break; } - start(authenticationMode); + return container; + } + + private FixedHostPortGenericContainer setupTLSContainer() { + final FixedHostPortGenericContainer container = + new FixedHostPortGenericContainer("bitnami/memcached:" + MEMCACHED_VERSION); + + container.withClasspathResourceMapping( + "/pki/test.pem", "/test-certs/test.pem", BindMode.READ_ONLY); + container.withClasspathResourceMapping( + "/pki/test.key", "/test-certs/test.key", BindMode.READ_ONLY); + + container.withCommand( + "memcached", + "--enable-ssl", + "-o", + "ssl_verify_mode=3", + "-o", + "ssl_chain_cert=/test-certs/test.pem", + "-o", + "ssl_key=/test-certs/test.key", + "-o", + "ssl_ca_cert=/test-certs/test.pem"); + return container; } public void stop() { @@ -102,6 +159,15 @@ public void start(AuthenticationMode authenticationMode) { if (username != null && password != null) { builder.withUsernamePassword(username, password); } + + if (useTLS) { + try { + builder.withSSLEngineFactory(new DefaultSSLEngineFactory(false)); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + } + switch (authenticationMode) { case NONE: case SASL: diff --git a/folsom/src/test/java/com/spotify/folsom/client/DefaultRawMemcacheClientTest.java b/folsom/src/test/java/com/spotify/folsom/client/DefaultRawMemcacheClientTest.java index 8ff979a0..6f7e9a2e 100644 --- a/folsom/src/test/java/com/spotify/folsom/client/DefaultRawMemcacheClientTest.java +++ b/folsom/src/test/java/com/spotify/folsom/client/DefaultRawMemcacheClientTest.java @@ -80,6 +80,7 @@ public void testInvalidRequest() throws Exception { new NoopMetrics(), 1024 * 1024, null, + null, null) .toCompletableFuture() .get(); @@ -182,6 +183,7 @@ public void testRequestTimeout() throws Exception { new NoopMetrics(), 1024 * 1024, null, + null, null) .toCompletableFuture() .get(); @@ -221,6 +223,7 @@ public void testBinaryRequestRetry() throws Exception { new NoopMetrics(), maxSetLength, null, + null, null) .toCompletableFuture() .get(); @@ -265,6 +268,7 @@ public void testAsciiRequestRetry() throws Exception { new NoopMetrics(), maxSetLength, null, + null, null) .toCompletableFuture() .get(); @@ -301,6 +305,7 @@ public void testShutdown() throws Exception { new NoopMetrics(), 1024 * 1024, null, + null, null) .toCompletableFuture() .get(); @@ -328,6 +333,7 @@ public void testShutdownRequestExceptionInsteadOfOverloaded() throws Throwable { new NoopMetrics(), 1024 * 1024, null, + null, null) .toCompletableFuture() .get(); @@ -376,6 +382,7 @@ public void testShutdownRequestException() throws Throwable { new NoopMetrics(), 1024 * 1024, null, + null, null) .toCompletableFuture() .get(); @@ -416,6 +423,7 @@ public void testOutstandingRequestMetric() throws Exception { metrics, 1024 * 1024, null, + null, null) .toCompletableFuture() .get(); diff --git a/folsom/src/test/resources/pki/README b/folsom/src/test/resources/pki/README new file mode 100644 index 00000000..5b234ae2 --- /dev/null +++ b/folsom/src/test/resources/pki/README @@ -0,0 +1,5 @@ +These test certificates were generated as follows: + +openssl req -x509 -newkey rsa:4096 -keyout test.key -out test.pem -sha256 -days 3650 -nodes -subj '/CN=localhost' +openssl pkcs12 -export -inkey test.key -in test.pem -out test.p12 -password pass:changeit + diff --git a/folsom/src/test/resources/pki/test.key b/folsom/src/test/resources/pki/test.key new file mode 100644 index 00000000..822760c8 --- /dev/null +++ b/folsom/src/test/resources/pki/test.key @@ -0,0 +1,52 @@ +-----BEGIN PRIVATE KEY----- +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 +G6gcbkorel8YtBdPnaweR3yhIwhcyA== +-----END PRIVATE KEY----- diff --git a/folsom/src/test/resources/pki/test.p12 b/folsom/src/test/resources/pki/test.p12 new file mode 100644 index 00000000..03d38dcf Binary files /dev/null and b/folsom/src/test/resources/pki/test.p12 differ diff --git a/folsom/src/test/resources/pki/test.pem b/folsom/src/test/resources/pki/test.pem new file mode 100644 index 00000000..d7cb9ade --- /dev/null +++ b/folsom/src/test/resources/pki/test.pem @@ -0,0 +1,29 @@ +-----BEGIN CERTIFICATE----- +MIIFCTCCAvGgAwIBAgIUW7SurLbUDUtQfw3EdyDv8HGmcZMwDQYJKoZIhvcNAQEL +BQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTIzMDkwNzA3MTczMVoXDTMzMDkw +NDA3MTczMVowFDESMBAGA1UEAwwJbG9jYWxob3N0MIICIjANBgkqhkiG9w0BAQEF +AAOCAg8AMIICCgKCAgEAwpKLIlpoI9IdUxW8l+rTQ23YonjzO+vkqY8Jyb3SB8aL +KoMw2nrRBnFxwIg/uY4VJRs0oyb/Bs3F7L0V0mgo54bDkMmtcScUKj8VibRTgIE4 +CX1r1USVoebf0UMmW+v5grxpFeMZgP7KRWoRsG3i70IVprJmKVHV1mTjs3xDjZM/ +Aw+KmOTTDshdV2EeryU48MWDoyaklxTF2+wnT/7IYjBVtaOb7ehSAyBnpqSKl9dZ +h+8WvNoF59AGiX/12wxS5ITmqmCBNNZ6f2QkAfn4aU6PyvuIibh9lhLBfoeq8/+E +7pK0/rWTWSQ3ogRV9Uok9o1CK13Opv/DAKKdUfI1piItbSkHvhBhUINUSe7Xa8bk +8xRemSD7jjvjt7rjQ4mheJw9xW+P4oXcHzAjRJp3hrAeer73KLzJO98oGPJ2o3So +CfUPGc7ety7g5E9sbW29SBIFkAg9rSMe6awdBTytZALVanWKZK9SvM/Yxxy11aWI +NuFP50XSw45sc7CctFLEIwNPG2k+WfvJYx+6WYgq/zrmvl6sn9OPmC5fwQ80VScV +PfzyRpvSe06CtltoWzm+WchotAP4GInvpTLf3QGitDWjr0/iz1PYeEAUg0+/hCk6 +LwFUFfaGgjzGozGBEDPe0pWCEU4H3cY8/11gYyuzvucR2Awd7UNsg7AXuidiUwUC +AwEAAaNTMFEwHQYDVR0OBBYEFAd3Z5PTSftej1gL82mVQQuOVOYEMB8GA1UdIwQY +MBaAFAd3Z5PTSftej1gL82mVQQuOVOYEMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZI +hvcNAQELBQADggIBAJbVyzamDYqiIOSeHe19e8lWyLu7Zb50k7yyw406dbBaPRZf +lBjyh2XWNdcVc/wjxdA9EDlIkIzNlXzwlyDa5VqPwzHdkel6dc6dqVQHzMWuoDzL +HY1K7kMaRP3It0cDlJszsMI+kri+9wWEGkILJiNsKUJcjCFAQUxTe6vQWyLu63VY +aY/9HgqkFqI5rWmLCYlXF5lCdDsMPmVGswBLQarJFrm6j4CxlwMArKfqceDvsJ17 +qPtDi/r6ItmTRv0xRAh1fJG0tRtyvx1mQ8t69GM1BFDrXYIlvhTXFDPE/DkypClo +toQWKX6PIyQBhWNQL6JGrEGyeY8ljc/atRYWUugxQSuZ5EWdcwHZas4Z0eFYjPLa +K0PYlFA3Y2bjzvMZVDNPggSDYB/RgfZQzGBNTqH4LJ8yqQhBMTkFlkHypSpAyru3 +tAMDQC5dLGeR1HNJcJxr9gU3PxmdRMSTRQxf9LtpznXnihZYmEjEnZ0ULKAN+8sq +eaREI6B3/3aTKkoBUV028GxlHjwmAtp9zhwV6UCg2z8SItCFvcW95MNGu312MMy1 +fTBv3wDgaGqyy9ZddrANQGbXleJcrEJDaIHWdsJ0q077BuWN/c8EnuyqhyOWklEO +ly2SJltjIA/P30BSpEyvb/MweSQ9LhNORfPrhGJotDmDHTw8mqbfsdUaVirq +-----END CERTIFICATE-----