Force SSL on App Engine Flexible Environment Custom Runtime
Asked Answered
C

6

17

We're running an instance of Metabase on a App Engine Flexible Custom Runtime with a Dockerfile based on openjdk:8. Currently it allows access on http://[metabase-project].appspot.com/ and https://[metabase-project].appspot.com/. I'd like to force SSL by having all http traffic redirected to https.

The Dockerfile looks something like this:

FROM openjdk:8
ADD https://dl.google.com/cloudsql/cloud_sql_proxy.linux.amd64 ./cloud_sql_proxy
ADD http://downloads.metabase.com/v0.21.1/metabase.jar ./metabase.jar
CMD ./cloud_sql_proxy -instances=$INSTANCE=tcp:$MB_DB_PORT -dir=/cloudsql & java -jar ./metabase.jar

Our app.yaml looks like:

service: metabase
runtime: custom
env: flex

In a normal App Engine app.yaml file, I'd want to add:

handlers:
- url: [something]
  secure: always

But in the custom runtime we don't have access to handlers like this. Is there a way to configure the Flexible runtime to perform the redirect for all traffic?

Coral answered 30/1, 2017 at 20:24 Comment(3)
Does your domain provider offer domain forwarding? You can forward all http: to https:Shortstop
@Shortstop this would only apply to a custom domain, and the project appspot.com subdomain would allow http without forwarding, right?Coral
Correct, it would only work for custom domain. I am not aware of a built-in for the appspot domain. You may need to test the request header for https, and force your own redirect for non-https requests.Shortstop
O
8

App Engine Flex doesn't support handlers, at all: https://cloud.google.com/appengine/docs/flexible/java/upgrading#appyaml_changes

If you need https:// redirects, you need to do it from within your application. Sorry!

Ottinger answered 31/1, 2017 at 23:27 Comment(2)
Is that only with Java apps? Or with everything?Paucker
I did that in the app following this https://mcmap.net/q/119969/-force-ssl-with-expressjs-3, but I am getting "redirected you too many times.". I think the https is terminating on nginx itself and hence it always gives me protocol as http and secure as false.Ugrian
U
9

Late to answer, but I had to struggle a lot in order to do this.

I followed various links which mentioned the following code,

app.use(function(req, res, next) {
  if(!req.secure) {
    return res.redirect(['https://', req.get('Host'), req.url].join(''));
  }
  next();
});

This might work in other cloud vendors.

But in GCP as rightly mentioned by @zengabor, our app will be running behind an nginx reverse proxy which terminates the SSL connection, we need to check the X-FORWARDED-PROTO which can be done by the following code,

app.use(function(req, res, next) {
  if(req.headers['x-forwarded-proto'] && req.headers['x-forwarded-proto'] === "http") {
    return res.redirect(['https://', req.get('Host'), req.url].join(''));
  }
  next();
});

Just adding my answer as after reading @zengabor's code I had to search again on how to achieve it. So above is the readymade code which will work.

Ugrian answered 21/1, 2019 at 11:50 Comment(1)
It is important to use the status code 307 to work with methods other than GET: res.redirect(307, `https://${req.get('Host')}${req.url}`);Unmeaning
O
8

App Engine Flex doesn't support handlers, at all: https://cloud.google.com/appengine/docs/flexible/java/upgrading#appyaml_changes

If you need https:// redirects, you need to do it from within your application. Sorry!

Ottinger answered 31/1, 2017 at 23:27 Comment(2)
Is that only with Java apps? Or with everything?Paucker
I did that in the app following this https://mcmap.net/q/119969/-force-ssl-with-expressjs-3, but I am getting "redirected you too many times.". I think the https is terminating on nginx itself and hence it always gives me protocol as http and secure as false.Ugrian
A
7

Since your app (env: flex in app.yaml) is running behind an nginx reverse proxy which terminates the SSL connection, you need to check the X-FORWARDED-PROTO header which will be either http or https. If it’s http then you can do the redirect.

Amary answered 16/4, 2017 at 15:23 Comment(3)
I am under the impression that I don't have the ability to modify the nginx config (#40854299). I'm using Metabase, which has explicitly decided that this should be supported by the hosting platform and not their tools. They have a config for EB that sets up the redirect correctly (github.com/metabase/metabase/issues/1356), but it appears that there's no way to do with on appengine flex.Coral
Since Metabase seems to be open source it is technically possible to extend it. :)Amary
Confirm that "X-Forwarded-Proto" header is present today and either "http" or "https".Apnea
S
4

This is what worked for me. In my case using Loopback based NodeJS application running in Cloud Sites App Engine flexible environment.

  1. Create a middleware, for example server/middleware/https-redirect.js with the following code:

    /**
    * Create a middleware to redirect http requests to https
    * @param {Object} options Options
    * @returns {Function} The express middleware handler
    */
    module.exports = function(options) {
      options = options || {};
      var httpsPort = options.httpsPort || 443;
      return function(req, res, next) {
        if (req.protocol != 'https' && process.env.NODE_ENV !== 'development') {
          var parts = req.get('host').split(':');
          var host = parts[0] || '127.0.0.1';
          return res.redirect('https://' + host + ':' + httpsPort + req.url);
        }
        next();
      };
    };
    

    (based on the step 8 in the post http://www.jonxie.com/blog/2014/11/12/setting-up-loopback-to-use-https-and-ssl-certificates/ but modified to use req.protocol instead of req.secure, also will only redirect if not running in development mode)

  2. Modify the file server/server.js to request:

    var httpsRedirect = require('./middleware/https-redirect');
    
  3. An then, after the boot line:

    var httpsPort = app.get('https-port');
    app.use(httpsRedirect({httpsPort: httpsPort}));
    app.set('trust proxy', true)
    

Setting app.set('trust proxy', true) will let the req.protocol read the X-Forwarded-Proto header.

References:

Submaxillary answered 3/1, 2018 at 22:17 Comment(0)
O
0

Use the following code

app.use (function (req, res, next) {
  var schema = (req.headers['x-forwarded-proto'] || '').toLowerCase();
  if (schema === 'https') {
    next();
  } else {
    res.redirect('https://' + req.headers.host + req.url);
  }
});
Onym answered 8/1, 2020 at 12:42 Comment(0)
R
0

Here's the Node.js Express code I use:

// set the env variable REQUIRE_HTTPS=1 to enable this middleware

.use(function(req, res, next) {
    // Redirect HTTP to HTTPS
    if (!process.env.REQUIRE_HTTPS) return next();
    if (req.headers["x-forwarded-proto"] === "https") return next();
    if (req.protocol === "https") return next();
    res.redirect(301, `https://${req.hostname}${req.url}`);
})

Put it as the first middleware in your Express app.

This code assumes that you're using standard ports for http/https, as is the case when you're on AppEngine.

Resendez answered 6/4, 2020 at 5:39 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.