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.5k stars 3.3k forks source link

The "Host" header is not preserved with "preserveHostHeader()" when the route's target URI starts with "https" #3217

Closed meletis closed 6 months ago

meletis commented 8 months ago

I configured the following route with Gateway MVC.fn:

    private RouterFunction<ServerResponse> createDefaultRoute(String routeId) {
        return route(routeId)
                .route(RequestPredicates.path("/**"), http("http://httpbin.org"))
                .before(preserveHostHeader())
                .filter(this.handlerFilterFunctionBuilder.buildRateLimitFilter(routeId))
                .filter(this.handlerFilterFunctionBuilder.buildSecureHeadersFilter(routeId))
                .build();
    }

When I test it with http://localhost:8080/headers, then I correctly get that the Host header has the value localhost:

{
  "headers": {
    "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8", 
    "Accept-Encoding": "gzip, deflate, br", 
    "Accept-Language": "en-US,el;q=0.5", 
    "Cookie": "[STRIPPED-OUT]", 
    "Forwarded": "proto=http;host=\"localhost:8080\";for=\"127.0.0.1:52521\"", 
    "Host": "localhost", 
    "Http2-Settings": "AAEAAEAAAAIAAAABAAMAAABkAAQBAAAAAAUAAEAA", 
    "Sec-Fetch-Dest": "document", 
    "Sec-Fetch-Mode": "navigate", 
    "Sec-Fetch-Site": "cross-site", 
    "Transfer-Encoding": "chunked", 
    "Upgrade": "h2c", 
    "Upgrade-Insecure-Requests": "1", 
    "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:121.0) Gecko/20100101 Firefox/121.0", 
    "X-Amzn-Trace-Id": "Root=1-65a52ebf-4150fa8f2b4495e91ff06def", 
    "X-Forwarded-Host": "localhost:8080"
  }
}

Then I change the route configuration to target https instead of http:

    private RouterFunction<ServerResponse> createDefaultRoute(String routeId) {
        return route(routeId)
                .route(RequestPredicates.path("/**"), http("https://httpbin.org"))
                .before(preserveHostHeader())
                .filter(this.handlerFilterFunctionBuilder.buildRateLimitFilter(routeId))
                .filter(this.handlerFilterFunctionBuilder.buildSecureHeadersFilter(routeId))
                .build();
    }

When I tested again, the remote application is receiving a Host header with value httpbin.org:

{
  "headers": {
    "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8", 
    "Accept-Encoding": "gzip, deflate, br", 
    "Accept-Language": "en-US,el;q=0.5", 
    "Cookie": "[STRIPPED-OUT]", 
    "Forwarded": "proto=http;host=\"localhost:8080\";for=\"127.0.0.1:52580\"", 
    "Host": "httpbin.org", 
    "Sec-Fetch-Dest": "document", 
    "Sec-Fetch-Mode": "navigate", 
    "Sec-Fetch-Site": "cross-site", 
    "Upgrade-Insecure-Requests": "1", 
    "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:121.0) Gecko/20100101 Firefox/121.0", 
    "X-Amzn-Trace-Id": "Root=1-65a52fae-595a56ec0688739134637885", 
    "X-Forwarded-Host": "localhost:8080"
  }
}

However, I would expect the Host header on the application side to continue to be localhost, since the preserveHostHeader() filter is used.

I took a look in the code but couldn't find any flaws, so I feel like I'm missing something, although this does look like a bug to me.

spencergibb commented 6 months ago

This has to do with the JDK HttpClient :-(

spencergibb commented 6 months ago

Using this system property:

-Djdk.httpclient.HttpClient.log=errors,requests,headers,frames:all,content,ssl,trace,channel

I see this being sent

2024-03-11T17:19:13.746-04:00  INFO 81163 --- [demogatewaymedreqbodygh3265] [ient-1-Worker-0] jdk.httpclient.HttpClient                : HEADERS: HEADERS FRAME (stream=1)
    :authority: httpbin.org
    :method: POST
    :path: /post
    :scheme: https

    accept: application/json, */*;q=0.5
    accept-encoding: gzip, deflate
    Content-Type: application/json;charset=UTF-8
    Forwarded: proto=http;host="localhost:8080";for="[0:0:0:0:0:0:0:1]:54434"
    Host: localhost:8080
    user-agent: HTTPie/3.2.2
    X-Forwarded-For: 0:0:0:0:0:0:0:1
    X-Forwarded-Host: localhost:8080
    X-Forwarded-Port: 8080
    X-Forwarded-Proto: http

So this must be something with httpbin specifically because the JDK HttpClient is sending the correct header.

meletis commented 6 months ago

Thank you, @spencergibb , for the feedback, that explains it!