I am running a rails app on elastic beanstalk, my problem is that I cannot use the load balancer health check, as it checks via http, not https. I have config.force_ssl set to true, and don't really want to change it, since it means I have 100% ssl coverage, HSTS, and secure cookies.
I have setup a controller to check at /health and can access this via curl.
There seems to be a contradiction between what is shown in elastic beanstalk (EC2 Instance Health Check), where I can only change the path and some timings, and the health check in the load balancer in EC2, where I can change the ping protocol, port and path to what I want, but this seems to have no effect other than causing a 503 error.
Ping Protocol: https
Ping Port: 443
Ping Path: /health
The load balancer has ports 80 and 443 open but redirects from 80 to 443 due to force_ssl.
Does anyone know of the correct settings to get around this, as I won't be able to scale the application without it?
Thank you
Eamon
The way to do this is to add this to your config, like this.
config.force_ssl
config.ssl_options = { redirect: { exclude: -> request { request.path =~ /health/ } } }
This is as per the rails docs and is valid for Rails 5:
http://api.rubyonrails.org/classes/ActionDispatch/SSL.html
Small hint for the accepted answer:
In case you have updated from Rails 4 to Rails 5 and have therefore a new_frameworks_default.rb in your initializers directory. You need to remove (or out-comment) the following line:
Rails.application.config.ssl_options = { hsts: { subdomains: true } }
Otherwise ssl_options is already set.
The accepted answer kept not working for me, but after hours of debugging I found this gem:
https://github.com/lserman/aws-healthcheck
It returns 200 on /healthcheck which works like a charm.
If, like me you are not using rails 5, the other way you can do this is to disable force SSL, and use NGINX to force all traffic onto https... You can use an ebextension file to do this. The exact version you need comes direct from AWS:
https://github.com/awslabs/elastic-beanstalk-docs/blob/master/.ebextensions/aws_provided/security%20configuration/https-redirect-ruby-puma.config
If you need more information on ebextensions, you can find them in the docs http://docs.aws.amazon.com/elasticbeanstalk/latest/dg/ebextensions.html
EDIT: The original link no longer works...
Try this:
https://github.com/awsdocs/elastic-beanstalk-samples/blob/master/configuration-files/aws-provided/security-configuration/https-redirect/ruby-puma/https-redirect-ruby-puma.config
Related
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:
Here's my load balancer and target group setup:
Not sure if this is an AWS issue or if it's a Puma issue or something else. Taking all ideas! Thank you!!
Answering your 2 questions.
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.
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/ } } }
I face the same situation, except that my framework is Ruby on Rails 4.2.6 (Ruby version 2.2.4)
I have do exactly the solution told, but when I try to login, always redirect to root page.(still not logined)
And I checked server log, login status was 200 success.
another clue is that when I go to the page which not enable
before_action :authenticate_user!
everything works fine. (domain not redirect to elb domain)
I think the problem is in the login part, but still not find the exact bug and solution.
How to make ec2 catch the host we expected (example.com), not elb host (elb.example.com)
Configure the CloudFront Cache Behavior settings to whitelist the Host header for forwarding. You may also need to whitelist one or more cookies, and possibly query strings. CloudFront forwards minimal headers by default, and no query parameters or cookies.
As a rule, the more things you forward, the lower your cache hit ratio... but obviously certain things must be forwarded unless the site is entirely static.
When using https, the request.remote_ip returns 127.0.0.1. This prevents geocode lookup.
Is there a way to get the correct remote IP?
I have seen a few possible workarounds:
request.env['REMOTE_ADDR']
request.env['HTTP_X_FORWARDED_FOR']
which return 10.102.1.1
request.env[‘HTTP_X_REAL_IP’]
which returns ""
It turns out this is a limitation of the way the server at ninefold is set up.
"Since our Rails stack is Apache Passenger, the client side IP headers are actually stripped off when they pass through the HA Proxy load balancer. In the CItrix implementation of this service, we are unable to pass those headers through to the rails app. At this stage its not possible to access the remote user's IP address."
As a possible work around, you could use a service like Fastly to do your load balancing, then point it directly at your app servers' IPs to bypass HAProxy on Ninefold. You'd get a nice, fast CDN in the process too.
I'm running my Rails app on port 3000, with mod_proxy forwarding requests to /rails, to http://localhost:3000.
I'm attempting to make link_to take the subdirectory into account. (For example, /rails/user/login instead of /user/login)
But when accessing through the Apache proxy, this doesn't happen. It does, however, work when accessing http://mysite.com:3000 directly.
Any ideas? I'm tearing my hair out!
Does anyone know of a plugin / gem that will log any HTTP requests your rails app may be making when responding to a request? For example if you are using HTTParty to hit an API, how can you see what outbound requests are coming out of your rails app?
You have to tell the outbound HTTP client to use a proxy.
For HTTParty it's fairly simple (from the docs),
class Twitter
include HTTParty
http_proxy 'http://myProxy', 1080
If you're looking for a proxy to set up, personally I like Paros proxy (Java so cross platform and does SSL).
Try also http_logger gem:
require 'http_logger'
Net::HTTP.logger = Logger.new(...) # defaults to Rails.logger if Rails is defined
Net::HTTP.colorize = true # Default: true
This will log all requests that goes through Net::HTTP library.
https://github.com/railsware/http_logger
If you're doing development on your own machine, Charles Proxy is a good option.
In production, you'd probably be better off creating your own logger.debug() messages.
The only way I got this to work was to specify only the IP as the first parameter to the http_proxy call:
http_proxy '10.2.2.1', 8888
The example above, with the http:// prefix, did not work, I got a SocketError: getaddrinfo: nodename nor servname provided
Try my httplog gem, you can customize it to log requests, responses, headers etc.