Skip to content
Merged
5 changes: 5 additions & 0 deletions docs/changelog/130909.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pr: 130909
summary: Allow adjustment of transport TLS handshake timeout
area: Network
type: enhancement
issues: []
Original file line number Diff line number Diff line change
Expand Up @@ -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]

Expand Down Expand Up @@ -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]

Expand Down Expand Up @@ -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]

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ public record SslConfiguration(
SslVerificationMode verificationMode,
SslClientAuthenticationMode clientAuth,
List<String> ciphers,
List<String> supportedProtocols
List<String> supportedProtocols,
long handshakeTimeoutMillis
) {

/**
Expand Down Expand Up @@ -71,7 +72,8 @@ public SslConfiguration(
SslVerificationMode verificationMode,
SslClientAuthenticationMode clientAuth,
List<String> ciphers,
List<String> supportedProtocols
List<String> supportedProtocols,
long handshakeTimeoutMillis
) {
this.settingPrefix = settingPrefix;
this.explicitlyConfigured = explicitlyConfigured;
Expand All @@ -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);
}
Expand Down Expand Up @@ -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
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<String> DEPRECATED_KEYS = new HashSet<>(
Arrays.asList(TRUSTSTORE_LEGACY_PASSWORD, KEYSTORE_LEGACY_PASSWORD, KEYSTORE_LEGACY_KEY_PASSWORD, KEY_LEGACY_PASSPHRASE)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -152,6 +154,8 @@ public abstract class SslConfigurationLoader {
private static final char[] EMPTY_PASSWORD = new char[0];
public static final List<X509Field> 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;
Expand Down Expand Up @@ -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));
Expand All @@ -321,7 +330,8 @@ public SslConfiguration load(Path basePath) {
verificationMode,
clientAuth,
ciphers,
protocols
protocols,
handshakeTimeoutMillis
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ public void testBasicConstruction() {
final SslClientAuthenticationMode clientAuth = randomFrom(SslClientAuthenticationMode.values());
final List<String> ciphers = randomSubsetOf(randomIntBetween(1, DEFAULT_CIPHERS.size()), DEFAULT_CIPHERS);
final List<String> protocols = randomSubsetOf(randomIntBetween(1, 4), VALID_PROTOCOLS);
final long handshakeTimeoutMillis = randomHandshakeTimeoutMillis();
final SslConfiguration configuration = new SslConfiguration(
"test.ssl",
true,
Expand All @@ -47,7 +48,8 @@ public void testBasicConstruction() {
verificationMode,
clientAuth,
ciphers,
protocols
protocols,
handshakeTimeoutMillis
);

assertThat(configuration.trustConfig(), is(trustConfig));
Expand All @@ -56,13 +58,15 @@ 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"));
assertThat(configuration.toString(), containsString(verificationMode.toString()));
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() {
Expand All @@ -72,6 +76,7 @@ public void testEqualsAndHashCode() {
final SslClientAuthenticationMode clientAuth = randomFrom(SslClientAuthenticationMode.values());
final List<String> ciphers = randomSubsetOf(randomIntBetween(1, DEFAULT_CIPHERS.size() - 1), DEFAULT_CIPHERS);
final List<String> protocols = randomSubsetOf(randomIntBetween(1, VALID_PROTOCOLS.length - 1), VALID_PROTOCOLS);
final long handshakeTimeoutMillis = randomHandshakeTimeoutMillis();
final SslConfiguration configuration = new SslConfiguration(
"test.ssl",
true,
Expand All @@ -80,7 +85,8 @@ public void testEqualsAndHashCode() {
verificationMode,
clientAuth,
ciphers,
protocols
protocols,
handshakeTimeoutMillis
);

EqualsHashCodeTestUtils.checkEqualsAndHashCode(
Expand All @@ -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,
Expand All @@ -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",
Expand All @@ -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",
Expand All @@ -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",
Expand All @@ -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);
Expand All @@ -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();
Expand All @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -344,18 +344,14 @@ public static Setting<String> 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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.
Expand All @@ -50,6 +54,7 @@ public class SSLConfigurationSettings {
final Setting<List<String>> caPaths;
final Setting<Optional<SslClientAuthenticationMode>> clientAuth;
final Setting<Optional<SslVerificationMode>> verificationMode;
final Setting<TimeValue> handshakeTimeout;

// public for PKI realm
private final Setting<SecureString> legacyTruststorePassword;
Expand Down Expand Up @@ -223,6 +228,11 @@ public class SSLConfigurationSettings {
public static final Function<String, Setting.AffixSetting<Optional<SslVerificationMode>>> VERIFICATION_MODE_SETTING_REALM =
VERIFICATION_MODE::realm;

public static final SslSetting<TimeValue> 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 (<code>""</code>) or a string
* ending in <code>"."</code>
Expand All @@ -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<Setting<? extends Object>> enabled = CollectionUtils.arrayAsArrayList(
ciphers,
Expand All @@ -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);
}
Expand Down Expand Up @@ -327,7 +348,8 @@ private static Collection<SslSetting<?>> settings() {
CERT,
CERT_AUTH_PATH,
CLIENT_AUTH_SETTING,
VERIFICATION_MODE
VERIFICATION_MODE,
HANDSHAKE_TIMEOUT
);
}

Expand Down
Loading
Loading