Skip to content

Instantly share code, notes, and snippets.

@prog893
Last active November 17, 2024 02:47
Show Gist options
  • Save prog893/4496c499a8f820f1aae71bcd8756b286 to your computer and use it in GitHub Desktop.
Save prog893/4496c499a8f820f1aae71bcd8756b286 to your computer and use it in GitHub Desktop.
CloudFront Signed URL generator in Python

CloudFront Signed URL generator in Python

For signed cookies, refer here

Usage

from cloudfront_signed_url import generate_cloudfront_signed_url

generate_cloudfront_signed_url("https://your-cf-domain.com/path/to/file.txt", 3600)

Prerequisites

  • Configured CloudFront Distribution
  • An Origin access identity and a CloudFront key
  • Origin and Behavior configured to Restrict Viewer Access

Reference: http://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/PrivateContent.html

Parameters

  • Make sure you have a CloudFront key pair and key group created and associated with distribution behavior, details here)
  • Replace newlines with \\n in private key and save the result to an SSM Parameter Store paramater named CF_SIGNED_URL_PRIVATE_KEY
  • Save key ID (key pair ID) to a parameter named CF_SIGNED_URL_KEY_ID

Notes

  • Unlike S3 pre-signed URLs, you can use a link generated once multiple times, as long as it is still valid (TTL).
  • You can modify make_policy to make other policies (not per-url, but broader clauses), or even signed cookies (Policy, Signature, Key-Pair-Id are generated in the same way for cookies too).

Dependencies

  • cryptography
  • boto3 (only for SSM)
import base64
import datetime
import json
import boto3
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import padding
ssm_client = boto3.client('ssm')
# CloudFront secret from SSM
CF_SIGNED_URL_KEY_PAIR_ID = ssm_client.get_parameter(f"CF_SIGNED_URL_KEY_PAIR_ID")
CF_SIGNED_URL_PRIVATE_KEY = ssm_client.get_parameter(f"CF_SIGNED_URL_PRIVATE_KEY")
# key pair id is expected to be str
# private key is expected to be bytes
# fix possible escaped newlines
CF_SIGNED_URL_PRIVATE_KEY = CF_SIGNED_URL_PRIVATE_KEY.replace(b'\\n', b'\n')
# or, define your own
def generate_cloudfront_signature(message: bytes, private_key: bytes):
private_key_signer = serialization.load_pem_private_key(
private_key,
password=None,
backend=default_backend()
)
return private_key_signer.sign(message, padding.PKCS1v15(), hashes.SHA1())
def make_cloudfront_policy(resource: str, expire_epoch_time: int):
policy = {
'Statement': [{
'Resource': resource,
'Condition': {
'DateLessThan': {
'AWS:EpochTime': expire_epoch_time
}
}
}]
}
return json.dumps(policy).replace(" ", "")
def url_base64_encode(data: bytes):
return base64.b64encode(data).replace(b'+', b'-').replace(b'=', b'_').replace(b'/', b'~').decode('utf-8')
def url_base64_decode(data: bytes):
return base64.b64encode(data).replace(b'-', b'+').replace(b'_', b'=').replace(b'~', b'/').decode('utf-8')
def generate_cloudfront_signed_url(url: str, expire_seconds: int):
expire_epoch_time = (datetime.datetime.now() + datetime.timedelta(seconds=expire_seconds)).timestamp()
expire_epoch_time = int(expire_epoch_time)
policy = make_cloudfront_policy(url, expire_epoch_time)
signature = generate_cloudfront_signature(policy.encode('utf-8'), CF_SIGNED_URL_PRIVATE_KEY)
signed_url = f"{url}?" \
f"Policy={url_base64_encode(policy.encode('utf-8'))}&" \
f"Signature={url_base64_encode(signature)}&" \
f"Key-Pair-Id={CF_SIGNED_URL_KEY_PAIR_ID}"
return signed_url
@dpmccabe
Copy link

Landed here via Google search. There are a bunch of things wrong with this gist, among them CF_SIGNED_URL_SECRET being undefined and generate_signature having the wrong argument order, and more. Until this functionality is added to boto3, it's better just to use botocore.

@prog893
Copy link
Author

prog893 commented Oct 28, 2022

@dpmccabe Hi there, thank you for your interest. I have rewritten the code and tested it against real CF distributions, you can take a look if you still need this.

@LA
Copy link

LA commented Nov 17, 2024

This works for me on my proof on concept project for Cloudfront Signed URLs. Thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment