0

I have a spring boot app running on GKE with the Google https load balancer via an ingress controller. The behavior I am looking for is for the Spring Boot app to redirect to https when an http request is received. In Spring Boot 2.2 and earlier I was able to do this with the following code and configuration.

configuration in my application yaml

server:
  port: 8877
  use-forward-headers: true # this should make it understand X-Forwarded-Proto header 

and in my Spring Security configuration I did

 @Override
  protected void configure(HttpSecurity http) throws Exception {

    /* When the app is running on GKE traffic will come in through the
     * GCP http load balancer. Which will set the X-Forwarded-Proto
     * header to http or https. When the app runs on a dev machine
     * these headers are not set.
     *
     * see https://cloud.google.com/load-balancing/docs/https/
     *
     * The code forces ssl if the x forwarded proto header is present
     * as that indicates the app is online and accessible to the
     * wider internet.
     *
     */
    http.requiresChannel()
        .requestMatchers(r -> r.getHeader("X-Forwarded-Proto") != null)
        .requiresSecure();
   // more stuff  omitted  
 } 

In Spring Boot 2.3 server.use-forward-headers was deprecated I changed my config to be

server:
  port: 7777
  forward-headers-strategy: native
  tomcat:
    remoteip:
      protocol-header: "X-Forwarded-Proto"
      remote-ip-header: "X-Forwarded-For"

I made no changes to the code that requires Secure channel. However, when a request like https://example.com arrives at boot through the GCP load balancer it is not being recognized as a secure connection and a redirect https://example.com is sent from spring boot. This causes the browser to say that an infinite redirect loop has been detected.

Question What is the correct way to redirect from http to https on Spring Boot 2.3 running on GKE behind a GCP load balancer configured via and Ingress controller?

ams
  • 60,316
  • 68
  • 200
  • 288

1 Answers1

3

Spring Boot is relying on Tomcat to evaluate the incoming request to determine if the request should be considered "secure". Tomcat remote IP valve is looking at two bits of information to evaluate if a request is secure.

  1. The ip address of the load balancer / proxy that sent the request to tomcat
  2. The value of the x-forwarded-proto http header

The ip address of the load balancer which is called internal proxy in tomcat config must be on the trusted list and the x-forwaded-proto must be https for tomcat to consider the request secure. Otherwise, a redirect is sent.

The google load balancer is sending the request with x-forwaded-proto: https but the source ip is one from the google range 35.191.0.0/16 and 130.211.0.0/22 so tomcat was considering https requests from the GCP loadbalancer to be insecure sending back a redirect. Thus causing the infinite redirect loop that the browser complaining about.

Spring Boot Configures the tomcat remote ip address using using the property server.tomcat.remoteip.internal-proxies which has default values for private ip addresses from RFC 1918 using the regex below.

10\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}|192\\.168\\.\\d{1,3}\\.\\d{1,3}|169\\.254\\.\\d{1,3}\\.\\d{1,3}|127\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}|172\\.1[6-9]{1}\\.\\d{1,3}\\.\\d{1,3}|172\\.2[0-9]{1}\\.\\d{1,3}\\.\\d{1,3}|172\\.3[0-1]{1}\\.\\d{1,3}\\.\\d{1,3}|0:0:0:0:0:0:0:1|::1

Tomcat does not know how to compare ip address ranges so the google source ips must be converted into a regex for this to work.

server:
  port: 7777
  forward-headers-strategy: native
  tomcat:
    remoteip:
      protocol-header: "X-Forwarded-Proto"
      remote-ip-header: "X-Forwarded-For"
      internal-proxies: ".*" # should Java regex to match ip address range of the load balancer servers. Not a best practice to trust all ip addresses. 
ams
  • 60,316
  • 68
  • 200
  • 288