Introduction
This article discusses HTTP to HTTPS redirection in software. I will show you one method of server-side redirects in Python 3 and Flask. I will start by analyzing the HTTP headers received by a Cloud Run container application and make recommendations on how to handle redirection to HTTPS. Today, all websites should deliver traffic over HTTPS. If you create a custom domain for your Cloud Run service URL, then implementing HTTP to HTTPS redirection is your responsibility.
Let’s Encrypt provides free SSL certificates. Let’s Encrypt minimizes the barriers to adopting HTTPS. Google uses Let’s Encrypt as its public SSL provider for Google Cloud services. Note, this is what I observe and I am not stating any form of Google policy. For Cloud Run, you do not have an option to use your own SSL certificate. Cloud Run is in beta so some of my comments and technical details might change with the production release.
Google Cloud Run Front-end to Container Connections
The Google Cloud Run Front-end supports both HTTP and HTTPS connections. The front-end accepts client requests to your service on either HTTP or HTTPS. The communications between the front-end and the Cloud Run container is HTTP over port $PORT. Today $PORT defaults to 8080, but Google mentions that this port number could change. This goal of this article is to redirect users who arrive on an HTTP endpoint to redirect to the HTTPS endpoint.
If communications between the front-end and the container are over HTTP, how do you perform HTTP redirection? The key is to process the HTTP headers that the front-end includes with requests to the container. Let’s change the program developed in Part 1 of this article series to display the HTTP headers.
Download Git Repository
Clone my repository to your system:
1 |
git clone https://github.com/jhanley-com/google-cloud-run-https-part-2.git |
Note: If you want to skip downloading and building and just get to the heart of this article, use the Cloud Run Service URLs from my project which I will leave public for a while.
- https://sample-flask-example-2-x5yqob7qaq-uc.a.run.app
- http://flask-python-example-2.jhanley.dev
- https://flask-python-example-2.jhanley.dev
Setting up Build and Deploy Environment
You will need your Project ID. The following CLI command will display your current project:
1 |
gcloud config list project |
Windows 10
Change to the directory google-cloud-run-https-part-2\scripts-windows
.
Modify the file env.bat
Change the PROJECT_ID, SERVICE_NAME, and IMAGE_NAME to suit your requirements.
1 2 3 4 5 6 7 |
@REM to get your current Project ID @REM gcloud config list project @set PROJECT_ID=development-123456 @set REGION=us-central1 @set SERVICE_NAME=sample-flask-example-2 @set IMAGE_NAME=sample-flask-example-2 |
Once you set up the environment, the file build.bat
will build the container and deploy to Cloud Run. Once the build completes make a note of the service URL.
Linux (Google Cloud Shell)
I have developed this example for Google Cloud Shell, but this code should work on any modern Linux.
Change to the directory google-cloud-run-https-part-2\scripts-linux
.
Modify the file env.sh
. Note: The file is already set up to read the Project ID from Cloud Shell’s GOOGLE_CLOUD_PROJECT environment variable.
Change the PROJECT_ID, SERVICE_NAME, and IMAGE_NAME to suit your requirements.
1 2 3 4 5 6 7 8 9 |
# to get your current Project ID # gcloud config list project # # This script uses the Google Cloud Shell for the Project ID export PROJECT_ID=$GOOGLE_CLOUD_PROJECT # export REGION=us-central1 export SERVICE_NAME=sample-flask-example-2 export IMAGE_NAME=sample-flask-example-2 |
Once you set up the environment, the file build.sh
will build the container and deploy to Cloud Run. Once the build completes make a note of the service URL.
Google Cloud Run Headers
Once you have built and deployed the container, a Service URL will be displayed. For my deployment the Service URL is https://sample-flask-example-2-x5yqob7qaq-uc.a.run.app.
I changed the Python program app.py
to support a /headers
page. This page sends back a table of the HTTP request headers. I will use curl to download this page and save it as a text file and then display it in the browser. We are doing this to bypass the browser’s automatic redirection due to HSTS or memorized 301 redirects.
1 |
curl http://sample-flask-example-2-x5yqob7qaq-uc.a.run.app/headers -o headers_http.html |
The output from this command when displayed in a browser looks like the following screenshot. We are interested in the header: X-Forwarded-Proto. This header tells us what the protocol is from the client to the Cloud Run Front-end. We can see that the protocol is HTTP.
Now we will repeat this again, but for the HTTPS endpoint.
1 |
curl https://sample-flask-example-2-x5yqob7qaq-uc.a.run.app/headers -o headers_https.html |
In this example, we can see that the protocol is HTTPS.
Repeat using Chrome or your favorite browser. You will notice a lot more headers. The additional headers include information about the User-Agent, Accept-Language, etc.
One client HTTP header of interest is Upgrade-Insecure-Requests
. This is a signal from the browser to the server that the client prefers an encrypted and authenticated response. More information here. The browser is saying, “I prefer HTTPS, so redirect me if I use HTTP”.
Program Changes to Support HTTP Redirection
Each language and framework/library will have its own method of handling requests/responses/headers.
For Flask, I wrote the following function. This function loops thru the HTTP request headers checking for x-forwarded-proto
. If found, check the protocol for http
. If found, send a redirect to the client to request the same page using the HTTPS protocol. This code will send a 301 which means Moved Permanently. You could also change the code to send a 307 “Temporary Redirect”. Sending a 307 is better when you are testing as browsers will cache 301.
1 2 3 4 5 6 7 8 9 10 11 |
def handle_proxy_http_to_https(): host = request.host if ':' in host: host = host.split(':')[0] for i in request.headers: if i[0].lower() == 'x-forwarded-proto': if i[1].lower() == 'http': url = 'https://' + host + request.path response = redirect(url, 301) return response return None |
The function handle_proxy_http_to_https() is called like this. You can see the full code in the package download.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
@app.route('/') def home(): response = handle_proxy_http_to_https() if response != None: return response # Continue with normal HTML output body = """ <h3>Hello Google Cloud Run World!</h3> <p> <a href="https://cloud.google.com/run/" target="_blank">Google Cloud Run Website</a> </p> <p> <a href="/headers">Click to see request headers</a> </p> """ return body return body |
Information Links:
Summary
In this article, I discussed how to implement simple HTTP redirection in Python 3 and Flask. Similar techniques are available for any framework that supports creating web servers. PHP makes this very easy.
In Part 3, I will perform several security tests against our Cloud Run service. It might shock you to find that a standard deployment will get an F on some tests. Upgrading from an F to an A is easy. I will show you how to make improvements using HTTP security headers.
Credits
I write free articles about technology. Recently, I learned about Pexels.com which provides free images. The image in this article is courtesy of Jean van der Meulen at Pexels.
Date created: May 15, 2019
Last updated: May 15, 2019
I design software for enterprise-class systems and data centers. My background is 30+ years in storage (SCSI, FC, iSCSI, disk arrays, imaging) virtualization. 20+ years in identity, security, and forensics.
For the past 14+ years, I have been working in the cloud (AWS, Azure, Google, Alibaba, IBM, Oracle) designing hybrid and multi-cloud software solutions. I am an MVP/GDE with several.
Leave a Reply