Add CloudFrontedS3Storage, which redirects to CloudFront for non-S3 ips
This commit is contained in:
parent
2d522764f7
commit
010dda2c52
14 changed files with 175 additions and 69 deletions
|
@ -3,8 +3,17 @@ import os
|
|||
import logging
|
||||
import copy
|
||||
|
||||
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
|
||||
|
||||
from cachetools import lru_cache
|
||||
from itertools import chain
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from botocore.signers import CloudFrontSigner
|
||||
from boto.exception import S3ResponseError
|
||||
import boto.s3.connection
|
||||
import boto.s3.multipart
|
||||
|
@ -590,3 +599,58 @@ class RadosGWStorage(_CloudStorage):
|
|||
# See https://github.com/ceph/ceph/pull/5139
|
||||
chunk_list = self._chunk_list_from_metadata(storage_metadata)
|
||||
self._client_side_chunk_join(final_path, chunk_list)
|
||||
|
||||
|
||||
class CloudFrontedS3Storage(S3Storage):
|
||||
""" An S3Storage engine that redirects to CloudFront for all requests outside of AWS. """
|
||||
def __init__(self, context, cloudfront_distribution_domain, cloudfront_key_id,
|
||||
cloudfront_privatekey_filename, storage_path, s3_bucket, *args, **kwargs):
|
||||
super(CloudFrontedS3Storage, self).__init__(context, storage_path, s3_bucket, *args, **kwargs)
|
||||
|
||||
self.cloudfront_distribution_domain = cloudfront_distribution_domain
|
||||
self.cloudfront_key_id = cloudfront_key_id
|
||||
self.cloudfront_privatekey = self._load_private_key(cloudfront_privatekey_filename)
|
||||
|
||||
def get_direct_download_url(self, path, request_ip=None, expires_in=60, requires_cors=False, head=False):
|
||||
logger.debug('Got direct download request for path "%s" with IP "%s"', path, request_ip)
|
||||
if request_ip is not None:
|
||||
# Lookup the IP address in our resolution table and determine whether it is under AWS. If it is *not*,
|
||||
# then return a CloudFront signed URL.
|
||||
resolved_ip_info = self._context.ip_resolver.resolve_ip(request_ip)
|
||||
logger.debug('Resolved IP information for IP %s: %s', request_ip, resolved_ip_info)
|
||||
if resolved_ip_info and resolved_ip_info.provider != 'aws':
|
||||
url = 'https://%s/%s' % (self.cloudfront_distribution_domain, path)
|
||||
expire_date = datetime.now() + timedelta(seconds=expires_in)
|
||||
signer = self._get_cloudfront_signer()
|
||||
signed_url = signer.generate_presigned_url(url, date_less_than=expire_date)
|
||||
logger.debug('Returning CloudFront URL for path "%s" with IP "%s": %s', path, resolved_ip_info, signed_url)
|
||||
return signed_url
|
||||
|
||||
return super(CloudFrontedS3Storage, self).get_direct_download_url(path, request_ip, expires_in, requires_cors,
|
||||
head)
|
||||
|
||||
@lru_cache(maxsize=1)
|
||||
def _get_cloudfront_signer(self):
|
||||
return CloudFrontSigner(self.cloudfront_key_id, self._get_rsa_signer())
|
||||
|
||||
@lru_cache(maxsize=1)
|
||||
def _get_rsa_signer(self):
|
||||
private_key = self.cloudfront_privatekey
|
||||
def handler(message):
|
||||
signer = private_key.signer(padding.PKCS1v15(), hashes.SHA1())
|
||||
signer.update(message)
|
||||
return signer.finalize()
|
||||
|
||||
return handler
|
||||
|
||||
@lru_cache(maxsize=1)
|
||||
def _load_private_key(self, cloudfront_privatekey_filename):
|
||||
""" Returns the private key, loaded from the config provider, used to sign direct
|
||||
download URLs to CloudFront.
|
||||
"""
|
||||
with self._context.config_provider.get_volume_file(cloudfront_privatekey_filename) as key_file:
|
||||
return serialization.load_pem_private_key(
|
||||
key_file.read(),
|
||||
password=None,
|
||||
backend=default_backend()
|
||||
)
|
||||
|
|
Reference in a new issue