DEV Community

loading...

Force HTTPS for AWS EB behind a Load Balancer

dannemanne profile image Daniel Viklund Originally published at dannemanne.com ・4 min read

Originally posted on my blog

Elastic Beanstalk is great! It is very easy to get a Rails app up and running on AWS quickly. Perfect for an app developer that prefers not to deal directly with sysops but still wants to deploy to and leverage the services on AWS.

Sooner or later though, you will probably run into something that you can't fix through the web console GUI.

HTTP to HTTPS redirect

It is straight forward to set up your listeners and your processes for the Elastic Load Balancer (ELB) to route the requests to your app, both with and without SSL. But there is no configurable option to redirect all HTTP requests to HTTPS, meaning you have to handle it in your app.

In Rails, you could force HTTPS redirect by adding force_ssl in ApplicationController, but it seems a bit unnecessary. Do we really need the request to go that far, just to be redirected? Plus, should this security setting really be up to the application?

EB Extensions to the rescue

A few years ago, I found the article Enable HTTPS redirect for AWS Elastic Beanstalk with a Rails App behind a Load Balancer, where this issue is addressed. I found it to be a good resource, but unfortunately, the link to the sample file is now broken. The Github repository has been updated and restructured since that article was written, but the folder containing the updated sample files can currently be found on https://github.com/awsdocs/elastic-beanstalk-samples. In case they change it again in the future, I have forked the current state so you can also find those files on https://github.com/dannemanne/elastic-beanstalk-samples

In that repo, there are two files. One is an extension file for Elastic Beanstalk, meant to be placed in the .ebextensions folder of your app source code. This extension creates a customized NginX configuration file, which has the following modifications:

set $redirect 0;
location / {
  if ($http_x_forwarded_proto != "https") {
    set $redirect 1;
  }
  if ($http_user_agent ~* "ELB-HealthChecker") {
    set $redirect 0;
  }
  if ($redirect = 1) {
    return 301 https://$host$request_uri;
  }
  passenger_enabled on;
}

And with the repository being updated, the example also contains those same redirect rules for the asset pipeline location.

# Rails asset pipeline support.
location ~ "^/assets/.+-[0-9a-f]{32}\..+" {
  if ($http_x_forwarded_proto != "https") {
    set $redirect 1;
  }
  if ($http_user_agent ~* "ELB-HealthChecker") {
    set $redirect 0;
  }
  if ($redirect = 1) {
    return 301 https://$host$request_uri;
  }

  error_page 490 = @static_asset;
  error_page 491 = @dynamic_request;
  recursive_error_pages on;

  if (-f $request_filename) {
    return 490;
  }
  if (!-f $request_filename) {
    return 491;
  }
}

Looking at the commit history, this seems to be a more recent improvement.

Configure Phusion Passenger

The other file in the example is a JSON file (passenger-config.json). This file is meant to be saved in the root of your app source code. Doing so allows you to pass options to Phusion Passenger as it boots.

Note! The filename passenger-config.json is correct for Passenger Phusion version 4, which is what Elastic Beanstalk is currently using. If/when they switch to version 5, the config filename will be Passengerfile.json, but that is not needed at the time of this article.

The passenger-config file in this example is needed because the extension is not replacing the original NginX configuration file that Elastic Beanstalk uses by default. It creates a new file in another location. The passenger config file can tell Passenger to use this new NginX config file rather than the default, which is exactly what it does in this example.

Alternative approach

If you prefer to keep the configurations to a minimum, you could replace the original NginX config file (which is located in /opt/elasticbeanstalk/support/conf/nginx_config.erb), but I would recommend going with the approach from the example. If not, then just imagine what would happen when it is time to upgrade to another version of the server image. This updated version might contain changes to the configuration, but you would never know because it is immediately overwritten during deployment.

If instead, you wrote to a custom location, you could (and should) compare the files for possible changes. It's just good practice!

Happy coding!

Resources

Enable HTTPS redirect for AWS Elastic Beanstalk with a Rails App behind a Load Balancer

https://github.com/awsdocs/elastic-beanstalk-samples

Advanced environment customization with configuration files (.ebextensions)

Introduction to configuring Passenger Standalone

Discussion (6)

pic
Editor guide
Collapse
gromnan profile image
Jérôme TAMARELLE

AWS ALB should support redirections from HTTP to HTTPS, according to this blog post:
aws.amazon.com/about-aws/whats-new...

Collapse
dannemanne profile image
Daniel Viklund Author

Yes, I've seen that the Elastic Load Balancer should be able to handle this. What I believe is the issue is that the configuration options in Elastic Beanstalk is not giving you the full interface that is available for the Load Balancer. So if you would go through EC2 > Load Balancer and manually configure it there, it would probably (I am not 100% on this) reset it in future deploys.

This is I believe one of the trade offs that you get when using the "easy" option of Elastic Beanstalk.

Once again, I can not guarantee this, but in my experience, anything that you configure outside of the EB interface, will get reset sooner or later. Unlike the configurations you apply through .ebextensions that will always be reapplied on deployments.

Collapse
gromnan profile image
Jérôme TAMARELLE

That issue could be reported to AWS support. As you said, redirecting from HTTP to HTTPS has become an ordinary requirement.

Also, this kind of limitation is a signal that you should move to CloudFormation or Terraform to deploy your stack.

Thread Thread
dannemanne profile image
Daniel Viklund Author

True, reporting it is a good idea. I have recently seen that several people at AWSCloud are reaching out to request feedback on missing features for their respective products, so might be a good time.

Collapse
turbomack profile image
Marek Fajkus

I think there is probably small issue with this setup. I think returning redirect is correct behavior for GET and HEAD requests but for other methods I believe the correct behavior would be to return 405 Method Not Allowed (or 307).

It's probably not a big deal in practice but returning redirect on other methods is not correct according to HTTP specification.

Collapse
dannemanne profile image
Daniel Viklund Author • Edited

Interesting, I had not considered that. Like I said, the content of the NginX config is from awsdocs so I didn't question this directive since it did what I wanted it to.

As you say, probably not a big deal, but to be more compliant to the spec, maybe something like this would be a better approach.

if ($redirect = 1) {
    set $status = 301;

    if ( $request_method !~ ^(GET|HEAD)$ ) {
        set $status = 405;
    }

    return $status https://$host$request_uri;
}

Cause I can't see a scenario where you would need it for anything other than GET or HEAD.

Edit: Or maybe the uri is not needed when returning 405... Will have to check the specs in more detail.