AWS Media Blog

AWS Elemental MediaStore: Ingest Acceleration


Oceans and borders can’t stop video providers from delivering their viewers amazing content from all over the planet. However, distance can create challenges when attempting to publish content that is ingested from a region with a limited AWS footprint.

To facilitate the use of AWS Elemental Media Services from regions of the globe where the services have not yet been deployed, we have documented a way to provide ingest acceleration video being sent to a AWS Elemental MediaStore container for origination. When implemented, data is securely transferred via the nearest Amazon CloudFront POP and stays within the AWS network, rather than transiting the public internet to the selected MediaStore region.

You can take the consistent performance and predictable low latency of MediaStore and ensure a steady user experience at the end of the video delivery chain, even when the encoder that is at the start of the chain is geographically a long way from the MediaStore region, and still maintain a reliable and secure workflow.

In this post, we’ll create a workflow to securely publish content ingested from a region where MediaStore has not yet been deployed. These steps will be easier to follow if you are already familiar with AWS Identity and Access Management, Amazon CloudFront, and AWS Elemental MediaStore.

Step 1: Set up AWS Elemental MediaStore

For those of you who are new to the service, here is an overview of how to get started with MediaStore:

  • Configure an AWS Identity and Access Management (IAM) user with permission to the MediaStore service.
  • Set up the container and its policy.

In-depth instructions can be found here, but we’ll walk through the steps needed for this workflow now.

1.1 Set up IAM configuration

  • Sign in to the AWS console.
  • Go to the IAM service and add a dedicated programmatic user to use MediaStore.

  • Attach the managed policy “AWSElementalMediaStoreFullAccess”.

  • Skip the tags for now. Go to the review page and hit the create user
  • Save the access and secret key that is provided from the console by either downloading the CSV file or copying the access key ID and secret access key to a secure location.

1.2 Create the container policy

Now that we’ve set up our IAM configuration, we will navigate to the MediaStore service page and create our first container.

  • Choose a name and hit the create container

By default, the service will create a container policy that will look like this:

    "Version": "2012-10-17",
    "Statement": [{
        "Sid": "MediaStoreFullAccess",
        "Action": [ "mediastore:*" ],
        "Principal": {"AWS" : "arn:aws:iam::<account_number>:root"},
        "Effect": "Allow",
        "Resource": "arn:aws:mediastore:<region>: <account_numbe>:container/<container_name>/*",
        "Condition": {
            "Bool": { "aws:SecureTransport": "true" }

  • This default container policy is enough for us to start using the service. However, to share our content with our user, we will modify the default policy to:

    "Version" : "2012-10-17",
    "Statement" : [ {
        "Sid" : " MediaStoreFullAccess ",
        "Effect" : "Allow",
        "Principal" : {"AWS" : "arn:aws:iam::<account_number>:root"},
        "Action" : "mediastore:*",
        "Resource" : "arn:aws:mediastore:<region>: <account_id>:container/<container_name>/*",
        "Condition" : {
        "Bool" : {
                    "aws:SecureTransport" : "true"
    }, {
        "Sid" : "PublicReadOverHttps",
        "Effect" : "Allow",
        "Principal" : "*",
        "Action" : [ "mediastore:GetObject", "mediastore:DescribeObject" ],
        "Resource" : "arn:aws:mediastore:<region>: <account_id>:container/<container_name>/*",
        "Condition" : {
              "Bool" : {
                   "aws:SecureTransport" : "true"
    } ]

  • Click the edit policy Copy the SID and paste it right after the existing SID, as shown below. Replace the information accordingly.

We are now ready to publish our first piece of content to MediaStore.

The two statements defined in the container policy provide full read and write access for content publication and playback. Statements can be customized to fit other requirements as well.

Step 2: Set up Amazon CloudFront

The configuration from the previous section works well when the source content sits in the same region as the MediaStore container.

Historically, it has been challenging to publish content using the open internet when the content being ingested is from a region with a limited AWS footprint or where MediaStore has not been deployed. This challenge has been greater across some international and cross-oceanic links.

We can address some of the challenged by using a Content Delivery Network (CDN) such as CloudFront, which provides accrued performance, security and cost benefits. To address this situation when writing objects to an Amazon S3 bucket that is in a region far from the uploading location, we created Amazon S3 Transfer Acceleration. This feature accelerates Amazon S3 data transfers by using optimized network protocols and the AWS edge infrastructure. You can think of the edge network as a bridge between your upload point (the desktop or on-premises data center) and the target bucket.

The same idea can be applied to the MediaStore service. We can set up a CloudFront distribution dedicated for the ingest of content in a remote region where the service has not deployed.

Conveniently, we don’t need to know all of the CloudFront edge locations by heart. We simply use the distribution domain name that will route our content to the nearest CloudFront edge location, or POP (Point of Presence), saving us from the oversea round-trip traffic on the open internet.

Here’s an overview of the steps we will follow to set up the CloudFront configuration:

  • Create a dedicated distribution allowing PUT method.
  • Modify our MediaStore container policy so that it accepts the PUT request from CloudFront.

2.1 Set up CloudFront configuration

  • Select the web delivery profile when creating your new CloudFront distribution.

  • Copy the MediaStore endpoint domain name URL and paste it into the “Origin Domain Name” field.

  • Leave the origin path empty and define the custom origin ID.

  • Select “HTTPS only” for the origin protocol policy.

  • Select “GET, HEAD, OPTIONS, PUT, POST, PATCH, DELETE” for the allowed HTTP methods.

  • Add a comment so we can identify this distribution as the MediaStore ingest later. Then, hit the create distribution

2.2 Modify the MediaStore container policy

Now, we will modify our container policy so CloudFront can write to our MediaStore container. Add the following statement to your MediaStore container policy:

"Sid" : "CloudFrontFullAccess",
"Effect" : "Allow",
"Principal" : "*",
"Action" : "mediastore:*",
"Resource" : "arn:aws:mediastore:<region>: <account_numbe>:container/<container_name>/*",
"Condition" : {
"Bool" : {
"aws:SecureTransport" : "true"

Our CloudFront ingest acceleration configuration is now complete. We can now push content to the CloudFront domain name and ingest content leveraging the AWS Global network.

Step 3: MediaStore delivery hardening

At this point, our content can be published from a remote region where MediaStore is not available yet. However, we just opened our container to write over the public internet, which isn’t very secure.

To solve the issue of security, we will tie up the CloudFront distribution and MediaStore container.

We will use specific headers from CloudFront to filter out incoming communication. By doing this, MediaStore will only accept incoming requests containing specific headers. The header we will use will be the “user-agent,” allowing us to specify our own value.

To secure the write permission from CloudFront to our MediaStore container, we will follow the steps below.

  • Generate a secret key.
  • Define an origin customer header.
  • Modify the policy to accept the origin custom header with a generated secret key.

3.1 Generate a secret key

Our secret key must compose of 20 characters and use only alphanumerical characters in order to be valid.

We can generate a secret key that will match the minimum requirement for the service using this random string generator. The link contains all the criteria to generate a compliant secret key.

3.2 Set up CloudFront hardening

Now, we will go back to our CloudFront distribution and navigate to the origins tab.

  • Select the MediaStore endpoint. Click edit.
  • At the bottom of the page, set up an origin custom header as shown below, and replace <secret_key> with the random string we generated.

Header Name= User-Agent
Value= MediaStore-Secret-<secret_key>

3.3 Set up MediaStore write access filtering

With CloudFront set up and its distribution deployed, we can modify the MediaStore container policy to match the origin custom header.

  • Go back to your container policy and edit it.
  • In the condition part of the statement “CloudFrontFullAccess”, add the following “stringEquals” constraint. Replace <secret_key> with the one we generated and used in the CloudFront origin custom header settings.

“StringEquals” : {
“aws:UserAgent” : “MediaStore-Secret-<sercret_key>”

The CloudFront distribution should now look like this:

“Sid” : “CloudFrontFullAccess”,
“Effect” : “Allow”,
“Principal” : “*”,
“Action” : “mediastore:*”,
“Resource” : “arn:aws:mediastore:<region>:<account_id>:container/<container_name>/*”,
“Condition” : {
“StringEquals” : {
“aws:UserAgent” : “MediaStore-Secret-<secret_key>”
“Bool” : {
“aws:SecureTransport” : “true”

Once the distribution is deployed, we can try it and confirm content playback.

As a best practice, we should secure the public read over HTTPS the same way. Create a dedicated CloudFront distribution with an origin custom header containing a different secret key and allowing only “GET, HEAD” as HTTP methods. This separates the customer read from your ingest writing to the MediaStore container.

Step 4: CloudFront delivery hardening

We can now push content from anywhere in the world and ingest it into our MediaStore container through CloudFront distribution. However, it appears the CloudFront distribution is still open to public write by any anonymous contributor.

To secure this part of the setup, we will deploy signed cookies and URLs using CloudFront distribution.

The signed URL will ensure that the CloudFront contributor is known and authorized to publish content on the distribution, as access will only be granted to links that are using the proper certificate to sign the link.

To deploy this, we will follow the steps below.

  • Generate an SSL key pair.
  • Register the key pair as a CloudFront key pair.
  • Retrieve the CF domain name.
  • Make use of the key string.
  • Generate the CF policy statement.
  • Encode the CF policy statement to base64.
  • Sign and hash the policy statement.
  • Append the CF key pair id.

5.1 Generate an SSL key pair

To generate an SSL key pair, we will need to use the openssl software. You’ll need to install it onto your device if you don’t already have it.

Run the below two commands to generate the private and public key, which will constitute your key pair.

openssl genrsa -out AEMS_Ingest_PrivateKey.pem 4096
openssl rsa -pubout -in AEMS_Ingest_PrivateKey.pem -out AEMS_Ingest_PublicKey.pem

The key must meet the following requirements:

  • The key pair must be an SSH-2 RSA key pair.
  • The key pair must be in base64 encoded PEM format.
  • The supported key lengths are 1024, 2048 and 4096 bits.

5.2 Register the key pair as a CloudFront key pair

Now that our key is ready, we will tell CloudFront to use it.

  • Log into the AWS console using root credentials. In the account menu, click “My Security Credentials”.

  • Expand “CloudFront key pairs” and click on the upload your own key pair
  • In the dialog box, select the public key we created in the previous step and hit upload.

  • Once loaded, the console will generate an access key ID.
  • Write this down, as we’ll need it later.

5.3 Retrieve your CF domain name

Once the key is in place, we can start generating our signed URL. The URL will start with the CloudFront domain name.

  • Open the CloudFront console and go to the distribution.
  • In the general tab, copy the domain name and paste it to a text file.

  • Append to it the path where we will upload our object and update the object name as follows:


5.4 Using the key string

To sign the URL, we need to make use of the key string to pass along the essential information needed by CloudFront to grant us access to the resources.

CloudFront will use three key strings:

  • One for the base64 encoded policy statement
  • One for the hashed and signed policy statement
  • One for the key pair id

Follow the steps below to ensure CloudFront grants us access using the key string:

  • Determine the key string pairs and start the line with “?”.
  • Collate them together in your text file. On the same line, separate each key-value pair with a “&”.
  • Add the policy key “&policy=”.
  • If the key strings in the application aren’t being used, remove the “&” in front of the policy from the previous step.
  • Append the whole line to the domain name.

5.5 Generate your CloudFront policy statement

To determine our key string, we need to generate our CloudFront policy statement.

Currently, the statement policy looks like this:

"Statement": [
"DateGreaterThan":{"AWS:EpochTime":<epochtime_start> }

Replace the values in between “<>” accordingly with the recorded CloudFront domain name, a range of whitelisted ip (optional), and a timestamp for when the statement validity starts and ends.

When we copy the CloudFront domain to the resource field, we also need to indicate which object CloudFront is allowed to serve in the policy.

To this, we need to provide the path of object we want to grant access to starting with a “/”. Alternatively, if we want to grant access to all the objects in a dedicated path, we will end it with “/*”.

In this case, we want to provide access to all objects in the container, so we will append “/*” to the CloudFront domain name in the policy.

Once completed, save it into a file called “CF_policy_statement.json”.

5.6 Encode your CloudFront policy statement to base64

With our policy statement ready, we can now encode it into base64.

Run the following command:

echo "$(jq '.' CF_policy_statement.json | sed 's/ //g' | tr -d '\n' | base64 | tr -d '\n')" | sed 's/\+/-/g' |sed 's/=/_/g'|sed 's/\//~/g'

This command will doing several things. Let’s break it down to see what we get out of it:

  • The sed command substitutes characters in the output using a pattern separated by “/”. The first character is then replaced by the second one. Using “s/ //g” will replace spaces with nothing.
  • The tr command removes new lines from the output of the previous command.
  • The jq command outputs the json file for processing its content.
  • The base64 command does the actual base64 encoding of our policy.

Once encoded in the base64 format, we will use another series of sed commands to remove the characters that are not valid for a URL, such as “+”, ”=”, and ”?”.

Copy the echo command output and append it to the CloudFront domain. Paste it into the key string “Policy=<base64_encoded_policy>”.

5.7 Signing and hashing the policy statement

To sign and hash the policy, we are going to use the following openssl command:

echo "$(jq '.' CF_policy_statement.json | sed 's/ //g' | tr -d '\n' | openssl dgst -sha1 -sign AEMS_Ingest_PrivateKey.pem | base64)" | sed 's/\+/-/g' |sed 's/=/_/g' | sed 's/\//~/g'

CloudFront expects the signature to use a SHA1 algorithm, and the hashing of the signature should be base64 like the encoded policy. Because of this, we will use openssl to generate the signature using SHA1 algorithm along with the private key we generated earlier. Then, we simply use the base64 command to hash the signature to base64 encoding.

5.8 Append the CloudFront key pair id

In our last step, we will append our CloudFront key pair id to the generated URL.

The current URL should look like this:<path>/<filename>?Policy=eyJTdGF0ZW1lbnQiOlt7IlJlc291cmNlIjoiaHR0cHM6Ly9kMnk2Y3k5NXNhNnNxai5jbG91ZGZyb250Lm5ldC8qIiwiQ29uZGl0aW9uIjp7IklwQWRkcmVzcyI6eyJBV1M6U291cmNlSXAiOiIwLjAuMC4wLzAifSwiRGF0Zuxlc3NuaGFuIjp7IkFXUzpFcG9jaFRpbWUiOjE1NDYyOTM2MDB9fX1dfQ__&Signature=tSq~2gecV9Y2vsHk4Y3tJC2KbdwgyOl54zOQjlehdg~i3BjMRmpWE0WvPmAVoYINSpBNDwjCBDgcPuCmAgP79AH9qu5meIZF6DnYwhBwburuwRVSmGK-n6EuD7W7n4N4HEGp0va6hNXt5NimDVnqZCYICk3VvA071x27zvS1cxthAqh3Y7HcpWdwYOxmTYbcRLcESTLnEWovAXXKk03a7ypn5MwSQqfdReb7JBHUy1WjzMJJ4NoLZM60-rTxagO3qd2QjNUtiHPppxFYsE6tKG6chiwdtVjKF~7JATymlwW4IlbwYLyb~Vaa6PdKCiF3hspdv0D49eJKXtu9U8zAxtGFlnFGuGMZBPP10B83g26TZMY3EqP~ZbctehOEk0Leh1y1GmkwHJ26UaPOtarwDBVRXUa3HJmc2podYqL2g6GO1nV6HgCTp12r-ost2001y85YUKAl95QaigLDfQIpPeKfPk~Z1WyocbR4d2hEVFZcAZSm-X8AE6~vUDXSQGLTq~7RtF4KBt7GeTMNpg5knuF0crvQxJCaEhlaOyE5l~kN7N3MYOJ8llrIU7QYBITn0b89BtCl2pFzaK3ZjCEvSA6Z1V17bF0YCNy2MkGyiUJRIE6IS1FTUiMJNMV6Ll9xcbUbIjKWGMnk~ynidQu96xXo0eAz-qezxCGF3cdENyA_&Key-Pair-Id=APKAIY4IFF2BJVM6IEWA

Finally, replace the path and filename with the appropriate values.


In this post, we learned how to securely ingest content into MediaStore from a region where the service is not currently deployed. Below, you can find a sample value set for verifying your configuration.

If you have any comments or suggestions, please let me know in the comments section.


Here is a sample value you can use to validate your implementation. It is public and should only be used in isolated testing. Please make sure to generate your own RSA private value once you move to a production environment.

Sample values:

Private key:


Domain name:

Policy statement:

"Statement": [

Base64 statement encoded:


Base64 encoded signature:



test command:

curl -T CF_policy_statement.json ""