Skip to content

Commit 88a6da5

Browse files
authored
Add support for Forwarded-For and Real-IP headers in HTTP inputs (#22872)
* wip initial push for forwarded-for and real-ip headers in http inputs * add changelog * improve parameter descriptions * add unit tests for forwarded/real ip headers add hashcode/equals to resolvable inet socket address wrapper, comparing them in tests add RawMessageHandler test fix subnet trusted proxy code (yay tests) * add license headers * add missing locale * the channel handlers deal with FullHttpMessage, switch the type to make it clear FullHttpMessage objects need to be retained, because they contain the body as well (the reason we switched the type) guard against invalid ip literals and log a warning in case we cannot parse the ip address debug log if we don't know any relaying proxy * also check channel remote address if we valid trusted proxies, some proxies don't include themselves in the forwarded-for list
1 parent 5547fdd commit 88a6da5

File tree

9 files changed

+688
-7
lines changed

9 files changed

+688
-7
lines changed

changelog/unreleased/pr-22872.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
type = "a"
2+
message = "Add support for X-Forwarded-For/Forwarded/X-Real-IP type headers in HTTP inputs to better support proxied connections"
3+
4+
pulls = ["22872"]

graylog2-server/src/main/java/org/graylog2/inputs/transports/AbstractHttpTransport.java

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,10 @@
2626
import io.netty.handler.codec.http.HttpResponseEncoder;
2727
import io.netty.handler.timeout.ReadTimeoutHandler;
2828
import jakarta.annotation.Nullable;
29+
import jakarta.inject.Named;
2930
import org.graylog2.configuration.TLSProtocolsConfiguration;
3031
import org.graylog2.inputs.transports.netty.EventLoopGroupFactory;
32+
import org.graylog2.inputs.transports.netty.HttpForwardedForHandler;
3133
import org.graylog2.inputs.transports.netty.HttpHandler;
3234
import org.graylog2.inputs.transports.netty.LenientDelimiterBasedFrameDecoder;
3335
import org.graylog2.plugin.InputFailureRecorder;
@@ -43,8 +45,10 @@
4345
import org.graylog2.plugin.inputs.annotations.ConfigClass;
4446
import org.graylog2.plugin.inputs.transports.AbstractTcpTransport;
4547
import org.graylog2.plugin.inputs.util.ThroughputCounter;
48+
import org.graylog2.utilities.IpSubnet;
4649

4750
import java.util.LinkedHashMap;
51+
import java.util.Set;
4852
import java.util.concurrent.Callable;
4953
import java.util.concurrent.TimeUnit;
5054

@@ -66,22 +70,33 @@ abstract public class AbstractHttpTransport extends AbstractTcpTransport {
6670
static final String CK_AUTHORIZATION_HEADER_VALUE = "authorization_header_value";
6771
private static final String AUTHORIZATION_HEADER_NAME_LABEL = "Authorization Header Name";
6872
private static final String AUTHORIZATION_HEADER_VALUE_LABEL = "Authorization Header Value";
73+
static final String CK_REAL_IP_HEADER_NAME = "real_ip_header_name";
74+
static final String CK_ENABLE_FORWARDED_FOR = "enable_forwarded_for";
75+
static final String CK_REQUIRE_TRUSTED_PROXIES = "require_trusted_proxies";
76+
static final String CK_ENABLE_REAL_IP_HEADER = "enable_real_ip_header";
6977

7078
protected final boolean enableBulkReceiving;
7179
protected final boolean enableCors;
7280
protected final int maxChunkSize;
7381
private final int idleWriterTimeout;
7482
private final String authorizationHeader;
7583
private final String authorizationHeaderValue;
84+
private final Set<IpSubnet> trustedProxies;
7685
private final String path;
86+
private final boolean enableForwardedFor;
87+
private final boolean requireTrustedProxies;
88+
private final boolean enableRealIpHeader;
89+
private final String realIpHeaders;
7790

7891
public AbstractHttpTransport(Configuration configuration,
7992
EventLoopGroup eventLoopGroup,
8093
EventLoopGroupFactory eventLoopGroupFactory,
8194
NettyTransportConfiguration nettyTransportConfiguration,
8295
ThroughputCounter throughputCounter,
8396
LocalMetricRegistry localRegistry,
84-
TLSProtocolsConfiguration tlsConfiguration, String path) {
97+
TLSProtocolsConfiguration tlsConfiguration,
98+
@Named("trusted_proxies") Set<IpSubnet> trustedProxies,
99+
String path) {
85100
super(configuration,
86101
throughputCounter,
87102
localRegistry,
@@ -95,6 +110,11 @@ public AbstractHttpTransport(Configuration configuration,
95110
this.idleWriterTimeout = configuration.intIsSet(CK_IDLE_WRITER_TIMEOUT) ? configuration.getInt(CK_IDLE_WRITER_TIMEOUT, DEFAULT_IDLE_WRITER_TIMEOUT) : DEFAULT_IDLE_WRITER_TIMEOUT;
96111
this.authorizationHeader = configuration.getString(CK_AUTHORIZATION_HEADER_NAME);
97112
this.authorizationHeaderValue = configuration.getString(CK_AUTHORIZATION_HEADER_VALUE);
113+
this.enableForwardedFor = configuration.getBoolean(CK_ENABLE_FORWARDED_FOR);
114+
this.requireTrustedProxies = configuration.getBoolean(CK_REQUIRE_TRUSTED_PROXIES);
115+
this.enableRealIpHeader = configuration.getBoolean(CK_ENABLE_REAL_IP_HEADER);
116+
this.realIpHeaders = configuration.getString(CK_REAL_IP_HEADER_NAME);
117+
this.trustedProxies = trustedProxies;
98118
this.path = path;
99119
}
100120

@@ -120,6 +140,7 @@ protected LinkedHashMap<String, Callable<? extends ChannelHandler>> getCustomChi
120140
handlers.put("decompressor", HttpContentDecompressor::new);
121141
handlers.put("encoder", HttpResponseEncoder::new);
122142
handlers.put("aggregator", () -> new HttpObjectAggregator(maxChunkSize));
143+
handlers.put("http-forwarded-for-handler", () -> new HttpForwardedForHandler(enableForwardedFor, enableRealIpHeader, realIpHeaders, requireTrustedProxies, trustedProxies));
123144
handlers.put("http-handler", () -> new HttpHandler(enableCors, authorizationHeader, authorizationHeaderValue, path));
124145
if (enableBulkReceiving) {
125146
handlers.put("http-bulk-newline-decoder",
@@ -180,6 +201,31 @@ public ConfigurationRequest getRequestedConfiguration() {
180201
"The secret authorization header value which all request must have in order to authenticate successfully. e.g. Bearer: <api-token>N",
181202
ConfigurationField.Optional.OPTIONAL,
182203
TextField.Attribute.IS_PASSWORD));
204+
r.addField(new BooleanField(
205+
CK_ENABLE_FORWARDED_FOR,
206+
"Take original client IP from X-Forwarded-For or Forwarded headers",
207+
false,
208+
"Parse X-Forwarded-For and Forwarded (RFC 7239) headers to find the original client IP address, when relayed through proxies or load balancers."
209+
));
210+
r.addField(new BooleanField(
211+
CK_REQUIRE_TRUSTED_PROXIES,
212+
"Only allow trusted proxies",
213+
false,
214+
"Check all relaying proxies in forwarded-for headers to match the server configuration's trusted_proxies setting."
215+
));
216+
r.addField(new BooleanField(
217+
CK_ENABLE_REAL_IP_HEADER,
218+
"Enable trusting the original client IP header(s)",
219+
false,
220+
"If enabled try to find the original client IP in one of the specified 'real ip' headers."
221+
));
222+
r.addField(new TextField(
223+
CK_REAL_IP_HEADER_NAME,
224+
"Header(s) containing the original client IP",
225+
"X-Real-IP, X-Client-IP, CF-Connecting-IP, True-Client-IP, Fastly-Client-IP",
226+
"Some services and proxies set the first non-proxy IP address in a special header, these headers are checked in order if specified. Separate multiple headers by comma.",
227+
ConfigurationField.Optional.OPTIONAL
228+
));
183229
return r;
184230
}
185231
}

graylog2-server/src/main/java/org/graylog2/inputs/transports/HttpTransport.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import com.google.inject.assistedinject.Assisted;
2020
import com.google.inject.assistedinject.AssistedInject;
2121
import io.netty.channel.EventLoopGroup;
22+
import jakarta.inject.Named;
2223
import org.graylog2.configuration.TLSProtocolsConfiguration;
2324
import org.graylog2.inputs.transports.netty.EventLoopGroupFactory;
2425
import org.graylog2.plugin.LocalMetricRegistry;
@@ -27,6 +28,9 @@
2728
import org.graylog2.plugin.inputs.annotations.FactoryClass;
2829
import org.graylog2.plugin.inputs.transports.Transport;
2930
import org.graylog2.plugin.inputs.util.ThroughputCounter;
31+
import org.graylog2.utilities.IpSubnet;
32+
33+
import java.util.Set;
3034

3135
public class HttpTransport extends AbstractHttpTransport {
3236
private static final String PATH = "/gelf";
@@ -38,9 +42,10 @@ public HttpTransport(@Assisted Configuration configuration,
3842
NettyTransportConfiguration nettyTransportConfiguration,
3943
ThroughputCounter throughputCounter,
4044
LocalMetricRegistry localRegistry,
41-
TLSProtocolsConfiguration tlsConfiguration) {
45+
TLSProtocolsConfiguration tlsConfiguration,
46+
@Named("trusted_proxies") Set<IpSubnet> trustedProxies) {
4247
super(configuration, eventLoopGroup, eventLoopGroupFactory, nettyTransportConfiguration, throughputCounter,
43-
localRegistry, tlsConfiguration, PATH);
48+
localRegistry, tlsConfiguration, trustedProxies, PATH);
4449
}
4550

4651
@FactoryClass

graylog2-server/src/main/java/org/graylog2/inputs/transports/RawHttpTransport.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import com.google.inject.assistedinject.Assisted;
2020
import com.google.inject.assistedinject.AssistedInject;
2121
import io.netty.channel.EventLoopGroup;
22+
import jakarta.inject.Named;
2223
import org.graylog2.configuration.TLSProtocolsConfiguration;
2324
import org.graylog2.inputs.transports.netty.EventLoopGroupFactory;
2425
import org.graylog2.plugin.LocalMetricRegistry;
@@ -27,6 +28,9 @@
2728
import org.graylog2.plugin.inputs.annotations.FactoryClass;
2829
import org.graylog2.plugin.inputs.transports.Transport;
2930
import org.graylog2.plugin.inputs.util.ThroughputCounter;
31+
import org.graylog2.utilities.IpSubnet;
32+
33+
import java.util.Set;
3034

3135
/**
3236
* Raw version of the HttpTransport which uses the `/raw` path instead of the `/gelf` path.
@@ -41,9 +45,10 @@ public RawHttpTransport(@Assisted Configuration configuration,
4145
NettyTransportConfiguration nettyTransportConfiguration,
4246
ThroughputCounter throughputCounter,
4347
LocalMetricRegistry localRegistry,
44-
TLSProtocolsConfiguration tlsConfiguration) {
48+
TLSProtocolsConfiguration tlsConfiguration,
49+
@Named("trusted_proxies") Set<IpSubnet> trustedProxies) {
4550
super(configuration, eventLoopGroup, eventLoopGroupFactory, nettyTransportConfiguration,
46-
throughputCounter, localRegistry, tlsConfiguration, PATH);
51+
throughputCounter, localRegistry, tlsConfiguration, trustedProxies, PATH);
4752

4853
}
4954

0 commit comments

Comments
 (0)