DevOps / GCP / k8s / NodeJS
Posted 6.31.2020
By Matt Glaser

The Case of the Missing http to https Redirect

Let’s start with a little background here. 

Back in the dark ages, if you wanted to serve your website over the Secure Sockets Layer (SSL) and thus make your webpage accessible via the https:// protocol, you had to pay some opportunistic jerks whose name rhymed with like “Schmerisign” or “Schmigicert” or “SchmoeDaddy” real money, sometimes hundreds of dollars a year, to buy a certificate and install it on your web server. It was basically a must for anyone transmitting sensitive information through e.g forms on their website, and there were no free options for most mortals. 

Then, something wonderful happened: Let’s Encrypt was born, and suddenly even mediocre developers could get a free SSL cert installed on any server for anyone. We use Let’s Encrypt for most of our projects, and we love them tons. 

Because of the advent of free SSL certificates, there was no longer a good excuse for your website to remain unsecured. Google noticed, and decided to force the issue by threatening to publicly shame sites being served via http in July of 2018

This was all good stuff. We completely agree with Google doing that. The thing is…

Google Cloud’s native tools make serving sites via https a huge pain

We’re mostly happy as Google Cloud Project customers. It’s a great platform and we find it to be a huge improvement in usability over AWS and Azure. The documentation is pretty great and we generally recommend the whole thing.

Most of our work is done within Google Kubernetes Engine (GKE) which offers the ability to manage most of our infrastructure declaratively, using manifests. If you understood some or none of that, here’s the salient part:

  1. We create a YAML file that declares what’s called an “ingress” in GKE and deploy it to our Kubernetes cluster. 
  2. Google automatically and conveniently spins up an official Cloud Load Balancer that offers complex routing, security and other fun stuff, and seamlessly connects it with whatever applications we are already running on GKE. 
  3. Within this file, we can specify routes to follow and tell it to use an SSL Certificate, which Google provisions and renews for us automatically. 

This is nearly nirvana for me, after many years of messing endlessly with expiring certificates, even free ones, on various hosting platforms. (If you’re ever having trouble sleeping, ask me about the time I set up Traefik with Let’s Encrypt certs on Docker Swarm.)  More so, being able to launch a website or application with SSL using a single vendor’s tools, without managing multiple accounts, expiration schedules, etc… It’s just a great thing to fantasize about. 

GKE’s implementation of SSL certificates comes tantalizingly close. I mean, you can easily provision a cert for your domain and it’ll just work shortly after associating that cert with your domain on a load balancer. Done and done! 

Except that if you’re like us, and like pretty much everyone else, you really want people to be able to access both https://yourdomain.com and http://yourdomain.com. It’s just a common thing for some folks to type in domains without a protocol, or for people to erroneously link to the insecure version of your site. It happens, and so you really need to accept connections at both port 80 (http) and port 443 (https) at the load balancer level. 

No problem! All you have to do is forward all the requests from http to https. This is a feature that is pretty widely available and so you’d assume that Google was on it. I looked for some documentation or a setting in the console… and found nothing. After some searching I found out that it was a feature request back in 2015

Five years ago? That’s forever. I’m surprised there were even websites back then so, you know, no big deal. The workaround at the time was “Just handle the redirects at the application level”. Now scroll down… waaaaay down. 

.

.

.

.

.

Here's a fun workaround from 2018 that seems insanely complicated but clever. All for a simple protocol redirect!

.

.

.

.

.

.

They fixed it! In April of 2020.

So yeah, it took awhile, but problem solved!

For some people at least. 

The problem is still a problem if you use GKE

Ok, so GKE is popular and Google pushes it pretty hard. Kubernetes is really big and Google is basically its biggest benefactor so I’m pretty sure there’s a lot of people using GKE to serve things via http/https. Somehow, in the 5 years that it took to add the ability to forward http to https from a load balancer, they managed to not make it possible to set this up via a GKE ingress. Three days after the release of this long awaited feature we get this polite question, which I was about to ask:

I’ll skip to the end here: Nope, there isn’t. As of July 10th, 2020 the answer is still “Just handle the redirects at the application level” or worse, turn off port 80 on your LB so if anyone visits the http version they get an error and can’t even access your site. Or I guess you could just not use Kubernetes anymore, which sounds honestly kind of great now. 

Now, please don’t delete all my accounts, Google. We’re friends and friends are honest with one another so I’ll just say it: This is pretty lame. Forwarding all these requests to my application means that not only am I incurring costs for the resources needed to handle those requests in the K8s cluster, we also can’t cache the redirects. Sure, these kinds of redirects are lightweight but now, if I want to host something in my cluster I need to go back to messing with server configs like Nginx confs or (good lord) .htaccess files, making my app more brittle and making me put in a bunch of dumb logic to NOT do these redirects on my local dev environment.

Our latest thing was needing to do this in an NodeJS/Express app. Among the great things the Google LB does is properly sends the `X-Forwarded-Proto` header, which tells us whether the request from the LB was http or https, so here’s the code snippet that solves this issue at the application level:

// Only handle stuff that came from the load balancer as insecure  
const isNotSecure = req.get('X-Forwarded-Proto') === 'http' 

if(isNotSecure) {
    return res.redirect(301, 'https://' + req.get('host') + req.url)
}

Yeah, I know. This is a whole lot of bitching for having to write a few lines of code. Just the same, for an institution that is (rightly) trying to move the entire world to serving their sites securely, they sure do make it a pain for their paying customers to join in on the effort.