Proper way to set up Rails 6 on AWS ECS

Viewed 365

I have Rails 6 (running with Puma) deployed on AWS ECS. There's an ECS Service and an ECS Task, and the Task spins up EC2 instances that host my app. I also created an Application Load Balancer with my ECS Service. I added an HTTPS listener to the load balancer. My listener points to a Target Group that accepts traffic specifically via the HTTP protocol.

My understanding of the request flow:

HTTPS request from internet:
  --> hits AWS load balancer 
    --> hits HTTPS listener
      --> passes traffic using HTTP to Target Group
        --> request finally reaches Rails app on Target Group EC2 targets *over HTTP*

Is this a valid setup? I read this StackOverflow answer and my interpretation is that that we only need HTTPS for our load balancer and not for Puma and thus not for the Target Group.

I also set up a health check over HTTP for my Target Group that expects a 301 status code response as a healthy target (because I have config.force_ssl on in my Rails config). The thing is, why does traffic from the load balancer not get redirected? Why does traffic from the health check get redirected? Aren't they both hitting the same Target Group? Why does one request result in a 200 while the other results in a 301?

I've made a drawing to try to capture my question/confusion/current understanding:

enter image description here

Here's my load balancer and target group setup:

enter image description here

enter image description here

Not sure if this is an AWS issue or if it's a Puma issue or something else. Taking all ideas! Thank you!!

1 Answers

Answering your 2 questions.

  1. Why does traffic from the load balancer not get redirected?

This answer explains it as well, but I'll dig a little bit deeper: when the load balancer receives a HTTPS connection and forwards it via HTTP to your Rails server, it will set a X-FORWARDED-PROTO=https header to the request, and Rails understands that as enough for the force_ssl config.

The code path is that Rails https://github.com/rails/rails/blob/main/actionpack/lib/action_dispatch/middleware/ssl.rb (which, as per the comments in that file, is included in the request chain when config.force_ssl is true) will call request.ssl?

request in that context is an instance of https://github.com/rails/rails/blob/main/actionpack/lib/action_dispatch/http/request.rb, but you won't find the ssl? method defined there; however, that class includes include Rack::Request::Helpers.

If you go to Rack's source code, you'll find the ssl? method defined in https://github.com/rack/rack/blob/master/lib/rack/request.rb, where it calls scheme method, and the X-Forwarded-Proto header will be checked in the elsif forwarded_scheme part. And there you go :) Even tough it's a HTTP request, the presence of that header will make the request.ssl? return true.

  1. Why does traffic from the health check get redirected?

In this case, it's a HTTP request from the load balancer to your application that doesn't carry that X-Forwarded-For or X-Forwarded-Proto header, so Rail's config.force_ssl is doing what it's supposed to do and redirecting.

Lastly, check out the documentation on config.force_ssl. You actually can set it to a hash of options, and exclude your healthcheck urls from the force_ssl behavior!

config.ssl_options = { redirect: { exclude: -> request { request.path =~ /healthcheck/ } } }
Related