Skip to content

Commit

Permalink
Move security http server logic to http server (#91870)
Browse files Browse the repository at this point in the history
This commit moves logic related to implementing security features into
the core http server implementation.
  • Loading branch information
Tim-Brooks committed Dec 2, 2022
1 parent dd82d3b commit 251e830
Show file tree
Hide file tree
Showing 17 changed files with 253 additions and 232 deletions.
2 changes: 2 additions & 0 deletions modules/transport-netty4/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ configurations {
}

dependencies {
api project(":libs:elasticsearch-ssl-config")

// network stack
api "io.netty:netty-buffer:${versions.netty}"
api "io.netty:netty-codec:${versions.netty}"
Expand Down
1 change: 1 addition & 0 deletions modules/transport-netty4/src/main/java/module-info.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
requires jdk.net;
requires org.elasticsearch.base;
requires org.elasticsearch.server;
requires org.elasticsearch.sslconfig;
requires org.elasticsearch.xcontent;
requires org.apache.logging.log4j;
requires org.apache.lucene.core;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,15 @@
import io.netty.handler.codec.http.HttpResponse;
import io.netty.handler.codec.http.HttpResponseEncoder;
import io.netty.handler.codec.http.HttpUtil;
import io.netty.handler.ssl.SslHandler;
import io.netty.handler.timeout.ReadTimeoutException;
import io.netty.handler.timeout.ReadTimeoutHandler;
import io.netty.util.AttributeKey;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.elasticsearch.ExceptionsHelper;
import org.elasticsearch.common.network.CloseableChannel;
import org.elasticsearch.common.network.NetworkService;
import org.elasticsearch.common.settings.ClusterSettings;
import org.elasticsearch.common.settings.Setting;
Expand All @@ -43,23 +45,29 @@
import org.elasticsearch.common.unit.ByteSizeValue;
import org.elasticsearch.common.util.concurrent.EsExecutors;
import org.elasticsearch.core.IOUtils;
import org.elasticsearch.core.Nullable;
import org.elasticsearch.http.AbstractHttpServerTransport;
import org.elasticsearch.http.HttpChannel;
import org.elasticsearch.http.HttpHandlingSettings;
import org.elasticsearch.http.HttpReadTimeoutException;
import org.elasticsearch.http.HttpServerChannel;
import org.elasticsearch.http.HttpServerTransport;
import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.tracing.Tracer;
import org.elasticsearch.transport.netty4.AcceptChannelHandler;
import org.elasticsearch.transport.netty4.NetUtils;
import org.elasticsearch.transport.netty4.Netty4Utils;
import org.elasticsearch.transport.netty4.Netty4WriteThrottlingHandler;
import org.elasticsearch.transport.netty4.NettyAllocator;
import org.elasticsearch.transport.netty4.NettyByteBufSizer;
import org.elasticsearch.transport.netty4.SSLExceptionHelper;
import org.elasticsearch.transport.netty4.SharedGroupFactory;
import org.elasticsearch.transport.netty4.TLSConfig;
import org.elasticsearch.xcontent.NamedXContentRegistry;

import java.net.InetSocketAddress;
import java.util.concurrent.TimeUnit;
import java.util.function.BiPredicate;

import static org.elasticsearch.http.HttpTransportSettings.SETTING_HTTP_MAX_CHUNK_SIZE;
import static org.elasticsearch.http.HttpTransportSettings.SETTING_HTTP_MAX_CONTENT_LENGTH;
Expand Down Expand Up @@ -133,6 +141,8 @@ public class Netty4HttpServerTransport extends AbstractHttpServerTransport {

private final SharedGroupFactory sharedGroupFactory;
private final RecvByteBufAllocator recvByteBufAllocator;
private final TLSConfig tlsConfig;
private final AcceptChannelHandler.AcceptPredicate acceptChannelPredicate;
private final int readTimeoutMillis;

private final int maxCompositeBufferComponents;
Expand All @@ -148,7 +158,10 @@ public Netty4HttpServerTransport(
Dispatcher dispatcher,
ClusterSettings clusterSettings,
SharedGroupFactory sharedGroupFactory,
Tracer tracer
Tracer tracer,
TLSConfig tlsConfig,
@Nullable AcceptChannelHandler.AcceptPredicate acceptChannelPredicate

) {
super(
settings,
Expand All @@ -163,6 +176,8 @@ public Netty4HttpServerTransport(
Netty4Utils.setAvailableProcessors(EsExecutors.allocatedProcessors(settings));
NettyAllocator.logAllocatorDescriptionIfNeeded();
this.sharedGroupFactory = sharedGroupFactory;
this.tlsConfig = tlsConfig;
this.acceptChannelPredicate = acceptChannelPredicate;

this.pipeliningMaxEvents = SETTING_PIPELINING_MAX_EVENTS.get(settings);

Expand Down Expand Up @@ -254,6 +269,9 @@ protected void doStart() {
serverBootstrap.childOption(ChannelOption.SO_REUSEADDR, reuseAddress);

bindServer();
if (acceptChannelPredicate != null) {
acceptChannelPredicate.setBoundAddress(boundAddress());
}
success = true;
} finally {
if (success == false) {
Expand Down Expand Up @@ -281,15 +299,31 @@ protected void stopInternal() {

@Override
public void onException(HttpChannel channel, Exception cause) {
if (cause instanceof ReadTimeoutException) {
if (lifecycle.started() == false) {
return;
}

if (SSLExceptionHelper.isNotSslRecordException(cause)) {
logger.warn("received plaintext http traffic on an https channel, closing connection {}", channel);
CloseableChannel.closeChannel(channel);
} else if (SSLExceptionHelper.isCloseDuringHandshakeException(cause)) {
logger.debug("connection {} closed during ssl handshake", channel);
CloseableChannel.closeChannel(channel);
} else if (SSLExceptionHelper.isInsufficientBufferRemainingException(cause)) {
logger.debug("connection {} closed abruptly", channel);
CloseableChannel.closeChannel(channel);
} else if (SSLExceptionHelper.isReceivedCertificateUnknownException(cause)) {
logger.warn("http client did not trust this server's certificate, closing connection {}", channel);
CloseableChannel.closeChannel(channel);
} else if (cause instanceof ReadTimeoutException) {
super.onException(channel, new HttpReadTimeoutException(readTimeoutMillis, cause));
} else {
super.onException(channel, cause);
}
}

public ChannelHandler configureServerChannelHandler() {
return new HttpChannelHandler(this, handlingSettings);
return new HttpChannelHandler(this, handlingSettings, tlsConfig, acceptChannelPredicate);
}

static final AttributeKey<Netty4HttpChannel> HTTP_CHANNEL_KEY = AttributeKey.newInstance("es-http-channel");
Expand All @@ -299,16 +333,35 @@ protected static class HttpChannelHandler extends ChannelInitializer<Channel> {

private final Netty4HttpServerTransport transport;
private final HttpHandlingSettings handlingSettings;

protected HttpChannelHandler(final Netty4HttpServerTransport transport, final HttpHandlingSettings handlingSettings) {
private final TLSConfig tlsConfig;
private final BiPredicate<String, InetSocketAddress> acceptChannelPredicate;

protected HttpChannelHandler(
final Netty4HttpServerTransport transport,
final HttpHandlingSettings handlingSettings,
final TLSConfig tlsConfig,
@Nullable final BiPredicate<String, InetSocketAddress> acceptChannelPredicate
) {
this.transport = transport;
this.handlingSettings = handlingSettings;
this.tlsConfig = tlsConfig;
this.acceptChannelPredicate = acceptChannelPredicate;
}

@Override
protected void initChannel(Channel ch) throws Exception {
Netty4HttpChannel nettyHttpChannel = new Netty4HttpChannel(ch);
ch.attr(HTTP_CHANNEL_KEY).set(nettyHttpChannel);
if (acceptChannelPredicate != null) {
ch.pipeline()
.addLast(
"accept_channel_handler",
new AcceptChannelHandler(acceptChannelPredicate, HttpServerTransport.HTTP_PROFILE_NAME)
);
}
if (tlsConfig.isTLSEnabled()) {
ch.pipeline().addLast("ssl", new SslHandler(tlsConfig.createServerSSLEngine()));
}
ch.pipeline()
.addLast("chunked_writer", new Netty4WriteThrottlingHandler(transport.getThreadPool().getThreadContext()))
.addLast("byte_buf_sizer", NettyByteBufSizer.INSTANCE);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

package org.elasticsearch.transport.netty4;

import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.ipfilter.AbstractRemoteAddressFilter;

import org.elasticsearch.common.transport.BoundTransportAddress;

import java.net.InetSocketAddress;
import java.util.function.BiPredicate;

@ChannelHandler.Sharable
public class AcceptChannelHandler extends AbstractRemoteAddressFilter<InetSocketAddress> {

private final BiPredicate<String, InetSocketAddress> predicate;
private final String profile;

public AcceptChannelHandler(final BiPredicate<String, InetSocketAddress> predicate, final String profile) {
this.predicate = predicate;
this.profile = profile;
}

@Override
protected boolean accept(final ChannelHandlerContext ctx, final InetSocketAddress remoteAddress) throws Exception {
return predicate.test(profile, remoteAddress);
}

public interface AcceptPredicate extends BiPredicate<String, InetSocketAddress> {

void setBoundAddress(BoundTransportAddress boundHttpTransportAddress);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,9 @@ public Map<String, Supplier<HttpServerTransport>> getHttpTransports(
dispatcher,
clusterSettings,
getSharedGroupFactory(settings),
tracer
tracer,
TLSConfig.noTLS(),
null
)
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
package org.elasticsearch.xpack.core.security.transport;

package org.elasticsearch.transport.netty4;

import io.netty.handler.codec.DecoderException;
import io.netty.handler.ssl.NotSslRecordException;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

package org.elasticsearch.transport.netty4;

import org.elasticsearch.common.ssl.SslConfiguration;

import javax.net.ssl.SSLEngine;

public record TLSConfig(SslConfiguration sslConfiguration, EngineProvider engineProvider) {

public boolean isTLSEnabled() {
return sslConfiguration != null;
}

public SSLEngine createServerSSLEngine() {
assert isTLSEnabled();
SSLEngine sslEngine = engineProvider.create(sslConfiguration, null, -1);
sslEngine.setUseClientMode(false);
return sslEngine;
}

public static TLSConfig noTLS() {
return new TLSConfig(null, null);
}

@FunctionalInterface
public interface EngineProvider {

SSLEngine create(SslConfiguration configuration, String host, int port);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@
import org.elasticsearch.common.settings.ClusterSettings;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.transport.TransportAddress;
import org.elasticsearch.common.util.MockPageCacheRecycler;
import org.elasticsearch.common.util.PageCacheRecycler;
import org.elasticsearch.common.util.concurrent.ThreadContext;
import org.elasticsearch.http.HttpServerTransport;
import org.elasticsearch.http.HttpTransportSettings;
Expand All @@ -30,6 +28,7 @@
import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.tracing.Tracer;
import org.elasticsearch.transport.netty4.SharedGroupFactory;
import org.elasticsearch.transport.netty4.TLSConfig;
import org.junit.After;
import org.junit.Before;

Expand All @@ -45,13 +44,11 @@
public class Netty4BadRequestTests extends ESTestCase {

private NetworkService networkService;
private PageCacheRecycler recycler;
private ThreadPool threadPool;

@Before
public void setup() throws Exception {
networkService = new NetworkService(Collections.emptyList());
recycler = new MockPageCacheRecycler(Settings.EMPTY);
threadPool = new TestThreadPool("test");
}

Expand Down Expand Up @@ -88,7 +85,9 @@ public void dispatchBadRequest(RestChannel channel, ThreadContext threadContext,
dispatcher,
new ClusterSettings(Settings.EMPTY, ClusterSettings.BUILT_IN_CLUSTER_SETTINGS),
new SharedGroupFactory(Settings.EMPTY),
Tracer.NOOP
Tracer.NOOP,
TLSConfig.noTLS(),
null
)
) {
httpServerTransport.start();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.tracing.Tracer;
import org.elasticsearch.transport.netty4.SharedGroupFactory;
import org.elasticsearch.transport.netty4.TLSConfig;
import org.junit.After;
import org.junit.Before;

Expand Down Expand Up @@ -104,7 +105,9 @@ class CustomNettyHttpServerTransport extends Netty4HttpServerTransport {
new NullDispatcher(),
new ClusterSettings(Settings.EMPTY, ClusterSettings.BUILT_IN_CLUSTER_SETTINGS),
new SharedGroupFactory(settings),
Tracer.NOOP
Tracer.NOOP,
TLSConfig.noTLS(),
null
);
}

Expand Down
Loading

0 comments on commit 251e830

Please sign in to comment.