Amazon CloudFront CFC

Amazon CloudFront is a CDN that sits atop their S3 file hosting service to provide caching and geographically dispersed delivery.  It's all very simple, except security.  Much like my Amazon S3 CFC's original goal, my new Amazon CloudFront CFC's primary purpose is to ease the creation of signed URLs for CloudFront.  You can grab a copy from amazoncloudfrontcfc.txt.  The API for the CFC is about what you'd expect:

<cfset cloudfront = createObject("component", "amazoncloudfront").init(keyPairId, privateKeyFile) />
<cfset signedUrl = cloudfront.signUrlWithTimeout(resourceUrl, 600) />

This will generate a signed URL for the CloudFront resourceUrl (direct domain or CNAMEd) which expires in 600 seconds (10 minutes).  Very much like the S3 CFC.  Here we're dealing with resourceURLs directly (which correspond to a bucket and object key) rather than separate buckets and object keys.  CloudFront doesn't distinguish between the two parts, so the CFC doesn't either.

The biggest gotcha, however, is with the signing mechanism.  S3 uses a simple pre-shared key, but CloudFront uses an RSA private key which is significantly more complicated to deal with.  Unfortunately, Amazon provides it's keys in PEM format, but core Java can only read DER format, so you must either convert your private key to DER format with OpenSSL, or use a third party library.  Fortunately, both are pretty simple.

Here's the command to convert your key with OpenSSL:

openssl pkcs8 -topk8 -in pk-KEYPAIRID.pem -nocrypt -outform DER -out pk-KEYPAIRID.der

Alternatively, you can use the not-yet-commons-ssl package, which provides support for reading keys in PEM format (among a pile of other things).  If you can add the JAR to your classpath, this is definitely a superior solution to manual conversion, since you can transparently use pretty much any RSA private key.  And there's nothing to enable in the CFC; if not-yet-commons-ssl is available on the classpath, it'll automatically use it for reading in the private key.  As an aside, the name 'not-yet-commons-ssl' reflects the fact that the package has applied fro Apache Commons incubation, but it hasn't been accepted yet.  The code is orgniazed in the 'org.apache.commons.ssl' package assuming it's acceptance, but it is still unofficial.

As always, updates and such are available on the project page.

14 responses to “Amazon CloudFront CFC”

  1. Gerald Guido

    Schweet! Thanx! I have been using your s3.CFC for a while now and I was trying to get my head around Cloud Front. Basically…. how to talk to it. And whadda ya know. Barney B is on the scene.

    We have been pushing our content delivery out to the cloud and it is allowing us to scrap the entire notion of a "server" or even "database". Pretty amazing stuff.

  2. Ted

    Thanks for making this. I have been trying to get it to work, but when I test the signed URL I get the standard "Access Denied" error from Amazon. Any help would be appreciated.

    Here's what I've done:

    1. I want to Amazon and created an ID and Private Key
    2. I converted the PEM key to DER format using OpenSSL (following your instructions)
    3. I made a test CFM file using one of my video files already on the S3. Here is the code of that file:

    This gives me back a URL which I try to load in my browser. Here is an example URL it returns:

    http://dltjymh90ljni.cloudfront.net/2010/July/Classes/Adrianne/adrianne_071810_lo.mp4?Expires=1280166513&Signature=ApvLy5iBBoruDM2RdiEnbQX5OzeN1iNu5jylWCRRW2rHJq9eYIxqulHjRLXPopbLqiIsPh8xsDbT7dUWfb5NHo-l6A55TIJD8AgxNjtbie7wlrBiVqHy86frEjDE4WK7OAJ-3FFASeKHjZW6RoQsw9IFx44D20v69O5eWzLFGJk_&Key-Pair-Id=APKAJBYFNKEQXFATT2VQ

    Clicking on that gives me the same "Access Denied" error I'd get without the whole Expires and Signature part, so obviously it didn't work. Just not sure why!

  3. Ted

    Looks like it didn't put up the code, here it is without the brackets:

    cfset my_cf = createObject("component", "amazoncloudfront").init(application.aws_key_pair_id,application.aws_private_key)

    cfset my_url = my_cf.signUrlWithTimeout("http://dltjymh90ljni.cloudfront.net/2010/July/Classes/Adrianne/adrianne_071810_lo.mp4″)

  4. Ted

    If I change the ACL on the S3 for a file to "Everyone = Read" then I can get the file just fine via HTTP or RTMP using the cloudfront URLs. It's just when I try to protect the file that I run into problems.

    When you say a "configuration error somewhere" what does that mean? There aren't exactly a whole lot of moving parts here!

  5. Ted

    Here is an example of an unprotected file I can access using cloudfront without a problem:

    http://dltjymh90ljni.cloudfront.net/2010/January/Classes/Kristi/kristi_010510_shortfull_hd.mp4

    I would think that suggests the bucket is set up correctly for cloudfront. I have also streamed the same file using RTMP (set up a different cloudfront URL for the same bucket for streaming).

  6. Ted

    Okay, so there is a "cloudfront" user I need to give access on the ACL? I don't see that user anywhere on the ACL using the Firefox S3 Manager., so I'm guessing this is a user I need to create? (I assume "cloudfront" isn't the right name for it). If you can point me in the right direction, it would be very much appreciated.

  7. Ted

    For those people wondering how to make the special "Origin Access ID" user, the way I figured out how to do it is using the cloudberry tool. There are very clear instructions here:
    http://blog.cloudberrylab.com/2009/11/managing-cloudfront-private-content.html

    My question is, do I have to go mark every single file as having "read" permissions for this user, or is there some way to set it up to automatically grant these permissions for all future files uploaded to the S3? Right now, all I see how to do is to "Edit ACL" on one of the S3 tools and turn on "read" for this special user. That does make the file readable. But for someone with hundreds of virtual folders and files, it could be a real headache.

  8. Ted Johnson

    Thanks. Has anyone tested this with streaming (RTMP) distributions? Working well with HTTP, but what I really want to use it for is to stream video via Amazon Cloudfront and not having luck getting the URLs to work. Is there anything special to do to get this to work for RTMP URLs?

  9. Ted Johnson

    In case others run across the same problem, I have figured out what the deal is with making this work for RTMP (streaming) URLs. What you need to do is feed only the last part of the url, everything after "cloudfront.net/cfx/st/" into the function. This is not the case with HTTP (works to feed the function the entire URL).

    So if your URL is:

    rtmp://s17f59fd6dzkkt.cloudfront.net/cfx/st/folder/filename.mp4

    You would call the function like this to make a signed URL:

    cf.signUrlWithTimeout("folder/filename.mp4″,9000)

  10. maria christensen

    Thanks Ted, you saved me from getting crazy. I have been digging into this problem for two days.
    /Maria