13

I'm having issues forcing HTTPS with a site that I'm deploying through AWS Elastic Beanstalk.

It's a frontend application using EmberJS. I've been going around in circles for many days trying to figure out how to redirect http traffic to https. I'm using Amazon Linux AMI on my EC2 machine.

I've come to the conclusion (still not sure if this is right) that it is not within Elastic Beanstalk that I force HTTPS. I'm allowing both HTTP and HTTPS through my Elastic Beanstalk Load Balancer, and am attempting to redirect on the server.

This is where I'm running into problems. I'm finding many answers about rewrite rules without mod_rewrite which are based around the X-Forwarded-Proto Header, but that file does not exist on my EC2 machine according to a find search.

I've also tried creating a config file within the .ebextensions directory, but that did not work either.

The main thing I am trying to do is have users directed to https when the try to hit the http address. Any pointers or suggestions are much appreciated, thanks!

EDIT: I'm using 64bit Debian jessie v1.4.1 running Python 3.4 (Preconfigured - Docker)

awwester
  • 267
  • 1
  • 3
  • 13
  • It seems that the Internet cannot agree on a single, complete and working solution to this problem. Hopefully you can get some help [here in my post](http://thehunk.blogspot.in/2017/11/how-to-force-redirect-http-to-https-in.html). I had to jump through hoops to come up with this, finally. – ADTC Nov 12 '17 at 00:08

4 Answers4

11

It is also possible to do that somewhat more easily, without touching the load balancer, by using the X-Forwarded-Proto header set by ELB. Here is what I ended up doing :

files:
  "/etc/nginx/sites-available/elasticbeanstalk-nginx-docker-proxy.conf":
    mode: "00644"
    owner: root
    group: root
    content: |
      map $http_upgrade $connection_upgrade {
        default        "upgrade";
        ""            "";
      }

      server {
        listen 80;

        gzip on;
        gzip_comp_level 4;
        gzip_types text/html text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript;

        access_log    /var/log/nginx/access.log;

        location / {
          proxy_pass            http://docker;
          proxy_http_version    1.1;

          proxy_set_header      Connection      $connection_upgrade;
          proxy_set_header      Upgrade         $http_upgrade;
          proxy_set_header      Host            $host;
          proxy_set_header      X-Real-IP       $remote_addr;
          proxy_set_header      X-Forwarded-For $proxy_add_x_forwarded_for;
        }

        if ($http_x_forwarded_proto = 'http') {
          return 301 https://$host$request_uri;
        }
      }
F.X.
  • 241
  • 2
  • 8
9

I think you need to specify what Elastic Beanstalk environment that you use (see: Supported Platforms), because different environment has different configuration.

Basically, you need to customize:

  • Elastic Load Balancer:
    • Listen on port 80 and proxy it to EC2 instance port 80.
    • Listen on port 443 and proxy it to EC2 instance port 443.
  • EC2 Web Server/Proxy:
    • Listen on port 80 and response with redirect to HTTPS.
    • Listen on port 443 and serve the request.

To customized it, you can use CLI or .ebextensions.

You can check on Enable HTTPS and HTTP-Redirect on AWS Elastic Beanstalk. It tells you how to configure Elastic Beanstalk Single Docker Container serve HTTPS and HTTP (redirect to HTTPS). You can adjust the config as your need.

Frederik
  • 3,293
  • 3
  • 30
  • 46
Edward Samuel
  • 781
  • 7
  • 9
  • hey, great article, I'm in the process of trying this. – awwester Jul 28 '15 at 15:01
  • any ideas of how to not include the certs inside that file, would rather not keep that in source control? Are the certs we have loaded already available somewhere? I can't seem to find them on the filesystem – awwester Jul 28 '15 at 15:51
  • You can put your SSL certificate file in S3. To allow Elastic Beanstalk download private S3 object, you can read [this](http://stackoverflow.com/questions/16339497/access-denied-to-an-s3-object-in-elastic-beanstalk-configuration-setting). – Edward Samuel Jul 28 '15 at 16:09
  • For the ELB SSL certificate, you can follow AWS documentation: [SSL Certificates for Elastic Load Balancing](http://docs.aws.amazon.com/ElasticLoadBalancing/latest/DeveloperGuide/ssl-server-cert.html). An then you can get the SSL certificate resource in `arn:aws:iam::123456789012:server-certificate/YourSSLCertificate` format. – Edward Samuel Jul 28 '15 at 16:13
  • I have the SSL cert set up and i have the arn that would go in 00-load-balancer (i'm actually doing the load balancer config through the UI), but can't seem to get the location to put in the server settings `ssl_certificate /opt/ssl/default-ssl.crt;` When I get the information for the cert, it gives me a "path" but it is just "/" – awwester Jul 28 '15 at 16:21
  • looks like the cert uploaded to EB isn't available in the EC2 instance. I'm going down the route of adding it into the config file like in your article – awwester Jul 28 '15 at 16:28
  • The SSL in `/opt/ssl/default-ssl.crt` is created using `.ebextensions`. The certificate content is same as your SSL certificate uploaded into ELB SSL config (the `arn:aws:iam::123456789012:server-certificate/*`). – Edward Samuel Jul 28 '15 at 16:31
  • alright, still having issues with nginx not liking my cert files, but this is the answer. Thanks. – awwester Jul 28 '15 at 17:38
3

Elastic Beanstalk doesn't support multiple ports from a Single Docker Container, so you need to handle this at the proxy level as suggested. However, your EC2 instance doesn't need to know about your certificate, because you can terminate the SSL connection at the load balancer.

In your .ebextensions directory, create a configuration for the nginx proxy that contains two server configs; one that proxies http://docker (the default configuration, port 80), and one that redirects to https (I chose port 8080).

.ebextensions/01-nginx-proxy.config:

files:
  "/etc/nginx/sites-available/000-default.conf":
    mode: "000644"
    owner: root
    group: root
    content: |
      map $http_upgrade $connection_upgrade {
          default        "upgrade";
          ""            "";
      }

      server {
          listen 80;

          gzip on;
          gzip_comp_level 4;
          gzip_types text/html text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript;

          access_log    /var/log/nginx/access.log;

          location / {
              proxy_pass            http://docker;
              proxy_http_version    1.1;

              proxy_set_header    Connection            $connection_upgrade;
              proxy_set_header    Upgrade                $http_upgrade;
              proxy_set_header    Host                $host;
              proxy_set_header    X-Real-IP            $remote_addr;
              proxy_set_header    X-Forwarded-For        $proxy_add_x_forwarded_for;
          }
      }

      server {
          listen 8080;

          location / {
              return 301 https://$host$request_uri;
          }
      }

commands:
   00_enable_site:
    command: 'rm -f /etc/nginx/sites-enabled/* && ln -s /etc/nginx/sites-available/000-default.conf /etc/nginx/sites-enabled/000-default.conf'

Create a second configuration for the EB load-balancer and security groups that sets them up as follows:

  • EC2 instance:
    • Allow traffic on ports 80/8080 from load balancer
    • Allow traffic on port 22 from anywhere (for ssh access, optional)
  • Load balancer:
    • Forward port 443 HTTPS to port 80 HTTP
    • Forward port 80 HTTP to port 8080 HTTP

.ebextensions/02-load-balancer.config:

"Resources" : {
  "AWSEBSecurityGroup": {
    "Type" : "AWS::EC2::SecurityGroup",
    "Properties" : {
      "GroupDescription" : "Instance security group (22/80/8080 in)",
      "SecurityGroupIngress" : [ {
          "IpProtocol" : "tcp",
          "FromPort" : "80",
          "ToPort" : "80",
          "SourceSecurityGroupId" : { "Ref" : "AWSEBLoadBalancerSecurityGroup" }
        }, {
          "IpProtocol" : "tcp",
          "FromPort" : "8080",
          "ToPort" : "8080",
          "SourceSecurityGroupId" : { "Ref" : "AWSEBLoadBalancerSecurityGroup" }
        }, {
          "IpProtocol" : "tcp",
          "FromPort" : "22",
          "ToPort" : "22",
          "CidrIp" : "0.0.0.0/0"
        } ]
    }
  },
  "AWSEBLoadBalancerSecurityGroup": {
    "Type" : "AWS::EC2::SecurityGroup",
    "Properties" : {
      "GroupDescription" : "Load balancer security group (80/443 in, 80/8080 out)",
      "VpcId" : "<vpc_id>",
      "SecurityGroupIngress" : [ {
          "IpProtocol" : "tcp",
          "FromPort" : "80",
          "ToPort" : "80",
          "CidrIp" : "0.0.0.0/0"
        }, {
          "IpProtocol" : "tcp",
          "FromPort" : "443",
          "ToPort" : "443",
          "CidrIp" : "0.0.0.0/0"
        } ],
      "SecurityGroupEgress": [ {
          "IpProtocol" : "tcp",
          "FromPort" : "80",
          "ToPort" : "80",
          "CidrIp" : "0.0.0.0/0"
        }, {
          "IpProtocol" : "tcp",
          "FromPort" : "8080",
          "ToPort" : "8080",
          "CidrIp" : "0.0.0.0/0"
        } ]
    }
  },
  "AWSEBLoadBalancer" : {
    "Type" : "AWS::ElasticLoadBalancing::LoadBalancer",
    "Properties" : {
      "Listeners" : [ {
          "LoadBalancerPort" : "80",
          "InstancePort" : "8080",
          "Protocol" : "HTTP"
        }, {
          "LoadBalancerPort" : "443",
          "InstancePort" : "80",
          "Protocol" : "HTTPS",
          "SSLCertificateId" : "arn:aws:iam::<certificate_id>:<certificate_path>"
        } ]
    }
  }
}

(Note: don't forget to replace the SSLCertificateId and VpcId with your values).

Any traffic on port 80 of the load balancer (HTTP) will hit port 8080 on the EC2 instance, which redirects to HTTPS. Traffic on port 443 on the load balancer (HTTPS) will end up being served by port 80 on the EC2 instance, which is the docker proxy.

0

Iam using Terraform to enable HTTP to HTTPS redirect on ElasticBeanstalk,

I just added additional Listener Rule

data "aws_alb_listener" "http" { //Get ARN of Listener on Port-80
  load_balancer_arn = aws_elastic_beanstalk_environment.myapp.load_balancers[0]
  port              = 80
}


resource "aws_alb_listener_rule" "redirect_http_to_https" {
  listener_arn = data.aws_alb_listener.http.arn
  action {
    type = "redirect"
    redirect {
      port        = "443"
      protocol    = "HTTPS"
      status_code = "HTTP_301"
    }
  }
  condition {
    host_header {
      values = ["*.*"]
    }
  }
}