Fixing CORS problem with Amazon CloudFront CDN in Rails apps

I am using Alpha SSL for SSL certificate for rubyplus.com. I did not have wildcard SSL certificate. The Chrome browser was freaking out with CORS related errors because I was accessing static resources using cloudfront.net domain. Did you know you can get free wildcard SSL from Amazon? As long as you are in virginia region, you can get a free SSL from Amazon. I am using Linode for deploying my Rails apps. The instructions will work for any VPS.

Create CNAME in your VPS

Create a CNAME for cdn.yourdomain.com pointing to https://your-cloudfront-cdn-id.cloudfront.net in Linode.

Test with Curl

View page source on your site and grab a link to an image and use curl to test.

curl -I http://cdn.rubyplus.com/assets/favicon-very-long-alpha-numeric-string.ico

This failed with the output:

HTTP/1.1 403 Forbidden
Server: CloudFront
Date: Thu, 26 May 2016 22:39:47 GMT
Content-Type: text/html
Content-Length: 551
Connection: keep-alive
X-Cache: Error from cloudfront
Via: 1.1 3245d45aedf7a8621aabe6b30d2f5a48.cloudfront.net (CloudFront)
X-Amz-Cf-Id: NrdZWePG5rUVcViciQkV87rDaaus25K5LjhJd5w2q3i8neb9MUDIzg==

Speficy CNAME in CloudFront Distribution

Add cdn.yourdomain.com as the CNAME in the CloudFront settings.

AWS Command Line Tool

Install AWS command line tool on your laptop.

sudo pip install awscli --ignore-installed six

Convert crt to pem on the server.

openssl x509 -in mycert.crt -out mycert.pem -outform PEM

Go the directory where the SSL certificate files are stored and run openssl.

openssl x509 -in intermediate_domain.crt -out intermediate_domain.pem -outform PEM

This generates the intermediate_domain.pem file.

 intermediate_domain.crt
 intermediate_domain.pem
 rubyplus.com.crt
 rubyplus.com.key

Copy the files on the server to your laptop:

$scp deployer@rubyplus.com:/home/deployer/certs/* .

You can upload the generated SSL intermediate certificate to AWS by using the AWS command line tool from your laptop.

aws iam upload-server-certificate --server-certificate-name rubyplus.com --certificate-body file://./rubyplus.com.crt --private-key file://./rubyplus.com.key --certificate-chain file://intermediate_domain.pem --path /cloudfront/rubyplus.com/

This gave the error:

Unable to locate credentials. You can configure credentials by running "aws configure".

Run the configuration tool on your laptop to specify your credentials:

$aws configure
AWS Access Key ID [None]: Your-access-key-id-here
AWS Secret Access Key [None]: secret-access-key
Default region name [None]: us-east-1
Default output format [None]: json

My Cloudfront is in the us-east-1 region. Go to .aws directory on your laptop and check the generated files.

~/.aws $ll
total 16
drwxr-xr-x    4 bparanj  staff   136B May 26 16:37 .
drwxr-xr-x+ 131 bparanj  staff   4.3K May 26 16:37 ..
-rw-------    1 bparanj  staff    43B May 26 16:37 config
-rw-------    1 bparanj  staff   116B May 26 16:37 credentials

Run the AWS command line tool again.

aws iam upload-server-certificate --server-certificate-name rubyplus.com --certificate-body file://./rubyplus.com.crt --private-key file://./rubyplus.com.key --certificate-chain file://intermediate_domain.pem --path /cloudfront/rubyplus.com/

This gave an error:

A client error (AccessDenied) occurred when calling the UploadServerCertificate operation: User: arn:aws:iam::875721448715:user/rubyplus is not authorized to perform: iam:UploadServerCertificate on resource: arn:aws:iam::875721448715:server-certificate/cloudfront/rubyplus.com/rubyplus.com

Providing the Permission to AWS User

Login to your Cloudfront account and provide the user the permission to upload files. Create a new user in AWS.

User created successfully.

Attach Policy to allow user to upload SSL certificate.

Run the AWS command line tool again:

aws iam upload-server-certificate --server-certificate-name rubyplus.com --certificate-body file://./rubyplus.com.crt --private-key file://./rubyplus.com.key --certificate-chain file://intermediate_domain.pem --path /cloudfront/rubyplus.com/

The command ran successfully with the output:

{
    "ServerCertificateMetadata": {
        "ServerCertificateId": "your-server-certificate-id", 
        "ServerCertificateName": "rubyplus.com", 
        "Expiration": "2016-12-07T23:12:37Z", 
        "Path": "/cloudfront/rubyplus.com/", 
        "Arn": "arn:aws:iam::875721448715:server-certificate/cloudfront/rubyplus.com/rubyplus.com", 
        "UploadDate": "2016-05-26T23:41:20.713Z"
    }
}

Chooose Custom SSL Certificate for the CDN in Distribution Settings.

Use curl to test.

curl -I https://cdn.rubyplus.com/assets/favicon-very-long-alphanumeric-string.ico

I got the error:

curl: (60) SSL certificate problem: Invalid certificate chain
More details here: http://curl.haxx.se/docs/sslcerts.html

curl performs SSL certificate verification by default, using a "bundle"
 of Certificate Authority (CA) public keys (CA certs). If the default
 bundle file isn't adequate, you can specify an alternate file
 using the --cacert option.
If this HTTPS server uses a certificate signed by a CA represented in
 the bundle, the certificate verification probably failed due to a
 problem with the certificate (it might be expired, or the name might
 not match the domain name in the URL).
If you'd like to turn off curl's verification of the certificate, use
 the -k (or --insecure) option.

Let's try with the -k switch:

 curl -k -I https://cdn.rubyplus.com/assets/favicon-b73a7c5b51d68c8f821e1c0e44083c8b8762c977bd620995642c32fb074d2941.ico

It had the same problem.

 HTTP/1.1 200 OK
 Content-Type: image/x-icon
 Content-Length: 7406
 Connection: keep-alive
 Date: Thu, 26 May 2016 23:42:38 GMT
 Server: Apache/2.2.22 (Ubuntu)
 Last-Modified: Thu, 03 Mar 2016 08:51:13 GMT
 Accept-Ranges: bytes
 Cache-Control: max-age=31536000
 Expires: Fri, 26 May 2017 23:42:38 GMT
 Age: 126
 X-Cache: Hit from cloudfront
 Via: 1.1 aa96a51fedae85199c643eb5c8eca4e4.cloudfront.net (CloudFront)
 X-Amz-Cf-Id: 2yYMTGBdQdIyK3om0URDD9E87Ehr8xdbXGOX6EYhahdkFF9U3osprQ==

Untrusted SSL Connection

Connection is not private error in Chrome.

Amazon Wildcard SSL

AWS Certificate Manager

Amazon Wildcard SSL pending validation

Approve Amazon SSL request.

Approving the Amazon SSL request.

Successful approval of Amazon SSL certificate.

Verify Amazon wildcard SSL status

Use Asset Host in Rails App

Edit the production.rb in your Rails app:

config.action_controller.asset_host = '//cdn.rubyplus.com'

Deploy your Rails app. Go to your site and view page source. Hyperlinks without protocol.

I was getting:

   Font from origin 'https://cdn.rubyplus.com' has been blocked from loading by Cross-Origin Resource Sharing policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'https://www.rubyplus.com' is therefore not allowed access.

I searched for this error:

Font from origin  has been blocked from loading by Cross-Origin Resource Sharing policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin  is therefore not allowed access.

I found that you have to set a CORS Configuration for your CloudFront in AWS console. In CloudFront -> Distribution -> Behaviors for this origin, use the Forward Headers: Whitelist option and whitelist the 'Origin' header.

Choosing wildcard SSL in CloudFront Distribution settings

Edit CloudFront behavior to set the forward header and whitelist header

Wait for 20 minutes or so while CloudFront propagates the new rule. Now your CloudFront distribution should cache different responses (with proper CORS headers) for different client Origin headers. Use curl to test the cloudfront setup by hitting an icon. The output shows that it worked.

HTTP/1.1 200 OK
Content-Type: image/x-icon
Content-Length: 7406
Connection: keep-alive
Date: Thu, 26 May 2016 23:42:38 GMT
Server: Apache/2.2.22 (Ubuntu)
Last-Modified: Thu, 03 Mar 2016 08:51:13 GMT
Accept-Ranges: bytes
Cache-Control: max-age=31536000
Expires: Fri, 26 May 2017 23:42:38 GMT
X-Cache: Miss from cloudfront
Via: 1.1 2239f0bfe6d7427183a4e375c4638619.cloudfront.net (CloudFront)
X-Amz-Cf-Id: 4X9YJwlXoEALb_LzdaVP62x2_TbaDh8bHufLlH263FwO8kSJNNZySw==

Alpha SSL on the home page of rubyplus.com

The Amazon SSL takes care of the wildcard SSL. The Alpha SSL takes care of the SSL for rubyplus.com and www.rubyplus.com.

Fixing Font Loading Issue due to CORS Error

If you inspect the page, Chrome shows the following error.

Font from origin 'https://cdn.rubyplus.com' has been blocked from loading by Cross-Origin Resource Sharing policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'https://rubyplus.com' is therefore not allowed access.

Firefox error is better, it shows specific cause of the problem and suggestions to fix them.

downloadable font: download failed (font-family: "OpenSans-Regular" style:normal weight:normal stretch:normal src index:0): bad URI or cross-site access not allowed
source: https://cdn.rubyplus.com/assets/OpenSans-Regular-e64e508b2aa2880f907e470c4550980ec4c0694d103a43f36150ac3f93189bee.ttf

Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at https://cdn.rubyplus.com/assets/OpenSans-Regular-e64e508b2aa2880f907e470c4550980ec4c0694d103a43f36150ac3f93189bee.ttf. This can be fixed by moving the resource to the same domain or enabling CORS.

Create a directory yourdomain.com under /var/www directory. Create a .htaccess file with:

<FilesMatch ".(ttf|otf|eot|woff)">
  Header set Access-Control-Allow-Origin "*" 
</FilesMatch>

You do not need to restart apache server for this change to take effect.

CloudFront Cache Behavior Settings

Default Cache Behavior Settings Screen

You must have OPTIONS also as shown here.

Make sure to whitelist the origin header.

The entire setup should look like this:

References

Setting Headers in Rails 4


Related Articles


Create your own user feedback survey