Amazon AWS

Creating and installing a SSL certificate on Amazon EC2.

In my previous posts I showed you how to setup HTTPS on Amazon’s EC2/Elastic Beanstalk.  In those posts I used a self-signed certificate.  This is a fine approach for the early stages of development or prototypes, but if you’re launching a real site you have to buy a real certificate from one of the certifying agencies otherwise your users may be scared off by  warnings about accessing an untrusted site.   Getting a SSL certificate is a fairly easy process but not always cheap,  you can do a search on the web for “cheap ssl” to try to find the best deal for your needs.  I chose GoDaddy because I had a coupon from them.  It pays to shop around as prices vary dramatically.

The first step is to generate a private key and the Certificate Signing Request needed for the certifying agency.  You can do this with openssl.  If you’re on a Mac like I am, openssl is already installed.  If you’re on another platform, here are some instructions on how to install it.   Ready? Let’s get started:

  1. Open a terminal window and create a directory for your certificate files.  Create this in a safe location and name it appropriately so you don’t accidentally delete it later.
    $ mkdir my-ssl-certifications
    $ cd my-ssl-certifications
  2. Create the private key according to GoDaddy’s requirements (other providers may have different requirement so be sure to check first).   Enter the following command.  You’ll be asked for a pass phrase.  Be sure you remember it, you’ll need it later.
    $ openssl genrsa -des3 -out host.key 2048
    Generating RSA private key, 2048 bit long modulus
    ..................................................+++
    ...............................+++
    e is 65537 (0x10001)
    Enter pass phrase for host.key:
    Verifying - Enter pass phrase for host.key:
  3. Now lets use the private key to create the certificate signing request (CSR).
    $ openssl req -new -key host.key -out host.csr
  4. You’ll be asked for your pass phrase again followed by several questions.  It is vital that you answer these correctly and accurately otherwise your certificate will not be valid.  In particular, be sure that the Organizational Unit Name, and the Common Name match the URL for your website.  You can leave the last two ‘extra’ questions unanswered.
    Enter pass phrase for host.key:
    You are about to be asked to enter information that will be incorporated
    into your certificate request.
    What you are about to enter is what is called a Distinguished Name or a DN.
    There are quite a few fields but you can leave some blank
    For some fields there will be a default value,
    If you enter '.', the field will be left blank.
    -----
    Country Name (2 letter code) [AU]:US
    State or Province Name (full name) [Some-State]:Missouri
    Locality Name (eg, city) []:Saint Louis
    Organization Name (eg, company) [Internet Widgits Pty Ltd]:My Company
    Organizational Unit Name (eg, section) []:www.mycompany.com
    Common Name (e.g. server FQDN or YOUR name) []:www.mycompany.com
    Email Address []:contact@mycompany.com
    
    Please enter the following 'extra' attributes
    to be sent with your certificate request
    A challenge password []:
    An optional company name []:
  5. Now that you have the host.csr file, you have to submit it to the certifying agency.  For GoDaddy I had to first purchase a standard single-domain SSL certificate on their website.  Upon checkout a  ‘SSL Get Started’ link appeared.  I clicked on that and finally got to a form that asked for the CSR.
  6. Open the hosts.csr file using a text editor and then copy and paste the entire contents (including the delimiters) into the GoDaddy CSR request form.
    -----BEGIN CERTIFICATE REQUEST-----
    [encoded text here]
    -----END CERTIFICATE REQUEST-----
  7. Submit the form.  The agency will then verify the information.  The process and the length of time this may take will differ depending on the certifying agency.  GoDaddy was able to verify my domain instantly because I purchased my domain through them so they knew the domain and had my contact information.
  8. Once you receive notice that the certificates are ready, download them into the directory you created earlier.  For GoDaddy, I received a zip file containing 2 files:  a [server].crt  (a randomly named file ending in .crt) and a gd_bundle.crt file
  9. Now we need to modify and upload these certs to Amazon.  So log into the Amazon EC2 Management Console.
  10. Click on Load Balancers in the left hand column
  11. Click on the checkbox next to Load Balancer for your instance (it may be the only one).
  12. Click on the Listeners tab that appears at the bottom of the page
  13. Click on the Change link in SSL Certificate column for the HTTPS row.
  14. Click on Upload a new SSL Certificate button in the popup window that appears
  15. For the Certificate Name, enter any name you want to identify this certificate.
  16. For Private key you need to get the text for the original host.key you created.  Type the following command in your terminal window and copy the results from the terminal window into the Private Key field on Amazon.

    $ openssl rsa -in host.key -text
  17. For the public key you need to convert the [server].crt file that you received into PEM format. Type the following command in your local terminal window  (replace server.crt with the actual name of the server cert file you received) and copy the results into the Public Key Certificate field on Amazon.
    $ openssl x509 -inform PEM -in server.crt
  18. Finally you need the certificate chain certification. The Amazon form says this is ‘optional’.  It isn’t.  Type this command in your local terminal window and copy the results into the Certificate Chain field on Amazon.
    $ openssl x509 -inform PEM -in gd_bundle.crt;
  19. Click Save.
  20. Give the instance a minute or two to recognize the changes and reboot. If you now visit your site using https you should no longer get a warning about accessing an untrusted site.

Updating WSGI automatically in Amazon AWS

Continuing from my previous post about Setting up HTTPs in Amazon AWS, I still haven’t found an elegant solution to automatically insert the necessary redirect rules into Amazon’s WSGI.conf file, but I have found a work around.   Here’s how to replicate what I’m doing at the moment…

  1. ssh into your EC2 instance
  2. copy the contents of /etc/httpd/conf.d/wsgi.conf into a local file called wsgi.conf which will be placed in the base folder of your application
  3. Edit the local version of wsgi.conf and add the following redirect rules within the <VirtualHost>…</VirtualHost> tags
    RewriteEngine On
    RewriteCond %{HTTP:X-Forwarded-Proto} !https
    RewriteRule !/about https://%{SERVER_NAME}%{REQUEST_URI} [L,R=301]
  4. Change the “/about” to whatever page you are using as a health check page.
  5. Save the file
  6. Edit your <app>.conf file inside your .ebextensions directory to add a container command to copy this version of wsgi.conf over Amazon’s version
    container_commands:
    01_syncdb:
      command: "django-admin.py syncdb --noinput" leader_only: true
    02_collectstatic:
      command: "django-admin.py collectstatic --noinput"
    03_wsgireplace:
     command: 'cp wsgi.conf ../wsgi.conf'
    ...
  7. Deploy the code.
  8. The deployed version of wsg.conf at /etc/httd/conf.d/wsgi.conf will now include the necessary redirect rules.

Voila!   It should work and the file will be properly updated for each deployment.  The only thing to watch for is if Amazon changes their base wsgi.conf file contents in the future, then your copy may no longer work.

Getting a Django app to use HTTPS on AWS Elastic Beanstalk

The site that I am building needs to be secure.  So I want all requests to go through HTTPS rather than HTTP.  This is simple to do with my normal toolset.   Doing this with Django deployed on Amazon’s Elastic Beanstalk was a whole different ordeal.  After much Googling, head scratching, and experimenting I finally found a configuration that works.

Here goes:

Phase 1 – Setup your certificate and open the HTTPS ports

  1. Deploy your Django app to AWS (see Amazon documentation or previous posts for how to do this).
  2. Create a certificate (self-signed is fine) and use the Amazon IAM tools to upload it.  You can find instructions here.
  3. Open the AWS Elastic Beanstalk admin console
  4. Click on your application, then click on ‘Configuration‘ in the left hand column
  5. Click on the gear icon in the ‘Load Balancing” box at the bottom of the page
  6. In “Secure listening port” select 443
  7. In “Protocol” below that, select HTTPS
  8. In “SSL certificate ID” select the certificate you uploaded in step 2.
  9. In Application health check URL: enter the path to some page in your site that will not be protected (i.e /about)
  10. You might want to note the Load Balancer ID at this time, you may need it to troubleshoot later.
  11. Click “Save
  12. The instance will restart

At this point your site should work if you type https://<mysite.com> instead of http://<mysite.com>.  If it isn’t connecting, then there are a few things to check out:

  1. Click on “Configuration” again in the left hand column
  2. Click on the gear icon in the “Instances” box
  3. Note the “EC2 security groups” id so you can find the same one in the next steps
  4. Go to the AWS EC2 admin console  https://console.aws.amazon.com/ec2/
  5. Click on “Security Groups” in the left hand column
  6. Find the security group id from step 3 and click on that
  7. Click on the “Inbound” tab
  8. Verify that there is a rule for both 80(HTTP) and 443(HTTPS).  If 443 is missing, add it and then click “Apply Rule Changes”
  9. Wait a minute and check the site again to see if you can access it using https.   If so, go to the next phase, otherwise continue here
  10. Click on “Load Balancers” in the left-hand column.
  11. Click on the Load Balancer id from Step 10 in the first section
  12. Click on the “Listeners” tab that appears
  13. Ensure there is an entry for HTTPS like below.  If anything is missing, correct it.  Ensure the SSL cert matches the one you uploaded earlier.
Load Balancer Protocol
Load Balancer Port
Instance Protocol
Instance Port
Cipher
SSL Certificate
Actions
HTTP
80
HTTP
80
N/A
N/A
HTTPS
443
HTTP
80
<your cert name>

Phase 2 – Configure Django to use HTTPS

By now your site is accessible through https, but it isn’t forcing users to use https.   Let’s do those steps now.

First let’s secure Django.  In the apps’ Django settings file, add the following:

SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True

The two ‘cookie’ settings tell Django not to accept or issue these if the request isn’t secure.  This effectively keeps users from signing in or posting data when using HTTP.

The SECURE_PROXY_SSL_HEADER is a little trickier to explain.  We setup the AWS Elastic Beanstalk Load Balancer to listen on port 443, but internally it talks to your app on port 80. This can be seen in the Load Balancer configuration above where port 443 redirects to port 80.   So traffic coming to your app is ALWAYS on port 80.  So how will it know if the user is actually using https?  Well, Amazon adds an HTTP header to the request it passes to your app if the original request is using https.   The name of that header is “X_Forwarded_Proto”.   By setting SECURE_PROXY_SSL_HEADER = (‘HTTP_X_FORWARDED_PROTO’, ‘https’) you’re telling Django to treat any request that contains that header as a https request.  Yes, the two headers have different names depending on if it is used in HTTP or in Django.

Go ahead and deploy those changes to the AWS server and wait a few minutes for it to restart.

If all went well, at this point you should be able to access your app on https and log into your app on https.   Logging in with http should fail and return the user to the login screen because the session cookie won’t be issued thanks to the settings you just deployed. Posting data using HTTP should also fail for CSRF not being secure (provided your app uses CSRF…and it should)

Phase 3 – Redirect HTTP user to HTTPS

We’re still not done.  We want to automatically switch the user to https if they access the app using http.  To accomplish that, we have to add some redirect rules to Apache to handle this so there’s one more configuration to change.

The steps below have been replaced with the steps in this post

  1. ssh into your EC2 instance (see previous posts for how to do this)
  2. cd /etc/httpd/conf.d/
  3. sudo vim wsgi.conf
  4. Add the following within the <VirtualHost>…</VirtualHost> marker
    RewriteEngine On
    RewriteCond %{HTTP:X-Forwarded-Proto} !https
    RewriteRule !/about https://%{SERVER_NAME}%{REQUEST_URI} [L,R=301]
  5. Change the /about to whatever page you decided to use back in Step 9 in the first section
  6. Save the file
  7. Return to your Elastic Beanstalk admin console and select Restart App Server(s) under the ‘Actions” menu.
  8. The servers will restart
  9. Go to your site using http://<mysite.com>, the browser should redirect automatically to https://<mysite.com>

Now everything should be working.  The only issue will be that wsgi.conf gets overwritten each time you deploy your app.  So you have to do Step 4 above each time.   I am still working out how to configure that to automatically happen. Stay tuned for an update on that. –UPDATE– New instructions can be found in this post: Updating WSGI automatically in Amazon AWS

Connecting to Amazon RDS

Once you can access your Amazon EC2 instance through SSH (see this post), getting access to your RDS instance is fairly simple.  Here’s how:

  1. Log into the Amazon RDS Admin Console
  2. Click on “Instances” in the left hand column
  3. Find the instance you want to access, it may be the only one listed.  If not, you can go to your Elastic Beanstalk admin console, click on your app, Configuration, RDS Instances and find the instance ID.
  4. Select and copy the Endpoint URL.  You’ll need this later.

Here you have a couple of choices:  The first option is to access the RDS instance through the SSH tunnel you created previously (more secure), or the second option is to open a port (less secure).

Option 1: Using the SSH tunnel

Many database tools have an option to use an SSH tunnel.  I’m using MySQLWorkbench, these steps may vary depending on your tool.

  1. You’re done with the Amazon RDS Admin Console. So you can close it.
  2. Open your database tool and create a new connection document
  3. Select “Standard TCP/IP over SSH” as the Connection Method
  4. Enter your EC2 Instance endpoint at the SSH hostname (see previous post for where to find this if you don’t have it)
  5. Enter “ec2-user” for the SSH Username
  6. Select the keypair you created for the SSH connection for “SSH Key File
  7. Enter the RDS Endpoint from step 4 in the first section as “MySQL Hostname
  8. Enter 3306 as the “MySQL Server Port” (this will differ if you aren’t using MySQL)
  9. Enter “ebroot” as the “Username
  10. Optionally, save your RDS password.
  11. Test and save the connection.

Option 2: Opening a port

  1. In the Amazon RDS Admin Console, note the ID of the link in the first “Security Groups” section, then click the link
  2. Find the group with the ID from the previous step and click on it.
  3. Select the “Inbound” tab in the section that appears
  4. Add a new rule for 3306 (MYSQL) or whatever database you are using.
  5. Specify your IP address as the Source.  You can also use 0.0.0.0/0 initially for testing but don’t leave it like that because it is a security risk.
  6. Click “Apply Rule Changes”

Whichever option you choose, you should now be able to access the RDS instance from your local machine.

Accessing an Amazon EC2 instance using SSH

Being able to have command line access to the server is important.   Amazon AWS allows this, but it isn’t setup by default.   Amazon provides instructions on how to do it, but this took me a while to configure correctly because a key piece isn’t documented.

First you need to create a key pair to secure your connection.

  1. Go the AWS EC2 admin console
  2. Click “Key Pairs” in the left hand column
  3. Click the “Create Key Pair” button on the top of the page
  4. Give it a name and click “Yes”
  5. The key pair will be downloaded to your desktop as a file called <keypair>.pem  It will be in your browser’s Dowloads folder.  You’ll need this file in a minute so find it!
  6. Click on Instances in the left hand column
  7. Find your App’s instance in the list (may be the only one) and click on it
  8. Note the “Security Groups” id in the Description section that appears
  9. Also select and copy the “Public DNS” URL.  You’ll need that later.
  10. Click on “Security Groups” in the left-hand column
  11. Find the group with the id from step 8 and click on it
  12. Click on the “Inbound” tab
  13. Add a rule for 22 (SSH) with the source as your local IP.  You can also use 0.0.0.0/0 if you are experimenting, but don’t leave it like that as it could be a security risk.
  14. Click “Apply Rule Changes
  15. Now here’s what’s not documented… Go to the AWS Elastic Beanstalk admin console
  16. Click on your app
  17. Click on “Configuration” in the left hand column
  18. Click on the gear icon in the “Instances” box
  19. Select your key pair in the “EC2 key pair” box.
  20. Click ‘Save
  21. The server will restart.

Now the server should allow you to access it on port 22 with the key pair you created.  Let’s try it!

  1. Open a terminal app and navigate to where ever your <keypair>.pem file is saved
  2. chmod 400 <keypair>.pem
  3. ssh-add <keypair>.pem    This will add your keypair to SSH so you don’t have to keep referring to it
  4. ssh ec2-user@ec2-xxx-xx-xxx-x.compute-1.amazonaws.com   –  Always use ec2-user as the username and use the public DNS that you copied in step 9 above.
  5. It should now work