|
|
@@ -5,30 +5,39 @@
|
|
|
*/
|
|
|
package org.elasticsearch.xpack.security.transport.nio;
|
|
|
|
|
|
+import org.apache.logging.log4j.message.ParameterizedMessage;
|
|
|
+import org.elasticsearch.common.Nullable;
|
|
|
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
|
|
|
+import org.elasticsearch.common.network.CloseableChannel;
|
|
|
import org.elasticsearch.common.network.NetworkService;
|
|
|
import org.elasticsearch.common.recycler.Recycler;
|
|
|
import org.elasticsearch.common.settings.Settings;
|
|
|
import org.elasticsearch.common.util.BigArrays;
|
|
|
import org.elasticsearch.common.util.PageCacheRecycler;
|
|
|
import org.elasticsearch.indices.breaker.CircuitBreakerService;
|
|
|
+import org.elasticsearch.nio.BytesChannelContext;
|
|
|
import org.elasticsearch.nio.InboundChannelBuffer;
|
|
|
import org.elasticsearch.nio.NioSelector;
|
|
|
import org.elasticsearch.nio.NioSocketChannel;
|
|
|
import org.elasticsearch.nio.ServerChannelContext;
|
|
|
+import org.elasticsearch.nio.SocketChannelContext;
|
|
|
import org.elasticsearch.threadpool.ThreadPool;
|
|
|
+import org.elasticsearch.transport.TcpChannel;
|
|
|
import org.elasticsearch.transport.TcpTransport;
|
|
|
import org.elasticsearch.transport.nio.NioTcpChannel;
|
|
|
import org.elasticsearch.transport.nio.NioTcpServerChannel;
|
|
|
import org.elasticsearch.transport.nio.NioTransport;
|
|
|
import org.elasticsearch.transport.nio.TcpReadWriteHandler;
|
|
|
import org.elasticsearch.xpack.core.XPackSettings;
|
|
|
+import org.elasticsearch.xpack.core.security.transport.SSLExceptionHelper;
|
|
|
import org.elasticsearch.xpack.core.security.transport.netty4.SecurityNetty4Transport;
|
|
|
import org.elasticsearch.xpack.core.ssl.SSLConfiguration;
|
|
|
import org.elasticsearch.xpack.core.ssl.SSLService;
|
|
|
+import org.elasticsearch.xpack.security.transport.filter.IPFilter;
|
|
|
|
|
|
import javax.net.ssl.SSLEngine;
|
|
|
import java.io.IOException;
|
|
|
+import java.net.InetSocketAddress;
|
|
|
import java.nio.ByteBuffer;
|
|
|
import java.nio.channels.ServerSocketChannel;
|
|
|
import java.nio.channels.SocketChannel;
|
|
|
@@ -36,6 +45,7 @@ import java.util.Collections;
|
|
|
import java.util.HashMap;
|
|
|
import java.util.Map;
|
|
|
import java.util.function.Consumer;
|
|
|
+import java.util.function.Predicate;
|
|
|
import java.util.function.Supplier;
|
|
|
|
|
|
import static org.elasticsearch.xpack.core.security.SecurityField.setting;
|
|
|
@@ -45,42 +55,83 @@ import static org.elasticsearch.xpack.core.security.SecurityField.setting;
|
|
|
* protocol that allows two channels to go through a handshake process prior to application data being
|
|
|
* exchanged. The handshake process enables the channels to exchange parameters that will allow them to
|
|
|
* encrypt the application data they exchange.
|
|
|
- *
|
|
|
+ * <p>
|
|
|
* The specific SSL/TLS parameters and configurations are setup in the {@link SSLService} class. The actual
|
|
|
* implementation of the SSL/TLS layer is in the {@link SSLChannelContext} and {@link SSLDriver} classes.
|
|
|
*/
|
|
|
public class SecurityNioTransport extends NioTransport {
|
|
|
|
|
|
- private final SSLConfiguration sslConfiguration;
|
|
|
+ private final IPFilter authenticator;
|
|
|
private final SSLService sslService;
|
|
|
private final Map<String, SSLConfiguration> profileConfiguration;
|
|
|
private final boolean sslEnabled;
|
|
|
|
|
|
- SecurityNioTransport(Settings settings, ThreadPool threadPool, NetworkService networkService, BigArrays bigArrays,
|
|
|
- PageCacheRecycler pageCacheRecycler, NamedWriteableRegistry namedWriteableRegistry,
|
|
|
- CircuitBreakerService circuitBreakerService, SSLService sslService) {
|
|
|
+ public SecurityNioTransport(Settings settings, ThreadPool threadPool, NetworkService networkService, BigArrays bigArrays,
|
|
|
+ PageCacheRecycler pageCacheRecycler, NamedWriteableRegistry namedWriteableRegistry,
|
|
|
+ CircuitBreakerService circuitBreakerService, @Nullable final IPFilter authenticator,
|
|
|
+ SSLService sslService) {
|
|
|
super(settings, threadPool, networkService, bigArrays, pageCacheRecycler, namedWriteableRegistry, circuitBreakerService);
|
|
|
+ this.authenticator = authenticator;
|
|
|
this.sslService = sslService;
|
|
|
this.sslEnabled = XPackSettings.TRANSPORT_SSL_ENABLED.get(settings);
|
|
|
final Settings transportSSLSettings = settings.getByPrefix(setting("transport.ssl."));
|
|
|
if (sslEnabled) {
|
|
|
- this.sslConfiguration = sslService.sslConfiguration(transportSSLSettings, Settings.EMPTY);
|
|
|
Map<String, Settings> profileSettingsMap = settings.getGroups("transport.profiles.", true);
|
|
|
Map<String, SSLConfiguration> profileConfiguration = new HashMap<>(profileSettingsMap.size() + 1);
|
|
|
for (Map.Entry<String, Settings> entry : profileSettingsMap.entrySet()) {
|
|
|
Settings profileSettings = entry.getValue();
|
|
|
final Settings profileSslSettings = SecurityNetty4Transport.profileSslSettings(profileSettings);
|
|
|
- SSLConfiguration configuration = sslService.sslConfiguration(profileSslSettings, transportSSLSettings);
|
|
|
+ SSLConfiguration configuration = sslService.sslConfiguration(profileSslSettings, transportSSLSettings);
|
|
|
profileConfiguration.put(entry.getKey(), configuration);
|
|
|
}
|
|
|
|
|
|
if (profileConfiguration.containsKey(TcpTransport.DEFAULT_PROFILE) == false) {
|
|
|
- profileConfiguration.put(TcpTransport.DEFAULT_PROFILE, sslConfiguration);
|
|
|
+ profileConfiguration.put(TcpTransport.DEFAULT_PROFILE, sslService.sslConfiguration(transportSSLSettings, Settings.EMPTY));
|
|
|
}
|
|
|
|
|
|
this.profileConfiguration = Collections.unmodifiableMap(profileConfiguration);
|
|
|
} else {
|
|
|
- throw new IllegalArgumentException("Currently only support SSL enabled.");
|
|
|
+ profileConfiguration = Collections.emptyMap();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ protected void doStart() {
|
|
|
+ super.doStart();
|
|
|
+ if (authenticator != null) {
|
|
|
+ authenticator.setBoundTransportAddress(boundAddress(), profileBoundAddresses());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void onException(TcpChannel channel, Exception e) {
|
|
|
+ if (!lifecycle.started()) {
|
|
|
+ // just close and ignore - we are already stopped and just need to make sure we release all resources
|
|
|
+ CloseableChannel.closeChannel(channel);
|
|
|
+ } else if (SSLExceptionHelper.isNotSslRecordException(e)) {
|
|
|
+ if (logger.isTraceEnabled()) {
|
|
|
+ logger.trace(
|
|
|
+ new ParameterizedMessage("received plaintext traffic on an encrypted channel, closing connection {}", channel), e);
|
|
|
+ } else {
|
|
|
+ logger.warn("received plaintext traffic on an encrypted channel, closing connection {}", channel);
|
|
|
+ }
|
|
|
+ CloseableChannel.closeChannel(channel);
|
|
|
+ } else if (SSLExceptionHelper.isCloseDuringHandshakeException(e)) {
|
|
|
+ if (logger.isTraceEnabled()) {
|
|
|
+ logger.trace(new ParameterizedMessage("connection {} closed during ssl handshake", channel), e);
|
|
|
+ } else {
|
|
|
+ logger.warn("connection {} closed during handshake", channel);
|
|
|
+ }
|
|
|
+ CloseableChannel.closeChannel(channel);
|
|
|
+ } else if (SSLExceptionHelper.isReceivedCertificateUnknownException(e)) {
|
|
|
+ if (logger.isTraceEnabled()) {
|
|
|
+ logger.trace(new ParameterizedMessage("client did not trust server's certificate, closing connection {}", channel), e);
|
|
|
+ } else {
|
|
|
+ logger.warn("client did not trust this server's certificate, closing connection {}", channel);
|
|
|
+ }
|
|
|
+ CloseableChannel.closeChannel(channel);
|
|
|
+ } else {
|
|
|
+ super.onException(channel, e);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -89,9 +140,13 @@ public class SecurityNioTransport extends NioTransport {
|
|
|
return new SecurityTcpChannelFactory(profileSettings, isClient);
|
|
|
}
|
|
|
|
|
|
- @Override
|
|
|
- protected void acceptChannel(NioSocketChannel channel) {
|
|
|
- super.acceptChannel(channel);
|
|
|
+ private boolean validateChannel(NioSocketChannel channel) {
|
|
|
+ if (authenticator != null) {
|
|
|
+ NioTcpChannel nioTcpChannel = (NioTcpChannel) channel;
|
|
|
+ return authenticator.accept(nioTcpChannel.getProfile(), nioTcpChannel.getRemoteAddress());
|
|
|
+ } else {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
private class SecurityTcpChannelFactory extends TcpChannelFactory {
|
|
|
@@ -101,30 +156,46 @@ public class SecurityNioTransport extends NioTransport {
|
|
|
|
|
|
private SecurityTcpChannelFactory(ProfileSettings profileSettings, boolean isClient) {
|
|
|
super(new RawChannelFactory(profileSettings.tcpNoDelay,
|
|
|
- profileSettings.tcpKeepAlive,
|
|
|
- profileSettings.reuseAddress,
|
|
|
- Math.toIntExact(profileSettings.sendBufferSize.getBytes()),
|
|
|
- Math.toIntExact(profileSettings.receiveBufferSize.getBytes())));
|
|
|
+ profileSettings.tcpKeepAlive,
|
|
|
+ profileSettings.reuseAddress,
|
|
|
+ Math.toIntExact(profileSettings.sendBufferSize.getBytes()),
|
|
|
+ Math.toIntExact(profileSettings.receiveBufferSize.getBytes())));
|
|
|
this.profileName = profileSettings.profileName;
|
|
|
this.isClient = isClient;
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
public NioTcpChannel createChannel(NioSelector selector, SocketChannel channel) throws IOException {
|
|
|
- SSLConfiguration defaultConfig = profileConfiguration.get(TcpTransport.DEFAULT_PROFILE);
|
|
|
- SSLEngine sslEngine = sslService.createSSLEngine(profileConfiguration.getOrDefault(profileName, defaultConfig), null, -1);
|
|
|
- SSLDriver sslDriver = new SSLDriver(sslEngine, isClient);
|
|
|
NioTcpChannel nioChannel = new NioTcpChannel(profileName, channel);
|
|
|
+ SocketChannelContext context;
|
|
|
Supplier<InboundChannelBuffer.Page> pageSupplier = () -> {
|
|
|
Recycler.V<byte[]> bytes = pageCacheRecycler.bytePage(false);
|
|
|
return new InboundChannelBuffer.Page(ByteBuffer.wrap(bytes.v()), bytes::close);
|
|
|
};
|
|
|
-
|
|
|
TcpReadWriteHandler readWriteHandler = new TcpReadWriteHandler(nioChannel, SecurityNioTransport.this);
|
|
|
InboundChannelBuffer buffer = new InboundChannelBuffer(pageSupplier);
|
|
|
Consumer<Exception> exceptionHandler = (e) -> onException(nioChannel, e);
|
|
|
- SSLChannelContext context = new SSLChannelContext(nioChannel, selector, exceptionHandler, sslDriver, readWriteHandler, buffer);
|
|
|
+ Predicate<NioSocketChannel> filter = SecurityNioTransport.this::validateChannel;
|
|
|
+
|
|
|
+ if (sslEnabled) {
|
|
|
+ SSLEngine sslEngine;
|
|
|
+ SSLConfiguration defaultConfig = profileConfiguration.get(TcpTransport.DEFAULT_PROFILE);
|
|
|
+ SSLConfiguration sslConfig = profileConfiguration.getOrDefault(profileName, defaultConfig);
|
|
|
+ boolean hostnameVerificationEnabled = sslConfig.verificationMode().isHostnameVerificationEnabled();
|
|
|
+ if (hostnameVerificationEnabled) {
|
|
|
+ InetSocketAddress inetSocketAddress = (InetSocketAddress) channel.getRemoteAddress();
|
|
|
+ // we create the socket based on the name given. don't reverse DNS
|
|
|
+ sslEngine = sslService.createSSLEngine(sslConfig, inetSocketAddress.getHostString(), inetSocketAddress.getPort());
|
|
|
+ } else {
|
|
|
+ sslEngine = sslService.createSSLEngine(sslConfig, null, -1);
|
|
|
+ }
|
|
|
+ SSLDriver sslDriver = new SSLDriver(sslEngine, isClient);
|
|
|
+ context = new SSLChannelContext(nioChannel, selector, exceptionHandler, sslDriver, readWriteHandler, buffer, filter);
|
|
|
+ } else {
|
|
|
+ context = new BytesChannelContext(nioChannel, selector, exceptionHandler, readWriteHandler, buffer, filter);
|
|
|
+ }
|
|
|
nioChannel.setContext(context);
|
|
|
+
|
|
|
return nioChannel;
|
|
|
}
|
|
|
|