Skip to content

Commit 48fdd65

Browse files
committed
Add sni name to SSLEngine in netty transport (elastic#33144)
This commit is related to elastic#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.
1 parent 7c58371 commit 48fdd65

File tree

6 files changed

+458
-23
lines changed

6 files changed

+458
-23
lines changed

modules/transport-netty4/src/main/java/org/elasticsearch/transport/netty4/Netty4Transport.java

+17-17
Original file line numberDiff line numberDiff line change
@@ -97,12 +97,12 @@ public class Netty4Transport extends TcpTransport {
9797
intSetting("transport.netty.boss_count", 1, 1, Property.NodeScope);
9898

9999

100-
protected final RecvByteBufAllocator recvByteBufAllocator;
101-
protected final int workerCount;
102-
protected final ByteSizeValue receivePredictorMin;
103-
protected final ByteSizeValue receivePredictorMax;
104-
protected volatile Bootstrap bootstrap;
105-
protected final Map<String, ServerBootstrap> serverBootstraps = newConcurrentMap();
100+
private final RecvByteBufAllocator recvByteBufAllocator;
101+
private final int workerCount;
102+
private final ByteSizeValue receivePredictorMin;
103+
private final ByteSizeValue receivePredictorMax;
104+
private volatile Bootstrap clientBootstrap;
105+
private final Map<String, ServerBootstrap> serverBootstraps = newConcurrentMap();
106106

107107
public Netty4Transport(Settings settings, ThreadPool threadPool, NetworkService networkService, BigArrays bigArrays,
108108
NamedWriteableRegistry namedWriteableRegistry, CircuitBreakerService circuitBreakerService) {
@@ -125,7 +125,7 @@ public Netty4Transport(Settings settings, ThreadPool threadPool, NetworkService
125125
protected void doStart() {
126126
boolean success = false;
127127
try {
128-
bootstrap = createBootstrap();
128+
clientBootstrap = createClientBootstrap();
129129
if (NetworkService.NETWORK_SERVER.get(settings)) {
130130
for (ProfileSettings profileSettings : profileSettings) {
131131
createServerBootstrap(profileSettings);
@@ -141,13 +141,11 @@ protected void doStart() {
141141
}
142142
}
143143

144-
private Bootstrap createBootstrap() {
144+
private Bootstrap createClientBootstrap() {
145145
final Bootstrap bootstrap = new Bootstrap();
146146
bootstrap.group(new NioEventLoopGroup(workerCount, daemonThreadFactory(settings, TRANSPORT_CLIENT_BOSS_THREAD_NAME_PREFIX)));
147147
bootstrap.channel(NioSocketChannel.class);
148148

149-
bootstrap.handler(getClientChannelInitializer());
150-
151149
bootstrap.option(ChannelOption.TCP_NODELAY, TCP_NO_DELAY.get(settings));
152150
bootstrap.option(ChannelOption.SO_KEEPALIVE, TCP_KEEP_ALIVE.get(settings));
153151

@@ -166,8 +164,6 @@ private Bootstrap createBootstrap() {
166164
final boolean reuseAddress = TCP_REUSE_ADDRESS.get(settings);
167165
bootstrap.option(ChannelOption.SO_REUSEADDR, reuseAddress);
168166

169-
bootstrap.validate();
170-
171167
return bootstrap;
172168
}
173169

@@ -215,7 +211,7 @@ protected ChannelHandler getServerChannelInitializer(String name) {
215211
return new ServerChannelInitializer(name);
216212
}
217213

218-
protected ChannelHandler getClientChannelInitializer() {
214+
protected ChannelHandler getClientChannelInitializer(DiscoveryNode node) {
219215
return new ClientChannelInitializer();
220216
}
221217

@@ -231,7 +227,11 @@ protected final void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
231227
@Override
232228
protected NettyTcpChannel initiateChannel(DiscoveryNode node, ActionListener<Void> listener) throws IOException {
233229
InetSocketAddress address = node.getAddress().address();
234-
ChannelFuture channelFuture = bootstrap.connect(address);
230+
Bootstrap bootstrapWithHandler = clientBootstrap.clone();
231+
bootstrapWithHandler.handler(getClientChannelInitializer(node));
232+
bootstrapWithHandler.remoteAddress(address);
233+
ChannelFuture channelFuture = bootstrapWithHandler.connect();
234+
235235
Channel channel = channelFuture.channel();
236236
if (channel == null) {
237237
ExceptionsHelper.maybeDieOnAnotherThread(channelFuture.cause());
@@ -294,9 +294,9 @@ protected void stopInternal() {
294294
}
295295
serverBootstraps.clear();
296296

297-
if (bootstrap != null) {
298-
bootstrap.config().group().shutdownGracefully(0, 5, TimeUnit.SECONDS).awaitUninterruptibly();
299-
bootstrap = null;
297+
if (clientBootstrap != null) {
298+
clientBootstrap.config().group().shutdownGracefully(0, 5, TimeUnit.SECONDS).awaitUninterruptibly();
299+
clientBootstrap = null;
300300
}
301301
});
302302
}

server/src/main/java/org/elasticsearch/node/Node.java

+8
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,7 @@
152152
import org.elasticsearch.usage.UsageService;
153153
import org.elasticsearch.watcher.ResourceWatcherService;
154154

155+
import javax.net.ssl.SNIHostName;
155156
import java.io.BufferedWriter;
156157
import java.io.Closeable;
157158
import java.io.IOException;
@@ -211,6 +212,13 @@ public class Node implements Closeable {
211212
throw new IllegalArgumentException(key + " cannot have leading or trailing whitespace " +
212213
"[" + value + "]");
213214
}
215+
if (value.length() > 0 && "node.attr.server_name".equals(key)) {
216+
try {
217+
new SNIHostName(value);
218+
} catch (IllegalArgumentException e) {
219+
throw new IllegalArgumentException("invalid node.attr.server_name [" + value + "]", e );
220+
}
221+
}
214222
return value;
215223
}, Property.NodeScope));
216224
public static final Setting<String> BREAKER_TYPE_KEY = new Setting<>("indices.breaker.type", "hierarchy", (s) -> {

server/src/test/java/org/elasticsearch/node/NodeTests.java

+19
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,25 @@ public void testNodeAttributes() throws IOException {
150150
assertSettingDeprecationsAndWarnings(new Setting<?>[] { NetworkModule.HTTP_ENABLED });
151151
}
152152

153+
public void testServerNameNodeAttribute() throws IOException {
154+
String attr = "valid-hostname";
155+
Settings.Builder settings = baseSettings().put(Node.NODE_ATTRIBUTES.getKey() + "server_name", attr);
156+
int i = 0;
157+
try (Node node = new MockNode(settings.build(), basePlugins())) {
158+
final Settings nodeSettings = randomBoolean() ? node.settings() : node.getEnvironment().settings();
159+
assertEquals(attr, Node.NODE_ATTRIBUTES.getAsMap(nodeSettings).get("server_name"));
160+
}
161+
162+
// non-LDH hostname not allowed
163+
attr = "invalid_hostname";
164+
settings = baseSettings().put(Node.NODE_ATTRIBUTES.getKey() + "server_name", attr);
165+
try (Node node = new MockNode(settings.build(), basePlugins())) {
166+
fail("should not allow a server_name attribute with an underscore");
167+
} catch (IllegalArgumentException e) {
168+
assertEquals("invalid node.attr.server_name [invalid_hostname]", e.getMessage());
169+
}
170+
}
171+
153172
private static Settings.Builder baseSettings() {
154173
final Path tempDir = createTempDir();
155174
return Settings.builder()

test/framework/src/main/java/org/elasticsearch/transport/AbstractSimpleTransportTestCase.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -2689,7 +2689,7 @@ private void closeConnectionChannel(Transport.Connection connection) {
26892689
}
26902690

26912691
@SuppressForbidden(reason = "need local ephemeral port")
2692-
private InetSocketAddress getLocalEphemeral() throws UnknownHostException {
2692+
protected InetSocketAddress getLocalEphemeral() throws UnknownHostException {
26932693
return new InetSocketAddress(InetAddress.getLocalHost(), 0);
26942694
}
26952695
}

x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/transport/netty4/SecurityNetty4Transport.java

+30-5
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,14 @@
1212
import io.netty.channel.ChannelPromise;
1313
import io.netty.handler.ssl.SslHandler;
1414
import org.apache.logging.log4j.message.ParameterizedMessage;
15+
import org.elasticsearch.cluster.node.DiscoveryNode;
1516
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
1617
import org.elasticsearch.common.network.NetworkService;
1718
import org.elasticsearch.common.settings.Settings;
1819
import org.elasticsearch.common.util.BigArrays;
1920
import org.elasticsearch.indices.breaker.CircuitBreakerService;
2021
import org.elasticsearch.threadpool.ThreadPool;
22+
import org.elasticsearch.transport.ConnectTransportException;
2123
import org.elasticsearch.transport.TcpChannel;
2224
import org.elasticsearch.transport.TcpTransport;
2325
import org.elasticsearch.transport.netty4.Netty4Transport;
@@ -26,7 +28,10 @@
2628
import org.elasticsearch.xpack.core.ssl.SSLConfiguration;
2729
import org.elasticsearch.xpack.core.ssl.SSLService;
2830

31+
import javax.net.ssl.SNIHostName;
32+
import javax.net.ssl.SNIServerName;
2933
import javax.net.ssl.SSLEngine;
34+
import javax.net.ssl.SSLParameters;
3035
import java.net.InetSocketAddress;
3136
import java.net.SocketAddress;
3237
import java.util.Collections;
@@ -105,8 +110,8 @@ protected ChannelHandler getNoSslChannelInitializer(final String name) {
105110
}
106111

107112
@Override
108-
protected ChannelHandler getClientChannelInitializer() {
109-
return new SecurityClientChannelInitializer();
113+
protected ChannelHandler getClientChannelInitializer(DiscoveryNode node) {
114+
return new SecurityClientChannelInitializer(node);
110115
}
111116

112117
@Override
@@ -166,16 +171,28 @@ protected ServerChannelInitializer getSslChannelInitializer(final String name, f
166171
private class SecurityClientChannelInitializer extends ClientChannelInitializer {
167172

168173
private final boolean hostnameVerificationEnabled;
174+
private final SNIHostName serverName;
169175

170-
SecurityClientChannelInitializer() {
176+
SecurityClientChannelInitializer(DiscoveryNode node) {
171177
this.hostnameVerificationEnabled = sslEnabled && sslConfiguration.verificationMode().isHostnameVerificationEnabled();
178+
String configuredServerName = node.getAttributes().get("server_name");
179+
if (configuredServerName != null) {
180+
try {
181+
serverName = new SNIHostName(configuredServerName);
182+
} catch (IllegalArgumentException e) {
183+
throw new ConnectTransportException(node, "invalid DiscoveryNode server_name [" + configuredServerName + "]", e);
184+
}
185+
} else {
186+
serverName = null;
187+
}
172188
}
173189

174190
@Override
175191
protected void initChannel(Channel ch) throws Exception {
176192
super.initChannel(ch);
177193
if (sslEnabled) {
178-
ch.pipeline().addFirst(new ClientSslHandlerInitializer(sslConfiguration, sslService, hostnameVerificationEnabled));
194+
ch.pipeline().addFirst(new ClientSslHandlerInitializer(sslConfiguration, sslService, hostnameVerificationEnabled,
195+
serverName));
179196
}
180197
}
181198
}
@@ -185,11 +202,14 @@ private static class ClientSslHandlerInitializer extends ChannelOutboundHandlerA
185202
private final boolean hostnameVerificationEnabled;
186203
private final SSLConfiguration sslConfiguration;
187204
private final SSLService sslService;
205+
private final SNIServerName serverName;
188206

189-
private ClientSslHandlerInitializer(SSLConfiguration sslConfiguration, SSLService sslService, boolean hostnameVerificationEnabled) {
207+
private ClientSslHandlerInitializer(SSLConfiguration sslConfiguration, SSLService sslService, boolean hostnameVerificationEnabled,
208+
SNIServerName serverName) {
190209
this.sslConfiguration = sslConfiguration;
191210
this.hostnameVerificationEnabled = hostnameVerificationEnabled;
192211
this.sslService = sslService;
212+
this.serverName = serverName;
193213
}
194214

195215
@Override
@@ -206,6 +226,11 @@ public void connect(ChannelHandlerContext ctx, SocketAddress remoteAddress,
206226
}
207227

208228
sslEngine.setUseClientMode(true);
229+
if (serverName != null) {
230+
SSLParameters sslParameters = sslEngine.getSSLParameters();
231+
sslParameters.setServerNames(Collections.singletonList(serverName));
232+
sslEngine.setSSLParameters(sslParameters);
233+
}
209234
ctx.pipeline().replace(this, "ssl", new SslHandler(sslEngine));
210235
super.connect(ctx, remoteAddress, localAddress, promise);
211236
}

0 commit comments

Comments
 (0)