class TestCloudfrontSigner(BaseSignerTest): def setUp(self): super(TestCloudfrontSigner, self).setUp() self.signer = CloudFrontSigner("MY_KEY_ID", lambda message: b'signed') # It helps but the long string diff will still be slightly different on # Python 2.6/2.7/3.x. We won't soly rely on that anyway, so it's fine. self.maxDiff = None def test_build_canned_policy(self): policy = self.signer.build_policy('foo', datetime.datetime(2016, 1, 1)) expected = ( '{"Statement":[{"Resource":"foo",' '"Condition":{"DateLessThan":{"AWS:EpochTime":1451606400}}}]}') self.assertEqual(json.loads(policy), json.loads(expected)) self.assertEqual(policy, expected) # This is to ensure the right order def test_build_custom_policy(self): policy = self.signer.build_policy( 'foo', datetime.datetime(2016, 1, 1), date_greater_than=datetime.datetime(2015, 12, 1), ip_address='12.34.56.78/9') expected = { "Statement": [{ "Resource": "foo", "Condition": { "DateGreaterThan": {"AWS:EpochTime": 1448928000}, "DateLessThan": {"AWS:EpochTime": 1451606400}, "IpAddress": {"AWS:SourceIp": "12.34.56.78/9"} }, }] } self.assertEqual(json.loads(policy), expected) def test_generate_presign_url_with_expire_time(self): signed_url = self.signer.generate_presigned_url( 'http://test.com/foo.txt', date_less_than=datetime.datetime(2016, 1, 1)) expected = ( 'http://test.com/foo.txt?Expires=1451606400&Signature=c2lnbmVk' '&Key-Pair-Id=MY_KEY_ID') assert_url_equal(signed_url, expected) def test_generate_presign_url_with_custom_policy(self): policy = self.signer.build_policy( 'foo', datetime.datetime(2016, 1, 1), date_greater_than=datetime.datetime(2015, 12, 1), ip_address='12.34.56.78/9') signed_url = self.signer.generate_presigned_url( 'http://test.com/index.html?foo=bar', policy=policy) expected = ( 'http://test.com/index.html?foo=bar' '&Policy=eyJTdGF0ZW1lbnQiOlt7IlJlc291cmNlIjoiZm9vIiwiQ29uZ' 'Gl0aW9uIjp7IkRhdGVMZXNzVGhhbiI6eyJBV1M6RXBvY2hUaW1lIj' 'oxNDUxNjA2NDAwfSwiSXBBZGRyZXNzIjp7IkFXUzpTb3VyY2VJcCI' '6IjEyLjM0LjU2Ljc4LzkifSwiRGF0ZUdyZWF0ZXJUaGFuIjp7IkFX' 'UzpFcG9jaFRpbWUiOjE0NDg5MjgwMDB9fX1dfQ__' '&Signature=c2lnbmVk&Key-Pair-Id=MY_KEY_ID') assert_url_equal(signed_url, expected)
class TestCloudfrontSigner(BaseSignerTest): def setUp(self): super(TestCloudfrontSigner, self).setUp() self.signer = CloudFrontSigner("MY_KEY_ID", lambda message: b'signed') # It helps but the long string diff will still be slightly different on # Python 2.6/2.7/3.x. We won't soly rely on that anyway, so it's fine. self.maxDiff = None def test_build_canned_policy(self): policy = self.signer.build_policy('foo', datetime.datetime(2016, 1, 1)) expected = ( '{"Statement":[{"Resource":"foo",' '"Condition":{"DateLessThan":{"AWS:EpochTime":1451606400}}}]}') self.assertEqual(json.loads(policy), json.loads(expected)) self.assertEqual(policy, expected) # This is to ensure the right order def test_build_custom_policy(self): policy = self.signer.build_policy( 'foo', datetime.datetime(2016, 1, 1), date_greater_than=datetime.datetime(2015, 12, 1), ip_address='12.34.56.78/9') expected = { "Statement": [{ "Resource": "foo", "Condition": { "DateGreaterThan": {"AWS:EpochTime": 1448928000}, "DateLessThan": {"AWS:EpochTime": 1451606400}, "IpAddress": {"AWS:SourceIp": "12.34.56.78/9"} }, }] } self.assertEqual(json.loads(policy), expected) def test_generate_presign_url_with_expire_time(self): signed_url = self.signer.generate_presigned_url( 'http://test.com/foo.txt', date_less_than=datetime.datetime(2016, 1, 1)) expected = ( 'http://test.com/foo.txt?Expires=1451606400&Signature=c2lnbmVk' '&Key-Pair-Id=MY_KEY_ID') self.assert_url_equal(signed_url, expected) def test_generate_presign_url_with_custom_policy(self): policy = self.signer.build_policy( 'foo', datetime.datetime(2016, 1, 1), date_greater_than=datetime.datetime(2015, 12, 1), ip_address='12.34.56.78/9') signed_url = self.signer.generate_presigned_url( 'http://test.com/index.html?foo=bar', policy=policy) expected = ( 'http://test.com/index.html?foo=bar' '&Policy=eyJTdGF0ZW1lbnQiOlt7IlJlc291cmNlIjoiZm9vIiwiQ29uZ' 'Gl0aW9uIjp7IkRhdGVMZXNzVGhhbiI6eyJBV1M6RXBvY2hUaW1lIj' 'oxNDUxNjA2NDAwfSwiSXBBZGRyZXNzIjp7IkFXUzpTb3VyY2VJcCI' '6IjEyLjM0LjU2Ljc4LzkifSwiRGF0ZUdyZWF0ZXJUaGFuIjp7IkFX' 'UzpFcG9jaFRpbWUiOjE0NDg5MjgwMDB9fX1dfQ__' '&Signature=c2lnbmVk&Key-Pair-Id=MY_KEY_ID') self.assert_url_equal(signed_url, expected)
def get_urls(self, obj): """Urls of the video for each type of encoding and in each resolution. Parameters ---------- obj : Type[models.Video] The video that we want to serialize Returns ------- Dictionary or None A dictionary of all urls for: - mp4 encodings of the video in each resolution - jpeg thumbnails of the video in each resolution None if the video is still not uploaded to S3 with success """ if obj.uploaded_on is None or obj.state != Video.READY: return None urls = {"mp4": {}, "thumbnails": {}} base = "{cloudfront:s}/{playlist!s}/{video!s}".format( cloudfront=settings.CLOUDFRONT_URL, playlist=obj.playlist.id, video=obj.id) date_less_than = timezone.now() + timedelta( seconds=settings.CLOUDFRONT_SIGNED_URLS_VALIDITY) for resolution in settings.VIDEO_RESOLUTIONS: # MP4 mp4_url = "{base:s}/videos/{stamp:s}_{resolution:d}.mp4".format( base=base, stamp=obj.active_stamp, resolution=resolution) # Thumbnails thumbnail_url = "{base:s}/thumbnails/{stamp:s}_{resolution:d}.0000000.jpg".format( base=base, stamp=obj.active_stamp, resolution=resolution) # Sign urls if the functionality is activated if settings.CLOUDFRONT_SIGNED_URLS_ACTIVE: cloudfront_signer = CloudFrontSigner( settings.CLOUDFRONT_ACCESS_KEY_ID, cloudfront_utils.rsa_signer) mp4_url = cloudfront_signer.generate_presigned_url( mp4_url, date_less_than=date_less_than) thumbnail_url = cloudfront_signer.generate_presigned_url( thumbnail_url, date_less_than=date_less_than) urls["mp4"][resolution] = mp4_url urls["thumbnails"][resolution] = thumbnail_url return json.dumps(urls)
def get_cdn_presigned(file_name): print 'Getting presigned url for {} from CDN - {}'.format(file_name, cdn_name) cdn_key_name, cdn_key_data = _get_cdn_data() print 'Got key name {} data {}'.format(cdn_key_name, cdn_key_data) def _rsa_signer(message): private_key = serialization.load_pem_private_key(cdn_key_data, password=None, backend=default_backend()) signer = private_key.signer(padding.PKCS1v15(), hashes.SHA1()) signer.update(message) return signer.finalize() if not hasattr(get_cdn_presigned, 'cdn_domain'): client = boto3.client('cloudfront') response = client.get_distribution(Id=cdn_name) print 'CDN {} got distribution info {}'.format(cdn_name, response) get_cdn_presigned.cdn_domain = response.get('Distribution',{}).get('DomainName') if not get_cdn_presigned.cdn_domain: print 'No domain on cdn {} to get signed url from'.format(cdn_name) current_time = datetime.utcnow() expire_time = current_time + timedelta(seconds=_cloudfront_url_duration()) cloudfront_signer = CloudFrontSigner(cdn_key_name, _rsa_signer) url = 'https://' + get_cdn_presigned.cdn_domain + '/' + file_name print 'Retrieving signed url for {}'.format(url) signed_url = cloudfront_signer.generate_presigned_url(url, date_less_than=expire_time) print 'Got signed url of {}'.format(signed_url) return signed_url
def lambda_handler(event, context): if 'recordingPath' not in event or not event['recordingPath'] or event['recordingPath'] == 'null': logger.info("No recordingPath in event; returning.") return None # retrieve secrets logger.info("Retrieving cloudfront credentials") session = boto3.session.Session() client = session.client(service_name='secretsmanager') sf_credentials_secrets_manager_arn = get_arg(os.environ, 'SF_CREDENTIALS_SECRETS_MANAGER_ARN') secrets = json.loads(client.get_secret_value(SecretId=sf_credentials_secrets_manager_arn)['SecretString']) private_key = secrets['CloudFrontPrivateKey'] access_key_id = secrets['CloudFrontAccessKeyID'] logger.info("Cloudfront credentials retrieved") # construct url to audio recording recordingPath = event['recordingPath'] # need to remove bucket name, connect dir from path if("/connect/" in recordingPath): recordingPath = "connect/" + recordingPath.split("/connect/", 1)[1] elif("/Analysis/" in recordingPath): recordingPath = "Analysis/" + recordingPath.split("/Analysis/", 1)[1] cloudfront_domain = get_arg(os.environ, 'CLOUDFRONT_DISTRIBUTION_DOMAIN_NAME') url = 'https://' + cloudfront_domain + '/' + recordingPath logger.info('Unsigned audio recording url: %s' % url) # sign url expire_date = datetime.datetime.utcnow() + datetime.timedelta(minutes=60) cloudfront_signer = CloudFrontSigner(access_key_id, rsa_signer(private_key)) signed_url = cloudfront_signer.generate_presigned_url( url, date_less_than=expire_date) logger.info('Signed audio recording url: %s' % signed_url) return signed_url
def get_url(self, obj): """Url of the Document. Parameters ---------- obj : Type[models.Document] The document that we want to serialize Returns ------- String or None the url to fetch the document on CloudFront None if the document is still not uploaded to S3 with success """ if obj.uploaded_on is None: return None url = ( f"{settings.AWS_S3_URL_PROTOCOL}://{settings.CLOUDFRONT_DOMAIN}/{obj.pk}/document/" f"{time_utils.to_timestamp(obj.uploaded_on)}{self._get_extension_string(obj)}?response" f"-content-disposition={quote_plus('attachment; filename=' + self.get_filename(obj))}" ) # Sign the document urls only if the functionality is activated if settings.CLOUDFRONT_SIGNED_URLS_ACTIVE: date_less_than = timezone.now() + timedelta( seconds=settings.CLOUDFRONT_SIGNED_URLS_VALIDITY) cloudfront_signer = CloudFrontSigner( settings.CLOUDFRONT_SIGNED_PUBLIC_KEY_ID, cloudfront_utils.rsa_signer) url = cloudfront_signer.generate_presigned_url( url, date_less_than=date_less_than) return url
def create_cloudfront_signed_url( object_name: str, expiration_date: datetime) -> str: """Generate a cloudfront signed URL to share an s3 object Arguments: object_name {str} -- Required. s3 object to share expiration_date {datetime} -- Required. Expiration datetime object of some future datetime Returns: str -- cloudfront signed URL """ key_id = environ.get('AWS_CLOUDFRONT_USER_ACCESS_ID') url = '{cloudfront_domain}/{object_name}'.format( cloudfront_domain=environ.get('AWS_CLOUDFRONT_DOMAIN'), object_name=object_name ) cloudfront_signer = CloudFrontSigner(key_id, rsa_signer) # Create a signed url that will be valid until the specfic expiry date # provided using a canned policy. signed_url = cloudfront_signer.generate_presigned_url( url, date_less_than=expiration_date) return signed_url
def get_presigned_url() -> str: """ Two query parameters are required: * url: Base URL for the file * resource: URL or stream name of the file This code uses botocore.signers.CloudFrontSigner to generate presigned URLs using a custom policy. See Amazon CloudFront documentation for more details on the signing process: https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-creating-signed-url-custom-policy.html Example using cURL: curl 'http://localhost:8000/presigned-url?url=https://www.example.com/images/image.jpg&resource=https://www.example.com/images/*' :return str: The signed URL """ config_file_path = os.path.join(os.path.dirname(__file__), 'config.json') with open(config_file_path) as config_file: config = json.load(config_file) private_key_id = config['private_key_id'] request = app.current_request url = request.query_params['url'] resource = request.query_params['resource'] expire_date = datetime.now() + timedelta(minutes=+5) cf_signer = CloudFrontSigner(private_key_id, rsa_signer) cf_policy = cf_signer.build_policy(resource=resource, date_less_than=expire_date) presigned_url = cf_signer.generate_presigned_url(url, policy=cf_policy) return presigned_url
def _run_main(self, args, parsed_globals): signer = CloudFrontSigner( args.key_pair_id, RSASigner(args.private_key).sign) date_less_than = parse_to_aware_datetime(args.date_less_than) date_greater_than = args.date_greater_than if date_greater_than is not None: date_greater_than = parse_to_aware_datetime(date_greater_than) if date_greater_than is not None or args.ip_address is not None: policy = signer.build_policy( args.url, date_less_than, date_greater_than=date_greater_than, ip_address=args.ip_address) sys.stdout.write(signer.generate_presigned_url( args.url, policy=policy)) else: sys.stdout.write(signer.generate_presigned_url( args.url, date_less_than=date_less_than)) return 0
def main(): key_id = 'KF4ZWB2OGIN4A' url = 'https://d1za37rregmkz7.cloudfront.net/app1/var3.json' current_time = datetime.datetime.utcnow() expire_date = current_time + datetime.timedelta(minutes=2) cloudfront_signer = CloudFrontSigner(key_id, rsa_signer) signed_url = cloudfront_signer.generate_presigned_url( url, date_less_than=expire_date) print(signed_url)
def get_image(event, context): event_string = json.dumps(event) print(event_string) image_name = event["queryStringParameters"]["image_name"] user_name = event["queryStringParameters"]["user_name"] if user_name != 'king-kong': return { "statusCode": 404, "body": json.dumps({ "message": "You are trying to access premium property images, you need to subscribe for premium membership.", }), "headers":{ 'Access-Control-Allow-Origin' : '*' } } bucket_name = getImagesBucketName() print("Bucket name is {0}".format(bucket_name)) try: #key_id = 'xxxxxxxxxxxxxxxxxxx' # reading value from parameter store key_id = getCloudFrontKeyId() print("Cloudfront Access key id is {0}".format(key_id)) # reading value from parameter store cf_distribution_name = getCloudFrontDistributionName() print("Cloudfront distribution name is {0}".format(cf_distribution_name)) url = "https://" + cf_distribution_name + "/" + image_name expire_date = datetime.datetime(2020, 10, 10) cloudfront_signer = CloudFrontSigner(key_id, rsa_signer) # Create a signed url that will be valid until the specfic expiry date # provided using a canned policy. signed_url = cloudfront_signer.generate_presigned_url(url, date_less_than=expire_date) print("Pre-signed URL is {0}".format(signed_url)) s3 = boto3.resource('s3') object = s3.Object(bucket_name,image_name).load() msg = "Image with name : " + image_name + " exists" return { "statusCode": 200, "body": json.dumps({ "message": signed_url, }), "headers":{ 'Access-Control-Allow-Origin' : '*' } } except BaseException as error: print("*** Failure to retrieve Car Image - Please check your request ***") return { "statusCode": 404, "body": json.dumps({ "message": str(error), }), "headers":{ 'Access-Control-Allow-Origin' : '*' } }
def sign_url(url, expiry, key_id): cloudfront_signer = CloudFrontSigner(key_id, rsa_signer) # Create a signed url that will be valid until the specfic expiry date # provided using a canned policy. signed_url = cloudfront_signer.generate_presigned_url( url=url, date_less_than=expiry) return signed_url
class CloudFrontS3Storage(S3Storage): """ Storage backend that uses S3 and CloudFront """ def __init__(self, request=None, domain=None, crypto_pk=None, key_id=None, **kwargs): super(CloudFrontS3Storage, self).__init__(request, **kwargs) self.domain = domain self.crypto_pk = crypto_pk self.key_id = key_id self.cf_signer = None if key_id is not None: self.cf_signer = CloudFrontSigner(self.key_id, self._rsa_signer) self.client = boto3.client("cloudfront") @classmethod def configure(cls, settings): kwargs = super(CloudFrontS3Storage, cls).configure(settings) kwargs["domain"] = settings["storage.cloud_front_domain"] kwargs["key_id"] = settings.get("storage.cloud_front_key_id") private_key = settings.get("storage.cloud_front_key_string") if private_key is None: key_file = settings.get("storage.cloud_front_key_file") if key_file: with open(key_file, "rb") as ifile: private_key = ifile.read() else: private_key = private_key.encode("utf-8") crypto_pk = serialization.load_pem_private_key( private_key, password=None, backend=default_backend()) kwargs["crypto_pk"] = crypto_pk return kwargs def _rsa_signer(self, message): """ Generate a RSA signature for a message """ return self.crypto_pk.sign(message, padding.PKCS1v15(), hashes.SHA1()) def _generate_url(self, package): """ Get the fully-qualified CloudFront path for a package """ path = self.get_path(package) url = self.domain + "/" + quote(path) # No key id, no signer, so we don't have to sign the URL if self.cf_signer is None: return url # To sign with a canned policy: expires = utcnow() + timedelta(seconds=self.expire_after) return self.cf_signer.generate_presigned_url(url, date_less_than=expires)
class CloudFrontS3Storage(S3Storage): """ Storage backend that uses S3 and CloudFront """ def __init__(self, request=None, domain=None, private_key=None, key_id=None, **kwargs): super(CloudFrontS3Storage, self).__init__(request, **kwargs) self.domain = domain self.private_key = private_key self.key_id = key_id self.private_key = private_key self.cf_signer = None if key_id is not None: self.cf_signer = CloudFrontSigner(self.key_id, self._rsa_signer) self.client = boto3.client('cloudfront') @classmethod def configure(cls, settings): kwargs = super(CloudFrontS3Storage, cls).configure(settings) kwargs['domain'] = settings['storage.cloud_front_domain'] kwargs['key_id'] = settings.get('storage.cloud_front_key_id') private_key = settings.get('storage.cloud_front_key_string') if private_key is None: key_file = settings.get('storage.cloud_front_key_file') if key_file: with open(key_file, 'r') as ifile: private_key = ifile.read() kwargs['private_key'] = private_key return kwargs def _rsa_signer(self, message): """ Generate a RSA signature for a message """ return rsa.sign(message, rsa.PrivateKey.load_pkcs1( self.private_key.encode('utf8')), 'SHA-1') # CloudFront requires SHA-1 hash def _generate_url(self, package): """ Get the fully-qualified CloudFront path for a package """ path = self.get_path(package) url = self.domain + '/' + quote(path) # No key id, no signer, so we don't have to sign the URL if self.cf_signer is None: return url # To sign with a canned policy: expires = datetime.utcnow() + timedelta(seconds=self.expire_after) return self.cf_signer.generate_presigned_url(url, date_less_than=expires)
class CloudFrontS3Storage(S3Storage): """ Storage backend that uses S3 and CloudFront """ def __init__( self, request=None, domain=None, crypto_pk=None, key_id=None, **kwargs ): super(CloudFrontS3Storage, self).__init__(request, **kwargs) self.domain = domain self.crypto_pk = crypto_pk self.key_id = key_id self.cf_signer = None if key_id is not None: self.cf_signer = CloudFrontSigner(self.key_id, self._rsa_signer) self.client = boto3.client("cloudfront") @classmethod def configure(cls, settings): kwargs = super(CloudFrontS3Storage, cls).configure(settings) kwargs["domain"] = settings["storage.cloud_front_domain"] kwargs["key_id"] = settings.get("storage.cloud_front_key_id") private_key = settings.get("storage.cloud_front_key_string") if private_key is None: key_file = settings.get("storage.cloud_front_key_file") if key_file: with open(key_file, "rb") as ifile: private_key = ifile.read() else: private_key = private_key.encode("utf-8") crypto_pk = serialization.load_pem_private_key( private_key, password=None, backend=default_backend() ) kwargs["crypto_pk"] = crypto_pk return kwargs def _rsa_signer(self, message): """ Generate a RSA signature for a message """ return self.crypto_pk.sign(message, padding.PKCS1v15(), hashes.SHA1()) def _generate_url(self, package): """ Get the fully-qualified CloudFront path for a package """ path = self.get_path(package) url = self.domain + "/" + quote(path) # No key id, no signer, so we don't have to sign the URL if self.cf_signer is None: return url # To sign with a canned policy: expires = datetime.utcnow() + timedelta(seconds=self.expire_after) return self.cf_signer.generate_presigned_url(url, date_less_than=expires)
def get_urls(self, obj): """urls for each media's pages.""" if obj.uploaded_on is None or obj.nb_pages is None: return None urls = {} stamp = time_utils.to_timestamp(obj.uploaded_on) base = ( f"{settings.AWS_S3_URL_PROTOCOL}://{settings.CLOUDFRONT_DOMAIN}/" f"{obj.video.pk}/sharedlivemedia/{obj.pk}/{stamp}") cloudfront_signer = None if settings.CLOUDFRONT_SIGNED_URLS_ACTIVE: date_less_than = timezone.now() + timedelta( seconds=settings.CLOUDFRONT_SIGNED_URLS_VALIDITY) cloudfront_signer = CloudFrontSigner( settings.CLOUDFRONT_ACCESS_KEY_ID, cloudfront_utils.rsa_signer) pages = {} for page_number in range(1, obj.nb_pages + 1): url = f"{base:s}_{page_number:d}.svg" if cloudfront_signer: url = cloudfront_signer.generate_presigned_url( url, date_less_than=date_less_than) pages[page_number] = url urls["pages"] = pages # Downloadable link can be generated only when cloudfront request is signed if (self.context.get("is_admin") or obj.show_download) and cloudfront_signer: extension = f".{obj.extension}" if obj.extension else "" urls["media"] = cloudfront_signer.generate_presigned_url( f"{base}/{stamp}/{stamp}{extension}?response-content-disposition=" f"{quote_plus('attachment; filename=' + self.get_filename(obj))}", date_less_than=date_less_than, ) return urls
def get_cdn_presigned_url(url, expiration): PUBLIC_KEY_ID = config('CF_SIGNER_PUBLIC_KEY') cloudront_signer = CloudFrontSigner(PUBLIC_KEY_ID, rsa_signer) signed_url = cloudront_signer.generate_presigned_url( url, date_less_than=expiration ) return signed_url
def create_cloudfront_signed_url(object_name, expiration_date): from app import app key_id = app.config['CF_ID'] url = app.config['CF_DOMAIN'] + object_name cloudfront_signer = CloudFrontSigner(key_id, ImageStorageService.rsa_signer) signed_url = cloudfront_signer.generate_presigned_url( url, date_less_than=expiration_date) return signed_url
def signed_cf_url(bucket, key, expires_in_days=7): policy = f"""{{ "Statement": [ {{ "Resource":"https://{bucket}/{key}", "Condition":{{ "DateLessThan":{{"AWS:EpochTime": {int((datetime.datetime.today() + datetime.timedelta(days=expires_in_days)).timestamp())}}} }} }} ] }}""" cloudfront_signer = CloudFrontSigner(settings.CF_ACCESS_KEY, rsa_signer) return cloudfront_signer.generate_presigned_url(f'https://{bucket}/{key}', policy=policy)
def generate_presigned_cloudfront_url(self, url, expire_time, keypair_id, private_key_string): # From https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/cloudfront.html#generate-a-signed-url-for-amazon-cloudfront def rsa_signer(message): private_key = serialization.load_pem_private_key( private_key_string, password=None, backend=default_backend()) return private_key.sign(message, padding.PKCS1v15(), hashes.SHA1()) cloudfront_signer = CloudFrontSigner(keypair_id, rsa_signer) # Create a signed url that will be valid until the specfic expiry date # provided using a canned policy. return cloudfront_signer.generate_presigned_url( url, date_less_than=expire_time)
def main(): sess = session.get_session() session.profile = SYSOP session.region = REGION # JST, not use timezone expire = datetime.now() + timedelta(minutes=EXPIRE) - timedelta(hours=9) cloudfront_signer = CloudFrontSigner(KEY_ID, get_rsa_signer) signed_url = cloudfront_signer.generate_presigned_url( URL, date_less_than=expire, ) print(signed_url)
def get_signed_url(url, config_map): """ Convenience function for getting cloudfront signed URL given a saved URL cjshaw, Jan 7, 2015 Follows: http://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/ private-content-creating-signed-url-canned-policy.html#private- content-creating-signed-url-canned-policy-procedure http://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/ PrivateContent.html http://boto.readthedocs.org/en/latest/ref/cloudfront.html May 25, 2017: Switch to boto3 """ # From https://stackoverflow.com/a/34322915 def rsa_signer(message): private_key = open(config_map['cloudfront_private_key_file'], 'r').read() return rsa.sign(message, rsa.PrivateKey.load_pkcs1(private_key.encode('utf8')), 'SHA-1') # CloudFront requires SHA-1 hash if any(config_map[key] == '' for key in [ 's3_bucket', 'cloudfront_distro', 'cloudfront_private_key_file', 'cloudfront_keypair_id' ]): # This is a test configuration return 'You are missing S3 and CF configs: https:///?Expires=X&Signature=X&Key-Pair-Id=' expires = datetime.datetime.utcnow() + datetime.timedelta(days=7) s3_bucket = config_map['s3_bucket'] url = url.replace(s3_bucket + '.s3.amazonaws.com', config_map['cloudfront_distro']) if not AWS_CLIENT.is_aws_cf_client_set(): cf_signer = CloudFrontSigner(config_map['cloudfront_keypair_id'], rsa_signer) AWS_CLIENT.set_aws_cf_client(cf_signer) else: cf_signer = AWS_CLIENT.cf signed_url = cf_signer.generate_presigned_url(url, date_less_than=expires) return signed_url
def generate_cloudfront_url(self, key, cloudfront_domain, cloudfront_key_id, cloudfront_private_key, url_expiration_time=60 * 60 * 24 * 90, force_http_url=False ): """ Generate presigned url for object on bucket :param url_expiration_time: time until url expires in seconds :type url_expiration_time: int :param key: path to object on bucket :return: url :rtype str """ if key is None: return key = ensure_unicode(key) key = key.lstrip('/') url = 'https://{}/{}'.format(cloudfront_domain, key) def rsa_signer(message): private_key = cloudfront_private_key return rsa.sign( message, rsa.PrivateKey.load_pkcs1(private_key.encode('utf8')), 'SHA-1' ) # CloudFront requires SHA-1 hash cloudfront_signer = CloudFrontSigner(cloudfront_key_id, rsa_signer) expiry_datetime = ( datetime.utcnow() + timedelta(seconds=url_expiration_time) ) presigned_url = cloudfront_signer.generate_presigned_url( url, date_less_than=expiry_datetime ) if force_http_url: presigned_url = presigned_url.replace('https://', 'http://') return presigned_url
def get_url(self, obj): """Url of the Document. Parameters ---------- obj : Type[models.Document] The document that we want to serialize Returns ------- String or None the url to fetch the document on CloudFront None if the document is still not uploaded to S3 with success """ if obj.uploaded_on is None: return None url = ( "{protocol:s}://{cloudfront:s}/{pk!s}/document/{stamp:s}{extension:s}" "?response-content-disposition={content_disposition:s}" ).format( protocol=settings.AWS_S3_URL_PROTOCOL, cloudfront=settings.CLOUDFRONT_DOMAIN, pk=obj.pk, stamp=time_utils.to_timestamp(obj.uploaded_on), content_disposition=quote_plus( "attachment; filename=" + self.get_filename(obj) ), extension=self._get_extension_string(obj), ) # Sign the document urls only if the functionality is activated if settings.CLOUDFRONT_SIGNED_URLS_ACTIVE: date_less_than = timezone.now() + timedelta( seconds=settings.CLOUDFRONT_SIGNED_URLS_VALIDITY ) cloudfront_signer = CloudFrontSigner( settings.CLOUDFRONT_ACCESS_KEY_ID, cloudfront_utils.rsa_signer ) url = cloudfront_signer.generate_presigned_url( url, date_less_than=date_less_than ) return url
def _sign_url(self, url): """Generate a presigned cloudfront url. Parameters ---------- url: string The url to sign Returns: string The signed url """ date_less_than = timezone.now() + timedelta( seconds=settings.CLOUDFRONT_SIGNED_URLS_VALIDITY) cloudfront_signer = CloudFrontSigner(settings.CLOUDFRONT_ACCESS_KEY_ID, cloudfront_utils.rsa_signer) return cloudfront_signer.generate_presigned_url( url, date_less_than=date_less_than)
def get_url(self, obj): """Url of the timed text track, signed with a CloudFront key if activated. Parameters ---------- obj : Type[models.TimedTextTrack] The timed text track that we want to serialize Returns ------- string or None The url for the timed text track converted to vtt. None if the timed text track is still not uploaded to S3 with success. """ if obj.uploaded_on: base = "{protocol:s}://{cloudfront:s}/{video!s}".format( protocol=settings.AWS_S3_URL_PROTOCOL, cloudfront=settings.CLOUDFRONT_DOMAIN, video=obj.video.pk, ) url = "{base:s}/timedtext/{stamp:s}_{language:s}{mode:s}.vtt".format( base=base, stamp=time_utils.to_timestamp(obj.uploaded_on), language=obj.language, mode="_{:s}".format(obj.mode) if obj.mode else "", ) # Sign the url only if the functionality is activated if settings.CLOUDFRONT_SIGNED_URLS_ACTIVE: date_less_than = timezone.now() + timedelta( seconds=settings.CLOUDFRONT_SIGNED_URLS_VALIDITY ) cloudfront_signer = CloudFrontSigner( settings.CLOUDFRONT_ACCESS_KEY_ID, cloudfront_utils.rsa_signer ) url = cloudfront_signer.generate_presigned_url( url, date_less_than=date_less_than ) return url return None
def generate_signed_cloudfront_url(path=None, cloudfront_url=settings.CLOUDFRONT_URL, *args, **kwargs): if path is None: raise ValueError("Must provide a path for the CloudFront url to sign") key_id = kwargs.get('key_id') or settings.CLOUDFRONT_KEY_ID expiry = kwargs.get('expiry') or 120 current_time = datetime.datetime.utcnow() expire_date = current_time + datetime.timedelta(seconds=expiry) rsa_signer_ = kwargs.get('rsa_signer') or rsa_signer cloudfront_signer = CloudFrontSigner(key_id, rsa_signer_) url = cloudfront_url + path # Create a signed url that will be valid until the specific expiry date provided using a canned policy signed_url = cloudfront_signer.generate_presigned_url( url, date_less_than=expire_date) return signed_url
def secure_cloudfront_video(request): """ Generate a redirect to the AWS CloudFront resource. The signed resource URL must point to /secure-cloudfront-video/?key=path-to-aws-resource The resource URL will have a expiration time of one minute. Note that the resource URL must have the slash at the beginning of the string. **Example Requests**: GET lms-url/secure-cloudfront-video/?key=path-to-aws-resource **Responses** Redirect 302: If the signing process was successful and the resource exists. Not Found 404: If the 'key' query string or any of the Amazon Cloud Front settings is missing. """ meta = request.META if not meta or meta.get('HTTP_HOST', '') not in meta.get( 'HTTP_REFERER', ''): raise Http404 key = request.GET.get('key', '') cloudfront_url = getattr(settings, 'SCV_CLOUDFRONT_URL', '') cloudfront_id = getattr(settings, 'SCV_CLOUDFRONT_ID', '') if not (key and cloudfront_url and cloudfront_id): raise Http404 try: cloudfront_signer = CloudFrontSigner(cloudfront_id, cloudfront_rsa_signer) redirect_url = cloudfront_signer.generate_presigned_url( url='{}{}'.format(cloudfront_url, key), date_less_than=utc_time_plus_one_minute(), ) except MissingCloudFrontInformationError as cloudfront_error: log.error(cloudfront_error.message) raise Http404 return redirect(redirect_url)
class TestCloudfrontSigner(unittest.TestCase): def setUp(self): self.signer = CloudFrontSigner("MY_KEY_ID", lambda message: b'signed') # It helps but the long string diff will still be slightly different on # Python 2.6/2.7/3.x. We won't soly rely on that anyway, so it's fine. self.maxDiff = None def test_build_canned_policy(self): policy = self.signer.build_policy('foo', datetime.datetime(2016, 1, 1)) expected = ( '{"Statement":[{"Resource":"foo",' '"Condition":{"DateLessThan":{"AWS:EpochTime":1451606400}}}]}') self.assertEqual(json.loads(policy), json.loads(expected)) self.assertEqual(policy, expected) # This is to ensure the right order def test_build_custom_policy(self): policy = self.signer.build_policy( 'foo', datetime.datetime(2016, 1, 1), date_greater_than=datetime.datetime(2015, 12, 1), ip_address='12.34.56.78/9') expected = { "Statement": [{ "Resource": "foo", "Condition": { "DateGreaterThan": {"AWS:EpochTime": 1448928000}, "DateLessThan": {"AWS:EpochTime": 1451606400}, "IpAddress": {"AWS:SourceIp": "12.34.56.78/9"} }, }] } self.assertEqual(json.loads(policy), expected) def _urlparse(self, url): if isinstance(url, six.binary_type): # Not really necessary, but it helps to reduce noise on Python 2.x url = url.decode('utf8') return dict(urlparse(url)._asdict()) # Needs an unordered dict here def assertEqualUrl(self, url1, url2): # We compare long urls by their dictionary parts parts1 = self._urlparse(url1) parts2 = self._urlparse(url2) self.assertEqual( parse_qs(parts1.pop('query')), parse_qs(parts2.pop('query'))) self.assertEqual(parts1, parts2) def test_generate_presign_url_with_expire_time(self): signed_url = self.signer.generate_presigned_url( 'http://test.com/foo.txt', date_less_than=datetime.datetime(2016, 1, 1)) expected = ( 'http://test.com/foo.txt?Expires=1451606400&Signature=c2lnbmVk' '&Key-Pair-Id=MY_KEY_ID') self.assertEqualUrl(signed_url, expected) def test_generate_presign_url_with_custom_policy(self): policy = self.signer.build_policy( 'foo', datetime.datetime(2016, 1, 1), date_greater_than=datetime.datetime(2015, 12, 1), ip_address='12.34.56.78/9') signed_url = self.signer.generate_presigned_url( 'http://test.com/index.html?foo=bar', policy=policy) expected = ( 'http://test.com/index.html?foo=bar' '&Policy=eyJTdGF0ZW1lbnQiOlt7IlJlc291cmNlIjoiZm9vIiwiQ29uZ' 'Gl0aW9uIjp7IkRhdGVMZXNzVGhhbiI6eyJBV1M6RXBvY2hUaW1lIj' 'oxNDUxNjA2NDAwfSwiSXBBZGRyZXNzIjp7IkFXUzpTb3VyY2VJcCI' '6IjEyLjM0LjU2Ljc4LzkifSwiRGF0ZUdyZWF0ZXJUaGFuIjp7IkFX' 'UzpFcG9jaFRpbWUiOjE0NDg5MjgwMDB9fX1dfQ__' '&Signature=c2lnbmVk&Key-Pair-Id=MY_KEY_ID') self.assertEqualUrl(signed_url, expected)
def get_urls(self, obj): """Urls of the video for each type of encoding. Parameters ---------- obj : Type[models.Video] The video that we want to serialize Returns ------- Dictionary or None A dictionary of all urls for: - mp4 encodings of the video in each resolution - jpeg thumbnails of the video in each resolution - manifest of the DASH encodings of the video - manifest of the HLS encodings of the video For a video in live mode only the HLS url is added None if the video is still not uploaded to S3 with success """ if obj.live_state is not None: # Adaptive Bit Rate manifests return { "manifests": { "hls": obj.live_info["mediapackage"]["endpoints"]["hls"]["url"], "dash": None, }, "mp4": {}, "thumbnails": {}, } if obj.uploaded_on is None: return None thumbnail_urls = {} try: thumbnail = obj.thumbnail except Thumbnail.DoesNotExist: pass else: if thumbnail.uploaded_on is not None: thumbnail_serialized = ThumbnailSerializer(thumbnail) thumbnail_urls.update(thumbnail_serialized.data.get("urls")) urls = {"mp4": {}, "thumbnails": {}} base = "{protocol:s}://{cloudfront:s}/{pk!s}".format( protocol=settings.AWS_S3_URL_PROTOCOL, cloudfront=settings.CLOUDFRONT_DOMAIN, pk=obj.pk, ) stamp = time_utils.to_timestamp(obj.uploaded_on) date_less_than = timezone.now() + timedelta( seconds=settings.CLOUDFRONT_SIGNED_URLS_VALIDITY ) stamp = time_utils.to_timestamp(obj.uploaded_on) filename = "{playlist_title:s}_{stamp:s}.mp4".format( playlist_title=slugify(obj.playlist.title), stamp=stamp ) for resolution in obj.resolutions: # MP4 mp4_url = ( "{base:s}/mp4/{stamp:s}_{resolution:d}.mp4" "?response-content-disposition={content_disposition:s}" ).format( base=base, stamp=stamp, resolution=resolution, content_disposition=quote_plus("attachment; filename=" + filename), ) # Thumbnails urls["thumbnails"][resolution] = thumbnail_urls.get( resolution, "{base:s}/thumbnails/{stamp:s}_{resolution:d}.0000000.jpg".format( base=base, stamp=stamp, resolution=resolution ), ) # Sign the urls of mp4 videos only if the functionality is activated if settings.CLOUDFRONT_SIGNED_URLS_ACTIVE: cloudfront_signer = CloudFrontSigner( settings.CLOUDFRONT_ACCESS_KEY_ID, cloudfront_utils.rsa_signer ) mp4_url = cloudfront_signer.generate_presigned_url( mp4_url, date_less_than=date_less_than ) urls["mp4"][resolution] = mp4_url # Adaptive Bit Rate manifests urls["manifests"] = { "dash": "{base:s}/cmaf/{stamp:s}.mpd".format(base=base, stamp=stamp), "hls": "{base:s}/cmaf/{stamp:s}.m3u8".format(base=base, stamp=stamp), } # Previews urls["previews"] = "{base:s}/previews/{stamp:s}_100.jpg".format( base=base, stamp=stamp ) return urls
from botocore.signers import CloudFrontSigner import rsa import datetime import sys print ": ", sys.argv[1] def rsa_signer(message): private_key = open( '/Users/mssalehi/git/CloudFrontSingedstuff/pk-APKAJDUHAEYL6L3T4ZUQ.cer', 'r').read() return rsa.sign(message, rsa.PrivateKey.load_pkcs1(private_key.encode('utf8')), 'SHA-1') cf_signer = CloudFrontSigner('APKAJDUHAEYL6L3T4ZUQ', rsa_signer) signed_url = cf_signer.generate_presigned_url(sys.argv[1], date_less_than=datetime.datetime( 2017, 12, 28)) print(signed_url)
def get_urls(self, obj): """Urls of the video for each type of encoding. Parameters ---------- obj : Type[models.Video] The video that we want to serialize Returns ------- Dictionary or None A dictionary of all urls for: - mp4 encodings of the video in each resolution - jpeg thumbnails of the video in each resolution - manifest of the DASH encodings of the video - manifest of the HLS encodings of the video None if the video is still not uploaded to S3 with success """ if obj.uploaded_on is None: return None urls = {"mp4": {}, "thumbnails": {}} base = "{cloudfront:s}/{resource!s}".format( cloudfront=settings.CLOUDFRONT_URL, resource=obj.resource_id ) stamp = time_utils.to_timestamp(obj.uploaded_on) date_less_than = timezone.now() + timedelta( seconds=settings.CLOUDFRONT_SIGNED_URLS_VALIDITY ) for resolution in settings.VIDEO_RESOLUTIONS: # MP4 mp4_url = "{base:s}/mp4/{stamp:s}_{resolution:d}.mp4".format( base=base, stamp=time_utils.to_timestamp(obj.uploaded_on), resolution=resolution, ) # Thumbnails urls["thumbnails"][ resolution ] = "{base:s}/thumbnails/{stamp:s}_{resolution:d}.0000000.jpg".format( base=base, stamp=stamp, resolution=resolution ) # Sign the urls of mp4 videos only if the functionality is activated if settings.CLOUDFRONT_SIGNED_URLS_ACTIVE: cloudfront_signer = CloudFrontSigner( settings.CLOUDFRONT_ACCESS_KEY_ID, cloudfront_utils.rsa_signer ) mp4_url = cloudfront_signer.generate_presigned_url( mp4_url, date_less_than=date_less_than ) urls["mp4"][resolution] = mp4_url # Adaptive Bit Rate manifests urls["manifests"] = { "dash": "{base:s}/cmaf/{stamp:s}.mpd".format(base=base, stamp=stamp), "hls": "{base:s}/cmaf/{stamp:s}.m3u8".format(base=base, stamp=stamp), } # Previews urls["previews"] = "{base:s}/previews/{stamp:s}_100.jpg".format( base=base, stamp=stamp ) return urls
from botocore.signers import CloudFrontSigner 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 import datetime from django.conf import settings def rsa_signer(message): #### .pem is the private keyfile downloaded from CloudFront keypair with open(settings.CLOUDFRONT_PK_FILE_NAME, 'rb') as key_file: private_key = serialization.load_pem_private_key( key_file.read(), password=None, backend=default_backend()) signer = private_key.signer(padding.PKCS1v15(), hashes.SHA1()) signer.update(message) return signer.finalize() key_id = settings.CLOUDFRONT_KEY_ID url = 'd1on7f8lkpfomn.cloudfront.net/media/2test2.jpg' current_time = datetime.datetime.utcnow() expire_date = current_time + datetime.timedelta(minutes=2) cloudfront_signer = CloudFrontSigner(key_id, rsa_signer) # Create a signed url that will be valid until the specific expiry date provided using a canned policy signed_url = cloudfront_signer.generate_presigned_url( url, date_less_than=expire_date) print(signed_url)
class TestCloudfrontSigner(unittest.TestCase): def setUp(self): self.signer = CloudFrontSigner("MY_KEY_ID", lambda message: b'signed') # It helps but the long string diff will still be slightly different on # Python 2.6/2.7/3.x. We won't soly rely on that anyway, so it's fine. self.maxDiff = None def test_build_canned_policy(self): policy = self.signer.build_policy('foo', datetime.datetime(2016, 1, 1)) expected = ( '{"Statement":[{"Resource":"foo",' '"Condition":{"DateLessThan":{"AWS:EpochTime":1451606400}}}]}') self.assertEqual(json.loads(policy), json.loads(expected)) self.assertEqual(policy, expected) # This is to ensure the right order def test_build_custom_policy(self): policy = self.signer.build_policy('foo', datetime.datetime(2016, 1, 1), date_greater_than=datetime.datetime( 2015, 12, 1), ip_address='12.34.56.78/9') expected = { "Statement": [{ "Resource": "foo", "Condition": { "DateGreaterThan": { "AWS:EpochTime": 1448928000 }, "DateLessThan": { "AWS:EpochTime": 1451606400 }, "IpAddress": { "AWS:SourceIp": "12.34.56.78/9" } }, }] } self.assertEqual(json.loads(policy), expected) def _urlparse(self, url): if isinstance(url, six.binary_type): # Not really necessary, but it helps to reduce noise on Python 2.x url = url.decode('utf8') return dict(urlparse(url)._asdict()) # Needs an unordered dict here def assertEqualUrl(self, url1, url2): # We compare long urls by their dictionary parts parts1 = self._urlparse(url1) parts2 = self._urlparse(url2) self.assertEqual(parse_qs(parts1.pop('query')), parse_qs(parts2.pop('query'))) self.assertEqual(parts1, parts2) def test_generate_presign_url_with_expire_time(self): signed_url = self.signer.generate_presigned_url( 'http://test.com/foo.txt', date_less_than=datetime.datetime(2016, 1, 1)) expected = ( 'http://test.com/foo.txt?Expires=1451606400&Signature=c2lnbmVk' '&Key-Pair-Id=MY_KEY_ID') self.assertEqualUrl(signed_url, expected) def test_generate_presign_url_with_custom_policy(self): policy = self.signer.build_policy('foo', datetime.datetime(2016, 1, 1), date_greater_than=datetime.datetime( 2015, 12, 1), ip_address='12.34.56.78/9') signed_url = self.signer.generate_presigned_url( 'http://test.com/index.html?foo=bar', policy=policy) expected = ('http://test.com/index.html?foo=bar' '&Policy=eyJTdGF0ZW1lbnQiOlt7IlJlc291cmNlIjoiZm9vIiwiQ29uZ' 'Gl0aW9uIjp7IkRhdGVMZXNzVGhhbiI6eyJBV1M6RXBvY2hUaW1lIj' 'oxNDUxNjA2NDAwfSwiSXBBZGRyZXNzIjp7IkFXUzpTb3VyY2VJcCI' '6IjEyLjM0LjU2Ljc4LzkifSwiRGF0ZUdyZWF0ZXJUaGFuIjp7IkFX' 'UzpFcG9jaFRpbWUiOjE0NDg5MjgwMDB9fX1dfQ__' '&Signature=c2lnbmVk&Key-Pair-Id=MY_KEY_ID') self.assertEqualUrl(signed_url, expected)