spring-cloud / spring-cloud-gateway

An API Gateway built on Spring Framework and Spring Boot providing routing and more.
http://cloud.spring.io
Apache License 2.0
4.51k stars 3.32k forks source link

No X-Forwarded-For (RemoteIP) header is written due to incompatibility between XForwardedHeadersFilter and ForwardedHeadersTransformer #2648

Open stefanocke opened 2 years ago

stefanocke commented 2 years ago

Describe the bug If ForwardedHeaderTransformer is enabled in Spring Cloud Gateway, X-Forwarded-For Header will not be sent to upstream services

Steps to reproduce

Debugging

Since a while, ForwardedHeaderTransformer in Spring Boot supports X-Forwarded-For header:

remoteAddress = UriComponentsBuilder.parseForwardedFor(request, remoteAddress);

In UriComponentsBuilder.parseForwardedFor, the address is build as following:

return InetSocketAddress.createUnresolved(host, port);

However, in Spring Cloud Gateway, we have the following in XForwardedHeadersFilter:

if (isForEnabled() && request.getRemoteAddress() != null
                && request.getRemoteAddress().getAddress() != null) {
            String remoteAddr = request.getRemoteAddress().getAddress().getHostAddress();
            write(updated, X_FORWARDED_FOR_HEADER, remoteAddr, isForAppend());
        }

Debugging shows, that request.getRemoteAddress().getAddress() is null and so, no X-Fowarded-For Header is written. I guess that is due to the InetSocketAddress.createUnresolved in UriComponentsBuilder.

I don't know whether ForwardedHeaderTransformer / UriComponentsBuilder or XForwardedHeadersFilter is wrong here, but together, they fail.

P.S.: createUnresolved seems to be intentionally, to avoid DNS resolution. See https://github.com/spring-projects/spring-framework/commit/c5ac8e8ab62cf7a616d5316bc6ef6d5d5e461c10 So, it would probably be the best solution if XForwardedHeadersFilter could (also) deal with an unresolved remote address.

justwiebe commented 1 year ago

Is there any solution to this?

hamish-spireon commented 11 months ago

Is there a work around for this?

blacklee123 commented 9 months ago

oh, gold, please I have this question too

NadChel commented 6 months ago

request.getRemoteAddress().getAddress() cannot be null in the current implementation since this line in XForwardedHeadersFilter

String remoteAddr = request.getRemoteAddress().getAddress().getHostAddress();

would throw an NPE. Since, as I understand, no DNS resolution is by design, we could replace that line with

InetAddress address = request.getRemoteAddress().getAddress();
String remoteAddr = (address == null) ? /* some default value; an empty string? */ : address.getHostAddress();

What that default value may be depends on whether there are any expectations of that value (e.g. if a specific format is expected, if it's in any way parsed at any place)

@spencergibb does this train of thought relate to you? If so, will an empty string do?

MichalStehlikCz commented 1 month ago

It might make sense to use same resolution like in ForwardedHeadersFilter

        String forValue;
        if (remoteAddress.isUnresolved()) {
            forValue = remoteAddress.getHostName();
        }
        else {
            InetAddress address = remoteAddress.getAddress();
            forValue = remoteAddress.getAddress().getHostAddress();
dreamstar-enterprises commented 1 day ago

Spring Webflux.

server:
  address: ${LOCALHOST}
  port: ${RESOURCE_SERVER_PORT}
  ssl:
    enabled: false
  forward-headers-strategy: native

framework caused removal of forwarded headers

But native was ignoring X-Forwarded-Prefix

So had to add this:

@Configuration
class WebConfig {

    @Bean
    fun forwardedHeaderTransformer(): ForwardedHeaderTransformer {
        val transformer = ForwardedHeaderTransformer()
        transformer.setRemoveOnly(false)
        return transformer
    }
}

@Component
internal class CustomWebFilter(
    private val transformer: ForwardedHeaderTransformer
): WebFilter {

    override fun filter(exchange: ServerWebExchange, chain: WebFilterChain): Mono<Void> {
        // Get the original request
        val originalRequest: ServerHttpRequest = exchange.request

        // Log original headers
        println("Original Headers: ${originalRequest.headers}")

        // Apply the ForwardedHeaderTransformer to the original request
        val transformedRequest: ServerHttpRequest = transformer.apply(originalRequest)

        // Log the transformed request path and headers
        println("Transformed Path: ${transformedRequest.path}")
        println("Transformed Headers: ${transformedRequest.headers}")

        // Mutate the exchange to use the transformed request
        val mutatedExchange = exchange.mutate()
            .request(transformedRequest)
            .build()

        // Proceed with the next filter in the chain
        return chain.filter(mutatedExchange)
    }
}

Request now does get transformed, but same problem, headers have disappeared

val remoteIp = exchange.request.remoteAddress?.address?.hostAddress val xForwardedFor = exchange.request.headers.getFirst("X-Forwarded-For") val XForwardedPrefix = exchange.request.headers.getFirst("X-Forwarded-Prefix")

rest-api-1 | X-Forwarded-For: null rest-api-1 | X-Forwarded-Prefix: null rest-api-1 | Remote IP: null