Add sni name to SSLEngine in netty transport (#33144)

This commit is related to #32517. It allows an "server_name"
attribute on a DiscoveryNode to be propagated to the server using
the TLS SNI extentsion. This functionality is only implemented for
the netty security transport.
This commit is contained in:
Tim Brooks 2018-09-05 16:12:10 -06:00 committed by GitHub
parent 6f9c9ab5e1
commit 88c178dca6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 466 additions and 24 deletions

View File

@ -97,12 +97,12 @@ public class Netty4Transport extends TcpTransport {
intSetting("transport.netty.boss_count", 1, 1, Property.NodeScope); intSetting("transport.netty.boss_count", 1, 1, Property.NodeScope);
protected final RecvByteBufAllocator recvByteBufAllocator; private final RecvByteBufAllocator recvByteBufAllocator;
protected final int workerCount; private final int workerCount;
protected final ByteSizeValue receivePredictorMin; private final ByteSizeValue receivePredictorMin;
protected final ByteSizeValue receivePredictorMax; private final ByteSizeValue receivePredictorMax;
protected volatile Bootstrap bootstrap; private volatile Bootstrap clientBootstrap;
protected final Map<String, ServerBootstrap> serverBootstraps = newConcurrentMap(); private final Map<String, ServerBootstrap> serverBootstraps = newConcurrentMap();
public Netty4Transport(Settings settings, ThreadPool threadPool, NetworkService networkService, BigArrays bigArrays, public Netty4Transport(Settings settings, ThreadPool threadPool, NetworkService networkService, BigArrays bigArrays,
NamedWriteableRegistry namedWriteableRegistry, CircuitBreakerService circuitBreakerService) { NamedWriteableRegistry namedWriteableRegistry, CircuitBreakerService circuitBreakerService) {
@ -125,7 +125,7 @@ public class Netty4Transport extends TcpTransport {
protected void doStart() { protected void doStart() {
boolean success = false; boolean success = false;
try { try {
bootstrap = createBootstrap(); clientBootstrap = createClientBootstrap();
if (NetworkService.NETWORK_SERVER.get(settings)) { if (NetworkService.NETWORK_SERVER.get(settings)) {
for (ProfileSettings profileSettings : profileSettings) { for (ProfileSettings profileSettings : profileSettings) {
createServerBootstrap(profileSettings); createServerBootstrap(profileSettings);
@ -141,13 +141,11 @@ public class Netty4Transport extends TcpTransport {
} }
} }
private Bootstrap createBootstrap() { private Bootstrap createClientBootstrap() {
final Bootstrap bootstrap = new Bootstrap(); final Bootstrap bootstrap = new Bootstrap();
bootstrap.group(new NioEventLoopGroup(workerCount, daemonThreadFactory(settings, TRANSPORT_CLIENT_BOSS_THREAD_NAME_PREFIX))); bootstrap.group(new NioEventLoopGroup(workerCount, daemonThreadFactory(settings, TRANSPORT_CLIENT_BOSS_THREAD_NAME_PREFIX)));
bootstrap.channel(NioSocketChannel.class); bootstrap.channel(NioSocketChannel.class);
bootstrap.handler(getClientChannelInitializer());
bootstrap.option(ChannelOption.TCP_NODELAY, TCP_NO_DELAY.get(settings)); bootstrap.option(ChannelOption.TCP_NODELAY, TCP_NO_DELAY.get(settings));
bootstrap.option(ChannelOption.SO_KEEPALIVE, TCP_KEEP_ALIVE.get(settings)); bootstrap.option(ChannelOption.SO_KEEPALIVE, TCP_KEEP_ALIVE.get(settings));
@ -166,8 +164,6 @@ public class Netty4Transport extends TcpTransport {
final boolean reuseAddress = TCP_REUSE_ADDRESS.get(settings); final boolean reuseAddress = TCP_REUSE_ADDRESS.get(settings);
bootstrap.option(ChannelOption.SO_REUSEADDR, reuseAddress); bootstrap.option(ChannelOption.SO_REUSEADDR, reuseAddress);
bootstrap.validate();
return bootstrap; return bootstrap;
} }
@ -216,7 +212,7 @@ public class Netty4Transport extends TcpTransport {
return new ServerChannelInitializer(name); return new ServerChannelInitializer(name);
} }
protected ChannelHandler getClientChannelInitializer() { protected ChannelHandler getClientChannelInitializer(DiscoveryNode node) {
return new ClientChannelInitializer(); return new ClientChannelInitializer();
} }
@ -226,7 +222,11 @@ public class Netty4Transport extends TcpTransport {
@Override @Override
protected Netty4TcpChannel initiateChannel(DiscoveryNode node, ActionListener<Void> listener) throws IOException { protected Netty4TcpChannel initiateChannel(DiscoveryNode node, ActionListener<Void> listener) throws IOException {
InetSocketAddress address = node.getAddress().address(); InetSocketAddress address = node.getAddress().address();
ChannelFuture channelFuture = bootstrap.connect(address); Bootstrap bootstrapWithHandler = clientBootstrap.clone();
bootstrapWithHandler.handler(getClientChannelInitializer(node));
bootstrapWithHandler.remoteAddress(address);
ChannelFuture channelFuture = bootstrapWithHandler.connect();
Channel channel = channelFuture.channel(); Channel channel = channelFuture.channel();
if (channel == null) { if (channel == null) {
ExceptionsHelper.maybeDieOnAnotherThread(channelFuture.cause()); ExceptionsHelper.maybeDieOnAnotherThread(channelFuture.cause());
@ -289,9 +289,9 @@ public class Netty4Transport extends TcpTransport {
} }
serverBootstraps.clear(); serverBootstraps.clear();
if (bootstrap != null) { if (clientBootstrap != null) {
bootstrap.config().group().shutdownGracefully(0, 5, TimeUnit.SECONDS).awaitUninterruptibly(); clientBootstrap.config().group().shutdownGracefully(0, 5, TimeUnit.SECONDS).awaitUninterruptibly();
bootstrap = null; clientBootstrap = null;
} }
}); });
} }

View File

@ -150,6 +150,7 @@ import org.elasticsearch.transport.TransportService;
import org.elasticsearch.usage.UsageService; import org.elasticsearch.usage.UsageService;
import org.elasticsearch.watcher.ResourceWatcherService; import org.elasticsearch.watcher.ResourceWatcherService;
import javax.net.ssl.SNIHostName;
import java.io.BufferedWriter; import java.io.BufferedWriter;
import java.io.Closeable; import java.io.Closeable;
import java.io.IOException; import java.io.IOException;
@ -209,6 +210,13 @@ public class Node implements Closeable {
throw new IllegalArgumentException(key + " cannot have leading or trailing whitespace " + throw new IllegalArgumentException(key + " cannot have leading or trailing whitespace " +
"[" + value + "]"); "[" + value + "]");
} }
if (value.length() > 0 && "node.attr.server_name".equals(key)) {
try {
new SNIHostName(value);
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException("invalid node.attr.server_name [" + value + "]", e );
}
}
return value; return value;
}, Property.NodeScope)); }, Property.NodeScope));
public static final Setting<String> BREAKER_TYPE_KEY = new Setting<>("indices.breaker.type", "hierarchy", (s) -> { public static final Setting<String> BREAKER_TYPE_KEY = new Setting<>("indices.breaker.type", "hierarchy", (s) -> {

View File

@ -156,6 +156,25 @@ public class NodeTests extends ESTestCase {
} }
} }
public void testServerNameNodeAttribute() throws IOException {
String attr = "valid-hostname";
Settings.Builder settings = baseSettings().put(Node.NODE_ATTRIBUTES.getKey() + "server_name", attr);
int i = 0;
try (Node node = new MockNode(settings.build(), basePlugins())) {
final Settings nodeSettings = randomBoolean() ? node.settings() : node.getEnvironment().settings();
assertEquals(attr, Node.NODE_ATTRIBUTES.getAsMap(nodeSettings).get("server_name"));
}
// non-LDH hostname not allowed
attr = "invalid_hostname";
settings = baseSettings().put(Node.NODE_ATTRIBUTES.getKey() + "server_name", attr);
try (Node node = new MockNode(settings.build(), basePlugins())) {
fail("should not allow a server_name attribute with an underscore");
} catch (IllegalArgumentException e) {
assertEquals("invalid node.attr.server_name [invalid_hostname]", e.getMessage());
}
}
private static Settings.Builder baseSettings() { private static Settings.Builder baseSettings() {
final Path tempDir = createTempDir(); final Path tempDir = createTempDir();
return Settings.builder() return Settings.builder()

View File

@ -2686,7 +2686,7 @@ public abstract class AbstractSimpleTransportTestCase extends ESTestCase {
} }
@SuppressForbidden(reason = "need local ephemeral port") @SuppressForbidden(reason = "need local ephemeral port")
private InetSocketAddress getLocalEphemeral() throws UnknownHostException { protected InetSocketAddress getLocalEphemeral() throws UnknownHostException {
return new InetSocketAddress(InetAddress.getLocalHost(), 0); return new InetSocketAddress(InetAddress.getLocalHost(), 0);
} }
} }

View File

@ -12,6 +12,7 @@ import io.netty.channel.ChannelOutboundHandlerAdapter;
import io.netty.channel.ChannelPromise; import io.netty.channel.ChannelPromise;
import io.netty.handler.ssl.SslHandler; import io.netty.handler.ssl.SslHandler;
import org.apache.logging.log4j.message.ParameterizedMessage; import org.apache.logging.log4j.message.ParameterizedMessage;
import org.elasticsearch.cluster.node.DiscoveryNode;
import org.elasticsearch.common.io.stream.NamedWriteableRegistry; import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
import org.elasticsearch.common.network.CloseableChannel; import org.elasticsearch.common.network.CloseableChannel;
import org.elasticsearch.common.network.NetworkService; import org.elasticsearch.common.network.NetworkService;
@ -19,6 +20,7 @@ import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.util.BigArrays; import org.elasticsearch.common.util.BigArrays;
import org.elasticsearch.indices.breaker.CircuitBreakerService; import org.elasticsearch.indices.breaker.CircuitBreakerService;
import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.transport.ConnectTransportException;
import org.elasticsearch.transport.TcpChannel; import org.elasticsearch.transport.TcpChannel;
import org.elasticsearch.transport.TcpTransport; import org.elasticsearch.transport.TcpTransport;
import org.elasticsearch.transport.netty4.Netty4Transport; import org.elasticsearch.transport.netty4.Netty4Transport;
@ -27,7 +29,10 @@ import org.elasticsearch.xpack.core.security.transport.SSLExceptionHelper;
import org.elasticsearch.xpack.core.ssl.SSLConfiguration; import org.elasticsearch.xpack.core.ssl.SSLConfiguration;
import org.elasticsearch.xpack.core.ssl.SSLService; import org.elasticsearch.xpack.core.ssl.SSLService;
import javax.net.ssl.SNIHostName;
import javax.net.ssl.SNIServerName;
import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLParameters;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.SocketAddress; import java.net.SocketAddress;
import java.util.Collections; import java.util.Collections;
@ -106,8 +111,8 @@ public class SecurityNetty4Transport extends Netty4Transport {
} }
@Override @Override
protected ChannelHandler getClientChannelInitializer() { protected ChannelHandler getClientChannelInitializer(DiscoveryNode node) {
return new SecurityClientChannelInitializer(); return new SecurityClientChannelInitializer(node);
} }
@Override @Override
@ -167,16 +172,28 @@ public class SecurityNetty4Transport extends Netty4Transport {
private class SecurityClientChannelInitializer extends ClientChannelInitializer { private class SecurityClientChannelInitializer extends ClientChannelInitializer {
private final boolean hostnameVerificationEnabled; private final boolean hostnameVerificationEnabled;
private final SNIHostName serverName;
SecurityClientChannelInitializer() { SecurityClientChannelInitializer(DiscoveryNode node) {
this.hostnameVerificationEnabled = sslEnabled && sslConfiguration.verificationMode().isHostnameVerificationEnabled(); this.hostnameVerificationEnabled = sslEnabled && sslConfiguration.verificationMode().isHostnameVerificationEnabled();
String configuredServerName = node.getAttributes().get("server_name");
if (configuredServerName != null) {
try {
serverName = new SNIHostName(configuredServerName);
} catch (IllegalArgumentException e) {
throw new ConnectTransportException(node, "invalid DiscoveryNode server_name [" + configuredServerName + "]", e);
}
} else {
serverName = null;
}
} }
@Override @Override
protected void initChannel(Channel ch) throws Exception { protected void initChannel(Channel ch) throws Exception {
super.initChannel(ch); super.initChannel(ch);
if (sslEnabled) { if (sslEnabled) {
ch.pipeline().addFirst(new ClientSslHandlerInitializer(sslConfiguration, sslService, hostnameVerificationEnabled)); ch.pipeline().addFirst(new ClientSslHandlerInitializer(sslConfiguration, sslService, hostnameVerificationEnabled,
serverName));
} }
} }
} }
@ -186,11 +203,14 @@ public class SecurityNetty4Transport extends Netty4Transport {
private final boolean hostnameVerificationEnabled; private final boolean hostnameVerificationEnabled;
private final SSLConfiguration sslConfiguration; private final SSLConfiguration sslConfiguration;
private final SSLService sslService; private final SSLService sslService;
private final SNIServerName serverName;
private ClientSslHandlerInitializer(SSLConfiguration sslConfiguration, SSLService sslService, boolean hostnameVerificationEnabled) { private ClientSslHandlerInitializer(SSLConfiguration sslConfiguration, SSLService sslService, boolean hostnameVerificationEnabled,
SNIServerName serverName) {
this.sslConfiguration = sslConfiguration; this.sslConfiguration = sslConfiguration;
this.hostnameVerificationEnabled = hostnameVerificationEnabled; this.hostnameVerificationEnabled = hostnameVerificationEnabled;
this.sslService = sslService; this.sslService = sslService;
this.serverName = serverName;
} }
@Override @Override
@ -207,6 +227,11 @@ public class SecurityNetty4Transport extends Netty4Transport {
} }
sslEngine.setUseClientMode(true); sslEngine.setUseClientMode(true);
if (serverName != null) {
SSLParameters sslParameters = sslEngine.getSSLParameters();
sslParameters.setServerNames(Collections.singletonList(serverName));
sslEngine.setSSLParameters(sslParameters);
}
ctx.pipeline().replace(this, "ssl", new SslHandler(sslEngine)); ctx.pipeline().replace(this, "ssl", new SslHandler(sslEngine));
super.connect(ctx, remoteAddress, localAddress, promise); super.connect(ctx, remoteAddress, localAddress, promise);
} }

View File

@ -0,0 +1,383 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.security.transport.netty4;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.ssl.SslHandler;
import org.elasticsearch.Version;
import org.elasticsearch.cluster.node.DiscoveryNode;
import org.elasticsearch.common.SuppressForbidden;
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
import org.elasticsearch.common.io.stream.OutputStreamStreamOutput;
import org.elasticsearch.common.network.NetworkService;
import org.elasticsearch.common.settings.ClusterSettings;
import org.elasticsearch.common.settings.MockSecureSettings;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.transport.TransportAddress;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.util.BigArrays;
import org.elasticsearch.env.TestEnvironment;
import org.elasticsearch.indices.breaker.NoneCircuitBreakerService;
import org.elasticsearch.node.Node;
import org.elasticsearch.test.transport.MockTransportService;
import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.transport.AbstractSimpleTransportTestCase;
import org.elasticsearch.transport.BindTransportException;
import org.elasticsearch.transport.ConnectTransportException;
import org.elasticsearch.transport.ConnectionProfile;
import org.elasticsearch.transport.TcpChannel;
import org.elasticsearch.transport.TcpTransport;
import org.elasticsearch.transport.Transport;
import org.elasticsearch.transport.TransportRequestOptions;
import org.elasticsearch.transport.TransportService;
import org.elasticsearch.xpack.core.common.socket.SocketAccess;
import org.elasticsearch.xpack.core.security.transport.netty4.SecurityNetty4Transport;
import org.elasticsearch.xpack.core.ssl.SSLConfiguration;
import org.elasticsearch.xpack.core.ssl.SSLService;
import javax.net.SocketFactory;
import javax.net.ssl.HandshakeCompletedListener;
import javax.net.ssl.SNIHostName;
import javax.net.ssl.SNIMatcher;
import javax.net.ssl.SNIServerName;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLParameters;
import javax.net.ssl.SSLSocket;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import java.nio.file.Path;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import static java.util.Collections.emptyMap;
import static java.util.Collections.emptySet;
import static org.elasticsearch.xpack.core.security.SecurityField.setting;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.instanceOf;
public class SimpleSecurityNetty4TransportTests extends AbstractSimpleTransportTestCase {
private static final ConnectionProfile SINGLE_CHANNEL_PROFILE;
static {
ConnectionProfile.Builder builder = new ConnectionProfile.Builder();
builder.addConnections(1,
TransportRequestOptions.Type.BULK,
TransportRequestOptions.Type.PING,
TransportRequestOptions.Type.RECOVERY,
TransportRequestOptions.Type.REG,
TransportRequestOptions.Type.STATE);
SINGLE_CHANNEL_PROFILE = builder.build();
}
private SSLService createSSLService() {
Path testnodeCert = getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testnode.crt");
Path testnodeKey = getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testnode.pem");
MockSecureSettings secureSettings = new MockSecureSettings();
secureSettings.setString("xpack.ssl.secure_key_passphrase", "testnode");
Settings settings = Settings.builder()
.put("xpack.security.transport.ssl.enabled", true)
.put("xpack.ssl.key", testnodeKey)
.put("xpack.ssl.certificate", testnodeCert)
.put("path.home", createTempDir())
.setSecureSettings(secureSettings)
.build();
try {
return new SSLService(settings, TestEnvironment.newEnvironment(settings));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public MockTransportService nettyFromThreadPool(Settings settings, ThreadPool threadPool, final Version version,
ClusterSettings clusterSettings, boolean doHandshake) {
NamedWriteableRegistry namedWriteableRegistry = new NamedWriteableRegistry(Collections.emptyList());
NetworkService networkService = new NetworkService(Collections.emptyList());
Settings settings1 = Settings.builder()
.put(settings)
.put("xpack.security.transport.ssl.enabled", true).build();
Transport transport = new SecurityNetty4Transport(settings1, threadPool,
networkService, BigArrays.NON_RECYCLING_INSTANCE, namedWriteableRegistry,
new NoneCircuitBreakerService(), createSSLService()) {
@Override
protected Version executeHandshake(DiscoveryNode node, TcpChannel channel, TimeValue timeout) throws IOException,
InterruptedException {
if (doHandshake) {
return super.executeHandshake(node, channel, timeout);
} else {
return version.minimumCompatibilityVersion();
}
}
@Override
protected Version getCurrentVersion() {
return version;
}
};
MockTransportService mockTransportService =
MockTransportService.createNewService(Settings.EMPTY, transport, version, threadPool, clusterSettings,
Collections.emptySet());
mockTransportService.start();
return mockTransportService;
}
@Override
protected MockTransportService build(Settings settings, Version version, ClusterSettings clusterSettings, boolean doHandshake) {
settings = Settings.builder().put(settings)
.put(TcpTransport.PORT.getKey(), "0")
.build();
MockTransportService transportService = nettyFromThreadPool(settings, threadPool, version, clusterSettings, doHandshake);
transportService.start();
return transportService;
}
public void testConnectException() throws UnknownHostException {
try {
serviceA.connectToNode(new DiscoveryNode("C", new TransportAddress(InetAddress.getByName("localhost"), 9876),
emptyMap(), emptySet(), Version.CURRENT));
fail("Expected ConnectTransportException");
} catch (ConnectTransportException e) {
assertThat(e.getMessage(), containsString("connect_exception"));
assertThat(e.getMessage(), containsString("[127.0.0.1:9876]"));
Throwable cause = e.getCause();
assertThat(cause, instanceOf(IOException.class));
}
}
public void testBindUnavailableAddress() {
// this is on a lower level since it needs access to the TransportService before it's started
int port = serviceA.boundAddress().publishAddress().getPort();
Settings settings = Settings.builder()
.put(Node.NODE_NAME_SETTING.getKey(), "foobar")
.put(TransportService.TRACE_LOG_INCLUDE_SETTING.getKey(), "")
.put(TransportService.TRACE_LOG_EXCLUDE_SETTING.getKey(), "NOTHING")
.put("transport.tcp.port", port)
.build();
ClusterSettings clusterSettings = new ClusterSettings(settings, ClusterSettings.BUILT_IN_CLUSTER_SETTINGS);
BindTransportException bindTransportException = expectThrows(BindTransportException.class, () -> {
MockTransportService transportService = nettyFromThreadPool(settings, threadPool, Version.CURRENT, clusterSettings, true);
try {
transportService.start();
} finally {
transportService.stop();
transportService.close();
}
});
assertEquals("Failed to bind to [" + port + "]", bindTransportException.getMessage());
}
@SuppressForbidden(reason = "Need to open socket connection")
public void testRenegotiation() throws Exception {
SSLService sslService = createSSLService();
final SSLConfiguration sslConfiguration = sslService.getSSLConfiguration("xpack.ssl");
SocketFactory factory = sslService.sslSocketFactory(sslConfiguration);
try (SSLSocket socket = (SSLSocket) factory.createSocket()) {
SocketAccess.doPrivileged(() -> socket.connect(serviceA.boundAddress().publishAddress().address()));
CountDownLatch handshakeLatch = new CountDownLatch(1);
HandshakeCompletedListener firstListener = event -> handshakeLatch.countDown();
socket.addHandshakeCompletedListener(firstListener);
socket.startHandshake();
handshakeLatch.await();
socket.removeHandshakeCompletedListener(firstListener);
OutputStreamStreamOutput stream = new OutputStreamStreamOutput(socket.getOutputStream());
stream.writeByte((byte) 'E');
stream.writeByte((byte) 'S');
stream.writeInt(-1);
stream.flush();
socket.startHandshake();
CountDownLatch renegotiationLatch = new CountDownLatch(1);
HandshakeCompletedListener secondListener = event -> renegotiationLatch.countDown();
socket.addHandshakeCompletedListener(secondListener);
AtomicReference<Exception> error = new AtomicReference<>();
CountDownLatch catchReadErrorsLatch = new CountDownLatch(1);
Thread renegotiationThread = new Thread(() -> {
try {
socket.setSoTimeout(50);
socket.getInputStream().read();
} catch (SocketTimeoutException e) {
// Ignore. We expect a timeout.
} catch (IOException e) {
error.set(e);
} finally {
catchReadErrorsLatch.countDown();
}
});
renegotiationThread.start();
renegotiationLatch.await();
socket.removeHandshakeCompletedListener(secondListener);
catchReadErrorsLatch.await();
assertNull(error.get());
stream.writeByte((byte) 'E');
stream.writeByte((byte) 'S');
stream.writeInt(-1);
stream.flush();
}
}
// TODO: These tests currently rely on plaintext transports
@Override
@AwaitsFix(bugUrl = "https://github.com/elastic/elasticsearch/issues/33285")
public void testTcpHandshake() {
}
// TODO: These tests as configured do not currently work with the security transport
@Override
@AwaitsFix(bugUrl = "https://github.com/elastic/elasticsearch/issues/33285")
public void testTransportProfilesWithPortAndHost() {
}
public void testSNIServerNameIsPropagated() throws Exception {
SSLService sslService = createSSLService();
final ServerBootstrap serverBootstrap = new ServerBootstrap();
boolean success = false;
try {
serverBootstrap.group(new NioEventLoopGroup(1));
serverBootstrap.channel(NioServerSocketChannel.class);
final String sniIp = "sni-hostname";
final SNIHostName sniHostName = new SNIHostName(sniIp);
final CountDownLatch latch = new CountDownLatch(2);
serverBootstrap.childHandler(new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel ch) {
SSLEngine serverEngine = sslService.createSSLEngine(sslService.getSSLConfiguration(setting("transport.ssl.")),
null, -1);
serverEngine.setUseClientMode(false);
SSLParameters sslParameters = serverEngine.getSSLParameters();
sslParameters.setSNIMatchers(Collections.singletonList(new SNIMatcher(0) {
@Override
public boolean matches(SNIServerName sniServerName) {
if (sniHostName.equals(sniServerName)) {
latch.countDown();
return true;
} else {
return false;
}
}
}));
serverEngine.setSSLParameters(sslParameters);
final SslHandler sslHandler = new SslHandler(serverEngine);
sslHandler.handshakeFuture().addListener(future -> latch.countDown());
ch.pipeline().addFirst("sslhandler", sslHandler);
}
});
serverBootstrap.validate();
ChannelFuture serverFuture = serverBootstrap.bind(getLocalEphemeral());
serverFuture.await();
InetSocketAddress serverAddress = (InetSocketAddress) serverFuture.channel().localAddress();
try (MockTransportService serviceC = build(
Settings.builder()
.put("name", "TS_TEST")
.put(TransportService.TRACE_LOG_INCLUDE_SETTING.getKey(), "")
.put(TransportService.TRACE_LOG_EXCLUDE_SETTING.getKey(), "NOTHING")
.build(),
version0,
null, true)) {
serviceC.acceptIncomingRequests();
HashMap<String, String> attributes = new HashMap<>();
attributes.put("server_name", sniIp);
DiscoveryNode node = new DiscoveryNode("server_node_id", new TransportAddress(serverAddress), attributes,
EnumSet.allOf(DiscoveryNode.Role.class), Version.CURRENT);
new Thread(() -> {
try {
serviceC.connectToNode(node, SINGLE_CHANNEL_PROFILE);
} catch (ConnectTransportException ex) {
// Ignore. The other side is not setup to do the ES handshake. So this will fail.
}
}).start();
latch.await();
serverBootstrap.config().group().shutdownGracefully(0, 5, TimeUnit.SECONDS);
success = true;
}
} finally {
if (success == false) {
serverBootstrap.config().group().shutdownGracefully(0, 5, TimeUnit.SECONDS);
}
}
}
public void testInvalidSNIServerName() throws Exception {
SSLService sslService = createSSLService();
final ServerBootstrap serverBootstrap = new ServerBootstrap();
boolean success = false;
try {
serverBootstrap.group(new NioEventLoopGroup(1));
serverBootstrap.channel(NioServerSocketChannel.class);
final String sniIp = "invalid_hostname";
serverBootstrap.childHandler(new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel ch) {
SSLEngine serverEngine = sslService.createSSLEngine(sslService.getSSLConfiguration(setting("transport.ssl.")),
null, -1);
serverEngine.setUseClientMode(false);
final SslHandler sslHandler = new SslHandler(serverEngine);
ch.pipeline().addFirst("sslhandler", sslHandler);
}
});
serverBootstrap.validate();
ChannelFuture serverFuture = serverBootstrap.bind(getLocalEphemeral());
serverFuture.await();
InetSocketAddress serverAddress = (InetSocketAddress) serverFuture.channel().localAddress();
try (MockTransportService serviceC = build(
Settings.builder()
.put("name", "TS_TEST")
.put(TransportService.TRACE_LOG_INCLUDE_SETTING.getKey(), "")
.put(TransportService.TRACE_LOG_EXCLUDE_SETTING.getKey(), "NOTHING")
.build(),
version0,
null, true)) {
serviceC.acceptIncomingRequests();
HashMap<String, String> attributes = new HashMap<>();
attributes.put("server_name", sniIp);
DiscoveryNode node = new DiscoveryNode("server_node_id", new TransportAddress(serverAddress), attributes,
EnumSet.allOf(DiscoveryNode.Role.class), Version.CURRENT);
ConnectTransportException connectException = expectThrows(ConnectTransportException.class,
() -> serviceC.connectToNode(node, SINGLE_CHANNEL_PROFILE));
assertThat(connectException.getMessage(), containsString("invalid DiscoveryNode server_name [invalid_hostname]"));
serverBootstrap.config().group().shutdownGracefully(0, 5, TimeUnit.SECONDS);
success = true;
}
} finally {
if (success == false) {
serverBootstrap.config().group().shutdownGracefully(0, 5, TimeUnit.SECONDS);
}
}
}
}

View File

@ -208,7 +208,14 @@ public class SimpleSecurityNioTransportTests extends AbstractSimpleTransportTest
// TODO: These tests currently rely on plaintext transports // TODO: These tests currently rely on plaintext transports
@Override @Override
@AwaitsFix(bugUrl = "") @AwaitsFix(bugUrl = "https://github.com/elastic/elasticsearch/issues/33285")
public void testTcpHandshake() throws IOException, InterruptedException { public void testTcpHandshake() throws IOException, InterruptedException {
} }
// TODO: These tests as configured do not currently work with the security transport
@Override
@AwaitsFix(bugUrl = "https://github.com/elastic/elasticsearch/issues/33285")
public void testTransportProfilesWithPortAndHost() {
}
} }