Walking With Certificate Manager

August 26, 2017

Last week, I talked about my SSL setup with Cloudflare. There were a few things I didn’t love about the setup. It caused some funkiness with my Google OAuth2 authentication redirects but more importantly, it terminated the SSL connection before getting to my application servers. In my ongoing pursuit of experimentation, I decided to get a certificate closer to home.

There were several goals I wanted to achieve with the transition.

  1. Get an SSL certificate on my servers at AWS (either on the load balancer - ELB or on the EC2 instance itself)
  2. Release my dependency on Cloudflare
  3. Maintain https always (http should redirect to https)
  4. Maintain www redirect to root domain (https://ramekintech.com)

As is typical with any technology transition, there were bumps in the road. I didn’t find everything I needed to get it all working in one place, so I put my pieced together solution here.

Ingredients

  • 64bit Amazon Linux 2017.03 v2.4.2 running Ruby 2.3 (Puma)
  • Nginx 1.10.3
  • Rails 5.1.2
  • Cloudflare with flexible SSL

Step 1: Get The Certificate

I decided to use Amazon’s Certificate Manager to acquire the certificate mainly because the site is hosted on the AWS platform. It was super easy to get the free certificate as I already had the correct domain email addresses setup (eg. [email protected], [email protected], etc.) Amazon’s documentation was all I needed for this step.

Step 2: Assign The Certificate

With the certificate in hand, the next step was to get the site to use it. Because I’m using the default installation given to me by Elastic Beanstalk, I have an Elastic Load Balancer (ELB) in front of my EC2 instance. This is fortunate because that is one of only two places an Amazon certificate can be placed (the other being CloudFront - not to be confused with Cloudflare).

This step was also fairly uneventful with the aid of the proper documentation. After adding the securelistener.config to my source tree and deploying via the simple eb deploy command, I was ready to test.

Step 3: Shutdown Cloadflare

I removed the domain from Cloudflare which meant I had to make sure all of my domain records and name servers were setup correctly at my registrar (I currently use Hover if you’re curious). The micro steps are as follows:

  1. Move nameservers from Cloudflare back to Hover
  2. Set the root CNAME record to point to the load balancer at Amazon. I don’t love that I have to use a CNAME. I would prefer an A record pointed at my elastic IP but unfortunately, that is not an option.
  3. Set the MX records so that email continues to flow.
  4. Set another CNAME record to route www to the root ramekintech.com

With all of this in place and about 10 minutes of waiting, I was able to hit http://ramekintech.com as well as https://ramekintech.com and the various forms preceded with www. So far so good. I only needed to ensure https was always used without requiring users to put it in manually. I like the idea of making security as easy as possible.

Step 4: Setup the NGINX Redirects

To get https always, I just needed a few lines in my nginx.conf file located in /etc/nginx. Those lines are:

http {
  ...
  server {
  ...
    if ($http_x_forwarded_proto = 'http') {
      return 301 https://ramekintech.com$request_uri;
    }
  ...
  }
}

Now, to get www to redirect to the root ramekintech.com, a few more lines in nginx.conf are needed:

http {
  ...
  server {
    server_name www.ramekintech.com;
    return 301 https://ramekintech.com$request_uri;
  }
  ...
}

Don’t forget to restart Nginx once the changes are made.

$ sudo service nginx restart

The second server block, by the way, is in addition to the first server. Let me clarify with the full config:

user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log;
pid /var/run/nginx.pid;

events {
    worker_connections 1024;
}

http {
    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

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

    sendfile            on;
    tcp_nopush          on;
    tcp_nodelay         on;
    keepalive_timeout   65;
    types_hash_max_size 2048;

    include             /etc/nginx/mime.types;
    default_type        application/octet-stream;

    index   index.html index.htm;

    upstream myapp {
        server unix:/var/run/puma/my_app.sock;
    }

    server {
        server_name www.ramekintech.com;
        return 301 https://ramekintech.com$request_uri;
    }

    server {
        listen       80 ;
        listen       [::]:80 ;
        server_name  ramekintech.com;
        root         /usr/share/nginx/html;

        if ($http_x_forwarded_proto = 'http') {
            return 301 https://ramekintech.com$request_uri;
        }

        location / {
            proxy_pass        http://myapp;
            proxy_set_header  X-Forwarded-Ssl on;
            proxy_set_header  X-Forwarded-Host $host;
        }

        error_page 404 /404.html;
            location = /40x.html {
        }

        error_page 500 502 503 504 /50x.html;
            location = /50x.html {
        }
    }
}

Wrap Up

That’s all there is to it. It was a fair amount of work but I’m happy with the results. All of my requirements are met and I dropped one dependency. Now if I ever loop Cloudflare back in again, it will be because I actually need/want them for DNS and DDoS protection.