Last active
January 16, 2025 21:41
-
-
Save mekza/b7cdc0858aa1dd22b016 to your computer and use it in GitHub Desktop.
Signed URLs and Signed Cookies for CloudFront in Python with boto
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| from boto.cloudfront.distribution import Distribution | |
| from cryptography.hazmat.primitives.asymmetric import padding | |
| from cryptography.hazmat.primitives import serialization | |
| from cryptography.hazmat.backends import default_backend | |
| from cryptography.hazmat.primitives import hashes | |
| import base64 | |
| class BetterThanBoto(Distribution): | |
| def sign_rsa(self, message): | |
| private_key = serialization.load_pem_private_key(self.keyfile, password=None, | |
| backend=default_backend()) | |
| signer = private_key.signer(padding.PKCS1v15(), hashes.SHA1()) | |
| message = message.encode('utf-8') | |
| signer.update(message) | |
| return signer.finalize() | |
| def _sign_string(self, message, private_key_file=None, private_key_string=None): | |
| if private_key_file: | |
| self.keyfile = open(private_key_file, 'rb').read() | |
| elif private_key_string: | |
| self.keyfile = private_key_string.encode('utf-8') | |
| return self.sign_rsa(message) | |
| @staticmethod | |
| def _url_base64_encode(msg): | |
| """ | |
| Base64 encodes a string using the URL-safe characters specified by | |
| Amazon. | |
| """ | |
| msg_base64 = base64.b64encode(msg).decode('utf-8') | |
| msg_base64 = msg_base64.replace('+', '-') | |
| msg_base64 = msg_base64.replace('=', '_') | |
| msg_base64 = msg_base64.replace('/', '~') | |
| return msg_base64 | |
| def generate_signature(self, policy, private_key_file=None): | |
| """ | |
| :param policy: no-whitespace json str (NOT encoded yet) | |
| :param private_key_file: your .pem file with which to sign the policy | |
| :return: encoded signature for use in cookie | |
| """ | |
| # Distribution._create_signing_params() | |
| signature = self._sign_string(policy, private_key_file) | |
| # now base64 encode the signature & make URL safe | |
| encoded_signature = self._url_base64_encode(signature) | |
| return encoded_signature | |
| def create_signed_cookies(self, url, private_key_file=None, keypair_id=None, | |
| expires_at=20, secure=True): | |
| """ | |
| generate the Cloudfront download distirbution signed cookies | |
| :param resource: The object or path of resource. | |
| Examples: 'dir/object.mp4', 'dir/*', '*' | |
| :param private_key_file: Path to the private key file (pem encoded) | |
| :param key_pair_id: ID of the keypair used to sign the cookie | |
| :param expire_minutes: The number of minutes until expiration | |
| :param secure: use https or http protocol for Cloudfront URL - update | |
| to match your distribution settings. | |
| :return: Cookies to be set | |
| """ | |
| # generate no-whitespace json policy, | |
| # then base64 encode & make url safe | |
| policy = self._custom_policy( | |
| url, | |
| expires_at | |
| ) | |
| encoded_policy = self._url_base64_encode(policy.encode('utf-8')) | |
| # assemble the 3 Cloudfront cookies | |
| signature = self.generate_signature( | |
| policy, private_key_file=private_key_file | |
| ) | |
| cookies = { | |
| "CloudFront-Policy": encoded_policy, | |
| "CloudFront-Signature": signature, | |
| "CloudFront-Key-Pair-Id": keypair_id | |
| } | |
| return cookies | |
| def sign_to_cloudfront(object_url, expires_at): | |
| """ Sign URL to distribute file""" | |
| cf = BetterThanBoto() | |
| url = cf.create_signed_url(url=object_url, | |
| keypair_id="XXXXXXXXXXX", | |
| expire_time=expires_at, | |
| private_key_file="ssl/key.pem") | |
| return url | |
| def create_signed_cookies(object_url, expires_at): | |
| """ | |
| Create a signed cookie | |
| """ | |
| cf = BetterThanBoto() | |
| cookies = cf.create_signed_cookies(url=object_url, | |
| keypair_id=keypair_id="XXXXXXXXXXX", | |
| expires_at=expires_at, | |
| private_key_file="ssl/key.pem") | |
| return cookies |
Hi abaptista, please check my latest code and make sure you choose the correct Trusted Key Group.
# example of variables
private_key_path = 'the-path-of-your-private-key' # e.g.: './private_key.pem'
key_id = 'KXXXXXXXXXXXQ'
base_url = 'https://xxxx.cloudfront.net'
obj_key = 'the-object-path-of-your-s3' # e.g.: 'images/avatar.png'
new code remove `base_url`, just using the file's cdn url, e.g.: `url = 'https://xxxx.cloudfront.net/images/avatar.png'`
@zhangwm404
Do you still have your updated code? I'm getting a 404 on the link you sent earlier. Any help would be much appreciated.
Latest link appears to be https://gist.github.com/zh4n7wm/f532475f394c1b14f0e33ef047f78e05#python-code. Seems to be a username change.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hi there, I was searching for a python version for signing cloudfront cookies and found yours.
@ox0spy I gave it a try with your implementation but I keep getting:
Error><Code>SignatureDoesNotMatch</Code><Message>The request signature we calculated does not match the signature you provided. Check your key and signing method.</Message>.I followed the steps you mentioned:
-the bucket policy is correctly configured to only accept access from the cloudfront distribution (if I disable the restricted access based on the cookies/urls it works fine.)
Do have any idea that might help? Maybe if you could share the values of the variables you are using could give an insight on what I might be doing wrong.
private_key_path=''; key_id = ''; base_url =''; obj_key ='';Thanks