Contact

Please send a message if you want to work together or if you have any questions.

Forcing SSL in an Elastic Beanstalk single instance environment with NGINX & Puma


For medium to large scale rails applications I like to use Elastic Beanstalk's load balancing/auto scaling web server environments. In these environments, you can setup ssl by uploading your certs via the AWS CLI, and then altering the network's configuration through the Elastic Beanstalk interface.

The AWS documentation does a good job of explaining the steps in this scenario. And after that is set up, you can force ssl on every page in a rails application by uncommenting the line config.force_ssl = true in the /config/environments/production.rb file.

Single instance environments provide a way to deploy an application to Elastic Beanstalk for a much lower cost by opting out of the load balancing features. When I first used this environment I ran into some pitfalls trying to configure and force ssl for an application. For example, you cannot upload certificates with the AWS CLI, and using the force_ssl = true option causes a redirect loop. So here are the steps to get it working (after purchasing an ssl certificate):

  1. Upload your server.crt & server.key files to a secure S3 bucket
  2. Give your ec2 role permission to access these files in S3
  3. Create the configuration files used during deployment to upload the certificates & configure NGINX.
Step One:
Create a new AWS S3 bucket and upload your ssl server.crt and server.key files to it. The bucket will be private by default, and we want to keep it that way so no unauthorized users can access your certificates. In the next step we'll configure the right permissions so your AWS account's ec2 role can access the files during deployment.

Step Two:
Open the IAM Management Console from your AWS dashboard and select 'roles' from the menu. If you have successfully deployed an application to Elastic Beanstalk through the EB/AWS CLI you will have a role named aws-elasticbeanstalk-ec2-role. Select this role and click Attach Policy. Then attach the AmazonS3FullAccess policy to the role. This role is used by your account during application deployments, so now it will have authorization to authenticate and access the ssl certificates which we saved to a secure S3 bucket in the previous step.

Step Three:
We'll need to create two separate config files in your app's .ebextensions folder which will be run during deployments. This first file authenticates as your ec2 role and copies the ssl certificates from your S3 bucket to the webserver.


Resources:
AWSEBAutoScalingGroup:
Metadata:
AWS::CloudFormation::Authentication:
S3Auth:
type: "s3"
buckets: ["yourbucketname"]
roleName:
"Fn::GetOptionSetting":
Namespace: "aws:asg:launchconfiguration"
OptionName: "IamInstanceProfile"
DefaultValue: "aws-elasticbeanstalk-ec2-role"
files:
"/tmp/server.crt" :
mode: "000400"
owner: root
group: root
authentication: "S3Auth"
source: "https://s3-us-west-2.amazonaws.com/yourbucketname/server.crt"
"/tmp/server.key" :
mode: "000400"
owner: root
group: root
authentication: "S3Auth"
source: "https://s3-us-west-2.amazonaws.com/yourbucketname/server.key"
container_commands:
move_cert:
command: "mv /tmp/server.crt /etc/pki/tls/certs/"
move_key:
command: "mv /tmp/server.key /etc/pki/tls/certs/"

Replace yourbucketname with your S3 bucket's name, and also verify that two file's S3 sources are correct, including the AWS region.

This next file will now configure NGINX to open port 443 to use ssl connections. (Note: this is my setup for Puma webservers, if you are running Passenger the setup may be different).

Resources:
sslSecurityGroupIngress:
Type: AWS::EC2::SecurityGroupIngress
Properties:
GroupId: {"Fn::GetAtt" : ["AWSEBSecurityGroup", "GroupId"]}
IpProtocol: tcp
ToPort: 443
FromPort: 443
CidrIp: 0.0.0.0/0
files:
/etc/nginx/conf.d/https.conf:
content: |
# HTTP server
server {
listen 80;
server_name yourwebsite.com;
# permanently redirect http requests to https
location / {
return 301 https://$host$request_uri;
}
}
# HTTPS server
server {
listen 443;
server_name localhost;
ssl on;
ssl_certificate /etc/pki/tls/certs/server.crt;
ssl_certificate_key /etc/pki/tls/certs/server.key;
ssl_session_timeout 5m;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH";
ssl_prefer_server_ciphers on;
location / {
proxy_pass http://my_app;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
location /assets {
alias /var/app/current/public/assets;
gzip_static on;
gzip on;
expires max;
add_header Cache-Control public;
}
location /public {
alias /var/app/current/public;
gzip_static on;
gzip on;
expires max;
add_header Cache-Control public;
}
}
container_commands:
01restart_nginx:
command: "service nginx restart"

This following block is what redirects all http requests to https, so it can be omitted if you do not want to force ssl.

# HTTP server
server {
listen 80;
server_name yourwebsite.com;
# permanently redirect http requests to https
location / {
return 301 https://$host$request_uri;
}
}

At this point you can run eb deploy and your scripts will upload the ssl certificate and key, and then configure the server's ports for ssl.