diff --git a/docs/changelog/130909.yaml b/docs/changelog/130909.yaml new file mode 100644 index 0000000000000..a00d6cbdca570 --- /dev/null +++ b/docs/changelog/130909.yaml @@ -0,0 +1,5 @@ +pr: 130909 +summary: Allow adjustment of transport TLS handshake timeout +area: Network +type: enhancement +issues: [] diff --git a/docs/reference/elasticsearch/configuration-reference/security-settings.md b/docs/reference/elasticsearch/configuration-reference/security-settings.md index 1ec6600038841..7ca0dbdb97993 100644 --- a/docs/reference/elasticsearch/configuration-reference/security-settings.md +++ b/docs/reference/elasticsearch/configuration-reference/security-settings.md @@ -1933,6 +1933,8 @@ You can configure the following TLS/SSL settings. `xpack.security.transport.ssl.trust_restrictions.x509_fields` ![logo cloud](https://doc-icons.s3.us-east-2.amazonaws.com/logo_cloud.svg "Supported on Elastic Cloud Hosted") : Specifies which field(s) from the TLS certificate is used to match for the restricted trust management that is used for remote clusters connections. This should only be set when a self managed cluster can not create certificates that follow the Elastic Cloud pattern. The default value is ["subjectAltName.otherName.commonName"], the Elastic Cloud pattern. "subjectAltName.dnsName" is also supported and can be configured in addition to or in replacement of the default. +`xpack.security.transport.ssl.handshake_timeout` +: Specifies the timeout for a TLS handshake when opening a transport connection. Defaults to `10s`. ### Transport TLS/SSL key and trusted certificate settings [security-transport-tls-ssl-key-trusted-certificate-settings] @@ -2131,6 +2133,9 @@ You can configure the following TLS/SSL settings. For more information, see Oracle’s [Java Cryptography Architecture documentation](https://docs.oracle.com/en/java/javase/11/security/oracle-providers.md#GUID-7093246A-31A3-4304-AC5F-5FB6400405E2). +`xpack.security.remote_cluster_server.ssl.handshake_timeout` +: Specifies the timeout for a TLS handshake when handling an inbound remote-cluster connection. Defaults to `10s`. + ### Remote cluster server (API key based model) TLS/SSL key and trusted certificate settings [security-remote-cluster-server-tls-ssl-key-trusted-certificate-settings] @@ -2260,6 +2265,9 @@ You can configure the following TLS/SSL settings. For more information, see Oracle’s [Java Cryptography Architecture documentation](https://docs.oracle.com/en/java/javase/11/security/oracle-providers.md#GUID-7093246A-31A3-4304-AC5F-5FB6400405E2). +`xpack.security.remote_cluster_client.ssl.handshake_timeout` +: Specifies the timeout for a TLS handshake when opening a remote-cluster connection. Defaults to `10s`. + ### Remote cluster client (API key based model) TLS/SSL key and trusted certificate settings [security-remote-cluster-client-tls-ssl-key-trusted-certificate-settings] diff --git a/libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/SslConfiguration.java b/libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/SslConfiguration.java index 30d846da46156..8f22063d3f27f 100644 --- a/libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/SslConfiguration.java +++ b/libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/SslConfiguration.java @@ -40,7 +40,8 @@ public record SslConfiguration( SslVerificationMode verificationMode, SslClientAuthenticationMode clientAuth, List ciphers, - List supportedProtocols + List supportedProtocols, + long handshakeTimeoutMillis ) { /** @@ -71,7 +72,8 @@ public SslConfiguration( SslVerificationMode verificationMode, SslClientAuthenticationMode clientAuth, List ciphers, - List supportedProtocols + List supportedProtocols, + long handshakeTimeoutMillis ) { this.settingPrefix = settingPrefix; this.explicitlyConfigured = explicitlyConfigured; @@ -85,6 +87,10 @@ public SslConfiguration( this.keyConfig = Objects.requireNonNull(keyConfig, "key config cannot be null"); this.verificationMode = Objects.requireNonNull(verificationMode, "verification mode cannot be null"); this.clientAuth = Objects.requireNonNull(clientAuth, "client authentication cannot be null"); + if (handshakeTimeoutMillis < 1L) { + throw new SslConfigException("handshake timeout must be at least 1ms"); + } + this.handshakeTimeoutMillis = handshakeTimeoutMillis; this.ciphers = Collections.unmodifiableList(ciphers); this.supportedProtocols = Collections.unmodifiableList(supportedProtocols); } @@ -164,11 +170,21 @@ public boolean equals(Object o) { && this.verificationMode == that.verificationMode && this.clientAuth == that.clientAuth && Objects.equals(this.ciphers, that.ciphers) - && Objects.equals(this.supportedProtocols, that.supportedProtocols); + && Objects.equals(this.supportedProtocols, that.supportedProtocols) + && this.handshakeTimeoutMillis == that.handshakeTimeoutMillis; } @Override public int hashCode() { - return Objects.hash(settingPrefix, trustConfig, keyConfig, verificationMode, clientAuth, ciphers, supportedProtocols); + return Objects.hash( + settingPrefix, + trustConfig, + keyConfig, + verificationMode, + clientAuth, + ciphers, + supportedProtocols, + handshakeTimeoutMillis + ); } } diff --git a/libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/SslConfigurationKeys.java b/libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/SslConfigurationKeys.java index 1c782a2fa5f31..777f68c518fbb 100644 --- a/libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/SslConfigurationKeys.java +++ b/libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/SslConfigurationKeys.java @@ -132,6 +132,10 @@ public class SslConfigurationKeys { * The use of this setting {@link #isDeprecated(String) is deprecated}. */ public static final String KEY_LEGACY_PASSPHRASE = "key_passphrase"; + /** + * The timeout for TLS handshakes in this context. + */ + public static final String HANDSHAKE_TIMEOUT = "handshake_timeout"; private static final Set DEPRECATED_KEYS = new HashSet<>( Arrays.asList(TRUSTSTORE_LEGACY_PASSWORD, KEYSTORE_LEGACY_PASSWORD, KEYSTORE_LEGACY_KEY_PASSWORD, KEY_LEGACY_PASSPHRASE) diff --git a/libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/SslConfigurationLoader.java b/libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/SslConfigurationLoader.java index 9d455807953e7..e2e9d92726b6a 100644 --- a/libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/SslConfigurationLoader.java +++ b/libs/ssl-config/src/main/java/org/elasticsearch/common/ssl/SslConfigurationLoader.java @@ -10,6 +10,7 @@ package org.elasticsearch.common.ssl; import org.elasticsearch.core.Nullable; +import org.elasticsearch.core.TimeValue; import java.nio.file.Path; import java.security.KeyStore; @@ -27,6 +28,7 @@ import static org.elasticsearch.common.ssl.SslConfigurationKeys.CERTIFICATE_AUTHORITIES; import static org.elasticsearch.common.ssl.SslConfigurationKeys.CIPHERS; import static org.elasticsearch.common.ssl.SslConfigurationKeys.CLIENT_AUTH; +import static org.elasticsearch.common.ssl.SslConfigurationKeys.HANDSHAKE_TIMEOUT; import static org.elasticsearch.common.ssl.SslConfigurationKeys.KEY; import static org.elasticsearch.common.ssl.SslConfigurationKeys.KEYSTORE_ALGORITHM; import static org.elasticsearch.common.ssl.SslConfigurationKeys.KEYSTORE_LEGACY_KEY_PASSWORD; @@ -152,6 +154,8 @@ public abstract class SslConfigurationLoader { private static final char[] EMPTY_PASSWORD = new char[0]; public static final List GLOBAL_DEFAULT_RESTRICTED_TRUST_FIELDS = List.of(X509Field.SAN_OTHERNAME_COMMONNAME); + public static final TimeValue DEFAULT_HANDSHAKE_TIMEOUT = TimeValue.timeValueSeconds(10); + private final String settingPrefix; private SslTrustConfig defaultTrustConfig; @@ -302,6 +306,11 @@ public SslConfiguration load(Path basePath) { X509Field::parseForRestrictedTrust, defaultRestrictedTrustFields ); + final long handshakeTimeoutMillis = resolveSetting( + HANDSHAKE_TIMEOUT, + s -> TimeValue.parseTimeValue(s, HANDSHAKE_TIMEOUT), + DEFAULT_HANDSHAKE_TIMEOUT + ).millis(); final SslKeyConfig keyConfig = buildKeyConfig(basePath); final SslTrustConfig trustConfig = buildTrustConfig(basePath, verificationMode, keyConfig, Set.copyOf(trustRestrictionsX509Fields)); @@ -321,7 +330,8 @@ public SslConfiguration load(Path basePath) { verificationMode, clientAuth, ciphers, - protocols + protocols, + handshakeTimeoutMillis ); } diff --git a/libs/ssl-config/src/test/java/org/elasticsearch/common/ssl/SslConfigurationTests.java b/libs/ssl-config/src/test/java/org/elasticsearch/common/ssl/SslConfigurationTests.java index 735edddd284bd..d93266e50ebd1 100644 --- a/libs/ssl-config/src/test/java/org/elasticsearch/common/ssl/SslConfigurationTests.java +++ b/libs/ssl-config/src/test/java/org/elasticsearch/common/ssl/SslConfigurationTests.java @@ -39,6 +39,7 @@ public void testBasicConstruction() { final SslClientAuthenticationMode clientAuth = randomFrom(SslClientAuthenticationMode.values()); final List ciphers = randomSubsetOf(randomIntBetween(1, DEFAULT_CIPHERS.size()), DEFAULT_CIPHERS); final List protocols = randomSubsetOf(randomIntBetween(1, 4), VALID_PROTOCOLS); + final long handshakeTimeoutMillis = randomHandshakeTimeoutMillis(); final SslConfiguration configuration = new SslConfiguration( "test.ssl", true, @@ -47,7 +48,8 @@ public void testBasicConstruction() { verificationMode, clientAuth, ciphers, - protocols + protocols, + handshakeTimeoutMillis ); assertThat(configuration.trustConfig(), is(trustConfig)); @@ -56,6 +58,7 @@ public void testBasicConstruction() { assertThat(configuration.clientAuth(), is(clientAuth)); assertThat(configuration.getCipherSuites(), is(ciphers)); assertThat(configuration.supportedProtocols(), is(protocols)); + assertThat(configuration.handshakeTimeoutMillis(), is(handshakeTimeoutMillis)); assertThat(configuration.toString(), containsString("TEST-TRUST")); assertThat(configuration.toString(), containsString("TEST-KEY")); @@ -63,6 +66,7 @@ public void testBasicConstruction() { assertThat(configuration.toString(), containsString(clientAuth.toString())); assertThat(configuration.toString(), containsString(randomFrom(ciphers))); assertThat(configuration.toString(), containsString(randomFrom(protocols))); + assertThat(configuration.toString(), containsString("handshakeTimeoutMillis=" + handshakeTimeoutMillis)); } public void testEqualsAndHashCode() { @@ -72,6 +76,7 @@ public void testEqualsAndHashCode() { final SslClientAuthenticationMode clientAuth = randomFrom(SslClientAuthenticationMode.values()); final List ciphers = randomSubsetOf(randomIntBetween(1, DEFAULT_CIPHERS.size() - 1), DEFAULT_CIPHERS); final List protocols = randomSubsetOf(randomIntBetween(1, VALID_PROTOCOLS.length - 1), VALID_PROTOCOLS); + final long handshakeTimeoutMillis = randomHandshakeTimeoutMillis(); final SslConfiguration configuration = new SslConfiguration( "test.ssl", true, @@ -80,7 +85,8 @@ public void testEqualsAndHashCode() { verificationMode, clientAuth, ciphers, - protocols + protocols, + handshakeTimeoutMillis ); EqualsHashCodeTestUtils.checkEqualsAndHashCode( @@ -93,14 +99,15 @@ public void testEqualsAndHashCode() { orig.verificationMode(), orig.clientAuth(), orig.getCipherSuites(), - orig.supportedProtocols() + orig.supportedProtocols(), + orig.handshakeTimeoutMillis() ), this::mutateSslConfiguration ); } private SslConfiguration mutateSslConfiguration(SslConfiguration orig) { - return switch (randomIntBetween(1, 4)) { + return switch (randomIntBetween(1, 5)) { case 1 -> new SslConfiguration( "test.ssl", true, @@ -109,7 +116,8 @@ private SslConfiguration mutateSslConfiguration(SslConfiguration orig) { randomValueOtherThan(orig.verificationMode(), () -> randomFrom(SslVerificationMode.values())), orig.clientAuth(), orig.getCipherSuites(), - orig.supportedProtocols() + orig.supportedProtocols(), + orig.handshakeTimeoutMillis() ); case 2 -> new SslConfiguration( "test.ssl", @@ -119,7 +127,8 @@ private SslConfiguration mutateSslConfiguration(SslConfiguration orig) { orig.verificationMode(), randomValueOtherThan(orig.clientAuth(), () -> randomFrom(SslClientAuthenticationMode.values())), orig.getCipherSuites(), - orig.supportedProtocols() + orig.supportedProtocols(), + orig.handshakeTimeoutMillis() ); case 3 -> new SslConfiguration( "test.ssl", @@ -129,7 +138,19 @@ private SslConfiguration mutateSslConfiguration(SslConfiguration orig) { orig.verificationMode(), orig.clientAuth(), DEFAULT_CIPHERS, - orig.supportedProtocols() + orig.supportedProtocols(), + orig.handshakeTimeoutMillis() + ); + case 4 -> new SslConfiguration( + "test.ssl", + true, + orig.trustConfig(), + orig.keyConfig(), + orig.verificationMode(), + orig.clientAuth(), + orig.getCipherSuites(), + Arrays.asList(VALID_PROTOCOLS), + orig.handshakeTimeoutMillis() ); default -> new SslConfiguration( "test.ssl", @@ -139,11 +160,16 @@ private SslConfiguration mutateSslConfiguration(SslConfiguration orig) { orig.verificationMode(), orig.clientAuth(), orig.getCipherSuites(), - Arrays.asList(VALID_PROTOCOLS) + orig.supportedProtocols(), + randomValueOtherThan(orig.handshakeTimeoutMillis(), SslConfigurationTests::randomHandshakeTimeoutMillis) ); }; } + private static long randomHandshakeTimeoutMillis() { + return randomLongBetween(1, 100000); + } + public void testDependentFiles() { final SslTrustConfig trustConfig = Mockito.mock(SslTrustConfig.class); final SslKeyConfig keyConfig = Mockito.mock(SslKeyConfig.class); @@ -155,7 +181,8 @@ public void testDependentFiles() { randomFrom(SslVerificationMode.values()), randomFrom(SslClientAuthenticationMode.values()), DEFAULT_CIPHERS, - SslConfigurationLoader.DEFAULT_PROTOCOLS + SslConfigurationLoader.DEFAULT_PROTOCOLS, + randomHandshakeTimeoutMillis() ); final Path dir = createTempDir(); @@ -182,7 +209,8 @@ public void testBuildSslContext() { randomFrom(SslVerificationMode.values()), randomFrom(SslClientAuthenticationMode.values()), DEFAULT_CIPHERS, - Collections.singletonList(protocol) + Collections.singletonList(protocol), + randomHandshakeTimeoutMillis() ); Mockito.when(trustConfig.createTrustManager()).thenReturn(null); diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/XPackSettings.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/XPackSettings.java index 31edc83c00b3a..fb29dde69ae7c 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/XPackSettings.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/XPackSettings.java @@ -344,18 +344,14 @@ public static Setting defaultStoredSecureTokenHashAlgorithmSetting( public static final SslClientAuthenticationMode REMOTE_CLUSTER_CLIENT_AUTH_DEFAULT = SslClientAuthenticationMode.NONE; public static final SslVerificationMode VERIFICATION_MODE_DEFAULT = SslVerificationMode.FULL; - // http specific settings public static final String HTTP_SSL_PREFIX = SecurityField.setting("http.ssl."); - private static final SSLConfigurationSettings HTTP_SSL = SSLConfigurationSettings.withPrefix(HTTP_SSL_PREFIX, true); - - // transport specific settings public static final String TRANSPORT_SSL_PREFIX = SecurityField.setting("transport.ssl."); - private static final SSLConfigurationSettings TRANSPORT_SSL = SSLConfigurationSettings.withPrefix(TRANSPORT_SSL_PREFIX, true); - - // remote cluster specific settings public static final String REMOTE_CLUSTER_SERVER_SSL_PREFIX = SecurityField.setting("remote_cluster_server.ssl."); public static final String REMOTE_CLUSTER_CLIENT_SSL_PREFIX = SecurityField.setting("remote_cluster_client.ssl."); + private static final SSLConfigurationSettings HTTP_SSL = SSLConfigurationSettings.withPrefix(HTTP_SSL_PREFIX, true); + private static final SSLConfigurationSettings TRANSPORT_SSL = SSLConfigurationSettings.withPrefix(TRANSPORT_SSL_PREFIX, true); + private static final SSLConfigurationSettings REMOTE_CLUSTER_SERVER_SSL = SSLConfigurationSettings.withPrefix( REMOTE_CLUSTER_SERVER_SSL_PREFIX, false diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ssl/SSLConfigurationSettings.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ssl/SSLConfigurationSettings.java index 055454847e154..6aa50df60a1f5 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ssl/SSLConfigurationSettings.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ssl/SSLConfigurationSettings.java @@ -13,9 +13,12 @@ import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.ssl.SslClientAuthenticationMode; import org.elasticsearch.common.ssl.SslConfigurationKeys; +import org.elasticsearch.common.ssl.SslConfigurationLoader; import org.elasticsearch.common.ssl.SslVerificationMode; import org.elasticsearch.common.ssl.X509Field; import org.elasticsearch.common.util.CollectionUtils; +import org.elasticsearch.core.TimeValue; +import org.elasticsearch.xpack.core.XPackSettings; import org.elasticsearch.xpack.core.security.authc.RealmConfig; import java.util.ArrayList; @@ -30,6 +33,7 @@ import javax.net.ssl.TrustManagerFactory; import static org.elasticsearch.common.ssl.SslConfigurationLoader.GLOBAL_DEFAULT_RESTRICTED_TRUST_FIELDS; +import static org.elasticsearch.xpack.core.XPackSettings.TRANSPORT_SSL_PREFIX; /** * Bridges SSLConfiguration into the {@link Settings} framework, using {@link Setting} objects. @@ -50,6 +54,7 @@ public class SSLConfigurationSettings { final Setting> caPaths; final Setting> clientAuth; final Setting> verificationMode; + final Setting handshakeTimeout; // public for PKI realm private final Setting legacyTruststorePassword; @@ -223,6 +228,11 @@ public class SSLConfigurationSettings { public static final Function>> VERIFICATION_MODE_SETTING_REALM = VERIFICATION_MODE::realm; + public static final SslSetting HANDSHAKE_TIMEOUT = SslSetting.setting( + SslConfigurationKeys.HANDSHAKE_TIMEOUT, + key -> Setting.positiveTimeSetting(key, SslConfigurationLoader.DEFAULT_HANDSHAKE_TIMEOUT, Property.NodeScope) + ); + /** * @param prefix The prefix under which each setting should be defined. Must be either the empty string ("") or a string * ending in "." @@ -246,6 +256,7 @@ private SSLConfigurationSettings(String prefix, boolean acceptNonSecurePasswords caPaths = CERT_AUTH_PATH.withPrefix(prefix); clientAuth = CLIENT_AUTH_SETTING.withPrefix(prefix); verificationMode = VERIFICATION_MODE.withPrefix(prefix); + handshakeTimeout = HANDSHAKE_TIMEOUT.withPrefix(prefix); final List> enabled = CollectionUtils.arrayAsArrayList( ciphers, @@ -270,6 +281,16 @@ private SSLConfigurationSettings(String prefix, boolean acceptNonSecurePasswords enabled.addAll(x509KeyPair.getEnabledSettings()); disabled.addAll(x509KeyPair.getDisabledSettings()); + if (TRANSPORT_SSL_PREFIX.equals(prefix) + || XPackSettings.REMOTE_CLUSTER_CLIENT_SSL_PREFIX.equals(prefix) + || XPackSettings.REMOTE_CLUSTER_SERVER_SSL_PREFIX.equals(prefix)) { + enabled.add(handshakeTimeout); + } else { + // Today the handshake timeout is only adjustable for transport connections - see SecurityNetty4Transport. In principle we + // could extend this to other contexts too, we just haven't done so yet. + disabled.add(handshakeTimeout); + } + this.enabledSettings = Collections.unmodifiableList(enabled); this.disabledSettings = Collections.unmodifiableList(disabled); } @@ -327,7 +348,8 @@ private static Collection> settings() { CERT, CERT_AUTH_PATH, CLIENT_AUTH_SETTING, - VERIFICATION_MODE + VERIFICATION_MODE, + HANDSHAKE_TIMEOUT ); } diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/core/security/transport/netty4/SecurityNetty4Transport.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/core/security/transport/netty4/SecurityNetty4Transport.java index ff8b6f5eaac39..fa16de22c865c 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/core/security/transport/netty4/SecurityNetty4Transport.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/core/security/transport/netty4/SecurityNetty4Transport.java @@ -240,6 +240,7 @@ protected void initChannel(Channel ch) throws Exception { SSLEngine serverEngine = sslService.createSSLEngine(configuration, null, -1); serverEngine.setUseClientMode(false); final SslHandler sslHandler = new SslHandler(serverEngine); + sslHandler.setHandshakeTimeoutMillis(configuration.handshakeTimeoutMillis()); ch.pipeline().addFirst("sslhandler", sslHandler); super.initChannel(ch); assert ch.pipeline().first() == sslHandler : "SSL handler must be first handler in pipeline"; @@ -340,6 +341,7 @@ public void connect(ChannelHandlerContext ctx, SocketAddress remoteAddress, Sock } final ChannelPromise connectPromise = ctx.newPromise(); final SslHandler sslHandler = new SslHandler(sslEngine); + sslHandler.setHandshakeTimeoutMillis(sslConfiguration.handshakeTimeoutMillis()); ctx.pipeline().replace(this, "ssl", sslHandler); final Future handshakePromise = sslHandler.handshakeFuture(); Netty4Utils.addListener(connectPromise, result -> { diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/transport/SecurityServerTransportInterceptorTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/transport/SecurityServerTransportInterceptorTests.java index 094751f51dd5b..68586a973bc8a 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/transport/SecurityServerTransportInterceptorTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/transport/SecurityServerTransportInterceptorTests.java @@ -1118,7 +1118,8 @@ public void testProfileFiltersCreatedDifferentlyForDifferentTransportAndRemoteCl randomFrom(SslVerificationMode.values()), SslClientAuthenticationMode.REQUIRED, List.of("TLS_AES_256_GCM_SHA384"), - List.of("TLSv1.3") + List.of("TLSv1.3"), + randomLongBetween(1, 100000) ) ); @@ -1131,7 +1132,8 @@ public void testProfileFiltersCreatedDifferentlyForDifferentTransportAndRemoteCl randomFrom(SslVerificationMode.values()), SslClientAuthenticationMode.NONE, List.of(Runtime.version().feature() < 24 ? "TLS_RSA_WITH_AES_256_GCM_SHA384" : "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"), - List.of("TLSv1.2") + List.of("TLSv1.2"), + randomLongBetween(1, 100000) ) ); doThrow(new AssertionError("profile filters should not be configured for remote cluster client")).when(sslService) @@ -1181,7 +1183,8 @@ public void testNoProfileFilterForRemoteClusterWhenTheFeatureIsDisabled() { randomFrom(SslVerificationMode.values()), SslClientAuthenticationMode.REQUIRED, List.of("TLS_AES_256_GCM_SHA384"), - List.of("TLSv1.3") + List.of("TLSv1.3"), + randomLongBetween(1, 100000) ) ); doThrow(new AssertionError("profile filters should not be configured for remote cluster server when the port is disabled")).when( diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/transport/netty4/SimpleSecurityNetty4ServerTransportTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/transport/netty4/SimpleSecurityNetty4ServerTransportTests.java index b984295155c1f..7a7896eb08d83 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/transport/netty4/SimpleSecurityNetty4ServerTransportTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/transport/netty4/SimpleSecurityNetty4ServerTransportTests.java @@ -11,6 +11,7 @@ import io.netty.channel.socket.nio.NioChannelOption; import io.netty.handler.ssl.SslHandshakeTimeoutException; +import org.apache.logging.log4j.Level; import org.apache.lucene.util.Constants; import org.elasticsearch.ExceptionsHelper; import org.elasticsearch.TransportVersion; @@ -35,6 +36,7 @@ import org.elasticsearch.common.unit.ByteSizeValue; import org.elasticsearch.common.util.PageCacheRecycler; import org.elasticsearch.core.IOUtils; +import org.elasticsearch.core.Nullable; import org.elasticsearch.core.Releasable; import org.elasticsearch.core.SuppressForbidden; import org.elasticsearch.core.TimeValue; @@ -42,6 +44,9 @@ import org.elasticsearch.indices.breaker.CircuitBreakerService; import org.elasticsearch.indices.breaker.NoneCircuitBreakerService; import org.elasticsearch.mocksocket.MockServerSocket; +import org.elasticsearch.mocksocket.MockSocket; +import org.elasticsearch.test.MockLog; +import org.elasticsearch.test.junit.annotations.TestLogging; import org.elasticsearch.test.transport.MockTransportService; import org.elasticsearch.test.transport.StubbableTransport; import org.elasticsearch.threadpool.ThreadPool; @@ -65,6 +70,7 @@ import org.elasticsearch.xpack.security.transport.SSLEngineUtils; import org.elasticsearch.xpack.security.transport.filter.IPFilter; +import java.io.EOFException; import java.io.IOException; import java.io.UncheckedIOException; import java.net.InetAddress; @@ -902,7 +908,15 @@ public void testTcpHandshakeTimeout() throws IOException { } } + @TestLogging(reason = "inbound timeout is reported at TRACE", value = "org.elasticsearch.transport.netty4.ESLoggingHandler:TRACE") public void testTlsHandshakeTimeout() throws IOException { + runOutboundTlsHandshakeTimeoutTest(null); + runOutboundTlsHandshakeTimeoutTest(randomLongBetween(1, 500)); + runInboundTlsHandshakeTimeoutTest(null); + runInboundTlsHandshakeTimeoutTest(randomLongBetween(1, 500)); + } + + private void runOutboundTlsHandshakeTimeoutTest(@Nullable /* to use the default */ Long handshakeTimeoutMillis) throws IOException { final CountDownLatch doneLatch = new CountDownLatch(1); try (ServerSocket socket = new MockServerSocket()) { socket.bind(getLocalEphemeral(), 1); @@ -928,16 +942,56 @@ public void testTlsHandshakeTimeout() throws IOException { TransportRequestOptions.Type.REG, TransportRequestOptions.Type.STATE ); - final var future = new TestPlainActionFuture(); - serviceA.connectToNode(dummy, builder.build(), future); - final var ex = expectThrows(ExecutionException.class, ConnectTransportException.class, future::get); // long wait - assertEquals("[][" + dummy.getAddress() + "] connect_exception", ex.getMessage()); - assertNotNull(ExceptionsHelper.unwrap(ex, SslHandshakeTimeoutException.class)); + final ConnectTransportException exception; + final var transportSettings = Settings.builder(); + if (handshakeTimeoutMillis == null) { + handshakeTimeoutMillis = 10000L; // default + } else { + transportSettings.put("xpack.security.transport.ssl.handshake_timeout", TimeValue.timeValueMillis(handshakeTimeoutMillis)); + } + try (var service = buildService(getTestName(), version0, transportVersion0, transportSettings.build())) { + final var future = new TestPlainActionFuture(); + service.connectToNode(dummy, builder.build(), future); + exception = expectThrows(ExecutionException.class, ConnectTransportException.class, future::get); // long wait + assertEquals("[][" + dummy.getAddress() + "] connect_exception", exception.getMessage()); + assertThat( + asInstanceOf(SslHandshakeTimeoutException.class, exception.getCause()).getMessage(), + equalTo("handshake timed out after " + handshakeTimeoutMillis + "ms") + ); + } } finally { doneLatch.countDown(); } } + @SuppressForbidden(reason = "test needs a simple TCP connection") + private void runInboundTlsHandshakeTimeoutTest(@Nullable /* to use the default */ Long handshakeTimeoutMillis) throws IOException { + final var transportSettings = Settings.builder(); + if (handshakeTimeoutMillis == null) { + handshakeTimeoutMillis = 10000L; // default + } else { + transportSettings.put("xpack.security.transport.ssl.handshake_timeout", TimeValue.timeValueMillis(handshakeTimeoutMillis)); + } + try ( + var service = buildService(getTestName(), version0, transportVersion0, transportSettings.build()); + Socket clientSocket = new MockSocket(); + MockLog mockLog = MockLog.capture("org.elasticsearch.transport.netty4.ESLoggingHandler") + ) { + mockLog.addExpectation( + new MockLog.SeenEventExpectation( + "timeout event message", + "org.elasticsearch.transport.netty4.ESLoggingHandler", + Level.TRACE, + "SslHandshakeTimeoutException: handshake timed out after " + handshakeTimeoutMillis + "ms" + ) + ); + + clientSocket.connect(service.boundAddress().boundAddresses()[0].address()); + expectThrows(EOFException.class, () -> clientSocket.getInputStream().skipNBytes(Long.MAX_VALUE)); + mockLog.assertAllExpectationsMatched(); + } + } + public void testTcpHandshakeConnectionReset() throws IOException, InterruptedException { assumeFalse("Can't run in a FIPS JVM, TrustAllConfig is not a SunJSSE TrustManagers", inFipsJvm()); SSLService sslService = createSSLService();