Skip to content

Commit c9b4c45

Browse files
authored
Add sni name to SSLEngine in netty transport (#33144) (#33513)
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.
1 parent 3414c3a commit c9b4c45

File tree

6 files changed

+466
-37
lines changed

6 files changed

+466
-37
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
@@ -153,6 +153,7 @@
153153
import org.elasticsearch.usage.UsageService;
154154
import org.elasticsearch.watcher.ResourceWatcherService;
155155

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

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

+21
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,27 @@ 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(), Collections.singleton(getTestTransportPlugin()))) {
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(), Collections.singleton(getTestTransportPlugin()))) {
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+
assertSettingDeprecationsAndWarnings(new Setting<?>[] { NetworkModule.HTTP_ENABLED });
172+
}
173+
153174
private static Settings.Builder baseSettings() {
154175
final Path tempDir = createTempDir();
155176
return Settings.builder()

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

+7-15
Original file line numberDiff line numberDiff line change
@@ -1937,16 +1937,12 @@ public void testTimeoutPerConnection() throws IOException {
19371937

19381938
public void testHandshakeWithIncompatVersion() {
19391939
assumeTrue("only tcp transport has a handshake method", serviceA.getOriginalTransport() instanceof TcpTransport);
1940-
NamedWriteableRegistry namedWriteableRegistry = new NamedWriteableRegistry(Collections.emptyList());
19411940
Version version = Version.fromString("2.0.0");
1942-
try (MockTcpTransport transport = new MockTcpTransport(Settings.EMPTY, threadPool, BigArrays.NON_RECYCLING_INSTANCE,
1943-
new NoneCircuitBreakerService(), namedWriteableRegistry, new NetworkService(Collections.emptyList()), version);
1944-
MockTransportService service = MockTransportService.createNewService(Settings.EMPTY, transport, version, threadPool, null,
1945-
Collections.emptySet())) {
1941+
try (MockTransportService service = build(Settings.EMPTY, version, null, true)) {
19461942
service.start();
19471943
service.acceptIncomingRequests();
1948-
DiscoveryNode node =
1949-
new DiscoveryNode("TS_TPC", "TS_TPC", transport.boundAddress().publishAddress(), emptyMap(), emptySet(), version0);
1944+
TransportAddress address = service.boundAddress().publishAddress();
1945+
DiscoveryNode node = new DiscoveryNode("TS_TPC", "TS_TPC", address, emptyMap(), emptySet(), version0);
19501946
ConnectionProfile.Builder builder = new ConnectionProfile.Builder();
19511947
builder.addConnections(1,
19521948
TransportRequestOptions.Type.BULK,
@@ -1962,15 +1958,11 @@ public void testHandshakeUpdatesVersion() throws IOException {
19621958
assumeTrue("only tcp transport has a handshake method", serviceA.getOriginalTransport() instanceof TcpTransport);
19631959
NamedWriteableRegistry namedWriteableRegistry = new NamedWriteableRegistry(Collections.emptyList());
19641960
Version version = VersionUtils.randomVersionBetween(random(), Version.CURRENT.minimumCompatibilityVersion(), Version.CURRENT);
1965-
try (MockTcpTransport transport = new MockTcpTransport(Settings.EMPTY, threadPool, BigArrays.NON_RECYCLING_INSTANCE,
1966-
new NoneCircuitBreakerService(), namedWriteableRegistry, new NetworkService(Collections.emptyList()), version);
1967-
MockTransportService service = MockTransportService.createNewService(Settings.EMPTY, transport, version, threadPool, null,
1968-
Collections.emptySet())) {
1961+
try (MockTransportService service = build(Settings.EMPTY, version, null, true)) {
19691962
service.start();
19701963
service.acceptIncomingRequests();
1971-
DiscoveryNode node =
1972-
new DiscoveryNode("TS_TPC", "TS_TPC", transport.boundAddress().publishAddress(), emptyMap(), emptySet(),
1973-
Version.fromString("2.0.0"));
1964+
TransportAddress address = service.boundAddress().publishAddress();
1965+
DiscoveryNode node = new DiscoveryNode("TS_TPC", "TS_TPC", address, emptyMap(), emptySet(), Version.fromString("2.0.0"));
19741966
ConnectionProfile.Builder builder = new ConnectionProfile.Builder();
19751967
builder.addConnections(1,
19761968
TransportRequestOptions.Type.BULK,
@@ -2689,7 +2681,7 @@ private void closeConnectionChannel(Transport.Connection connection) {
26892681
}
26902682

26912683
@SuppressForbidden(reason = "need local ephemeral port")
2692-
private InetSocketAddress getLocalEphemeral() throws UnknownHostException {
2684+
protected InetSocketAddress getLocalEphemeral() throws UnknownHostException {
26932685
return new InetSocketAddress(InetAddress.getLocalHost(), 0);
26942686
}
26952687
}

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)