Skip to content

Commit bedaf3c

Browse files
Security authn via netty channel validator (#95112)
Hooks "REST" authN, as a "validator", into the new netty channel interceptor for http headers.
1 parent 213f08d commit bedaf3c

33 files changed

+1207
-524
lines changed

docs/changelog/95112.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
pr: 95112
2+
summary: Header validator with Security
3+
area: Authentication
4+
type: enhancement
5+
issues: []

modules/transport-netty4/src/main/java/module-info.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,5 @@
2323

2424
exports org.elasticsearch.http.netty4;
2525
exports org.elasticsearch.transport.netty4;
26+
exports org.elasticsearch.http.netty4.internal to org.elasticsearch.security;
2627
}

modules/transport-netty4/src/main/java/org/elasticsearch/http/netty4/Netty4HttpHeaderValidator.java

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
package org.elasticsearch.http.netty4;
1010

1111
import io.netty.buffer.Unpooled;
12-
import io.netty.channel.Channel;
1312
import io.netty.channel.ChannelHandlerContext;
1413
import io.netty.channel.ChannelInboundHandlerAdapter;
1514
import io.netty.handler.codec.DecoderResult;
@@ -21,8 +20,8 @@
2120

2221
import org.elasticsearch.action.ActionListener;
2322
import org.elasticsearch.action.support.ContextPreservingActionListener;
24-
import org.elasticsearch.common.TriConsumer;
2523
import org.elasticsearch.common.util.concurrent.ThreadContext;
24+
import org.elasticsearch.http.netty4.internal.HttpValidator;
2625
import org.elasticsearch.transport.Transports;
2726

2827
import java.util.ArrayDeque;
@@ -35,17 +34,12 @@
3534

3635
public class Netty4HttpHeaderValidator extends ChannelInboundHandlerAdapter {
3736

38-
public static final TriConsumer<HttpRequest, Channel, ActionListener<Void>> NOOP_VALIDATOR = ((
39-
httpRequest,
40-
channel,
41-
listener) -> listener.onResponse(null));
42-
43-
private final TriConsumer<HttpRequest, Channel, ActionListener<Void>> validator;
37+
private final HttpValidator validator;
4438
private final ThreadContext threadContext;
4539
private ArrayDeque<HttpObject> pending = new ArrayDeque<>(4);
4640
private State state = WAITING_TO_START;
4741

48-
public Netty4HttpHeaderValidator(TriConsumer<HttpRequest, Channel, ActionListener<Void>> validator, ThreadContext threadContext) {
42+
public Netty4HttpHeaderValidator(HttpValidator validator, ThreadContext threadContext) {
4943
this.validator = validator;
5044
this.threadContext = threadContext;
5145
}
@@ -129,7 +123,7 @@ private void requestStart(ChannelHandlerContext ctx) {
129123
);
130124
// this prevents thread-context changes to propagate beyond the validation, as netty worker threads are reused
131125
try (ThreadContext.StoredContext ignore = threadContext.newStoredContext()) {
132-
validator.apply(httpRequest, ctx.channel(), contextPreservingActionListener);
126+
validator.validate(httpRequest, ctx.channel(), contextPreservingActionListener);
133127
}
134128
}
135129
}

modules/transport-netty4/src/main/java/org/elasticsearch/http/netty4/Netty4HttpRequest.java

Lines changed: 54 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
import io.netty.buffer.ByteBuf;
1212
import io.netty.buffer.Unpooled;
1313
import io.netty.handler.codec.http.DefaultFullHttpRequest;
14-
import io.netty.handler.codec.http.DefaultHttpHeaders;
1514
import io.netty.handler.codec.http.FullHttpRequest;
1615
import io.netty.handler.codec.http.HttpHeaderNames;
1716
import io.netty.handler.codec.http.HttpHeaders;
@@ -41,59 +40,35 @@ public class Netty4HttpRequest implements HttpRequest {
4140

4241
private final FullHttpRequest request;
4342
private final BytesReference content;
44-
private final HttpHeadersMap headers;
43+
private final Map<String, List<String>> headers;
4544
private final AtomicBoolean released;
4645
private final Exception inboundException;
4746
private final boolean pooled;
48-
4947
private final int sequence;
5048

5149
Netty4HttpRequest(int sequence, FullHttpRequest request) {
52-
this(
53-
sequence,
54-
request,
55-
new HttpHeadersMap(request.headers()),
56-
new AtomicBoolean(false),
57-
true,
58-
Netty4Utils.toBytesReference(request.content())
59-
);
50+
this(sequence, request, new AtomicBoolean(false), true, Netty4Utils.toBytesReference(request.content()));
6051
}
6152

6253
Netty4HttpRequest(int sequence, FullHttpRequest request, Exception inboundException) {
63-
this(
64-
sequence,
65-
request,
66-
new HttpHeadersMap(request.headers()),
67-
new AtomicBoolean(false),
68-
true,
69-
Netty4Utils.toBytesReference(request.content()),
70-
inboundException
71-
);
54+
this(sequence, request, new AtomicBoolean(false), true, Netty4Utils.toBytesReference(request.content()), inboundException);
7255
}
7356

74-
private Netty4HttpRequest(
75-
int sequence,
76-
FullHttpRequest request,
77-
HttpHeadersMap headers,
78-
AtomicBoolean released,
79-
boolean pooled,
80-
BytesReference content
81-
) {
82-
this(sequence, request, headers, released, pooled, content, null);
57+
private Netty4HttpRequest(int sequence, FullHttpRequest request, AtomicBoolean released, boolean pooled, BytesReference content) {
58+
this(sequence, request, released, pooled, content, null);
8359
}
8460

8561
private Netty4HttpRequest(
8662
int sequence,
8763
FullHttpRequest request,
88-
HttpHeadersMap headers,
8964
AtomicBoolean released,
9065
boolean pooled,
9166
BytesReference content,
9267
Exception inboundException
9368
) {
9469
this.sequence = sequence;
9570
this.request = request;
96-
this.headers = headers;
71+
this.headers = getHttpHeadersAsMap(request.headers());
9772
this.content = content;
9873
this.pooled = pooled;
9974
this.released = released;
@@ -102,36 +77,7 @@ private Netty4HttpRequest(
10277

10378
@Override
10479
public RestRequest.Method method() {
105-
HttpMethod httpMethod = request.method();
106-
if (httpMethod == HttpMethod.GET) return RestRequest.Method.GET;
107-
108-
if (httpMethod == HttpMethod.POST) return RestRequest.Method.POST;
109-
110-
if (httpMethod == HttpMethod.PUT) return RestRequest.Method.PUT;
111-
112-
if (httpMethod == HttpMethod.DELETE) return RestRequest.Method.DELETE;
113-
114-
if (httpMethod == HttpMethod.HEAD) {
115-
return RestRequest.Method.HEAD;
116-
}
117-
118-
if (httpMethod == HttpMethod.OPTIONS) {
119-
return RestRequest.Method.OPTIONS;
120-
}
121-
122-
if (httpMethod == HttpMethod.PATCH) {
123-
return RestRequest.Method.PATCH;
124-
}
125-
126-
if (httpMethod == HttpMethod.TRACE) {
127-
return RestRequest.Method.TRACE;
128-
}
129-
130-
if (httpMethod == HttpMethod.CONNECT) {
131-
return RestRequest.Method.CONNECT;
132-
}
133-
134-
throw new IllegalArgumentException("Unexpected http method: " + httpMethod);
80+
return translateRequestMethod(request.method());
13581
}
13682

13783
@Override
@@ -170,7 +116,6 @@ public HttpRequest releaseAndCopy() {
170116
request.headers(),
171117
request.trailingHeaders()
172118
),
173-
headers,
174119
new AtomicBoolean(false),
175120
false,
176121
Netty4Utils.toBytesReference(copiedContent)
@@ -210,28 +155,19 @@ public HttpVersion protocolVersion() {
210155

211156
@Override
212157
public HttpRequest removeHeader(String header) {
213-
HttpHeaders headersWithoutContentTypeHeader = new DefaultHttpHeaders();
214-
headersWithoutContentTypeHeader.add(request.headers());
215-
headersWithoutContentTypeHeader.remove(header);
216-
HttpHeaders trailingHeaders = new DefaultHttpHeaders();
217-
trailingHeaders.add(request.trailingHeaders());
218-
trailingHeaders.remove(header);
158+
HttpHeaders copiedHeadersWithout = request.headers().copy();
159+
copiedHeadersWithout.remove(header);
160+
HttpHeaders copiedTrailingHeadersWithout = request.trailingHeaders().copy();
161+
copiedTrailingHeadersWithout.remove(header);
219162
FullHttpRequest requestWithoutHeader = new DefaultFullHttpRequest(
220163
request.protocolVersion(),
221164
request.method(),
222165
request.uri(),
223166
request.content(),
224-
headersWithoutContentTypeHeader,
225-
trailingHeaders
226-
);
227-
return new Netty4HttpRequest(
228-
sequence,
229-
requestWithoutHeader,
230-
new HttpHeadersMap(requestWithoutHeader.headers()),
231-
released,
232-
pooled,
233-
content
167+
copiedHeadersWithout,
168+
copiedTrailingHeadersWithout
234169
);
170+
return new Netty4HttpRequest(sequence, requestWithoutHeader, released, pooled, content);
235171
}
236172

237173
@Override
@@ -249,6 +185,46 @@ public Exception getInboundException() {
249185
return inboundException;
250186
}
251187

188+
public io.netty.handler.codec.http.HttpRequest getNettyRequest() {
189+
return request;
190+
}
191+
192+
public static RestRequest.Method translateRequestMethod(HttpMethod httpMethod) {
193+
if (httpMethod == HttpMethod.GET) return RestRequest.Method.GET;
194+
195+
if (httpMethod == HttpMethod.POST) return RestRequest.Method.POST;
196+
197+
if (httpMethod == HttpMethod.PUT) return RestRequest.Method.PUT;
198+
199+
if (httpMethod == HttpMethod.DELETE) return RestRequest.Method.DELETE;
200+
201+
if (httpMethod == HttpMethod.HEAD) {
202+
return RestRequest.Method.HEAD;
203+
}
204+
205+
if (httpMethod == HttpMethod.OPTIONS) {
206+
return RestRequest.Method.OPTIONS;
207+
}
208+
209+
if (httpMethod == HttpMethod.PATCH) {
210+
return RestRequest.Method.PATCH;
211+
}
212+
213+
if (httpMethod == HttpMethod.TRACE) {
214+
return RestRequest.Method.TRACE;
215+
}
216+
217+
if (httpMethod == HttpMethod.CONNECT) {
218+
return RestRequest.Method.CONNECT;
219+
}
220+
221+
throw new IllegalArgumentException("Unexpected http method: " + httpMethod);
222+
}
223+
224+
public static Map<String, List<String>> getHttpHeadersAsMap(HttpHeaders httpHeaders) {
225+
return new HttpHeadersMap(httpHeaders);
226+
}
227+
252228
/**
253229
* A wrapper of {@link HttpHeaders} that implements a map to prevent copying unnecessarily. This class does not support modifications
254230
* and due to the underlying implementation, it performs case insensitive lookups of key to values.

0 commit comments

Comments
 (0)