Laravel 4 SSL on single instance AWS Elastic Beanstalk

Setting up SSL on Laravel 4 on an Elastic Beanstalk server is tricky, there are a couple of land mines to avoid, which took me a day of coding to find and resolve.

This guide will help you through the process, and will remind me when I next have the problem and am searching for the solution – you’re welcome future me!

  1. Apply for your secure certificate
  2. Create singlessl.config file in your .ebextensions folder
  3. Copy in your certificates
  4. Teach Laravel how to identify a secure connection (It’s not as simple as it sounds)
  5. Update all internal links to https
  6. Deploy

Step 1 – 3

Apply for your SSL cert, grab the contents of the files (including the key you generated) and throw them into the following singlessl.config file in your .ebextensions folder

Resources:
  sslSecurityGroupIngress:
    Type: AWS::EC2::SecurityGroupIngress
    Properties:
      GroupName: {Ref : AWSEBSecurityGroup}
      IpProtocol: tcp
      ToPort: 443
      FromPort: 443
      CidrIp: 0.0.0.0/0

packages:
  yum:
    mod24_ssl : []

files:
  /etc/httpd/conf.d/ssl.conf:
    mode: 000777
    owner: ec2-user
    group: ec2-user
    content: |
      LoadModule ssl_module modules/mod_ssl.so
      Listen 443
      
        
          Order deny,allow
          Allow from all
        
        SSLEngine on
        SSLCertificateFile "/tmp/server.crt"
        SSLCertificateKeyFile "/tmp/server.key"
        SSLCertificateChainFile "/tmp/gd_bundle.crt"

        ProxyPass / http://localhost:80/ retry=0
        ProxyPassReverse / http://localhost:80/
        ProxyPreserveHost on

        LogFormat "%h (%{X-Forwarded-For}i) %l %u %t "%r" %>s %b "%{Referer}i" "%{User-Agent}i""
        ErrorLog /var/log/httpd/elasticbeanstalk-error_log
        TransferLog /var/log/httpd/elasticbeanstalk-access_log
      

  /tmp/server.crt:
    mode: 000777
    owner: ec2-user
    group: ec2-user
    content: |
      -----BEGIN CERTIFICATE-----
      [insert cert here]
      -----END CERTIFICATE-----

  /tmp/gd_bundle.crt:
    mode: 000777
    owner: ec2-user
    group: ec2-user
    content: |
      -----BEGIN CERTIFICATE-----
      [insert server cert here]
      -----END CERTIFICATE-----
      -----BEGIN CERTIFICATE-----
      [insert intermediate cert here]
      -----END CERTIFICATE-----
      -----BEGIN CERTIFICATE-----
      [insert intermediate cert 2 here, if there is one]
      -----END CERTIFICATE-----
      -----BEGIN CERTIFICATE-----
      [insert root cert here, if there is one]
      -----END CERTIFICATE-----

  /tmp/server.key:
    mode: 000777
    owner: ec2-user
    group: ec2-user
    content: |
      -----BEGIN RSA PRIVATE KEY-----
      [insert key here]
      -----END RSA PRIVATE KEY-----

Some browsers (including Android) need the intermediate certificate supplied, which is why we have added the gd_bundle.crt file for your intermediate certificate. This should contain your server.crt file, then your intermediate certs, then your root file.

This file needs to be space indented, otherwise the deploy will fail.

Step 4

We now need to teach Laravel how to identify a secure connection.

There is a laravel function, and many php functions that will let you know if the current connection is secure, these don’t work in this situation, and the configuration of the servers is slightly odd.

When you connect to an Elastic Beanstalk application you aren’t talking directly to your ec2 server, but through an intermediate server, even if you are not using a load balancer. This intermediate server will then communicate with your ec2 server on port 80, even if your external connection is secure. As this communication is all internal it doesn’t need to be secure, but it does mean that the ec2 instance sees the use of port 80, and assumes the connection is not secure.

When using .htaccess to redirect all http connections to https you can use the following code. If we don’t add this second line then .htaccess always thinks we are not secure and we get stuck in a redirect loop.

RewriteCond %{SERVER_PORT} !=443
RewriteCond %{SERVER_ADDR}   !^127.0.0.1
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L]

Step 5

Now the site knows when we are secure and will forward us accordingly, but all our internal links are still pointing to the http versions of the urls.

This can be resolved by adding a filter to the contents of each page after it has been generated

Modify the App::after filter in the app/filters.php file

App::after(function($request, $response)
{
	if ($response instanceof IlluminateHttpResponse) {

		//get the content of the page
		$content = $response->getContent();

		//If we are on the production site 
		if (App::environment('prod')){

			//convert all urls on our domain from http to https
			$content = str_replace(url('/'), secure_url('/'), $content );
		}

		//return the modified content to be rendered
		$response->setContent($content);
	}
});

Now all internal links will point to the https versions, leaving any external links as they were.

6. Deploy

You can now deploy your app, all communication will be secure, all http requests will be forwarded to https, and all internal links with point to https.

Leave a Reply

Your email address will not be published. Required fields are marked *