AWSNinja Code Library

Name: awsninja_cloudfrontprivatestreaming

AWS Services: CloudFront, S3

Source: at GitHub

In a Sentence: Setting up Private Streaming on CloudFront is hard, so I automated it.

Setting up private streaming on CloudFront is way more difficult that it probably needs to be. There are all sorts of little niggling details that will have you pulling your hair out. My hope is that this post will help ease the pain, by gliding you right past the rough edges so you can get it done and maintain your sanity.

To do that, this tutorial will set up a CloudFront Streaming Distribution, set up the security, upload some videos, and set up a webpage to view those videos using private streaming.  In addition to the AWS Ninja code that I’ve written, the package includes the JW Player and the AWS SDK for PHP, both of which are included consistent with the licenses granted by those works.

If you’re just trying to learn more about the architecture of CloudFront and the API, you might just want to take a look at the source of oneTimeSetup.php, pushFilesToCloudFront.php, and cleanup.php.  The steps for signing requests are in index.php.

CloudFront doesn’t do everything

The first way I will attempt to save your time is by informing you that CloudFront Private Streaming lacks certain security features. It is not the most secure way to deliver your streams. It has been noted in CloudFront Forums that there are are two specific security techniques that CloudFront doesn’t support:

  1. While CloudFront supports RTMPE (encrypted streaming), it does not not have a way to stop end users from unencrypted streaming. This means that if a user has access to your stream, he will have the capability of saving a copy of it using certain third-party tools which can be found online. A user with some level of technical sophistication could obtain a copy of your content in this way.
  2. CloudFront does not support SWF verification. This is a technique whereby the streaming server will only stream to a SWF file that is “approved.” This is another method of preventing your stream from being copied.

Despite these limitations, CloudFront Private Streaming does give you a lot. It prevents unauthorized streaming of your content by IP address of the user and/or date range.  This allows you to effectively set up a pay-walled or limited access video site or intranet for content you do not want to make available to the world. Also, CloudFront is one of the AWS services that is still in beta, and we can reasonably expect Amazon to add these and other missing security features at some point.

You should also keep in mind, nothing is 100% secure. It’s a truism that Anybody who can watch your video can steal your video. Your objective is to employ cost-effective means of making your content hard to steal.

If you decide that CloudFront does meet your requirements, what follows is your guide to go from zero to streaming.

Set Up

First, download the awsninja_cloudfrontprivatestreaming project from GitHub. The project also includes a copy of the newly-relaunched AWS SDK for PHP from Amazon and the JW Player flash player. Once you’ve downloaded and uncompressed the library, you need to set up the SDK for PHP config.  Rename the “./aws/sdk/config-sample.inc.php” file to “”./aws/sdk/config.inc.php.”  Then, specify all of the parameters that are listed there:

  • AWS_KEY
  • AWS_SECRET_KEY
  • AWS_ACCOUNT_ID
  • AWS_CANONICAL_ID
  • AWS_CANONICAL_NAME
  • AWS_CLOUDFRONT_KEYPAIR_ID
  • AWS_CLOUDFRONT_PRIVATE_KEY_PEM

Instructions for how to set all of these can be found in the “config.inc.php” file itself.

One-Time Steps

Once you’ve completed the SDK config file, you should then run the “oneTimeSetup.php” script on the command line.  This is found in the “examples” directory. This script automates and ensures  completion of all of the steps of the setup process. Here are the pitfalls the script helps you avoid:

  • You must specify a “Caller Reference” when you create an Origin Access Identity (OAI). You’ll probably never use it, but the CloudFront API requires that you have one.  When using the “oneTimeSetup.php”, a Caller Reference is created for you automatically.
  • When creating a private streaming distribution, there are several required pieces of information:
    1. The OAI Id – You must specify the OAI Id, so you need to create the OAI before creating the distribution.
    2. The Streaming Flag – A “Distribution” and a “Streaming Distribution” are really different things. In order to create a “Streaming Distribution” you must add the Streaming=true argument. If you forget to include it you will create a normal Distribution. There is no way to change a normal Distribution to a Streaming Distribution or vise-versa.
    3. The TrustedSigners Flag – In order to control how your streams are used (which is bascially what we mean by “private”), you must make the stream unavailable to requesters who don’t properly sign their requests. To activate that requirement, you must set the TrustedSigners flag to either “Self” or to the Canonical Id of another AWS user. There is no setting called “Require Signed Request” or anything like that. Whether or not a signed request is required is determined by whether or not you specify a TrustedSigner.Furthermore, if you do not set up any TrustedSigners for your Streaming Distribution but the requester sends a signed request – the stream will still work. This might mislead you into thinking that your stream is protected when in reality it would work equally well with an unsigned request.

The “oneTimeSetup.php” script will take up to 15 minutes to run because it takes that long for your new Streaming Distribution to become active and deployed. Once that completes successfully, rename the “config-sample.php” file to “config.php.” The “oneTimeSetup.php” script will echo out the PHP define() methods that you need to put in the “config.php” file (not the AWS SDK “config.inc.php” file!). This completes the configuration of the other scripts.

To see that your Streaming Distribution is all set up, log into the AWS Console and click the CloudFront tab:

Your Streaming Distribution is Ready
Your Streaming Distribution is Ready

Uploading Files

Now that your CloudFront Origin bucket, OAI, and Streaming Distribution are all set up and configured, it’s time to upload some videos to your streaming bucket.

Included in the package is a file called “getCrockfordVids.sh.” If you run this file on your command line, it will download five videos of Douglas Crockford’s talks on JavaScript from YUI Theater. If that’s not enough JavaScript instruction for you, there are about 15 more videos in the file that are also available for download if you uncomment those lines. If you’re wondering what to do next New Year’s Eve, I highly recommend downloading them all and hosting a “Douglas Crockford Viewing Marathon.” Best New Years EVER! (BTW – Douglas Crockford is awesome and I’ve learned a lot from his presentations even though I’ve only watched a small portion of what he’s put out.)

Anyway, the “getCrockfordVids.sh” file will download Crockford videos to the “video” directory (warning: the five files that are set to download by default comprise 1.6GB of video). Once that is complete, you can run the “pushFilesToCloudFront.php” script to upload the files to the S3 bucket that was created for you by the “oneTimeSetup.php” script. In this script, there are two calls to the S3 API. The first one simply puts the object to S3, specifying that the owner has full control:

echo("Put $file on S3\n");
$cbr = $s3->create_object(NINJA_STREAMING_BUCKET, $file, array(
	'fileUpload'=>NINJA_BASEPATH . 'awsninja_cloudfrontprivatestreaming/videos/' . $file,
	'acl'=>AmazonS3::ACL_OWNER_FULL_CONTROL
));

The second step sets the Access Control List (ACL) so that the Origin Identity we previously set up has READ access. This is the step that allows your CloudFront Distribution to read your files from S3.

$acl = array(
	array(
		'id'=>AWS_CANONICAL_ID,
		'permission'=>AmazonS3::GRANT_FULL_CONTROL
	),
	array(
		'id'=>NINJA_STREAMING_CANONICALID,
		'permission'=>AmazonS3::GRANT_READ
	)
);
echo("Update the ACL for $file on S3.\n\n");
$sas = $s3->set_object_acl(NINJA_STREAMING_BUCKET, $file, $acl);

Note that the $acl array of arrays holds the permissions for the owner (indicated by the AWS_CANONICAL_ID constant) and the Origin Access Identity (indicated by NINJA_STREAMING_CANONICALID).

Streaming your Content

Next, you need to set up the webpage that will be used for streaming. All of the files you need to stream to are in the “www” directory. If your project is not in your web root, you will need to either move the “www” directory (and fix the include paths in “index.php”) or use a symbolic link so that you can view that directory in your web browser.

Extending the AmazonCloudFrontClass
In order to make this tutorial work, there is a minor change that needed to be made to the AmazonCloudFront service class in the SDK for PHP. The original AmazonCloudFront service contains a method called get_private_object_url() which is designed to create the signed url requests that are used to serve private content. The method returns a complete signed url with domain name, path and query string. This is a problem because JW Player actually wants the domain name and the path as two separate arguments. To solve this, I extended the AmazonCloudFront class from the SDK and added a method called get_private_object_path() which just consists of the portion of the get_private_object_url() which generates the path. The get_private_object_url() remains the same (except that the logic that was moved to get_private_object_path() was replaced with a call to the new method). You can inspect this work in the “ninja.cloudfront.class.php” file.

Protection Options
There are a few different options for protecting your stream. At a minimum, you need to set an expiration date (called “DateLessThan”) after which the stream stops working. Additionally, you can set a start date (“DateGreaterThan”) and/or an IP Address. If you set the IPAddress, the request must come from that IP Address in order to work.

If you want to make sure that your content can only be streamed from a web page that you control, the best way to do that is to sign the request using the $_SERVER['REMOTE_ADDR'] PHP global. Your arguments in “index.php” would look like this:

$expiration = strtotime('+4320 minutes');  //three days

$opt = array(
//	'BecomeAvailable'=>strtotime('+10 minutes'),  //starts working in 10 minutes
	'IPAddress'=> $_SERVER['REMOTE_ADDR']  //limit to the current IP address (prevents the user from embedding the flash player to serve the content to other users)
);

$cfsn = new AmazonCloudFrontNinja();
$urlcloud = $cfsn->get_private_object_path($item, $expiration, $opt);;

Note: “BecomeAvailable” is the argument you send to the AmazonCloudFront service (the SDK for PHP). Inside the service class, it gets changed to “DateGreaterThan” which is the correct name for the CloudFront API. Since the “BecomeAvailable” parameter is commented out in the code above, the stream would be available immediately.

Load-er Up!

Pull up the “index.php” file in your web browser  - and you should see your videos.  Happy New Year!

Tear-er Down!

Once you’re done working though this tutorial, the “cleanup.php” script will remove all of the Streaming Distributions and OAIs. The process of removing these objects is nearly as complicated as creating themm but I an not going to describe it. The source code has documentation and explains what’s going on. Take Heed: If you’re currently running a live Streaming Distribution, this script will delete that one too.