CloudFront Signed URL

Secure Your Content: CloudFront Signed URLs Explained

Shiv Pal Singh Kaundal
7 min readJul 21, 2024

--

Amazon CloudFront is a powerful Content Delivery Network (CDN) service provided by AWS that accelerates the delivery of your website’s content to users globally. By caching content at edge locations around the world, CloudFront reduces latency and ensures high-speed access to your static and dynamic assets. CloudFront signed URLs allow you to secure your content by providing controlled, temporary access to specific resources, ensuring only authorized users can access your valuable assets. Whether you’re delivering static images, videos, or API responses, CloudFront optimizes the delivery experience for your users while maintaining strict access control.

In this post, I will guide you through creating a CloudFront distribution with an S3 bucket as the origin. Initially, we will set up the CloudFront distribution without restricting viewer access to the content in the bucket, allowing access through the CloudFront distribution. This setup will ensure that anyone with the CloudFront URL can access your content.

Later, we will restrict viewer access, rendering the content inaccessible to the public. This step is crucial for securing sensitive data or restricting access to authorized users only. Finally, we will generate a presigned URL to regain access to the restricted content. This presigned URL will grant temporary access to specific users, ensuring secure and controlled distribution of your content.

Create a Private S3 Bucket

Navigate to the “S3” service in the AWS Management Console and create a private S3 bucket. This bucket will serve as the origin for your CloudFront distribution. Load the files you would like to distribute through CloudFront into this bucket. Ensure the bucket’s permissions are set to private to prevent unauthorized access.

S3 Bucket

Generate Public and Private Keys

To create the presigned URL, we need a pair of public and private keys. The public key will be uploaded to CloudFront and associated with the distribution we created, while the private key will be used to generate the presigned URL. First, run the below commands on an EC2 instance to generate the key pair. This process will create both a public key and a private key. Download both keys on your system.

openssl genpkey -algorithm RSA -out private_key.pem

openssl rsa -pubout -in private_key.pem -out public_key.pem

Create a CloudFront Distribution

Navigate to the “CloudFront” service in the AWS Management Console and click on “Create Distribution” to start the process.

CloudFront Distribution

Select the S3 bucket we created earlier as the origin and adjust the “Name” of the distribution as needed. Under “Origin access,” choose “Origin access control settings (recommended)” to ensure that the content in the S3 bucket is accessible only through CloudFront.

Click on “Create new OAC” to establish an origin access control (OAC) that will allow access to the S3 bucket defined as the origin.

OAC — Origin Access Control

Change the “Name” of the OAC as needed and leave the default settings as they are and create the OAC. After the CloudFront distribution is created, update the S3 bucket policy to allow access from this OAC. CloudFront will provide the necessary bucket policy details once the distribution setup is complete.

OAC Bucket Policy

Leave most of the settings as they are during the setup process. Ensure that “Restrict Viewer Access” is set to “No” to allow users to access the content without needing a presigned URL. This configuration will enable public access to the content for testing purposes. Once the CloudFront distribution is fully set up and you have confirmed that the content is accessible through CloudFront, we will revisit this setting. At that point, we will enable “Restrict Viewer Access” to secure the content and require presigned URLs for access.

Restrict Viewer Access

Set “Do not enable security protections” for the “Web Application Firewall” (WAF) section, as we do not want to enable WAF at this time. This will skip the WAF configuration during the initial setup.

Web Application Firewall Setting

Under settings, select “North America and Europe” to minimize costs, and then click on “Create Distribution” to complete the process. This region selection helps reduce expenses by utilizing CloudFront edge locations closer to these regions.

Edge Locations

Once the distribution status changes to “Enabled” we can access the distribution content using the “Domain Name”.

CloudFront Distribution

Update S3 Bucket Policy

Once the CloudFront distribution is created we need to update the S3 bucket policy before accessing the content through distribution. The policy can be accessed by editing the origin as shown below.

CloudFront Distribution
S3 Bucket Policy

Copy the policy and update the bucket policy. Replace “aws_account_id” and the resource as needed.

{
"Version": "2008-10-17",
"Id": "PolicyForCloudFrontPrivateContent",
"Statement": [
{
"Sid": "AllowCloudFrontServicePrincipal",
"Effect": "Allow",
"Principal": {
"Service": "cloudfront.amazonaws.com"
},
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::preimum-content-20240718/*",
"Condition": {
"StringEquals": {
"AWS:SourceArn": "arn:aws:cloudfront::aws_account_id:distribution/E2IL584JUBXXRO"
}
}
}
]
}

After updating the policy we can access the content using the CloudFront distribution where “d2phx0j7qqb8qf.cloudfront.net” is the domain and “Penguins.jpg” is the object in the S3 bucket.

https://d2phx0j7qqb8qf.cloudfront.net/Penguins.jpg

Content from S3

Public Key and Key Group

Now we can access the content using CloudFront without a presigned URL. Next, we will enable “Restrict Viewer Access” to ensure users cannot access the content without presigned URLs. Before doing this, we need to load the public key and create a key group, as this will be required when enabling “Restrict Viewer Access.”

Navigate to the “Public Keys” option under CloudFront and click on “Create public key”.

Generate Public Key

Copy the content of the public key downloaded earlier as shown below.

Generate Public Key

Once the public key is created, make sure to note down the key ID, as it will be required when generating the presigned URL.

Public Key ID

Next, navigate to the “Key Groups” option and create a key group by selecting the public key created in previous step.

Key Group

Generate Presigned URL

Navigate to the distribution created earlier and enable the “Restrict Viewer Access” by editing the behavior as shown below.

CloudFront Distribution
Enable Restrict Viewer Access

Select the “Key Group” created earlier and save the changes. Wait for the changes to be deployed. Then, try to access the same URL again, and you should receive the “Missing Key” error.

To access the content, we need to generate a presigned URL using the private key downloaded earlier. Here is an example code in Python that generates the presigned URL. Replace `private_key_path`, `key_pair_id`, less_than (i.e. url validity time) and `cloudfront_url` variables as needed.

import json
import datetime
import base64
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.primitives.serialization import load_pem_private_key
from cryptography.hazmat.backends import default_backend
from botocore.signers import CloudFrontSigner
from typing import Tuple, Optional

# Path to your private key file and CloudFront key pair ID
private_key_path = 'private_key.pem'
key_pair_id = 'K2BPJC9X1LJIVV'

def rsa_signer(message):
with open(private_key_path, 'rb') as key_file:
private_key = load_pem_private_key(key_file.read(), password=None, backend=default_backend())
signature = private_key.sign(
message,
padding.PKCS1v15(),
hashes.SHA1()
)
return signature

def get_signed_url(cloudfront_url: str) -> Tuple[Optional[str], Optional[int]]:
cf_signer = CloudFrontSigner(key_pair_id, rsa_signer)
current_datetime = datetime.datetime.utcnow()
less_than = current_datetime + datetime.timedelta(hours=24.0)
signed_url = cf_signer.generate_presigned_url(cloudfront_url, date_less_than=less_than)
return signed_url, 200

# Example usage
cloudfront_url = "https://d2phx0j7qqb8qf.cloudfront.net/Penguins.jpg"
signed_url, status = get_signed_url(cloudfront_url)
print(f'Signed URL: {signed_url}, Status: {status}')

Here is a Presigned URL generated using the Python code. This URL allows the user to access the secured content.

https://d2phx0j7qqb8qf.cloudfront.net/Penguins.jpg?Expires=1721658749&Signature=YWk7XSwjhTMNu4gMCP13UHaCy6ZaCmxg77jAqFOaMoptCxU8M~HRm47g5ezfJLEZ9~5Yc6KkK1tT8rqUWCJGXEE9OIAKctQlIiYHwhtcr3iRjlCc8f7x0eRVUx7vfYy5mu2RKQQ-28fFYip0fCnWA7Rv4tlPw-3ct7c0Fa~T1h8VXujaYtPdbj46fddGbz~Zll9VwMJW8FA3PqcWB93KurXKDebvRQGr5RnqrDJECBmsv7JhI5Oo~DC38ZiXTvaAeVyISrqOCqxey38O11ojEtk19zUpgtEaUKzv6~U0IoLiix2NP3PFFew-6S6PSX8MsCZ5clKYDJ29RU1aUbYI3A__&Key-Pair-Id=K2BPJC9X1LJIVV

Conclusion

By implementing CloudFront signed URLs, you can effectively secure your content while leveraging the power of Amazon CloudFront’s global CDN. The process involves setting up a private S3 bucket, generating and configuring public and private keys, and creating a CloudFront distribution with restricted viewer access. By generating presigned URLs, you ensure that only authorized users can access your secured assets temporarily. This approach not only optimizes content delivery but also provides a robust mechanism for managing access control, protecting your valuable data from unauthorized use.

--

--

Shiv Pal Singh Kaundal

Cloud Enthusiast with experience in architecting cloud infrastructure for SaaS applications, micro services on AWS