diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/transport/netty3/SecurityNetty3Transport.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/transport/netty3/SecurityNetty3Transport.java index 1d67786e9c4..b2b90737bfa 100644 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/transport/netty3/SecurityNetty3Transport.java +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/transport/netty3/SecurityNetty3Transport.java @@ -47,31 +47,50 @@ public class SecurityNetty3Transport extends Netty3Transport { public static final boolean SSL_DEFAULT = false; public static final Setting DEPRECATED_HOSTNAME_VERIFICATION_SETTING = - Setting.boolSetting(setting("ssl.hostname_verification"), true, Property.NodeScope, Property.Filtered, Property.Deprecated); + Setting.boolSetting( + setting("ssl.hostname_verification"), + true, + new Property[]{Property.NodeScope, Property.Filtered, Property.Deprecated, Property.Shared}); + public static final Setting HOSTNAME_VERIFICATION_SETTING = Setting.boolSetting(featureEnabledSetting("ssl.hostname_verification"), DEPRECATED_HOSTNAME_VERIFICATION_SETTING, - Property.NodeScope, Property.Filtered); + Property.NodeScope, Property.Filtered, Property.Shared); + public static final Setting HOSTNAME_VERIFICATION_RESOLVE_NAME_SETTING = - Setting.boolSetting(setting("ssl.hostname_verification.resolve_name"), true, Property.NodeScope, Property.Filtered); + Setting.boolSetting( + setting("ssl.hostname_verification.resolve_name"), + true, + new Property[]{Property.NodeScope, Property.Filtered, Property.Shared}); public static final Setting DEPRECATED_SSL_SETTING = Setting.boolSetting(setting("transport.ssl"), SSL_DEFAULT, - Property.Filtered, Property.NodeScope, Property.Deprecated); + Property.Filtered, Property.NodeScope, Property.Deprecated, Property.Shared); + public static final Setting SSL_SETTING = - Setting.boolSetting(setting("transport.ssl.enabled"), DEPRECATED_SSL_SETTING, Property.Filtered, Property.NodeScope); + Setting.boolSetting( + setting("transport.ssl.enabled"), + DEPRECATED_SSL_SETTING, + new Property[]{Property.Filtered, Property.NodeScope, Property.Shared}); public static final Setting CLIENT_AUTH_SETTING = - new Setting<>(setting("transport.ssl.client.auth"), CLIENT_AUTH_DEFAULT, - SSLClientAuth::parse, Property.NodeScope, Property.Filtered); + new Setting<>( + setting("transport.ssl.client.auth"), + CLIENT_AUTH_DEFAULT, + SSLClientAuth::parse, + new Property[]{Property.NodeScope, Property.Filtered, Property.Shared}); public static final Setting DEPRECATED_PROFILE_SSL_SETTING = - Setting.boolSetting(setting("ssl"), SSL_SETTING, Property.Filtered, Property.NodeScope, Property.Deprecated); + Setting.boolSetting(setting("ssl"), SSL_SETTING, Property.Filtered, Property.NodeScope, Property.Deprecated, Property.Shared); + public static final Setting PROFILE_SSL_SETTING = - Setting.boolSetting(setting("ssl.enabled"), SSL_DEFAULT, Property.Filtered, Property.NodeScope); + Setting.boolSetting(setting("ssl.enabled"), SSL_DEFAULT, Property.Filtered, Property.NodeScope, Property.Shared); public static final Setting PROFILE_CLIENT_AUTH_SETTING = - new Setting<>(setting("ssl.client.auth"), CLIENT_AUTH_SETTING, SSLClientAuth::parse, - Property.NodeScope, Property.Filtered); + new Setting<>( + setting("ssl.client.auth"), + CLIENT_AUTH_SETTING, + SSLClientAuth::parse, + new Property[]{Property.NodeScope, Property.Filtered, Property.Shared}); private final ServerSSLService serverSslService; private final ClientSSLService clientSSLService; diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/transport/netty4/SecurityNetty4HttpServerTransport.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/transport/netty4/SecurityNetty4HttpServerTransport.java index 897ca19b8d4..9a9299c3f75 100644 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/transport/netty4/SecurityNetty4HttpServerTransport.java +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/transport/netty4/SecurityNetty4HttpServerTransport.java @@ -5,6 +5,7 @@ */ package org.elasticsearch.xpack.security.transport.netty4; +import io.netty.channel.Channel; import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.socket.SocketChannel; @@ -107,7 +108,7 @@ public class SecurityNetty4HttpServerTransport extends Netty4HttpServerTransport } @Override - protected void initChannel(SocketChannel ch) throws Exception { + protected void initChannel(Channel ch) throws Exception { super.initChannel(ch); if (ssl) { final SSLEngine engine = sslService.createSSLEngine(sslSettings); diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/transport/netty4/SecurityNetty4Transport.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/transport/netty4/SecurityNetty4Transport.java index 38d5f58af42..4a235900c6e 100644 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/transport/netty4/SecurityNetty4Transport.java +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/transport/netty4/SecurityNetty4Transport.java @@ -6,6 +6,7 @@ package org.elasticsearch.xpack.security.transport.netty4; import io.netty.channel.Channel; +import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOutboundHandlerAdapter; @@ -18,6 +19,7 @@ import org.elasticsearch.common.inject.Inject; import org.elasticsearch.common.inject.internal.Nullable; import org.elasticsearch.common.io.stream.NamedWriteableRegistry; import org.elasticsearch.common.network.NetworkService; +import org.elasticsearch.common.settings.Setting; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.util.BigArrays; import org.elasticsearch.indices.breaker.CircuitBreakerService; @@ -35,20 +37,67 @@ import java.net.InetSocketAddress; import java.net.SocketAddress; import java.util.concurrent.TimeUnit; +import static org.elasticsearch.xpack.security.Security.featureEnabledSetting; +import static org.elasticsearch.xpack.security.Security.setting; import static org.elasticsearch.xpack.security.Security.settingPrefix; -import static org.elasticsearch.xpack.security.transport.netty3.SecurityNetty3Transport.CLIENT_AUTH_SETTING; -import static org.elasticsearch.xpack.security.transport.netty3.SecurityNetty3Transport.DEPRECATED_PROFILE_SSL_SETTING; -import static org.elasticsearch.xpack.security.transport.netty3.SecurityNetty3Transport.HOSTNAME_VERIFICATION_RESOLVE_NAME_SETTING; -import static org.elasticsearch.xpack.security.transport.netty3.SecurityNetty3Transport.HOSTNAME_VERIFICATION_SETTING; -import static org.elasticsearch.xpack.security.transport.netty3.SecurityNetty3Transport.PROFILE_CLIENT_AUTH_SETTING; -import static org.elasticsearch.xpack.security.transport.netty3.SecurityNetty3Transport.PROFILE_SSL_SETTING; -import static org.elasticsearch.xpack.security.transport.netty3.SecurityNetty3Transport.SSL_SETTING; + +import org.elasticsearch.common.settings.Setting.Property; + /** * Implementation of a transport that extends the {@link Netty4Transport} to add SSL and IP Filtering */ public class SecurityNetty4Transport extends Netty4Transport { + public static final String CLIENT_AUTH_DEFAULT = SSLClientAuth.REQUIRED.name(); + public static final boolean SSL_DEFAULT = false; + + public static final Setting DEPRECATED_HOSTNAME_VERIFICATION_SETTING = + Setting.boolSetting( + setting("ssl.hostname_verification"), + true, + new Property[]{Property.NodeScope, Property.Filtered, Property.Deprecated, Property.Shared}); + + public static final Setting HOSTNAME_VERIFICATION_SETTING = + Setting.boolSetting(featureEnabledSetting("ssl.hostname_verification"), DEPRECATED_HOSTNAME_VERIFICATION_SETTING, + Property.NodeScope, Property.Filtered, Property.Shared); + + public static final Setting HOSTNAME_VERIFICATION_RESOLVE_NAME_SETTING = + Setting.boolSetting( + setting("ssl.hostname_verification.resolve_name"), + true, + new Property[]{Property.NodeScope, Property.Filtered, Property.Shared}); + + public static final Setting DEPRECATED_SSL_SETTING = + Setting.boolSetting(setting("transport.ssl"), SSL_DEFAULT, + Property.Filtered, Property.NodeScope, Property.Deprecated, Property.Shared); + + public static final Setting SSL_SETTING = + Setting.boolSetting( + setting("transport.ssl.enabled"), + DEPRECATED_SSL_SETTING, + new Property[]{Property.Filtered, Property.NodeScope, Property.Shared}); + + public static final Setting CLIENT_AUTH_SETTING = + new Setting<>( + setting("transport.ssl.client.auth"), + CLIENT_AUTH_DEFAULT, + SSLClientAuth::parse, + new Property[]{Property.NodeScope, Property.Filtered, Property.Shared}); + + public static final Setting DEPRECATED_PROFILE_SSL_SETTING = + Setting.boolSetting(setting("ssl"), SSL_SETTING, Property.Filtered, Property.NodeScope, Property.Deprecated, Property.Shared); + + public static final Setting PROFILE_SSL_SETTING = + Setting.boolSetting(setting("ssl.enabled"), SSL_DEFAULT, Property.Filtered, Property.NodeScope, Property.Shared); + + public static final Setting PROFILE_CLIENT_AUTH_SETTING = + new Setting<>( + setting("ssl.client.auth"), + CLIENT_AUTH_SETTING, + SSLClientAuth::parse, + new Property[]{Property.NodeScope, Property.Filtered, Property.Shared}); + private final ServerSSLService serverSslService; private final ClientSSLService clientSSLService; @Nullable private final IPFilter authenticator; @@ -77,12 +126,12 @@ public class SecurityNetty4Transport extends Netty4Transport { } @Override - protected ChannelInitializer getServerChannelInitializer(String name, Settings settings) { + protected ChannelHandler getServerChannelInitializer(String name, Settings settings) { return new SecurityServerChannelInitializer(name, settings); } @Override - protected ChannelInitializer getClientChannelInitializer() { + protected ChannelHandler getClientChannelInitializer() { return new SecurityClientChannelInitializer(); } @@ -113,7 +162,7 @@ public class SecurityNetty4Transport extends Netty4Transport { } @Override - protected void initChannel(SocketChannel ch) throws Exception { + protected void initChannel(Channel ch) throws Exception { super.initChannel(ch); if (sslEnabled) { Settings securityProfileSettings = settings.getByPrefix(settingPrefix()); @@ -131,7 +180,7 @@ public class SecurityNetty4Transport extends Netty4Transport { class SecurityClientChannelInitializer extends ClientChannelInitializer { @Override - protected void initChannel(SocketChannel ch) throws Exception { + protected void initChannel(Channel ch) throws Exception { super.initChannel(ch); if (ssl) { ch.pipeline().addFirst(new ClientSslHandlerInitializer()); diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/http/netty4/Netty4HttpMockUtil.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/http/netty4/Netty4HttpMockUtil.java new file mode 100644 index 00000000000..87e3e78cbc4 --- /dev/null +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/http/netty4/Netty4HttpMockUtil.java @@ -0,0 +1,23 @@ +/* + * 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.http.netty4; + +import org.elasticsearch.transport.netty4.Netty4OpenChannelsHandler; + +import static org.mockito.Mockito.mock; + +/** Allows setting a mock into Netty3HttpServerTransport */ +public class Netty4HttpMockUtil { + + /** + * We don't really need to start Netty for these tests, but we can't create a pipeline + * with a null handler. So we set it to a mock for tests. + */ + public static void setOpenChannelsHandlerToMock(Netty4HttpServerTransport transport) throws Exception { + transport.serverOpenChannels = mock(Netty4OpenChannelsHandler.class); + } + +} \ No newline at end of file diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/transport/netty4/Netty4MockUtil.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/transport/netty4/Netty4MockUtil.java new file mode 100644 index 00000000000..216ac5743a9 --- /dev/null +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/transport/netty4/Netty4MockUtil.java @@ -0,0 +1,24 @@ +/* + * 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.transport.netty4; + +import org.elasticsearch.transport.netty3.Netty3OpenChannelsHandler; +import org.elasticsearch.transport.netty3.Netty3Transport; + +import static org.mockito.Mockito.mock; + +/** Allows setting a mock into Netty3Transport */ +public class Netty4MockUtil { + + /** + * We don't really need to start Netty for these tests, but we can't create a pipeline + * with a null handler. So we set it to a mock for tests. + */ + public static void setOpenChannelsHandlerToMock(Netty4Transport transport) throws Exception { + transport.serverOpenChannels = mock(Netty4OpenChannelsHandler.class); + } + +} diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/transport/netty4/IpFilterRemoteAddressFilterTests.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/transport/netty4/IpFilterRemoteAddressFilterTests.java new file mode 100644 index 00000000000..0bb289deab4 --- /dev/null +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/transport/netty4/IpFilterRemoteAddressFilterTests.java @@ -0,0 +1,314 @@ +/* + * 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.buffer.ByteBufAllocator; +import io.netty.channel.Channel; +import io.netty.channel.ChannelConfig; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelId; +import io.netty.channel.ChannelMetadata; +import io.netty.channel.ChannelPipeline; +import io.netty.channel.ChannelProgressivePromise; +import io.netty.channel.ChannelPromise; +import io.netty.channel.EventLoop; +import io.netty.util.Attribute; +import io.netty.util.AttributeKey; +import io.netty.util.concurrent.EventExecutor; +import org.elasticsearch.common.component.Lifecycle; +import org.elasticsearch.common.network.InetAddresses; +import org.elasticsearch.common.settings.ClusterSettings; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.transport.BoundTransportAddress; +import org.elasticsearch.common.transport.InetSocketTransportAddress; +import org.elasticsearch.common.transport.TransportAddress; +import org.elasticsearch.http.HttpServerTransport; +import org.elasticsearch.license.XPackLicenseState; +import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.transport.Transport; +import org.elasticsearch.transport.TransportSettings; +import org.elasticsearch.xpack.security.audit.AuditTrailService; +import org.elasticsearch.xpack.security.transport.filter.IPFilter; +import org.elasticsearch.xpack.security.transport.netty3.IPFilterNetty3UpstreamHandler; +import org.jboss.netty.channel.ChannelEvent; +import org.jboss.netty.channel.UpstreamMessageEvent; +import org.junit.Before; + +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; + +import static org.hamcrest.Matchers.is; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class IpFilterRemoteAddressFilterTests extends ESTestCase { + private IpFilterRemoteAddressFilter handler; + + @Before + public void init() throws Exception { + Settings settings = Settings.builder() + .put("xpack.security.transport.filter.allow", "127.0.0.1") + .put("xpack.security.transport.filter.deny", "10.0.0.0/8") + .build(); + + boolean isHttpEnabled = randomBoolean(); + + Transport transport = mock(Transport.class); + InetSocketTransportAddress address = new InetSocketTransportAddress(InetAddress.getLoopbackAddress(), 9300); + when(transport.boundAddress()).thenReturn(new BoundTransportAddress(new TransportAddress[] { address }, address)); + when(transport.lifecycleState()).thenReturn(Lifecycle.State.STARTED); + ClusterSettings clusterSettings = new ClusterSettings(Settings.EMPTY, new HashSet<>(Arrays.asList( + IPFilter.HTTP_FILTER_ALLOW_SETTING, + IPFilter.HTTP_FILTER_DENY_SETTING, + IPFilter.IP_FILTER_ENABLED_HTTP_SETTING, + IPFilter.IP_FILTER_ENABLED_SETTING, + IPFilter.TRANSPORT_FILTER_ALLOW_SETTING, + IPFilter.TRANSPORT_FILTER_DENY_SETTING, + TransportSettings.TRANSPORT_PROFILES_SETTING))); + XPackLicenseState licenseState = mock(XPackLicenseState.class); + when(licenseState.isIpFilteringAllowed()).thenReturn(true); + AuditTrailService auditTrailService = new AuditTrailService(settings, Collections.emptyList(), licenseState); + IPFilter ipFilter = new IPFilter(settings, auditTrailService, clusterSettings, licenseState); + ipFilter.setBoundTransportAddress(transport.boundAddress(), transport.profileBoundAddresses()); + if (isHttpEnabled) { + HttpServerTransport httpTransport = mock(HttpServerTransport.class); + InetSocketTransportAddress httpAddress = new InetSocketTransportAddress(InetAddress.getLoopbackAddress(), 9200); + when(httpTransport.boundAddress()).thenReturn(new BoundTransportAddress(new TransportAddress[] { httpAddress }, httpAddress)); + when(httpTransport.lifecycleState()).thenReturn(Lifecycle.State.STARTED); + ipFilter.setBoundHttpTransportAddress(httpTransport.boundAddress()); + } + + if (isHttpEnabled) { + handler = new IpFilterRemoteAddressFilter(ipFilter, IPFilter.HTTP_PROFILE_NAME); + } else { + handler = new IpFilterRemoteAddressFilter(ipFilter, "default"); + } + } + + public void testThatFilteringWorksByIp() throws Exception { + InetSocketAddress localhostAddr = new InetSocketAddress(InetAddresses.forString("127.0.0.1"), 12345); + assertThat(handler.accept(new NullChannelHandlerContext(), localhostAddr), is(true)); + + InetSocketAddress remoteAddr = new InetSocketAddress(InetAddresses.forString("10.0.0.8"), 12345); + assertThat(handler.accept(new NullChannelHandlerContext(), remoteAddr), is(false)); + } + + private static class NullChannelHandlerContext implements ChannelHandlerContext { + + @Override + public Channel channel() { + return null; + } + + @Override + public EventExecutor executor() { + return null; + } + + @Override + public String name() { + return null; + } + + @Override + public ChannelHandler handler() { + return null; + } + + @Override + public boolean isRemoved() { + return false; + } + + @Override + public ChannelHandlerContext fireChannelRegistered() { + return null; + } + + @Override + public ChannelHandlerContext fireChannelUnregistered() { + return null; + } + + @Override + public ChannelHandlerContext fireChannelActive() { + return null; + } + + @Override + public ChannelHandlerContext fireChannelInactive() { + return null; + } + + @Override + public ChannelHandlerContext fireExceptionCaught(Throwable cause) { + return null; + } + + @Override + public ChannelHandlerContext fireUserEventTriggered(Object evt) { + return null; + } + + @Override + public ChannelHandlerContext fireChannelRead(Object msg) { + return null; + } + + @Override + public ChannelHandlerContext fireChannelReadComplete() { + return null; + } + + @Override + public ChannelHandlerContext fireChannelWritabilityChanged() { + return null; + } + + @Override + public ChannelHandlerContext read() { + return null; + } + + @Override + public ChannelHandlerContext flush() { + return null; + } + + @Override + public ChannelPipeline pipeline() { + return null; + } + + @Override + public ByteBufAllocator alloc() { + return null; + } + + @Override + public Attribute attr(AttributeKey key) { + return null; + } + + @Override + public boolean hasAttr(AttributeKey key) { + return false; + } + + @Override + public ChannelFuture bind(SocketAddress localAddress) { + return null; + } + + @Override + public ChannelFuture connect(SocketAddress remoteAddress) { + return null; + } + + @Override + public ChannelFuture connect(SocketAddress remoteAddress, SocketAddress localAddress) { + return null; + } + + @Override + public ChannelFuture disconnect() { + return null; + } + + @Override + public ChannelFuture close() { + return null; + } + + @Override + public ChannelFuture deregister() { + return null; + } + + @Override + public ChannelFuture bind(SocketAddress localAddress, ChannelPromise promise) { + return null; + } + + @Override + public ChannelFuture connect(SocketAddress remoteAddress, ChannelPromise promise) { + return null; + } + + @Override + public ChannelFuture connect(SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise) { + return null; + } + + @Override + public ChannelFuture disconnect(ChannelPromise promise) { + return null; + } + + @Override + public ChannelFuture close(ChannelPromise promise) { + return null; + } + + @Override + public ChannelFuture deregister(ChannelPromise promise) { + return null; + } + + @Override + public ChannelFuture write(Object msg) { + return null; + } + + @Override + public ChannelFuture write(Object msg, ChannelPromise promise) { + return null; + } + + @Override + public ChannelFuture writeAndFlush(Object msg, ChannelPromise promise) { + return null; + } + + @Override + public ChannelFuture writeAndFlush(Object msg) { + return null; + } + + @Override + public ChannelPromise newPromise() { + return null; + } + + @Override + public ChannelProgressivePromise newProgressivePromise() { + return null; + } + + @Override + public ChannelFuture newSucceededFuture() { + return null; + } + + @Override + public ChannelFuture newFailedFuture(Throwable cause) { + return null; + } + + @Override + public ChannelPromise voidPromise() { + return null; + } + + } + +} diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/transport/netty4/SecurityNetty4HttpServerTransportTests.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/transport/netty4/SecurityNetty4HttpServerTransportTests.java new file mode 100644 index 00000000000..0fffdf7acaa --- /dev/null +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/transport/netty4/SecurityNetty4HttpServerTransportTests.java @@ -0,0 +1,155 @@ +/* + * 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.channel.ChannelHandler; +import io.netty.channel.embedded.EmbeddedChannel; +import io.netty.handler.ssl.SslHandler; +import org.elasticsearch.common.network.NetworkService; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.util.BigArrays; +import org.elasticsearch.env.Environment; +import org.elasticsearch.http.HttpTransportSettings; +import org.elasticsearch.http.netty4.Netty4HttpMockUtil; +import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.threadpool.ThreadPool; +import org.elasticsearch.xpack.security.ssl.SSLConfiguration.Global; +import org.elasticsearch.xpack.security.ssl.ServerSSLService; +import org.elasticsearch.xpack.security.transport.SSLClientAuth; +import org.elasticsearch.xpack.security.transport.filter.IPFilter; +import org.junit.Before; + +import javax.net.ssl.SSLEngine; + +import java.nio.file.Path; +import java.util.Locale; + +import static org.hamcrest.Matchers.arrayContaining; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; +import static org.mockito.Mockito.mock; + +public class SecurityNetty4HttpServerTransportTests extends ESTestCase { + + private ServerSSLService serverSSLService; + + @Before + public void createSSLService() throws Exception { + Path testNodeStore = getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testnode.jks"); + Settings settings = Settings.builder() + .put("xpack.security.ssl.keystore.path", testNodeStore) + .put("xpack.security.ssl.keystore.password", "testnode") + .build(); + Environment env = new Environment(Settings.builder().put("path.home", createTempDir()).build()); + serverSSLService = new ServerSSLService(settings, env, new Global(settings)); + } + + public void testDefaultClientAuth() throws Exception { + Settings settings = Settings.builder().put(SecurityNetty4HttpServerTransport.SSL_SETTING.getKey(), true).build(); + SecurityNetty4HttpServerTransport transport = new SecurityNetty4HttpServerTransport(settings, mock(NetworkService.class), + mock(BigArrays.class), mock(IPFilter.class), serverSSLService, mock(ThreadPool.class)); + Netty4HttpMockUtil.setOpenChannelsHandlerToMock(transport); + ChannelHandler handler = transport.configureServerChannelHandler(); + final EmbeddedChannel ch = new EmbeddedChannel(handler); + assertThat(ch.pipeline().get(SslHandler.class).engine().getNeedClientAuth(), is(false)); + assertThat(ch.pipeline().get(SslHandler.class).engine().getWantClientAuth(), is(false)); + } + + public void testOptionalClientAuth() throws Exception { + String value = randomFrom(SSLClientAuth.OPTIONAL.name(), SSLClientAuth.OPTIONAL.name().toLowerCase(Locale.ROOT)); + Settings settings = Settings.builder() + .put(SecurityNetty4HttpServerTransport.SSL_SETTING.getKey(), true) + .put(SecurityNetty4HttpServerTransport.CLIENT_AUTH_SETTING.getKey(), value).build(); + SecurityNetty4HttpServerTransport transport = new SecurityNetty4HttpServerTransport(settings, mock(NetworkService.class), + mock(BigArrays.class), mock(IPFilter.class), serverSSLService, mock(ThreadPool.class)); + Netty4HttpMockUtil.setOpenChannelsHandlerToMock(transport); + ChannelHandler handler = transport.configureServerChannelHandler(); + final EmbeddedChannel ch = new EmbeddedChannel(handler); + assertThat(ch.pipeline().get(SslHandler.class).engine().getNeedClientAuth(), is(false)); + assertThat(ch.pipeline().get(SslHandler.class).engine().getWantClientAuth(), is(true)); + } + + public void testRequiredClientAuth() throws Exception { + String value = randomFrom(SSLClientAuth.REQUIRED.name(), SSLClientAuth.REQUIRED.name().toLowerCase(Locale.ROOT), "true", "TRUE"); + Settings settings = Settings.builder() + .put(SecurityNetty4HttpServerTransport.SSL_SETTING.getKey(), true) + .put(SecurityNetty4HttpServerTransport.CLIENT_AUTH_SETTING.getKey(), value).build(); + SecurityNetty4HttpServerTransport transport = new SecurityNetty4HttpServerTransport(settings, mock(NetworkService.class), + mock(BigArrays.class), mock(IPFilter.class), serverSSLService, mock(ThreadPool.class)); + Netty4HttpMockUtil.setOpenChannelsHandlerToMock(transport); + ChannelHandler handler = transport.configureServerChannelHandler(); + final EmbeddedChannel ch = new EmbeddedChannel(handler); + assertThat(ch.pipeline().get(SslHandler.class).engine().getNeedClientAuth(), is(true)); + assertThat(ch.pipeline().get(SslHandler.class).engine().getWantClientAuth(), is(false)); + } + + public void testNoClientAuth() throws Exception { + String value = randomFrom(SSLClientAuth.NO.name(), SSLClientAuth.NO.name().toLowerCase(Locale.ROOT), "false", "FALSE"); + Settings settings = Settings.builder() + .put(SecurityNetty4HttpServerTransport.SSL_SETTING.getKey(), true) + .put(SecurityNetty4HttpServerTransport.CLIENT_AUTH_SETTING.getKey(), value).build(); + SecurityNetty4HttpServerTransport transport = new SecurityNetty4HttpServerTransport(settings, mock(NetworkService.class), + mock(BigArrays.class), mock(IPFilter.class), serverSSLService, mock(ThreadPool.class)); + Netty4HttpMockUtil.setOpenChannelsHandlerToMock(transport); + ChannelHandler handler = transport.configureServerChannelHandler(); + final EmbeddedChannel ch = new EmbeddedChannel(handler); + assertThat(ch.pipeline().get(SslHandler.class).engine().getNeedClientAuth(), is(false)); + assertThat(ch.pipeline().get(SslHandler.class).engine().getWantClientAuth(), is(false)); + } + + public void testCustomSSLConfiguration() throws Exception { + Settings settings = Settings.builder() + .put(SecurityNetty4HttpServerTransport.SSL_SETTING.getKey(), true).build(); + SecurityNetty4HttpServerTransport transport = new SecurityNetty4HttpServerTransport(settings, mock(NetworkService.class), + mock(BigArrays.class), mock(IPFilter.class), serverSSLService, mock(ThreadPool.class)); + Netty4HttpMockUtil.setOpenChannelsHandlerToMock(transport); + ChannelHandler handler = transport.configureServerChannelHandler(); + EmbeddedChannel ch = new EmbeddedChannel(handler); + SSLEngine defaultEngine = ch.pipeline().get(SslHandler.class).engine(); + + settings = Settings.builder() + .put(SecurityNetty4HttpServerTransport.SSL_SETTING.getKey(), true) + .put("xpack.security.http.ssl.supported_protocols", "TLSv1.2") + .build(); + transport = new SecurityNetty4HttpServerTransport(settings, mock(NetworkService.class), + mock(BigArrays.class), mock(IPFilter.class), serverSSLService, mock(ThreadPool.class)); + Netty4HttpMockUtil.setOpenChannelsHandlerToMock(transport); + handler = transport.configureServerChannelHandler(); + ch = new EmbeddedChannel(handler); + SSLEngine customEngine = ch.pipeline().get(SslHandler.class).engine(); + assertThat(customEngine.getEnabledProtocols(), arrayContaining("TLSv1.2")); + assertThat(customEngine.getEnabledProtocols(), not(equalTo(defaultEngine.getEnabledProtocols()))); + } + + public void testDisablesCompressionByDefaultForSsl() throws Exception { + Settings settings = Settings.builder() + .put(SecurityNetty4HttpServerTransport.SSL_SETTING.getKey(), true).build(); + + Settings.Builder pluginSettingsBuilder = Settings.builder(); + SecurityNetty4HttpServerTransport.overrideSettings(pluginSettingsBuilder, settings); + assertThat(HttpTransportSettings.SETTING_HTTP_COMPRESSION.get(pluginSettingsBuilder.build()), is(false)); + } + + public void testLeavesCompressionOnIfNotSsl() throws Exception { + Settings settings = Settings.builder() + .put(SecurityNetty4HttpServerTransport.SSL_SETTING.getKey(), false).build(); + Settings.Builder pluginSettingsBuilder = Settings.builder(); + SecurityNetty4HttpServerTransport.overrideSettings(pluginSettingsBuilder, settings); + assertThat(pluginSettingsBuilder.build().isEmpty(), is(true)); + } + + public void testDoesNotChangeExplicitlySetCompression() throws Exception { + Settings settings = Settings.builder() + .put(SecurityNetty4HttpServerTransport.SSL_SETTING.getKey(), true) + .put(HttpTransportSettings.SETTING_HTTP_COMPRESSION.getKey(), true) + .build(); + + Settings.Builder pluginSettingsBuilder = Settings.builder(); + SecurityNetty4HttpServerTransport.overrideSettings(pluginSettingsBuilder, settings); + assertThat(pluginSettingsBuilder.build().isEmpty(), is(true)); + } +} diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/transport/netty4/SecurityNetty4TransportTests.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/transport/netty4/SecurityNetty4TransportTests.java new file mode 100644 index 00000000000..65bb84e4d37 --- /dev/null +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/transport/netty4/SecurityNetty4TransportTests.java @@ -0,0 +1,265 @@ +/* + * 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.channel.ChannelHandler; +import io.netty.channel.embedded.EmbeddedChannel; +import io.netty.handler.ssl.SslHandler; +import org.elasticsearch.common.io.stream.NamedWriteableRegistry; +import org.elasticsearch.common.network.NetworkService; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.util.BigArrays; +import org.elasticsearch.env.Environment; +import org.elasticsearch.indices.breaker.CircuitBreakerService; +import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.threadpool.ThreadPool; +import org.elasticsearch.transport.netty4.Netty4MockUtil; +import org.elasticsearch.xpack.security.ssl.ClientSSLService; +import org.elasticsearch.xpack.security.ssl.SSLConfiguration.Global; +import org.elasticsearch.xpack.security.ssl.ServerSSLService; +import org.elasticsearch.xpack.security.transport.SSLClientAuth; +import org.junit.Before; + +import java.nio.file.Path; +import java.util.Locale; + +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; +import static org.hamcrest.Matchers.nullValue; +import static org.mockito.Mockito.mock; + +public class SecurityNetty4TransportTests extends ESTestCase { + private ServerSSLService serverSSLService; + private ClientSSLService clientSSLService; + + @Before + public void createSSLService() throws Exception { + Path testnodeStore = getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testnode.jks"); + Settings settings = Settings.builder() + .put("xpack.security.ssl.keystore.path", testnodeStore) + .put("xpack.security.ssl.keystore.password", "testnode") + .build(); + Environment env = new Environment(Settings.builder().put("path.home", createTempDir()).build()); + Global globalSSLConfiguration = new Global(settings); + serverSSLService = new ServerSSLService(settings, env, globalSSLConfiguration); + clientSSLService = new ClientSSLService(settings, env, globalSSLConfiguration); + } + + public void testThatSSLCanBeDisabledByProfile() throws Exception { + Settings settings = Settings.builder().put(SecurityNetty4Transport.SSL_SETTING.getKey(), true).build(); + SecurityNetty4Transport transport = + new SecurityNetty4Transport( + settings, + mock(ThreadPool.class), + mock(NetworkService.class), + mock(BigArrays.class), + mock(NamedWriteableRegistry.class), + mock(CircuitBreakerService.class), + null, + serverSSLService, + clientSSLService); + Netty4MockUtil.setOpenChannelsHandlerToMock(transport); + ChannelHandler handler = transport.getServerChannelInitializer("client", + Settings.builder().put("xpack.security.ssl", false).build()); + final EmbeddedChannel ch = new EmbeddedChannel(handler); + assertThat(ch.pipeline().get(SslHandler.class), nullValue()); + } + + public void testThatSSLCanBeEnabledByProfile() throws Exception { + Settings settings = Settings.builder().put(SecurityNetty4Transport.SSL_SETTING.getKey(), false).build(); + SecurityNetty4Transport transport = + new SecurityNetty4Transport( + settings, + mock(ThreadPool.class), + mock(NetworkService.class), + mock(BigArrays.class), + mock(NamedWriteableRegistry.class), + mock(CircuitBreakerService.class), + null, + serverSSLService, + clientSSLService); + Netty4MockUtil.setOpenChannelsHandlerToMock(transport); + ChannelHandler handler = transport.getServerChannelInitializer("client", + Settings.builder().put("xpack.security.ssl", true).build()); + final EmbeddedChannel ch = new EmbeddedChannel(handler); + assertThat(ch.pipeline().get(SslHandler.class), notNullValue()); + } + + public void testThatProfileTakesDefaultSSLSetting() throws Exception { + Settings settings = Settings.builder().put(SecurityNetty4Transport.SSL_SETTING.getKey(), true).build(); + SecurityNetty4Transport transport = + new SecurityNetty4Transport( + settings, + mock(ThreadPool.class), + mock(NetworkService.class), + mock(BigArrays.class), + mock(NamedWriteableRegistry.class), + mock(CircuitBreakerService.class), + null, + serverSSLService, + clientSSLService); + Netty4MockUtil.setOpenChannelsHandlerToMock(transport); + ChannelHandler handler = transport.getServerChannelInitializer("client", Settings.EMPTY); + final EmbeddedChannel ch = new EmbeddedChannel(handler); + assertThat(ch.pipeline().get(SslHandler.class).engine(), notNullValue()); + } + + public void testDefaultClientAuth() throws Exception { + Settings settings = Settings.builder().put(SecurityNetty4Transport.SSL_SETTING.getKey(), true).build(); + SecurityNetty4Transport transport = + new SecurityNetty4Transport( + settings, + mock(ThreadPool.class), + mock(NetworkService.class), + mock(BigArrays.class), + mock(NamedWriteableRegistry.class), + mock(CircuitBreakerService.class), + null, + serverSSLService, + clientSSLService); + Netty4MockUtil.setOpenChannelsHandlerToMock(transport); + ChannelHandler handler = transport.getServerChannelInitializer("client", Settings.EMPTY); + final EmbeddedChannel ch = new EmbeddedChannel(handler); + assertThat(ch.pipeline().get(SslHandler.class).engine().getNeedClientAuth(), is(true)); + assertThat(ch.pipeline().get(SslHandler.class).engine().getWantClientAuth(), is(false)); + } + + public void testRequiredClientAuth() throws Exception { + String value = randomFrom(SSLClientAuth.REQUIRED.name(), SSLClientAuth.REQUIRED.name().toLowerCase(Locale.ROOT), "true"); + Settings settings = Settings.builder() + .put(SecurityNetty4Transport.SSL_SETTING.getKey(), true) + .put(SecurityNetty4Transport.CLIENT_AUTH_SETTING.getKey(), value).build(); + SecurityNetty4Transport transport = + new SecurityNetty4Transport( + settings, + mock(ThreadPool.class), + mock(NetworkService.class), + mock(BigArrays.class), + mock(NamedWriteableRegistry.class), + mock(CircuitBreakerService.class), + null, + serverSSLService, + clientSSLService); + Netty4MockUtil.setOpenChannelsHandlerToMock(transport); + ChannelHandler handler = transport.getServerChannelInitializer("client", Settings.EMPTY); + final EmbeddedChannel ch = new EmbeddedChannel(handler); + assertThat(ch.pipeline().get(SslHandler.class).engine().getNeedClientAuth(), is(true)); + assertThat(ch.pipeline().get(SslHandler.class).engine().getWantClientAuth(), is(false)); + } + + public void testNoClientAuth() throws Exception { + String value = randomFrom(SSLClientAuth.NO.name(), "false", "FALSE", SSLClientAuth.NO.name().toLowerCase(Locale.ROOT)); + Settings settings = Settings.builder() + .put(SecurityNetty4Transport.SSL_SETTING.getKey(), true) + .put(SecurityNetty4Transport.CLIENT_AUTH_SETTING.getKey(), value).build(); + SecurityNetty4Transport transport = + new SecurityNetty4Transport( + settings, + mock(ThreadPool.class), + mock(NetworkService.class), + mock(BigArrays.class), + mock(NamedWriteableRegistry.class), + mock(CircuitBreakerService.class), + null, + serverSSLService, + clientSSLService); + Netty4MockUtil.setOpenChannelsHandlerToMock(transport); + ChannelHandler handler = transport.getServerChannelInitializer("client", Settings.EMPTY); + final EmbeddedChannel ch = new EmbeddedChannel(handler); + assertThat(ch.pipeline().get(SslHandler.class).engine().getNeedClientAuth(), is(false)); + assertThat(ch.pipeline().get(SslHandler.class).engine().getWantClientAuth(), is(false)); + } + + public void testOptionalClientAuth() throws Exception { + String value = randomFrom(SSLClientAuth.OPTIONAL.name(), SSLClientAuth.OPTIONAL.name().toLowerCase(Locale.ROOT)); + Settings settings = Settings.builder() + .put(SecurityNetty4Transport.SSL_SETTING.getKey(), true) + .put(SecurityNetty4Transport.CLIENT_AUTH_SETTING.getKey(), value).build(); + SecurityNetty4Transport transport = + new SecurityNetty4Transport( + settings, + mock(ThreadPool.class), + mock(NetworkService.class), + mock(BigArrays.class), + mock(NamedWriteableRegistry.class), + mock(CircuitBreakerService.class), + null, + serverSSLService, + clientSSLService); + Netty4MockUtil.setOpenChannelsHandlerToMock(transport); + ChannelHandler handler = transport.getServerChannelInitializer("client", Settings.EMPTY); + final EmbeddedChannel ch = new EmbeddedChannel(handler); + assertThat(ch.pipeline().get(SslHandler.class).engine().getNeedClientAuth(), is(false)); + assertThat(ch.pipeline().get(SslHandler.class).engine().getWantClientAuth(), is(true)); + } + + public void testProfileRequiredClientAuth() throws Exception { + String value = randomFrom(SSLClientAuth.REQUIRED.name(), SSLClientAuth.REQUIRED.name().toLowerCase(Locale.ROOT), "true", "TRUE"); + Settings settings = Settings.builder().put(SecurityNetty4Transport.SSL_SETTING.getKey(), true).build(); + SecurityNetty4Transport transport = + new SecurityNetty4Transport( + settings, + mock(ThreadPool.class), + mock(NetworkService.class), + mock(BigArrays.class), + mock(NamedWriteableRegistry.class), + mock(CircuitBreakerService.class), + null, + serverSSLService, + clientSSLService); + Netty4MockUtil.setOpenChannelsHandlerToMock(transport); + ChannelHandler handler = transport.getServerChannelInitializer("client", + Settings.builder().put(SecurityNetty4Transport.PROFILE_CLIENT_AUTH_SETTING, value).build()); + final EmbeddedChannel ch = new EmbeddedChannel(handler); + assertThat(ch.pipeline().get(SslHandler.class).engine().getNeedClientAuth(), is(true)); + assertThat(ch.pipeline().get(SslHandler.class).engine().getWantClientAuth(), is(false)); + } + + public void testProfileNoClientAuth() throws Exception { + String value = randomFrom(SSLClientAuth.NO.name(), "false", "FALSE", SSLClientAuth.NO.name().toLowerCase(Locale.ROOT)); + Settings settings = Settings.builder().put(SecurityNetty4Transport.SSL_SETTING.getKey(), true).build(); + SecurityNetty4Transport transport = + new SecurityNetty4Transport( + settings, + mock(ThreadPool.class), + mock(NetworkService.class), + mock(BigArrays.class), + mock(NamedWriteableRegistry.class), + mock(CircuitBreakerService.class), + null, + serverSSLService, + clientSSLService); + Netty4MockUtil.setOpenChannelsHandlerToMock(transport); + ChannelHandler handler = transport.getServerChannelInitializer("client", + Settings.builder().put(SecurityNetty4Transport.PROFILE_CLIENT_AUTH_SETTING.getKey(), value).build()); + final EmbeddedChannel ch = new EmbeddedChannel(handler); + assertThat(ch.pipeline().get(SslHandler.class).engine().getNeedClientAuth(), is(false)); + assertThat(ch.pipeline().get(SslHandler.class).engine().getWantClientAuth(), is(false)); + } + + public void testProfileOptionalClientAuth() throws Exception { + String value = randomFrom(SSLClientAuth.OPTIONAL.name(), SSLClientAuth.OPTIONAL.name().toLowerCase(Locale.ROOT)); + Settings settings = Settings.builder().put(SecurityNetty4Transport.SSL_SETTING.getKey(), true).build(); + SecurityNetty4Transport transport = + new SecurityNetty4Transport( + settings, + mock(ThreadPool.class), + mock(NetworkService.class), + mock(BigArrays.class), + mock(NamedWriteableRegistry.class), + mock(CircuitBreakerService.class), + null, + serverSSLService, + clientSSLService); + Netty4MockUtil.setOpenChannelsHandlerToMock(transport); + final ChannelHandler handler = transport.getServerChannelInitializer("client", + Settings.builder().put(SecurityNetty4Transport.PROFILE_CLIENT_AUTH_SETTING.getKey(), value).build()); + final EmbeddedChannel ch = new EmbeddedChannel(handler); + assertThat(ch.pipeline().get(SslHandler.class).engine().getNeedClientAuth(), is(false)); + assertThat(ch.pipeline().get(SslHandler.class).engine().getWantClientAuth(), is(true)); + } + +}