8000 Use native request to match with trustud-prosies by Dmitrii-Grigorev3 · Pull Request #4075 · spring-cloud/spring-cloud-gateway · GitHub
[go: up one dir, main page]

Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -138,10 +138,11 @@ public int getOrder() {
public HttpHeaders filter(HttpHeaders input, ServerWebExchange exchange) {
ServerHttpRequest request = exchange.getRequest();

if (request.getRemoteAddress() != null
&& !trustedProxies.isTrusted(request.getRemoteAddress().getHostString())) {
InetSocketAddress peerAddress = ForwardedHeadersFilterUtils.extractPeerRemoteAddress(request);

if (peerAddress != null && !trustedProxies.isTrusted(peerAddress.getHostString())) {
log.trace(LogMessage.format("Remote address not trusted. pattern %s remote address %s", trustedProxies,
request.getRemoteAddress()));
peerAddress));
return input;
}

Expand Down Expand Up @@ -177,25 +178,24 @@ public HttpHeaders filter(HttpHeaders input, ServerWebExchange exchange) {
forwarded.put("host", host);
}

InetSocketAddress remoteAddress = request.getRemoteAddress();
// TODO: only add if "remoteAddress" value matches trustedProxies
if (remoteAddress != null) {
if (peerAddress != null) {
// If remoteAddress is unresolved, calling getHostAddress() would cause a
// NullPointerException.
String forValue;
if (remoteAddress.isUnresolved()) {
forValue = remoteAddress.getHostName();
if (peerAddress.isUnresolved()) {
forValue = peerAddress.getHostName();
}
else {
InetAddress address = remoteAddress.getAddress();
forValue = remoteAddress.getAddress().getHostAddress();
InetAddress address = peerAddress.getAddress();
forValue = peerAddress.getAddress().getHostAddress();
if (address instanceof Inet6Address) {
forValue = "[" + forValue + "]";
}
}
if (trustedProxies.isTrusted(forValue)) {
// only add for value if trusted
int port = remoteAddress.getPort();
int port = peerAddress.getPort();
if (port >= 0) {
forValue = forValue + ":" + port;
}
Expand Down
8000
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*
* Copyright 2026-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.springframework.cloud.gateway.filter.headers;

import java.net.InetSocketAddress;

import org.jspecify.annotations.Nullable;

import org.springframework.http.server.reactive.AbstractServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;

/**
* Utility methods for forwarded headers filters.
*
* @author Dmitrii Grigorev
*/
public final class ForwardedHeadersFilterUtils {

private ForwardedHeadersFilterUtils() {
}

/**
* Get the real (peer) remote address by unwrapping to the native request when
* possible.
*/
public static @Nullable InetSocketAddress extractPeerRemoteAddress(ServerHttpRequest request) {
if (hasNativeRequest(request)) {
try {
ServerHttpRequest nativeRequest = ServerHttpRequestDecorator.getNativeRequest(request);
InetSocketAddress remoteAddress = nativeRequest.getRemoteAddress();
if (remoteAddress != null) {
return remoteAddress;
}
}
catch (RuntimeException ignored) {
// e.g. MockServerHttpRequest extends AbstractServerHttpRequest but throws
}
}
return request.getRemoteAddress();
}

private static boolean hasNativeRequest(ServerHttpRequest request) {
return request instanceof ServerHttpRequestDecorator || request instanceof AbstractServerHttpRequest;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package org.springframework.cloud.gateway.filter.headers;

import java.net.InetSocketAddress;
import java.net.URI;
import java.util.LinkedHashSet;
import java.util.List;
Expand Down Expand Up @@ -220,11 +221,11 @@ public void setPrefixAppend(boolean prefixAppend) {
@Override
public HttpHeaders filter(HttpHeaders input, ServerWebExchange exchange) {
ServerHttpRequest request = exchange.getRequest();
InetSocketAddress peerAddress = ForwardedHeadersFilterUtils.extractPeerRemoteAddress(request);

if (request.getRemoteAddress() != null
&& !trustedProxies.isTrusted(request.getRemoteAddress().getHostString())) {
if (peerAddress != null && !trustedProxies.isTrusted(peerAddress.getHostString())) {
log.trace(LogMessage.format("Remote address not trusted. pattern %s remote address %s", trustedProxies,
request.getRemoteAddress()));
peerAddress));
return input;
}

Expand All @@ -237,8 +238,8 @@ public HttpHeaders filter(HttpHeaders input, ServerWebExchange exchange) {

if (isForEnabled()) {
String remoteAddr = null;
if (request.getRemoteAddress() != null && request.getRemoteAddress().getAddress() != null) {
remoteAddr = request.getRemoteAddress().getHostString();
if (peerAddress != null && peerAddress.getAddress() != null) {
remoteAddr = peerAddress.getHostString();
}
// match xforwarded for against trusted proxies
write(updated, X_FORWARDED_FOR_HEADER, remoteAddr, isForAppend(), trustedProxies::isTrusted);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
/*
* Copyright 2026-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.springframework.cloud.gateway.filter.headers;

import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.URI;
import java.util.Objects;

import org.junit.jupiter.api.Test;
import reactor.core.publisher.Flux;

import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpCookie;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.server.reactive.AbstractServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.SslInfo;
import org.springframework.mock.http.server.reactive.MockServerH 4D24 ttpRequest;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;

import static org.assertj.core.api.Assertions.assertThat;

/**
* Tests for {@link ForwardedHeadersFilterUtils}.
*
* @author Dmitrii Grigorev
*/
class ForwardedHeadersFilterUtilsTests {

@Test
void extractRemoteAddressFromNativeRequest() throws Exception {
InetSocketAddress peerAddress = new InetSocketAddress(InetAddress.getByName("1.1.1.1"), 80);
ServerHttpRequest request = new TestServerHttpRequestWithNative(peerAddress);

InetSocketAddress result = ForwardedHeadersFilterUtils.extractPeerRemoteAddress(request);

assertThat(result).isNotNull();
assertThat(result.getHostString()).isEqualTo("1.1.1.1");
assertThat(result.getPort()).isEqualTo(80);
}

@Test
void extractRemoteAddressFromNativeRequestOverrides() throws Exception {
InetSocketAddress peerAddress = new InetSocketAddress(InetAddress.getByName("1.1.1.1"), 80);
ServerHttpRequest nativeRequest = new TestServerHttpRequestWithNative(peerAddress);

InetSocketAddress clientAddress = new InetSocketAddress(InetAddress.getByName("2.2.2.2"), 80);

ServerHttpRequest.Builder builder = nativeRequest.mutate();
// such behaviour is here:
// org.springframework.web.server.adapter.ForwardedHeaderTransformer
builder.remoteAddress(clientAddress);
ServerHttpRequest transformedRequest = builder.build();

InetSocketAddress result = ForwardedHeadersFilterUtils.extractPeerRemoteAddress(transformedRequest);

assertThat(result).isNotNull();
// the transformed request's remote address is overridden, we can't rely on it.
assertThat(Objects.requireNonNull(transformedRequest.getRemoteAddress()).getHostString()).isEqualTo("2.2.2.2");
// only native request's has the real peer remote address
assertThat(result.getHostString()).isEqualTo("1.1.1.1");
assertThat(result.getPort()).isEqualTo(80);
}

@Test
void extractRemoteAddressNull() {
ServerHttpRequest request = MockServerHttpRequest.get("http://localhost/get").build();

InetSocketAddress result = ForwardedHeadersFilterUtils.extractPeerRemoteAddress(request);

assertThat(result).isNull();
}

/**
* Minimal AbstractServerHttpRequest that exposes a native request with a peer remote
* address.
*/
private static final class TestServerHttpRequestWithNative extends AbstractServerHttpRequest {

private final InetSocketAddress nativePeerAddress;

TestServerHttpRequestWithNative(InetSocketAddress nativePeerAddress) {
super(HttpMethod.GET, URI.create("http://localhost/"), null, new HttpHeaders());
this.nativePeerAddress = nativePeerAddress;
}

@Override
protected MultiValueMap<String, HttpCookie> initCookies() {
return new LinkedMultiValueMap<>();
}

@Override
protected SslInfo initSslInfo() {
return null;
}

@Override
public ServerHttpRequest getNativeRequest() {
return MockServerHttpRequest.get("http://localhost/").remoteAddress(this.nativePeerAddress).build();
}

@Override
public InetSocketAddress getRemoteAddress() {
return null;
}

@Override
public Flux<DataBuffer> getBody() {
return Flux.empty();
}

}

}
0