From 2ac20c03cf29b629df150788569a3fb4b5b133f7 Mon Sep 17 00:00:00 2001 From: Tim Brooks Date: Fri, 31 Mar 2023 02:52:26 -0600 Subject: [PATCH] Add mechanism for early http header validation (#92220) This introduces a way to validate HTTP headers prior to reading the request body. Co-authored-by: Albert Zaharovits --- .../netty4/Netty4HttpHeaderValidator.java | 238 +++++++ .../netty4/Netty4HttpServerTransport.java | 26 +- .../transport/netty4/Netty4Plugin.java | 1 + .../http/netty4/Netty4BadRequestTests.java | 3 +- .../Netty4HttpHeaderValidatorTests.java | 586 ++++++++++++++++++ .../Netty4HttpServerPipeliningTests.java | 3 +- .../Netty4HttpServerTransportTests.java | 32 +- .../xpack/security/Security.java | 4 +- ...ecurityNetty4HttpServerTransportTests.java | 22 +- 9 files changed, 890 insertions(+), 25 deletions(-) create mode 100644 modules/transport-netty4/src/main/java/org/elasticsearch/http/netty4/Netty4HttpHeaderValidator.java create mode 100644 modules/transport-netty4/src/test/java/org/elasticsearch/http/netty4/Netty4HttpHeaderValidatorTests.java diff --git a/modules/transport-netty4/src/main/java/org/elasticsearch/http/netty4/Netty4HttpHeaderValidator.java b/modules/transport-netty4/src/main/java/org/elasticsearch/http/netty4/Netty4HttpHeaderValidator.java new file mode 100644 index 0000000000000..85cd3fba6eb0b --- /dev/null +++ b/modules/transport-netty4/src/main/java/org/elasticsearch/http/netty4/Netty4HttpHeaderValidator.java @@ -0,0 +1,238 @@ +/* + * 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.http.netty4; + +import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; +import io.netty.handler.codec.DecoderResult; +import io.netty.handler.codec.http.HttpContent; +import io.netty.handler.codec.http.HttpObject; +import io.netty.handler.codec.http.HttpRequest; +import io.netty.handler.codec.http.LastHttpContent; +import io.netty.util.ReferenceCountUtil; + +import org.elasticsearch.action.ActionListener; +import org.elasticsearch.common.TriConsumer; + +import java.util.ArrayDeque; + +import static org.elasticsearch.http.netty4.Netty4HttpHeaderValidator.State.DROPPING_DATA_PERMANENTLY; +import static org.elasticsearch.http.netty4.Netty4HttpHeaderValidator.State.DROPPING_DATA_UNTIL_NEXT_REQUEST; +import static org.elasticsearch.http.netty4.Netty4HttpHeaderValidator.State.FORWARDING_DATA_UNTIL_NEXT_REQUEST; +import static org.elasticsearch.http.netty4.Netty4HttpHeaderValidator.State.QUEUEING_DATA; +import static org.elasticsearch.http.netty4.Netty4HttpHeaderValidator.State.WAITING_TO_START; + +public class Netty4HttpHeaderValidator extends ChannelInboundHandlerAdapter { + + public static final TriConsumer> NOOP_VALIDATOR = (( + httpRequest, + channel, + listener) -> listener.onResponse(null)); + + private final TriConsumer> validator; + private ArrayDeque pending = new ArrayDeque<>(4); + private State state = WAITING_TO_START; + + public Netty4HttpHeaderValidator(TriConsumer> validator) { + this.validator = validator; + } + + State getState() { + return state; + } + + @SuppressWarnings("fallthrough") + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { + assert msg instanceof HttpObject; + final HttpObject httpObject = (HttpObject) msg; + + switch (state) { + case WAITING_TO_START: + assert pending.isEmpty(); + pending.add(ReferenceCountUtil.retain(httpObject)); + requestStart(ctx); + assert state == QUEUEING_DATA; + break; + case QUEUEING_DATA: + pending.add(ReferenceCountUtil.retain(httpObject)); + break; + case FORWARDING_DATA_UNTIL_NEXT_REQUEST: + assert pending.isEmpty(); + if (httpObject instanceof LastHttpContent) { + state = WAITING_TO_START; + } + ctx.fireChannelRead(httpObject); + break; + case DROPPING_DATA_UNTIL_NEXT_REQUEST: + assert pending.isEmpty(); + if (httpObject instanceof LastHttpContent) { + state = WAITING_TO_START; + } + // fall-through + case DROPPING_DATA_PERMANENTLY: + assert pending.isEmpty(); + ReferenceCountUtil.release(httpObject); // consume without enqueuing + break; + } + + setAutoReadForState(ctx, state); + } + + private void requestStart(ChannelHandlerContext ctx) { + assert state == WAITING_TO_START; + + if (pending.isEmpty()) { + return; + } + + final HttpObject httpObject = pending.getFirst(); + final HttpRequest httpRequest; + if (httpObject instanceof HttpRequest && httpObject.decoderResult().isSuccess()) { + // a properly decoded HTTP start message is expected to begin validation + // anything else is probably an error that the downstream HTTP message aggregator will have to handle + httpRequest = (HttpRequest) httpObject; + } else { + httpRequest = null; + } + + state = QUEUEING_DATA; + + if (httpRequest == null) { + // this looks like a malformed request and will forward without validation + ctx.channel().eventLoop().submit(() -> forwardFullRequest(ctx)); + } else { + validator.apply(httpRequest, ctx.channel(), new ActionListener<>() { + @Override + public void onResponse(Void unused) { + // Always use "Submit" to prevent reentrancy concerns if we are still on event loop + ctx.channel().eventLoop().submit(() -> forwardFullRequest(ctx)); + } + + @Override + public void onFailure(Exception e) { + // Always use "Submit" to prevent reentrancy concerns if we are still on event loop + ctx.channel().eventLoop().submit(() -> forwardRequestWithDecoderExceptionAndNoContent(ctx, e)); + } + }); + } + } + + private void forwardFullRequest(ChannelHandlerContext ctx) { + assert ctx.channel().eventLoop().inEventLoop(); + assert ctx.channel().config().isAutoRead() == false; + assert state == QUEUEING_DATA; + + boolean fullRequestForwarded = forwardData(ctx, pending); + + assert fullRequestForwarded || pending.isEmpty(); + if (fullRequestForwarded) { + state = WAITING_TO_START; + requestStart(ctx); + } else { + state = FORWARDING_DATA_UNTIL_NEXT_REQUEST; + } + + assert state == WAITING_TO_START || state == QUEUEING_DATA || state == FORWARDING_DATA_UNTIL_NEXT_REQUEST; + setAutoReadForState(ctx, state); + } + + private void forwardRequestWithDecoderExceptionAndNoContent(ChannelHandlerContext ctx, Exception e) { + assert ctx.channel().eventLoop().inEventLoop(); + assert ctx.channel().config().isAutoRead() == false; + assert state == QUEUEING_DATA; + + HttpObject messageToForward = pending.getFirst(); + boolean fullRequestDropped = dropData(pending); + if (messageToForward instanceof HttpContent toReplace) { + // if the request to forward contained data (which got dropped), replace with empty data + messageToForward = toReplace.replace(Unpooled.EMPTY_BUFFER); + } + messageToForward.setDecoderResult(DecoderResult.failure(e)); + ctx.fireChannelRead(messageToForward); + + assert fullRequestDropped || pending.isEmpty(); + if (fullRequestDropped) { + state = WAITING_TO_START; + requestStart(ctx); + } else { + state = DROPPING_DATA_UNTIL_NEXT_REQUEST; + } + + assert state == WAITING_TO_START || state == QUEUEING_DATA || state == DROPPING_DATA_UNTIL_NEXT_REQUEST; + setAutoReadForState(ctx, state); + } + + @Override + public void channelInactive(ChannelHandlerContext ctx) throws Exception { + state = DROPPING_DATA_PERMANENTLY; + while (true) { + if (dropData(pending) == false) { + break; + } + } + super.channelInactive(ctx); + } + + private static boolean forwardData(ChannelHandlerContext ctx, ArrayDeque pending) { + final int pendingMessages = pending.size(); + try { + HttpObject toForward; + while ((toForward = pending.poll()) != null) { + ctx.fireChannelRead(toForward); + ReferenceCountUtil.release(toForward); // reference cnt incremented when enqueued + if (toForward instanceof LastHttpContent) { + return true; + } + } + return false; + } finally { + maybeResizePendingDown(pendingMessages, pending); + } + } + + private static boolean dropData(ArrayDeque pending) { + final int pendingMessages = pending.size(); + try { + HttpObject toDrop; + while ((toDrop = pending.poll()) != null) { + ReferenceCountUtil.release(toDrop, 2); // 1 for enqueuing, 1 for consuming + if (toDrop instanceof LastHttpContent) { + return true; + } + } + return false; + } finally { + maybeResizePendingDown(pendingMessages, pending); + } + } + + private static void maybeResizePendingDown(int largeSize, ArrayDeque pending) { + if (pending.size() <= 4 && largeSize > 32) { + // Prevent the ArrayDeque from becoming forever large due to a single large message. + ArrayDeque old = pending; + pending = new ArrayDeque<>(4); + pending.addAll(old); + } + } + + private static void setAutoReadForState(ChannelHandlerContext ctx, State state) { + ctx.channel().config().setAutoRead((state == QUEUEING_DATA || state == DROPPING_DATA_PERMANENTLY) == false); + } + + enum State { + WAITING_TO_START, + QUEUEING_DATA, + FORWARDING_DATA_UNTIL_NEXT_REQUEST, + DROPPING_DATA_UNTIL_NEXT_REQUEST, + DROPPING_DATA_PERMANENTLY + } +} diff --git a/modules/transport-netty4/src/main/java/org/elasticsearch/http/netty4/Netty4HttpServerTransport.java b/modules/transport-netty4/src/main/java/org/elasticsearch/http/netty4/Netty4HttpServerTransport.java index e741772eedb5a..263a8e1b8c5dc 100644 --- a/modules/transport-netty4/src/main/java/org/elasticsearch/http/netty4/Netty4HttpServerTransport.java +++ b/modules/transport-netty4/src/main/java/org/elasticsearch/http/netty4/Netty4HttpServerTransport.java @@ -23,6 +23,7 @@ import io.netty.handler.codec.http.HttpContentCompressor; import io.netty.handler.codec.http.HttpContentDecompressor; import io.netty.handler.codec.http.HttpObjectAggregator; +import io.netty.handler.codec.http.HttpRequest; import io.netty.handler.codec.http.HttpRequestDecoder; import io.netty.handler.codec.http.HttpResponse; import io.netty.handler.codec.http.HttpResponseEncoder; @@ -35,6 +36,8 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.elasticsearch.ExceptionsHelper; +import org.elasticsearch.action.ActionListener; +import org.elasticsearch.common.TriConsumer; import org.elasticsearch.common.network.CloseableChannel; import org.elasticsearch.common.network.NetworkService; import org.elasticsearch.common.settings.ClusterSettings; @@ -143,6 +146,7 @@ public class Netty4HttpServerTransport extends AbstractHttpServerTransport { private final RecvByteBufAllocator recvByteBufAllocator; private final TLSConfig tlsConfig; private final AcceptChannelHandler.AcceptPredicate acceptChannelPredicate; + private final TriConsumer> headerValidator; private final int readTimeoutMillis; private final int maxCompositeBufferComponents; @@ -160,8 +164,8 @@ public Netty4HttpServerTransport( SharedGroupFactory sharedGroupFactory, Tracer tracer, TLSConfig tlsConfig, - @Nullable AcceptChannelHandler.AcceptPredicate acceptChannelPredicate - + @Nullable AcceptChannelHandler.AcceptPredicate acceptChannelPredicate, + @Nullable TriConsumer> headerValidator ) { super( settings, @@ -178,6 +182,7 @@ public Netty4HttpServerTransport( this.sharedGroupFactory = sharedGroupFactory; this.tlsConfig = tlsConfig; this.acceptChannelPredicate = acceptChannelPredicate; + this.headerValidator = headerValidator; this.pipeliningMaxEvents = SETTING_PIPELINING_MAX_EVENTS.get(settings); @@ -323,7 +328,7 @@ public void onException(HttpChannel channel, Exception cause) { } public ChannelHandler configureServerChannelHandler() { - return new HttpChannelHandler(this, handlingSettings, tlsConfig, acceptChannelPredicate); + return new HttpChannelHandler(this, handlingSettings, tlsConfig, acceptChannelPredicate, headerValidator); } static final AttributeKey HTTP_CHANNEL_KEY = AttributeKey.newInstance("es-http-channel"); @@ -335,17 +340,20 @@ protected static class HttpChannelHandler extends ChannelInitializer { private final HttpHandlingSettings handlingSettings; private final TLSConfig tlsConfig; private final BiPredicate acceptChannelPredicate; + private final TriConsumer> headerValidator; protected HttpChannelHandler( final Netty4HttpServerTransport transport, final HttpHandlingSettings handlingSettings, final TLSConfig tlsConfig, - @Nullable final BiPredicate acceptChannelPredicate + @Nullable final BiPredicate acceptChannelPredicate, + @Nullable final TriConsumer> headerValidator ) { this.transport = transport; this.handlingSettings = handlingSettings; this.tlsConfig = tlsConfig; this.acceptChannelPredicate = acceptChannelPredicate; + this.headerValidator = headerValidator; } @Override @@ -374,11 +382,17 @@ protected void initChannel(Channel ch) throws Exception { handlingSettings.maxChunkSize() ); decoder.setCumulator(ByteToMessageDecoder.COMPOSITE_CUMULATOR); + ch.pipeline().addLast("decoder", decoder); // parses the HTTP bytes request into HTTP message pieces + if (headerValidator != null) { + // runs a validation function on the first HTTP message piece which contains all the headers + // if validation passes, the pieces of that particular request are forwarded, otherwise they are discarded + ch.pipeline().addLast("header_validator", new Netty4HttpHeaderValidator(headerValidator)); + } + // combines the HTTP message pieces into a single full HTTP request (with headers and body) final HttpObjectAggregator aggregator = new HttpObjectAggregator(handlingSettings.maxContentLength()); aggregator.setMaxCumulationBufferComponents(transport.maxCompositeBufferComponents); ch.pipeline() - .addLast("decoder", decoder) - .addLast("decoder_compress", new HttpContentDecompressor()) + .addLast("decoder_compress", new HttpContentDecompressor()) // this handles request body decompression .addLast("encoder", new HttpResponseEncoder() { @Override protected boolean isContentAlwaysEmpty(HttpResponse msg) { diff --git a/modules/transport-netty4/src/main/java/org/elasticsearch/transport/netty4/Netty4Plugin.java b/modules/transport-netty4/src/main/java/org/elasticsearch/transport/netty4/Netty4Plugin.java index e5d6042382b7a..a81222aed0c17 100644 --- a/modules/transport-netty4/src/main/java/org/elasticsearch/transport/netty4/Netty4Plugin.java +++ b/modules/transport-netty4/src/main/java/org/elasticsearch/transport/netty4/Netty4Plugin.java @@ -114,6 +114,7 @@ public Map> getHttpTransports( getSharedGroupFactory(settings), tracer, TLSConfig.noTLS(), + null, null ) ); diff --git a/modules/transport-netty4/src/test/java/org/elasticsearch/http/netty4/Netty4BadRequestTests.java b/modules/transport-netty4/src/test/java/org/elasticsearch/http/netty4/Netty4BadRequestTests.java index 4b0757dd5144f..fc31e51259b45 100644 --- a/modules/transport-netty4/src/test/java/org/elasticsearch/http/netty4/Netty4BadRequestTests.java +++ b/modules/transport-netty4/src/test/java/org/elasticsearch/http/netty4/Netty4BadRequestTests.java @@ -87,7 +87,8 @@ public void dispatchBadRequest(RestChannel channel, ThreadContext threadContext, new SharedGroupFactory(Settings.EMPTY), Tracer.NOOP, TLSConfig.noTLS(), - null + null, + randomFrom(Netty4HttpHeaderValidator.NOOP_VALIDATOR, null) ) ) { httpServerTransport.start(); diff --git a/modules/transport-netty4/src/test/java/org/elasticsearch/http/netty4/Netty4HttpHeaderValidatorTests.java b/modules/transport-netty4/src/test/java/org/elasticsearch/http/netty4/Netty4HttpHeaderValidatorTests.java new file mode 100644 index 0000000000000..de5d63bee6984 --- /dev/null +++ b/modules/transport-netty4/src/test/java/org/elasticsearch/http/netty4/Netty4HttpHeaderValidatorTests.java @@ -0,0 +1,586 @@ +/* + * 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.http.netty4; + +import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; +import io.netty.channel.embedded.EmbeddedChannel; +import io.netty.handler.codec.http.DefaultHttpContent; +import io.netty.handler.codec.http.DefaultHttpRequest; +import io.netty.handler.codec.http.DefaultLastHttpContent; +import io.netty.handler.codec.http.HttpHeaderNames; +import io.netty.handler.codec.http.HttpMethod; +import io.netty.handler.codec.http.HttpRequest; +import io.netty.handler.codec.http.HttpVersion; +import io.netty.handler.codec.http.LastHttpContent; + +import org.elasticsearch.ElasticsearchException; +import org.elasticsearch.ElasticsearchSecurityException; +import org.elasticsearch.action.ActionListener; +import org.elasticsearch.common.TriConsumer; +import org.elasticsearch.test.ESTestCase; + +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; + +import static org.elasticsearch.http.netty4.Netty4HttpHeaderValidator.State.DROPPING_DATA_UNTIL_NEXT_REQUEST; +import static org.elasticsearch.http.netty4.Netty4HttpHeaderValidator.State.FORWARDING_DATA_UNTIL_NEXT_REQUEST; +import static org.elasticsearch.http.netty4.Netty4HttpHeaderValidator.State.QUEUEING_DATA; +import static org.elasticsearch.http.netty4.Netty4HttpHeaderValidator.State.WAITING_TO_START; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.nullValue; +import static org.hamcrest.Matchers.sameInstance; + +public class Netty4HttpHeaderValidatorTests extends ESTestCase { + + private final AtomicReference header = new AtomicReference<>(); + private final AtomicReference> listener = new AtomicReference<>(); + private EmbeddedChannel channel; + private Netty4HttpHeaderValidator netty4HttpHeaderValidator; + + @Override + public void setUp() throws Exception { + super.setUp(); + reset(); + } + + private void reset() { + channel = new EmbeddedChannel(); + header.set(null); + listener.set(null); + TriConsumer> validator = (httpRequest, channel, validationCompleteListener) -> { + header.set(httpRequest); + listener.set(validationCompleteListener); + }; + netty4HttpHeaderValidator = new Netty4HttpHeaderValidator(validator); + channel.pipeline().addLast(netty4HttpHeaderValidator); + } + + public void testValidationPausesAndResumesData() { + assertTrue(channel.config().isAutoRead()); + assertThat(netty4HttpHeaderValidator.getState(), equalTo(WAITING_TO_START)); + + final DefaultHttpRequest request = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "/uri"); + DefaultHttpContent content = new DefaultHttpContent(Unpooled.buffer(4)); + channel.writeInbound(request); + channel.writeInbound(content); + + assertThat(header.get(), sameInstance(request)); + // channel is paused + assertThat(channel.readInbound(), nullValue()); + assertFalse(channel.config().isAutoRead()); + + // channel is resumed + listener.get().onResponse(null); + channel.runPendingTasks(); + + assertTrue(channel.config().isAutoRead()); + assertThat(netty4HttpHeaderValidator.getState(), equalTo(FORWARDING_DATA_UNTIL_NEXT_REQUEST)); + assertThat(channel.readInbound(), sameInstance(request)); + assertThat(channel.readInbound(), sameInstance(content)); + assertThat(channel.readInbound(), nullValue()); + assertThat(content.refCnt(), equalTo(1)); + + // channel continues in resumed state after request finishes + DefaultLastHttpContent lastContent = new DefaultLastHttpContent(Unpooled.buffer(4)); + channel.writeInbound(lastContent); + assertTrue(channel.config().isAutoRead()); + assertThat(netty4HttpHeaderValidator.getState(), equalTo(WAITING_TO_START)); + assertThat(channel.readInbound(), sameInstance(lastContent)); + assertThat(lastContent.refCnt(), equalTo(1)); + + // channel is again paused while validating next request + channel.writeInbound(request); + assertFalse(channel.config().isAutoRead()); + assertThat(netty4HttpHeaderValidator.getState(), equalTo(QUEUEING_DATA)); + } + + public void testContentForwardedAfterValidation() { + assertTrue(channel.config().isAutoRead()); + assertThat(netty4HttpHeaderValidator.getState(), equalTo(WAITING_TO_START)); + + final DefaultHttpRequest request = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "/uri"); + channel.writeInbound(request); + + DefaultHttpContent content1 = null; + if (randomBoolean()) { + content1 = new DefaultHttpContent(Unpooled.buffer(4)); + channel.writeInbound(content1); + } + + assertThat(header.get(), sameInstance(request)); + // channel is paused + assertThat(channel.readInbound(), nullValue()); + assertFalse(channel.config().isAutoRead()); + + // channel is resumed + listener.get().onResponse(null); + channel.runPendingTasks(); + + // resumed channel after successful validation forwards data + assertTrue(channel.config().isAutoRead()); + assertThat(netty4HttpHeaderValidator.getState(), equalTo(FORWARDING_DATA_UNTIL_NEXT_REQUEST)); + // write more content to the channel after validation passed + DefaultHttpContent content2 = new DefaultHttpContent(Unpooled.buffer(4)); + channel.writeInbound(content2); + assertThat(channel.readInbound(), sameInstance(request)); + DefaultHttpContent content3 = new DefaultHttpContent(Unpooled.buffer(4)); + channel.writeInbound(content3); + if (content1 != null) { + assertThat(channel.readInbound(), sameInstance(content1)); + assertThat(content1.refCnt(), equalTo(1)); + } + assertThat(channel.readInbound(), sameInstance(content2)); + assertThat(content2.refCnt(), equalTo(1)); + DefaultHttpContent content4 = null; + if (randomBoolean()) { + content4 = new DefaultHttpContent(Unpooled.buffer(4)); + channel.writeInbound(content4); + } + assertThat(channel.readInbound(), sameInstance(content3)); + assertThat(content3.refCnt(), equalTo(1)); + if (content4 != null) { + assertThat(channel.readInbound(), sameInstance(content4)); + assertThat(content4.refCnt(), equalTo(1)); + } + + // channel continues in resumed state after request finishes + DefaultLastHttpContent lastContent = new DefaultLastHttpContent(Unpooled.buffer(4)); + channel.writeInbound(lastContent); + + assertTrue(channel.config().isAutoRead()); + assertThat(netty4HttpHeaderValidator.getState(), equalTo(WAITING_TO_START)); + assertThat(channel.readInbound(), sameInstance(lastContent)); + assertThat(lastContent.refCnt(), equalTo(1)); + + if (randomBoolean()) { + channel.writeInbound(request); + assertFalse(channel.config().isAutoRead()); + assertThat(netty4HttpHeaderValidator.getState(), equalTo(QUEUEING_DATA)); + } + } + + public void testContentDroppedAfterValidationFailure() { + assertTrue(channel.config().isAutoRead()); + assertThat(netty4HttpHeaderValidator.getState(), equalTo(WAITING_TO_START)); + + final DefaultHttpRequest request = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "/uri"); + channel.writeInbound(request); + + DefaultHttpContent content1 = null; + if (randomBoolean()) { + content1 = new DefaultHttpContent(Unpooled.buffer(4)); + channel.writeInbound(content1); + } + + assertThat(header.get(), sameInstance(request)); + // channel is paused + assertThat(channel.readInbound(), nullValue()); + assertFalse(channel.config().isAutoRead()); + + // channel is resumed + listener.get().onFailure(new ElasticsearchException("Boom")); + channel.runPendingTasks(); + + // resumed channel after failed validation drops data + assertTrue(channel.config().isAutoRead()); + assertThat(netty4HttpHeaderValidator.getState(), equalTo(DROPPING_DATA_UNTIL_NEXT_REQUEST)); + // write more content to the channel after validation passed + DefaultHttpContent content2 = new DefaultHttpContent(Unpooled.buffer(4)); + channel.writeInbound(content2); + assertThat(channel.readInbound(), sameInstance(request)); + DefaultHttpContent content3 = new DefaultHttpContent(Unpooled.buffer(4)); + channel.writeInbound(content3); + if (content1 != null) { + assertThat(channel.readInbound(), nullValue()); + assertThat(content1.refCnt(), equalTo(0)); + } + assertThat(channel.readInbound(), nullValue()); // content2 + assertThat(content2.refCnt(), equalTo(0)); + DefaultHttpContent content4 = null; + if (randomBoolean()) { + content4 = new DefaultHttpContent(Unpooled.buffer(4)); + channel.writeInbound(content4); + } + assertThat(channel.readInbound(), nullValue()); // content3 + assertThat(content3.refCnt(), equalTo(0)); + if (content4 != null) { + assertThat(channel.readInbound(), nullValue()); + assertThat(content4.refCnt(), equalTo(0)); + } + + assertThat(channel.readInbound(), nullValue()); // extra read still returns "null" + + // channel continues in resumed state after request finishes + DefaultLastHttpContent lastContent = new DefaultLastHttpContent(Unpooled.buffer(4)); + channel.writeInbound(lastContent); + + assertTrue(channel.config().isAutoRead()); + assertThat(netty4HttpHeaderValidator.getState(), equalTo(WAITING_TO_START)); + assertThat(channel.readInbound(), nullValue()); // lastContent + assertThat(lastContent.refCnt(), equalTo(0)); + + if (randomBoolean()) { + channel.writeInbound(new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "/uri")); + assertFalse(channel.config().isAutoRead()); + assertThat(netty4HttpHeaderValidator.getState(), equalTo(QUEUEING_DATA)); + } + } + + public void testValidationErrorForwardsAsDecoderErrorMessage() { + for (Exception exception : List.of( + new Exception("Failure"), + new ElasticsearchException("Failure"), + new ElasticsearchSecurityException("Failure") + )) { + assertTrue(channel.config().isAutoRead()); + assertThat(netty4HttpHeaderValidator.getState(), equalTo(WAITING_TO_START)); + + final DefaultHttpRequest request = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "/uri"); + final DefaultHttpContent content = new DefaultHttpContent(Unpooled.buffer(4)); + channel.writeInbound(request); + channel.writeInbound(content); + + assertThat(header.get(), sameInstance(request)); + assertThat(channel.readInbound(), nullValue()); + assertFalse(channel.config().isAutoRead()); + + listener.get().onFailure(exception); + channel.runPendingTasks(); + assertTrue(channel.config().isAutoRead()); + DefaultHttpRequest failed = channel.readInbound(); + assertThat(failed, sameInstance(request)); + assertThat(failed.headers().get(HttpHeaderNames.CONNECTION), nullValue()); + assertTrue(failed.decoderResult().isFailure()); + Exception cause = (Exception) failed.decoderResult().cause(); + assertThat(cause, equalTo(exception)); + assertThat(netty4HttpHeaderValidator.getState(), equalTo(DROPPING_DATA_UNTIL_NEXT_REQUEST)); + + assertThat(channel.readInbound(), nullValue()); + assertThat(content.refCnt(), equalTo(0)); + + DefaultLastHttpContent lastContent = new DefaultLastHttpContent(Unpooled.buffer(4)); + channel.writeInbound(lastContent); + assertTrue(channel.config().isAutoRead()); + assertThat(netty4HttpHeaderValidator.getState(), equalTo(WAITING_TO_START)); + assertThat(channel.readInbound(), nullValue()); + assertThat(lastContent.refCnt(), equalTo(0)); + + reset(); + } + } + + public void testValidationHandlesMultipleQueuedUpMessages() { + assertTrue(channel.config().isAutoRead()); + assertThat(netty4HttpHeaderValidator.getState(), equalTo(WAITING_TO_START)); + + final DefaultHttpRequest request1 = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "/uri"); + DefaultHttpContent content1 = new DefaultHttpContent(Unpooled.buffer(4)); + DefaultLastHttpContent lastContent1 = new DefaultLastHttpContent(Unpooled.buffer(4)); + channel.writeInbound(request1); + channel.writeInbound(content1); + channel.writeInbound(lastContent1); + final DefaultHttpRequest request2 = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "/uri"); + DefaultHttpContent content2 = new DefaultHttpContent(Unpooled.buffer(4)); + DefaultLastHttpContent lastContent2 = new DefaultLastHttpContent(Unpooled.buffer(4)); + channel.writeInbound(request2); + channel.writeInbound(content2); + channel.writeInbound(lastContent2); + + assertThat(header.get(), sameInstance(request1)); + assertThat(channel.readInbound(), nullValue()); + assertFalse(channel.config().isAutoRead()); + assertThat(netty4HttpHeaderValidator.getState(), equalTo(QUEUEING_DATA)); + + listener.get().onResponse(null); + channel.runPendingTasks(); + assertThat(channel.readInbound(), sameInstance(request1)); + assertThat(channel.readInbound(), sameInstance(content1)); + assertThat(channel.readInbound(), sameInstance(lastContent1)); + assertThat(content1.refCnt(), equalTo(1)); + assertThat(lastContent1.refCnt(), equalTo(1)); + + assertThat(header.get(), sameInstance(request2)); + + assertFalse(channel.config().isAutoRead()); + assertThat(netty4HttpHeaderValidator.getState(), equalTo(QUEUEING_DATA)); + assertThat(channel.readInbound(), nullValue()); + + listener.get().onResponse(null); + channel.runPendingTasks(); + assertThat(channel.readInbound(), sameInstance(request2)); + assertThat(channel.readInbound(), sameInstance(content2)); + assertThat(channel.readInbound(), sameInstance(lastContent2)); + assertThat(content2.refCnt(), equalTo(1)); + assertThat(lastContent2.refCnt(), equalTo(1)); + + assertTrue(channel.config().isAutoRead()); + assertThat(netty4HttpHeaderValidator.getState(), equalTo(WAITING_TO_START)); + assertThat(channel.readInbound(), nullValue()); + } + + public void testValidationFailureRecoversForEnqueued() { + assertTrue(channel.config().isAutoRead()); + assertThat(netty4HttpHeaderValidator.getState(), equalTo(WAITING_TO_START)); + + // write 2 requests before validation for the first one fails + final DefaultHttpRequest request1 = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "/uri"); + DefaultHttpContent content1 = new DefaultHttpContent(Unpooled.buffer(4)); + DefaultLastHttpContent lastContent1 = new DefaultLastHttpContent(Unpooled.buffer(4)); + channel.writeInbound(request1); + channel.writeInbound(content1); + channel.writeInbound(lastContent1); + final DefaultHttpRequest request2 = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "/uri"); + DefaultHttpContent content2 = new DefaultHttpContent(Unpooled.buffer(4)); + DefaultLastHttpContent lastContent2 = new DefaultLastHttpContent(Unpooled.buffer(4)); + channel.writeInbound(request2); + channel.writeInbound(content2); + + boolean finishSecondRequest = randomBoolean(); + if (finishSecondRequest) { + channel.writeInbound(lastContent2); + } + + // channel is paused and both requests are queued + assertThat(header.get(), sameInstance(request1)); + assertThat(channel.readInbound(), nullValue()); + assertFalse(channel.config().isAutoRead()); + assertThat(netty4HttpHeaderValidator.getState(), equalTo(QUEUEING_DATA)); + assertThat(content1.refCnt(), equalTo(2)); + assertThat(lastContent1.refCnt(), equalTo(2)); + assertThat(content2.refCnt(), equalTo(2)); + if (finishSecondRequest) { + assertThat(lastContent2.refCnt(), equalTo(2)); + } + + // validation for the 1st request FAILS + Exception exception = new ElasticsearchException("Boom"); + listener.get().onFailure(exception); + channel.runPendingTasks(); + + // request1 becomes a decoder exception and its content is dropped + assertThat(channel.readInbound(), sameInstance(request1)); + assertThat(request1.headers().get(HttpHeaderNames.CONNECTION), nullValue()); + assertTrue(request1.decoderResult().isFailure()); + Exception cause = (Exception) request1.decoderResult().cause(); + assertThat(cause, equalTo(exception)); + assertThat(content1.refCnt(), equalTo(0)); // content is dropped + assertThat(lastContent1.refCnt(), equalTo(0)); // content is dropped + assertThat(channel.readInbound(), nullValue()); + + // channel pauses for the validation of the 2nd request + assertThat(header.get(), sameInstance(request2)); + assertFalse(channel.config().isAutoRead()); + assertThat(netty4HttpHeaderValidator.getState(), equalTo(QUEUEING_DATA)); + assertThat(channel.readInbound(), nullValue()); + + // validation for the 2nd request SUCCEEDS + listener.get().onResponse(null); + channel.runPendingTasks(); + + // 2nd request is forwarded correctly + assertThat(channel.readInbound(), sameInstance(request2)); + assertThat(channel.readInbound(), sameInstance(content2)); + assertThat(content2.refCnt(), equalTo(1)); + + if (finishSecondRequest == false) { + assertThat(netty4HttpHeaderValidator.getState(), equalTo(FORWARDING_DATA_UNTIL_NEXT_REQUEST)); + assertTrue(channel.config().isAutoRead()); + assertThat(channel.readInbound(), nullValue()); + // while in forwarding state the request can continue + if (randomBoolean()) { + DefaultHttpContent content = new DefaultHttpContent(Unpooled.buffer(4)); + channel.writeInbound(content); + assertThat(channel.readInbound(), sameInstance(content)); + assertThat(content.refCnt(), equalTo(1)); + } + channel.writeInbound(lastContent2); + } + + assertThat(channel.readInbound(), sameInstance(lastContent2)); + assertThat(lastContent2.refCnt(), equalTo(1)); + assertThat(netty4HttpHeaderValidator.getState(), equalTo(WAITING_TO_START)); + assertTrue(channel.config().isAutoRead()); + } + + public void testValidationFailureRecoversForInbound() { + assertTrue(channel.config().isAutoRead()); + assertThat(netty4HttpHeaderValidator.getState(), equalTo(WAITING_TO_START)); + + // write a single request, but don't finish it yet, for which the validation fails + final DefaultHttpRequest request1 = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "/uri"); + DefaultHttpContent content1 = new DefaultHttpContent(Unpooled.buffer(4)); + channel.writeInbound(request1); + channel.writeInbound(content1); + + // channel is paused and the request is queued + assertThat(header.get(), sameInstance(request1)); + assertThat(channel.readInbound(), nullValue()); + assertFalse(channel.config().isAutoRead()); + assertThat(netty4HttpHeaderValidator.getState(), equalTo(QUEUEING_DATA)); + assertThat(content1.refCnt(), equalTo(2)); + + // validation for the 1st request FAILS + Exception exception = new ElasticsearchException("Boom"); + listener.get().onFailure(exception); + channel.runPendingTasks(); + + // request1 becomes a decoder exception and its content is dropped + assertThat(channel.readInbound(), sameInstance(request1)); + assertThat(request1.headers().get(HttpHeaderNames.CONNECTION), nullValue()); + assertTrue(request1.decoderResult().isFailure()); + Exception cause = (Exception) request1.decoderResult().cause(); + assertThat(cause, equalTo(exception)); + assertThat(content1.refCnt(), equalTo(0)); // content is dropped + assertThat(channel.readInbound(), nullValue()); + assertThat(netty4HttpHeaderValidator.getState(), equalTo(DROPPING_DATA_UNTIL_NEXT_REQUEST)); + + if (randomBoolean()) { + channel.writeInbound(new DefaultHttpContent(Unpooled.buffer(4))); + } + DefaultLastHttpContent lastContent1 = new DefaultLastHttpContent(Unpooled.buffer(4)); + channel.writeInbound(lastContent1); + if (randomBoolean()) { + assertThat(channel.readInbound(), nullValue()); + } + assertThat(lastContent1.refCnt(), equalTo(0)); // content is dropped + + // write 2nd request after the 1st one failed validation + final DefaultHttpRequest request2 = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "/uri"); + DefaultHttpContent content2 = new DefaultHttpContent(Unpooled.buffer(4)); + DefaultLastHttpContent lastContent2 = new DefaultLastHttpContent(Unpooled.buffer(4)); + channel.writeInbound(request2); + channel.writeInbound(content2); + boolean finishSecondRequest = randomBoolean(); + if (finishSecondRequest) { + channel.writeInbound(lastContent2); + } + + // channel pauses for the validation of the 2nd request + assertThat(header.get(), sameInstance(request2)); + assertFalse(channel.config().isAutoRead()); + assertThat(netty4HttpHeaderValidator.getState(), equalTo(QUEUEING_DATA)); + assertThat(channel.readInbound(), nullValue()); + + // validation for the 2nd request SUCCEEDS + listener.get().onResponse(null); + channel.runPendingTasks(); + + // 2nd request is forwarded correctly + assertThat(channel.readInbound(), sameInstance(request2)); + assertThat(channel.readInbound(), sameInstance(content2)); + assertThat(content2.refCnt(), equalTo(1)); + + if (finishSecondRequest == false) { + assertThat(netty4HttpHeaderValidator.getState(), equalTo(FORWARDING_DATA_UNTIL_NEXT_REQUEST)); + assertTrue(channel.config().isAutoRead()); + assertThat(channel.readInbound(), nullValue()); + // while in forwarding state the request can continue + if (randomBoolean()) { + DefaultHttpContent content = new DefaultHttpContent(Unpooled.buffer(4)); + channel.writeInbound(content); + assertThat(channel.readInbound(), sameInstance(content)); + assertThat(content.refCnt(), equalTo(1)); + } + channel.writeInbound(lastContent2); + } + + assertThat(channel.readInbound(), sameInstance(lastContent2)); + assertThat(lastContent2.refCnt(), equalTo(1)); + assertThat(netty4HttpHeaderValidator.getState(), equalTo(WAITING_TO_START)); + assertTrue(channel.config().isAutoRead()); + } + + public void testValidationSuccessForLargeMessage() { + assertTrue(channel.config().isAutoRead()); + + final DefaultHttpRequest request = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "/uri"); + channel.writeInbound(request); + + int messageLength = randomIntBetween(32, 128); + for (int i = 0; i < messageLength; ++i) { + channel.writeInbound(new DefaultHttpContent(Unpooled.buffer(4))); + } + channel.writeInbound(new DefaultLastHttpContent(Unpooled.buffer(4))); + boolean followupRequest = randomBoolean(); + if (followupRequest) { + channel.writeInbound(request); + } + + assertThat(header.get(), sameInstance(request)); + assertThat(channel.readInbound(), nullValue()); + assertFalse(channel.config().isAutoRead()); + + listener.get().onResponse(null); + channel.runPendingTasks(); + if (followupRequest) { + assertFalse(channel.config().isAutoRead()); + assertThat(netty4HttpHeaderValidator.getState(), equalTo(QUEUEING_DATA)); + } else { + assertTrue(channel.config().isAutoRead()); + assertThat(netty4HttpHeaderValidator.getState(), equalTo(WAITING_TO_START)); + } + assertThat(channel.readInbound(), sameInstance(request)); + for (int i = 0; i < messageLength; ++i) { + Object content = channel.readInbound(); + assertThat(content, instanceOf(DefaultHttpContent.class)); + assertThat(((DefaultHttpContent) content).refCnt(), equalTo(1)); + } + assertThat(channel.readInbound(), instanceOf(LastHttpContent.class)); + assertThat(channel.readInbound(), nullValue()); + } + + public void testValidationFailureForLargeMessage() { + assertTrue(channel.config().isAutoRead()); + + final DefaultHttpRequest request = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "/uri"); + channel.writeInbound(request); + + int messageLength = randomIntBetween(32, 128); + DefaultHttpContent[] messageContents = new DefaultHttpContent[messageLength]; + for (int i = 0; i < messageLength; ++i) { + messageContents[i] = new DefaultHttpContent(Unpooled.buffer(4)); + channel.writeInbound(messageContents[i]); + } + DefaultLastHttpContent lastHttpContent = new DefaultLastHttpContent(Unpooled.buffer(4)); + channel.writeInbound(lastHttpContent); + boolean followupRequest = randomBoolean(); + if (followupRequest) { + channel.writeInbound(new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "/uri")); + } + + assertThat(header.get(), sameInstance(request)); + assertThat(channel.readInbound(), nullValue()); + assertFalse(channel.config().isAutoRead()); + + Exception exception = new ElasticsearchException("Boom"); + listener.get().onFailure(exception); + channel.runPendingTasks(); + if (followupRequest) { + assertFalse(channel.config().isAutoRead()); + assertThat(netty4HttpHeaderValidator.getState(), equalTo(QUEUEING_DATA)); + } else { + assertTrue(channel.config().isAutoRead()); + assertThat(netty4HttpHeaderValidator.getState(), equalTo(WAITING_TO_START)); + } + assertThat(channel.readInbound(), sameInstance(request)); + assertThat(request.headers().get(HttpHeaderNames.CONNECTION), nullValue()); + assertTrue(request.decoderResult().isFailure()); + Exception cause = (Exception) request.decoderResult().cause(); + assertThat(cause, equalTo(exception)); + for (int i = 0; i < messageLength; ++i) { + assertThat(channel.readInbound(), nullValue()); + assertThat(messageContents[i].refCnt(), equalTo(0)); + } + assertThat(channel.readInbound(), nullValue()); + assertThat(lastHttpContent.refCnt(), equalTo(0)); + assertThat(channel.readInbound(), nullValue()); + } +} diff --git a/modules/transport-netty4/src/test/java/org/elasticsearch/http/netty4/Netty4HttpServerPipeliningTests.java b/modules/transport-netty4/src/test/java/org/elasticsearch/http/netty4/Netty4HttpServerPipeliningTests.java index 42573ea4fe3fb..4040637a04e82 100644 --- a/modules/transport-netty4/src/test/java/org/elasticsearch/http/netty4/Netty4HttpServerPipeliningTests.java +++ b/modules/transport-netty4/src/test/java/org/elasticsearch/http/netty4/Netty4HttpServerPipeliningTests.java @@ -107,7 +107,8 @@ class CustomNettyHttpServerTransport extends Netty4HttpServerTransport { new SharedGroupFactory(settings), Tracer.NOOP, TLSConfig.noTLS(), - null + null, + randomFrom(Netty4HttpHeaderValidator.NOOP_VALIDATOR, null) ); } diff --git a/modules/transport-netty4/src/test/java/org/elasticsearch/http/netty4/Netty4HttpServerTransportTests.java b/modules/transport-netty4/src/test/java/org/elasticsearch/http/netty4/Netty4HttpServerTransportTests.java index 8fd764299cfe6..db3da9364292d 100644 --- a/modules/transport-netty4/src/test/java/org/elasticsearch/http/netty4/Netty4HttpServerTransportTests.java +++ b/modules/transport-netty4/src/test/java/org/elasticsearch/http/netty4/Netty4HttpServerTransportTests.java @@ -177,7 +177,8 @@ public void dispatchBadRequest(RestChannel channel, ThreadContext threadContext, new SharedGroupFactory(settings), Tracer.NOOP, TLSConfig.noTLS(), - null + null, + randomFrom(Netty4HttpHeaderValidator.NOOP_VALIDATOR, null) ) ) { transport.start(); @@ -228,7 +229,8 @@ public void testBindUnavailableAddress() { new SharedGroupFactory(Settings.EMPTY), Tracer.NOOP, TLSConfig.noTLS(), - null + null, + randomFrom(Netty4HttpHeaderValidator.NOOP_VALIDATOR, null) ) ) { transport.start(); @@ -248,7 +250,8 @@ public void testBindUnavailableAddress() { new SharedGroupFactory(settings), Tracer.NOOP, TLSConfig.noTLS(), - null + null, + randomFrom(Netty4HttpHeaderValidator.NOOP_VALIDATOR, null) ) ) { BindHttpException bindHttpException = expectThrows(BindHttpException.class, otherTransport::start); @@ -302,7 +305,8 @@ public void dispatchBadRequest(final RestChannel channel, final ThreadContext th new SharedGroupFactory(settings), Tracer.NOOP, TLSConfig.noTLS(), - null + null, + randomFrom(Netty4HttpHeaderValidator.NOOP_VALIDATOR, null) ) ) { transport.start(); @@ -372,11 +376,18 @@ public void dispatchBadRequest(final RestChannel channel, final ThreadContext th new SharedGroupFactory(Settings.EMPTY), Tracer.NOOP, TLSConfig.noTLS(), - null + null, + randomFrom(Netty4HttpHeaderValidator.NOOP_VALIDATOR, null) ) { @Override public ChannelHandler configureServerChannelHandler() { - return new HttpChannelHandler(this, handlingSettings, TLSConfig.noTLS(), null) { + return new HttpChannelHandler( + this, + handlingSettings, + TLSConfig.noTLS(), + null, + randomFrom(Netty4HttpHeaderValidator.NOOP_VALIDATOR, null) + ) { @Override protected void initChannel(Channel ch) throws Exception { super.initChannel(ch); @@ -471,7 +482,8 @@ public void dispatchBadRequest(final RestChannel channel, final ThreadContext th new SharedGroupFactory(settings), Tracer.NOOP, TLSConfig.noTLS(), - null + null, + randomFrom(Netty4HttpHeaderValidator.NOOP_VALIDATOR, null) ) ) { transport.start(); @@ -543,7 +555,8 @@ public void dispatchBadRequest(final RestChannel channel, final ThreadContext th new SharedGroupFactory(settings), Tracer.NOOP, TLSConfig.noTLS(), - null + null, + randomFrom(Netty4HttpHeaderValidator.NOOP_VALIDATOR, null) ) ) { transport.start(); @@ -615,7 +628,8 @@ public void dispatchBadRequest(final RestChannel channel, final ThreadContext th new SharedGroupFactory(settings), Tracer.NOOP, TLSConfig.noTLS(), - null + null, + randomFrom(Netty4HttpHeaderValidator.NOOP_VALIDATOR, null) ) ) { transport.start(); diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java index 86390c2918df9..959d803f95045 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java @@ -50,6 +50,7 @@ import org.elasticsearch.env.NodeEnvironment; import org.elasticsearch.env.NodeMetadata; import org.elasticsearch.http.HttpServerTransport; +import org.elasticsearch.http.netty4.Netty4HttpHeaderValidator; import org.elasticsearch.http.netty4.Netty4HttpServerTransport; import org.elasticsearch.index.IndexModule; import org.elasticsearch.indices.SystemIndexDescriptor; @@ -1633,7 +1634,8 @@ public boolean test(String profile, InetSocketAddress peerAddress) { getNettySharedGroupFactory(settings), tracer, new TLSConfig(sslConfiguration, sslService::createSSLEngine), - acceptPredicate + acceptPredicate, + Netty4HttpHeaderValidator.NOOP_VALIDATOR ); }); return httpTransports; diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/transport/netty4/SecurityNetty4HttpServerTransportTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/transport/netty4/SecurityNetty4HttpServerTransportTests.java index 6be9b738aaaf5..6887fb6208a40 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/transport/netty4/SecurityNetty4HttpServerTransportTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/transport/netty4/SecurityNetty4HttpServerTransportTests.java @@ -18,6 +18,7 @@ import org.elasticsearch.env.TestEnvironment; import org.elasticsearch.http.AbstractHttpServerTransportTestCase; import org.elasticsearch.http.NullDispatcher; +import org.elasticsearch.http.netty4.Netty4HttpHeaderValidator; import org.elasticsearch.http.netty4.Netty4HttpServerTransport; import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.tracing.Tracer; @@ -77,7 +78,8 @@ public void testDefaultClientAuth() throws Exception { new SharedGroupFactory(settings), Tracer.NOOP, new TLSConfig(sslService.getHttpTransportSSLConfiguration(), sslService::createSSLEngine), - null + null, + randomFrom(Netty4HttpHeaderValidator.NOOP_VALIDATOR, null) ); ChannelHandler handler = transport.configureServerChannelHandler(); final EmbeddedChannel ch = new EmbeddedChannel(handler); @@ -103,7 +105,8 @@ public void testOptionalClientAuth() throws Exception { new SharedGroupFactory(settings), Tracer.NOOP, new TLSConfig(sslService.getHttpTransportSSLConfiguration(), sslService::createSSLEngine), - null + null, + randomFrom(Netty4HttpHeaderValidator.NOOP_VALIDATOR, null) ); ChannelHandler handler = transport.configureServerChannelHandler(); final EmbeddedChannel ch = new EmbeddedChannel(handler); @@ -129,7 +132,8 @@ public void testRequiredClientAuth() throws Exception { new SharedGroupFactory(settings), Tracer.NOOP, new TLSConfig(sslService.getHttpTransportSSLConfiguration(), sslService::createSSLEngine), - null + null, + randomFrom(Netty4HttpHeaderValidator.NOOP_VALIDATOR, null) ); ChannelHandler handler = transport.configureServerChannelHandler(); final EmbeddedChannel ch = new EmbeddedChannel(handler); @@ -155,7 +159,8 @@ public void testNoClientAuth() throws Exception { new SharedGroupFactory(settings), Tracer.NOOP, new TLSConfig(sslService.getHttpTransportSSLConfiguration(), sslService::createSSLEngine), - null + null, + randomFrom(Netty4HttpHeaderValidator.NOOP_VALIDATOR, null) ); ChannelHandler handler = transport.configureServerChannelHandler(); final EmbeddedChannel ch = new EmbeddedChannel(handler); @@ -176,7 +181,8 @@ public void testCustomSSLConfiguration() throws Exception { new SharedGroupFactory(settings), Tracer.NOOP, new TLSConfig(sslService.getHttpTransportSSLConfiguration(), sslService::createSSLEngine), - null + null, + randomFrom(Netty4HttpHeaderValidator.NOOP_VALIDATOR, null) ); ChannelHandler handler = transport.configureServerChannelHandler(); EmbeddedChannel ch = new EmbeddedChannel(handler); @@ -198,7 +204,8 @@ public void testCustomSSLConfiguration() throws Exception { new SharedGroupFactory(settings), Tracer.NOOP, new TLSConfig(sslService.getHttpTransportSSLConfiguration(), sslService::createSSLEngine), - null + null, + randomFrom(Netty4HttpHeaderValidator.NOOP_VALIDATOR, null) ); handler = transport.configureServerChannelHandler(); ch = new EmbeddedChannel(handler); @@ -229,7 +236,8 @@ public void testNoExceptionWhenConfiguredWithoutSslKeySSLDisabled() throws Excep new SharedGroupFactory(settings), Tracer.NOOP, new TLSConfig(sslService.getHttpTransportSSLConfiguration(), sslService::createSSLEngine), - null + null, + randomFrom(Netty4HttpHeaderValidator.NOOP_VALIDATOR, null) ); assertNotNull(transport.configureServerChannelHandler()); }