0

I've added an http redirect to my Ktor application and it's redirecting to https://0.0.0.0 instead of to the actual domain's https

@ExperimentalTime
fun Application.module() {

    if (ENV.env != LOCAL) {
        install(ForwardedHeaderSupport)
        install(XForwardedHeaderSupport)
        install(HttpsRedirect)
    }

Intercepting the route and printing out the host

    routing {

            intercept(ApplicationCallPipeline.Features) {
            val host = this.context.request.host()

i seem to be getting 0:0:0:0:0:0:0:0 for the host

Do i need to add any special headers to Google Cloud's Load Balancer for this https redirect to work correctly? Seems like it's not picking up the correct host

Jan Vladimir Mostert
  • 12,380
  • 15
  • 80
  • 137

2 Answers2

1

As your Ktor server is hidden behind a reverse proxy, it isn't tied to the "external" host of your site. Ktor has specific feature to handle working behind reverse proxy, so it should be as simple as install(XForwardedHeaderSupport) during configuration and referencing request.origin.remoteHost to get actual host.

Let's try to see what's going on.

You create a service under http://example.org. On the port 80 of the host for example.org, there is a load balancer. It handles all the incoming traffic, routing it to servers behind itself. Your actual application is running on another virtual machine. It has its own IP address, internal to your cloud, and accessible by the load balancer.

Let's see a flow of HTTP request and response for this system.

  1. An external user sends an HTTP request to GET / with Host: example.org on port 80 of example.org.
  2. The load balancer gets the request, checks its rules and finds an internal server to direct the request to.
  3. Load balancer crafts the new HTTP request, mostly copying incoming data, but updating Host header and adding several X-Forwarded-* headers to keep information about the proxied request (see here for info specific to GCP).
  4. The request hits your server. At this point you can analyze X-Forwarded-* headers to see if you are behind a reverse proxy, and get needed details of the actual query sent by the actual user, like original host.
  5. You craft the HTTP response, and your server sends it back to the load balancer.
  6. Load balancer passes this respone to the external user.

Note that although there is RFC 7239 for specifying information on request forwarding, GCP load balancer seems to use de-facto standard X-Forwarded-* headers, so you need XForwardedHeaderSupport, not ForwardedHeaderSupport (note additional X).

Community
  • 1
  • 1
r4zzz4k
  • 656
  • 5
  • 10
  • using `request.origin.remoteHost` with only `XForwardedHeaderSupport`, i get my own IP address, not even the load balancer's IP address. This is so weird, it works fine on another project. let me try just `ForwardedHeaderSupport` on its own to see what it does – Jan Vladimir Mostert Aug 24 '20 at 08:03
  • i think this is an issue with the new gcloud load balancers, on older projects, this is working fine, a brand new google cloud project i setup on Sunday is forwarding 0.0.0.0 in the host, hence https redirecting not working properly – Jan Vladimir Mostert Aug 24 '20 at 20:20
  • I've printed out the headers and X-forwarded-host is not in there, there is however a Host header and X-forwarded-proto contains the http / https. X-forwarded-for contains the wrong host if that is what i need to use. Using the HOST header, i'm just going to roll my own. Thanks for the detailed answer though – Jan Vladimir Mostert Aug 24 '20 at 20:42
1

So it seems either Google Cloud Load Balancer is sending the wrong headers or Ktor is reading the wrong headers or both.

I've tried

    install(ForwardedHeaderSupport)
    install(XForwardedHeaderSupport)
    install(HttpsRedirect)

or

    //install(ForwardedHeaderSupport)
    install(XForwardedHeaderSupport)
    install(HttpsRedirect)

or

    install(ForwardedHeaderSupport)
    //install(XForwardedHeaderSupport)
    install(HttpsRedirect)

or

    //install(ForwardedHeaderSupport)
    //install(XForwardedHeaderSupport)
    install(HttpsRedirect)

All these combinations are working on another project, but that project is using an older version of Ktor (this being the one that was released with 1.4 rc) and that project is also using an older Google Cloud load balancer setup.

So i've decided to roll my own. This line will log all the headers coming in with your request,

                log.info(context.request.headers.toMap().toString())

then just pick the relevant ones and build an https redirect:

routing {

    intercept(ApplicationCallPipeline.Features) {
        if (ENV.env != LOCAL) {

            log.info(context.request.headers.toMap().toString())

            // workaround for call.request.host that contains the wrong host
            // and not redirecting properly to the correct https url
            val proto = call.request.header("X-Forwarded-Proto")
            val host = call.request.header("Host")
            val path = call.request.path()

            if (host == null || proto == null) {
                log.error("Unknown host / port")
            } else if (proto == "http") {
                val newUrl = "https://$host$path"
                log.info("https redirecting to $newUrl")

                // redirect browser
                this.context.respondRedirect(url = newUrl, permanent = true)
                this.finish()
            }
        }
    }
Jan Vladimir Mostert
  • 12,380
  • 15
  • 80
  • 137