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:
parent
6f9c9ab5e1
commit
88c178dca6
|
@ -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;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) -> {
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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() {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue