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)
Top comments (6)
AWS ALB should support redirections from HTTP to HTTPS, according to this blog post:
aws.amazon.com/about-aws/whats-new...
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.
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.
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.
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.
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.
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.