Forwarding http to https in AWS Load Balancers - amazon-elb

I'm using Elastic Beanstalk, and I installed my SSL certificate in my EB load balancer. Every time the server is not healthy, the load balancer delete the instance and creates a new one, which means I'm gonna lose my redirection codes and SSL set up that I installed inside the instance.
That's why I installed my SSL on the load balancer. However, how can I redirect HTTP to HTTPS on the load balancer?
I used to redirect by putting the below codes inside the instance, but if I keep doing this way, when the load balancer removes an unhealthy instance, I'm gonna lose my redirection from HTTP to HTTPS.
What should I do?
<VirtualHost _default_:80>
ServerName (domain).com
ServerAlias www.(domain).com
RedirectPermanent / https://www.(domain).com/
</VirtualHost>
<VirtualHost _default_:443>
RewriteEngine On
RewriteCond %{HTTP_HOST} !^www\. [NC]
RewriteRule ^(.*) https://www.%{SERVER_NAME}%{REQUEST_URI} [L,R=301]
</VirtualHost>
UPDATE
Here's my listeners on Load Balancers in EC2

When using a load balancer in AWS, the common use case is to install the SSL certificate on the load balancer. This is commonly called SSL offloading or Layer 7 load balancing. Traffic from the load balancer to the EC2 instance is not encrypted and typically over port 80 (sometimes 8080) (HTTP). Traffic from the client to your load balancer is defined by the listeners that you created.
Layer 4 load balancing uses TCP to talk to your EC2 instance and you install the SSL certificate on your web server. Your web server then knows what port the client is connecting on and the following DOES NOT APPLY.
Your code running your website needs to check if the client connected to the load balancer using HTTPS. If true, don't redirect the client. If the client connected to the load balancer using HTTP, then redirect the client.
The following PHP code shows how to read the Load Balancer headers that are sent to your EC2 instance to determine if the client connected over HTTPS. If you are not using a language such as PHP or Nodes.js, you can also create Apache configurations that know how to read the correct headers (example at the bottom).
function require_ssl()
{
global $config_require_ssl;
if ($config_require_ssl == FALSE)
{
return;
}
if (!empty($_SERVER['HTTP_X_FORWARDED_PROTO']))
{
if (strpos($_SERVER['HTTP_X_FORWARDED_PROTO'], 'https') !== false)
{
$_SERVER['HTTPS']='on';
}
}
if(empty($_SERVER['HTTPS']) || $_SERVER["HTTPS"] != "on")
{
header("Location: https://" . $_SERVER["HTTP_HOST"] . $_SERVER["REQUEST_URI"]);
exit();
}
}
This is the code for Apache:
<VirtualHost *:80>
RequestHeader set X-Forwarded-Proto "http"
RewriteEngine On
RewriteCond %{HTTP:X-Forwarded-Proto} !https
RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI} [R,L]
…
</VirtualHost>

You'll need to set up the application that you've got running in Elastic Beanstalk to configure Http to Https redirection.
Then when setting up the Load Balancer through Elastic Beanstalk create 2 listeners one for port 80 and one for 433, which both forward on.
https://docs.aws.amazon.com/elasticbeanstalk/latest/dg/environments-cfg-alb.html

You can offload the SSL traffic at the Application Load Balancer(ALB) and the communication between ALB and you can configure the redirection from http to https using EC2 web server configuration.
You are right that if there no running servers the redirection won't work. Infact the response will be a different error where the resource is not accessible. If you configure the autoscaling and load balancing with more than one instance, for most of the cases at least one healthy instance should be able to do the redirection.

Related

How to have an AWS ELB forward the actual host name to the target group instead of the ELB's host name?

We have a Ruby/Rails website we're migrating from Heroku to AWS. The original dev is not available. I'm now trying to complete the migration. My background is in the Windows / .NET world. This Linux / Ruby/Rails environment is quite foreign to me...
Here's the current environment I've set-up:
Route 53
Record Name
Record Type
Alias
Alias Route Traffic To
foo.example.com
A
yes
cloudfront: xyz.cloudfront.net
CloudFront
Domain Name
Alternate Domain Names
Origin Domain
Origin Protocol
Behavior Protocol
xyz.cloudfront.net
foo.example.com
foo.us-west-2.elb.amazonaws.com
HTTP only
Redirect HTTP to HTTPS
The CloudFront distribution:
uses an AWS issued SSL cert
handles the http to https redirect
forwards the request to the ELB over http (not https)
Load Balancer
DNS Name
Listener Rule
Forward To
foo.us-west-2.elb.amazonaws.com
HTTP 80: default action
Target Group: foo-ec2
Target Group: foo-ec2 contains a single Ubuntu ec2 instance running nginx/1.18.0 + Phusion Passenger 6.0.10 to serve up the Ruby/Rails site.
nginx config
server {
listen 80 default_server;
listen [::]:80 default_server;
# SSL Config - we should NEVER receive 443/https traffic;
# CloudFront manages https traffic => AWS ELB => http to this server
#listen 443 ssl default_server;
#listen [::]:443 ssl default_server;
server_name foo.example.com
foo.us-west-2.elb.amazonaws.com;
# Tell Nginx and Passenger where the app's 'public' directory is
root /var/www/foo_example/public;
# Turn on Passenger
passenger_enabled on;
passenger_app_env production;
passenger_ruby /home/ubuntu/.rbenv/versions/2.6.8/bin/ruby;
}
Issue
The rails app starts up without error and is served over https. However, when a user attempts to log in / authenticate, the Devise gem sends back a redirect using http and the ELB's DNS name.
Example
sign_in request
Request URL: https://foo.example.com/users/sign_in
Request Method: POST
Status Code: 302
sign_in response
location: http://foo.us-west-2.elb.amazonaws.com/users
server: nginx/1.18.0 + Phusion Passenger(R) 6.0.10
status: 302 Found
Notice the request was over https and our domain:
https://foo.example.com
But now we're over http and the ELB's domain:
http://foo.us-west-2.elb.amazonaws.com
My assumption
The devise gem is seeing the host from the ELB and then generates the URL from the ELB host, creating two issues:
we are now on http since the ELB communicates with the ec2 instance over http
we are now on the ELB's host name, foo.us-west-2.elb.amazonaws.com, instead of our name, foo.example.com
I've looked into the devise documentation to see if we can just pass in the http protocol and domain to use when creating the post back, but my ruby knowledge is limited. Plus, I think this would be the "bad" path; where the "good" path would be to have the AWS ELB forward the actual domain name, instead of it's own.
I've reviewed several SO and related stack sites with similar questions, but I've either ended up with an infinite loop redirect, or the various config changes have resulted in the same behavior of the devise gem creating the wrong URL post back.
These two questions seem to be the closest, but I'm not quite able to make the "connection" between the answers and my limited knowledge of this ecosystem.
AWS Cloudfront + Load Balancer, url changes from main domain to load balancer subdomain
cloudfront domain replaced by application load balancer dns name when redirecting from http to https
Question
How can I get the AWS ELB to forward our domain, foo.example.com, to the ec2 target group and not the ELB's domain?
After more experimentation with AWS settings, the solution is actually rather simple. The other answers I posted in the question were vague in the actual settings, so here's the concrete solution.
In CloudFront, you need to create a new origin request policy, not a cache policy:
Open up CloudFront
go to Policies (left nav)
click the "Origin Request" tab
click the "create origin request policy" button
name the policy whatever you want, i.e., "my origin request policy"
under "Origin request settings" > Headers: select "Include the following headers"
under "Add header": check the "Host" option
click the "Create" button
The policy will look like this:
Once the new origin request policy has been created:
head back to the CloudFront distributions
click your distribution's Id so you can edit it
click the "Behaviors" tab
select your behavior and edit
scroll down to "Cache key and origin requests"
make sure the "Cache policy and origin request policy (recommended)" is selected
under the "Origin request policy - optional", select your new policy, i.e., "my origin request policy"
save changes
The behavior will look like this (I'm using no caching for now to verify the ec2 instance is getting all the requests):
That's it. The host header is now correctly passed through to the ELB and ec2 instance. Nothing else needs to be done with the ELB.
I verified the host header was being used in all requests by modifying the nginx logging option to include the $host variable in the log file (and did a bit more customization to the OOB format):
# prefixed log with '[my-log]', but it's not needed; remove.
log_format my-log '[my-log] $http_x_forwarded_for - $remote_user [$time_local] '
'"$request_method $scheme://$host$request_uri $server_protocol" '
'$status $body_bytes_sent "$http_referer" "$http_user_agent" $request_time';
server {
listen 80 default_server;
listen [::]:80 default_server;
# SSL Config - we should NEVER receive 443/https traffic;
# CloudFront manages https traffic => AWS ELB => http to this server
#listen 443 ssl default_server;
#listen [::]:443 ssl default_server;
server_name foo.example.com
foo.us-west-2.elb.amazonaws.com;
# create the our log file
access_log /var/log/nginx/my-log.access.log my-log;
# Tell Nginx and Passenger where the app's 'public' directory is
root /var/www/foo_example/public;
# Turn on Passenger
passenger_enabled on;
passenger_app_env production;
passenger_ruby /home/ubuntu/.rbenv/versions/2.6.8/bin/ruby;
}
Surely this will help future me as well as others.
For infinite redirects, I made this additional change and the site is serving fine.
Select Origin. Edit origin
Select Match viewer in Protocol. Save. Invalidate. Done

Using apache SSL reverse proxy with a rails application

I have a rails application running on a private subnet, using port 8080, without SSL enabled.
I also have an Apache SSL server on a DMZ, which I use as a reverse proxy to the rails application I mention first.
The problem is, rails include some absolute url in the generated code, with adresses beginning with http://...
If i use config.force_ssl = true as I read here or there, there is a infinite redirection, because rails sees the requests coming as plain HTTP and issues a redirect to HTTPS, but as the client already is.
The solution was simple enough: put this line in the reverse proxy configuration file :
RequestHeader set X-Forwarded-Proto "https"

AWS Opsworks: Load balancing via https

I've setup 2 Rails server instances with an Elastic Load Balancer. I setup SSL via opsworks and when I hit the IP of my instances with https e.g. https://1.2.3.4 I can see the correct certificate.
However, when I hit the domain of the Elastic Load Balancer, a timeout is thrown (loads endlessly).
How should I setup ELB, to properly redirect with https to my instances?
Found the answer was in Security Groups. Besides setting all inbound connections to accept HTTPS protocol and port 443, you have to set the outbound connections of the ELB. I found out that they were only to HTTP, and trying to access the instances with HTTPS it failed. I setup a new outbound rule HTTPS to anywhere and it worked!

Rails with thin and ssl: http request not auto-redirected to https

Recently I wanted to secure my rails 4.2.1 app with https the easiest way. I found this question as well as this answer about WEBrick+SSL, both referencing to this post which is unfortunately not reachable any more. Then I found this answer recommending to use thin instead (naming other advantages of using thin). Then I followed this step-by-step guide, finally running thin start --ssl --ssl-key-file .ssl/key.pem --ssl-cert-file .ssl/cert.pem -e production with self-signed certificate. My config/environments/production.rb contains config.force_ssl = true.
Now I would like to access the web normally by typing example.com expecting to be automatically redirected to https://example.com but this does not happen. Typing looong https://example.com works fine. Here is a 2-year-old question with similar issue but any answer doesn't work either and something could have also changed since then.
How can I make it work? Or is there any different recent but simple enough way to start using ssl with rails? Thanks!
In your config/environment/production.rb file make sure you have the following:
config.force_ssl = true
Also make sure to update your cookie settings in config/initializers/session_store.rb:
Rails.application.config.session_store :cookie_store, key: '_secure_domain_session', httponly: true, secure: true
You also need to specify secure: true in the config/initializers/devise.rb file if you are using Devise
Also make sure to clear the cache on your browser
If you have a load balancer in front of your website that is terminating the TLS/SSL and then connecting via HTTPS to the backend, this would mean the connection from the load balancer to your server is HTTPS, even though the client connection to the load balancer is not. Your load balancer should send the X-Forwarded-Proto header which Rails should take into account.
If you are running Rails under Passenger inside Nginx (or Apache), you may need to configure that to forward the header and/or port.
passenger_set_header X-Forwarded-Proto $http_x_forwarded_proto;
passenger_set_header X-Forwarded-Port $server_port;
Note, however, that Rails looks first at the HTTPS environment variable before it looks at the header, and that might be set to "on" because connection to your web server is HTTPS.
In that case you can redirect all traffic from HTTP to HTTPS inside Nginx:
if ($http_x_forwarded_proto != 'https') {
rewrite ^ https://$host$uri permanent;
}

Amazon Load Balancer sticky sessions with ajp:8009

We configured ELB with sticky sessions for the JSESSIONID cookie for two tomcats (tomcat1 and tomcat2)(Flow is - Apache Http Server - ELB - tomcats)
AJP protocol with port 8009 has been configured on tomcat side as from AWS ELB there is no AJP option, we have configured with tcp:8009
So the Apache httpd.conf entry is, (xxx.amazonaws.com is ELB name)
BalancerMember ajp://xxx.amazonaws.com:8009
Somehow the sticky session is not working, the http request is sent to both tomcat servers. Is it because of the protocol on ELB side (tcp:8009)? We are not sure what is missing here, Need help!!
Once you change it to TCP you lose sticky sessions. It is an ELB limitation. You might be able to get away with switching the protocol to HTTP but with a different port other than 80.
Unless I am mistaken, you might have to setup an HA Proxy or something else instead of the ELB. Something that can do both TCP with sticky.
It is well know that websockets+sticky doesn't work on amazon.
https://forums.aws.amazon.com/thread.jspa?messageID=627367

Resources