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 create_signed_cookies(self, resource, expire_minutes=3): """ generate the Cloudfront download distirbution signed cookies @resource path to the file, path, or wildcard pattern to generate policy for @expire_minutes number of minutes until expiration return tuple with domain used within policy (so it matches cookie domain), and dict of cloudfront cookies you should set in request header """ http_resource = self.get_http_resource_url( resource, secure=True ) #per-file access #NOTE secure should match security settings of cloudfront distribution cloudfront_signer = CloudFrontSigner( settings.CLOUDFRONT_SIGNED_COOKIES_KEY_PAIR_ID, rsa_signer) expires = SignedCookiedCloudfrontDistribution.get_expires( expire_minutes) policy = cloudfront_signer.build_policy( http_resource, datetime.fromtimestamp(expires)) encoded_policy = cloudfront_signer._url_b64encode( policy.encode('utf-8')).decode('utf-8') #assemble the 3 Cloudfront cookies signature = rsa_signer(policy.encode('utf-8')) encoded_signature = cloudfront_signer._url_b64encode(signature).decode( 'utf-8') cookies = { "CloudFront-Policy": encoded_policy, "CloudFront-Signature": encoded_signature, "CloudFront-Key-Pair-Id": settings.CLOUDFRONT_SIGNED_COOKIES_KEY_PAIR_ID, } return cookies
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 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 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
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 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
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 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 __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")
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 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 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 _cloud_front_signer_from_pem(key_id, pem): key = load_pem_private_key(pem, password=None, backend=default_backend()) return CloudFrontSigner( key_id, lambda x: key.sign(x, padding.PKCS1v15(), hashes.SHA1()))
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 __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')
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 generate_cloudfront_urls_signed_parameters(resource, date_less_than): """ Generate all parameters use by a cloudfront signed url. Mainly extracted from CloudFrontSigner class. """ cloudfront_signer = CloudFrontSigner( settings.CLOUDFRONT_SIGNED_PUBLIC_KEY_ID, rsa_signer, ) policy = cloudfront_signer.build_policy( resource=resource, date_less_than=date_less_than).encode("utf8") signature = cloudfront_signer.rsa_signer(policy) return [ f"Policy={_url_b64encode(policy).decode('utf8')}", f"Signature={_url_b64encode(signature).decode('utf8')}", f"Key-Pair-Id={cloudfront_signer.key_id}", ]
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 _cloud_front_signer_from_pem(key_id, pem): if isinstance(pem, str): pem = pem.encode('ascii') key = load_pem_private_key( pem, password=None, backend=default_backend()) return CloudFrontSigner( key_id, lambda x: key.sign(x, padding.PKCS1v15(), hashes.SHA1()))
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 _cloudfront_signer(self): with open(settings.CLOUDFRONT_PRIVATE_KEY_PATH, "rb") as key_file: private_key = serialization.load_pem_private_key( key_file.read(), password=None, backend=default_backend()) return CloudFrontSigner( settings.CLOUDFRONT_KEY_PAIR_ID, lambda m: private_key.sign(m, padding.PKCS1v15(), hashes.SHA1()), )
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)
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 = utcnow() + timedelta(seconds=self.expire_after) return self.cf_signer.generate_presigned_url(url, date_less_than=expires)
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
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 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 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
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)