From e8a17d9ccdd46647243a70216304ff4a9483f7bb Mon Sep 17 00:00:00 2001 From: jaymode Date: Wed, 21 Jan 2015 08:06:53 -0500 Subject: [PATCH] SSL/TLS: Add options to configure session cache size and timeout The default settings for the SSL session cache is unbounded with a timeout of 24 hours. This can lead to memory issues when clients do not resume connections. This adds a default limit of 1000 sessions in the cache in addition to exposing settings to control these values. Closes elastic/elasticsearch#602 Original commit: elastic/x-pack-elasticsearch@9cdc7b613c665caf510c56b0f86c78f8113cb295 --- .../elasticsearch/shield/ssl/SSLService.java | 17 ++++++++---- .../shield/ssl/SSLServiceTests.java | 26 +++++++++++++++++++ 2 files changed, 38 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/elasticsearch/shield/ssl/SSLService.java b/src/main/java/org/elasticsearch/shield/ssl/SSLService.java index 8e957f2e4ab..1ec713b4a7c 100644 --- a/src/main/java/org/elasticsearch/shield/ssl/SSLService.java +++ b/src/main/java/org/elasticsearch/shield/ssl/SSLService.java @@ -8,8 +8,10 @@ package org.elasticsearch.shield.ssl; import org.elasticsearch.ElasticsearchException; import org.elasticsearch.common.component.AbstractComponent; import org.elasticsearch.common.inject.Inject; +import org.elasticsearch.common.primitives.Ints; import org.elasticsearch.common.settings.ImmutableSettings; import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.common.util.concurrent.ConcurrentCollections; import org.elasticsearch.shield.ShieldSettingsException; @@ -27,6 +29,7 @@ public class SSLService extends AbstractComponent { static final String[] DEFAULT_CIPHERS = new String[]{ "TLS_RSA_WITH_AES_128_CBC_SHA256", "TLS_RSA_WITH_AES_128_CBC_SHA", "TLS_DHE_RSA_WITH_AES_128_CBC_SHA", "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA" }; static final String[] DEFAULT_SUPPORTED_PROTOCOLS = new String[] {"TLSv1", "TLSv1.1", "TLSv1.2"}; + static final TimeValue DEFAULT_SESSION_CACHE_TIMEOUT = TimeValue.timeValueHours(24); private Map sslContexts = ConcurrentCollections.newConcurrentMap(); @@ -99,15 +102,17 @@ public class SSLService extends AbstractComponent { String key = keyStorePath + trustStorePath + sslProtocol; SSLContext sslContext = sslContexts.get(key); if (sslContext == null) { - logger.debug("using keyStore[{}], keyAlgorithm[{}], trustStore[{}], truststoreAlgorithm[{}], TLS protocol[{}]", - keyStorePath, keyStoreAlgorithm, trustStorePath, trustStoreAlgorithm, sslProtocol); + int sessionCacheSize = settings.getAsInt("session.cache_size", componentSettings.getAsInt("session.cache_size", 1000)); + TimeValue sessionCacheTimeout = settings.getAsTime("session.cache_timeout", componentSettings.getAsTime("session.cache_timeout", DEFAULT_SESSION_CACHE_TIMEOUT)); + logger.debug("using keystore[{}], key_algorithm[{}], truststore[{}], truststore_algorithm[{}], tls_protocol[{}], session_cache_size[{}], session_cache_timeout[{}]", + keyStorePath, keyStoreAlgorithm, trustStorePath, trustStoreAlgorithm, sslProtocol, sessionCacheSize, sessionCacheTimeout); TrustManagerFactory trustFactory = getTrustFactory(trustStorePath, trustStorePassword, trustStoreAlgorithm); KeyManagerFactory keyManagerFactory = createKeyManagerFactory(keyStorePath, keyStorePassword, keyStoreAlgorithm, keyPassword); - sslContext = createSslContext(keyManagerFactory, trustFactory, sslProtocol); + sslContext = createSslContext(keyManagerFactory, trustFactory, sslProtocol, sessionCacheSize, sessionCacheTimeout); sslContexts.put(key, sslContext); } else { - logger.trace("found keystore[{}], trustStore[{}], TLS protocol[{}] in SSL context cache, reusing", keyStorePath, trustStorePath, sslProtocol); + logger.trace("found keystore[{}], truststore[{}], tls_protocol[{}] in SSL context cache, reusing", keyStorePath, trustStorePath, sslProtocol); } return sslContext; @@ -144,11 +149,13 @@ public class SSLService extends AbstractComponent { } } - private SSLContext createSslContext(KeyManagerFactory keyManagerFactory, TrustManagerFactory trustFactory, String sslProtocol) { + private SSLContext createSslContext(KeyManagerFactory keyManagerFactory, TrustManagerFactory trustFactory, String sslProtocol, int sessionCacheSize, TimeValue sessionCacheTimeout) { // Initialize sslContext try { SSLContext sslContext = SSLContext.getInstance(sslProtocol); sslContext.init(keyManagerFactory.getKeyManagers(), trustFactory.getTrustManagers(), null); + sslContext.getServerSessionContext().setSessionCacheSize(sessionCacheSize); + sslContext.getServerSessionContext().setSessionTimeout(Ints.checkedCast(sessionCacheTimeout.seconds())); return sslContext; } catch (Exception e) { throw new ElasticsearchSSLException("failed to initialize the SSLContext", e); diff --git a/src/test/java/org/elasticsearch/shield/ssl/SSLServiceTests.java b/src/test/java/org/elasticsearch/shield/ssl/SSLServiceTests.java index c8a2ed33ee8..82d1cc12e5f 100644 --- a/src/test/java/org/elasticsearch/shield/ssl/SSLServiceTests.java +++ b/src/test/java/org/elasticsearch/shield/ssl/SSLServiceTests.java @@ -6,12 +6,14 @@ package org.elasticsearch.shield.ssl; import org.elasticsearch.common.settings.ImmutableSettings; +import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.test.ElasticsearchTestCase; import org.junit.Before; import org.junit.Test; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLSessionContext; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Arrays; @@ -100,4 +102,28 @@ public class SSLServiceTests extends ElasticsearchTestCase { SSLEngine engine = sslService.createSSLEngine(); assertThat(Arrays.asList(engine.getEnabledProtocols()), not(hasItem("SSLv3"))); } + + @Test + public void testThatSSLSessionCacheHasDefaultLimits() throws Exception { + SSLService sslService = new SSLService(settingsBuilder() + .put("shield.ssl.keystore.path", testnodeStore) + .put("shield.ssl.keystore.password", "testnode") + .build()); + SSLSessionContext context = sslService.getSslContext().getServerSessionContext(); + assertThat(context.getSessionCacheSize(), equalTo(1000)); + assertThat(context.getSessionTimeout(), equalTo((int) TimeValue.timeValueHours(24).seconds())); + } + + @Test + public void testThatSettingSSLSessionCacheLimitsWorks() throws Exception { + SSLService sslService = new SSLService(settingsBuilder() + .put("shield.ssl.keystore.path", testnodeStore) + .put("shield.ssl.keystore.password", "testnode") + .put("shield.ssl.session.cache_size", "300") + .put("shield.ssl.session.cache_timeout", "600s") + .build()); + SSLSessionContext context = sslService.getSslContext().getServerSessionContext(); + assertThat(context.getSessionCacheSize(), equalTo(300)); + assertThat(context.getSessionTimeout(), equalTo(600)); + } }