1
0
mirror of https://github.com/quay/quay.git synced 2026-01-26 06:21:37 +03:00
Files
quay/storage/cloudflarestorage.py
Syed Ahmed 705ea386d2 Add S3 Region to CloudFlare URL (PROJQUAY-3512) (#1630)
This will be used by the CloudFlare worker to redirect
to the correct S3 origin bucket
2022-11-21 11:59:21 -05:00

104 lines
3.6 KiB
Python

import base64
import logging
import urllib.parse
from datetime import datetime, timedelta
from functools import lru_cache
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization, hashes
from cryptography.hazmat.primitives.asymmetric import padding
logger = logging.getLogger(__name__)
from storage.cloud import S3Storage
class CloudFlareS3Storage(S3Storage):
"""
CloudFlare CDN backed by S3 storage
"""
def __init__(
self,
context,
cloudflare_domain,
cloudflare_privatekey_filename,
storage_path,
s3_bucket,
s3_region,
*args,
**kwargs,
):
super(CloudFlareS3Storage, self).__init__(context, storage_path, s3_bucket, *args, **kwargs)
self.cloudflare_domain = cloudflare_domain
self.cloudflare_privatekey = self._load_private_key(cloudflare_privatekey_filename)
self.region = s3_region
def get_direct_download_url(
self, path, request_ip=None, expires_in=60, requires_cors=False, head=False, **kwargs
):
# If CloudFront could not be loaded, fall back to normal S3.
s3_presigned_url = super(CloudFlareS3Storage, self).get_direct_download_url(
path, request_ip, expires_in, requires_cors, head
)
logger.debug(f"s3 presigned_url: {s3_presigned_url}")
if self.cloudflare_privatekey is None or request_ip is None:
return s3_presigned_url
logger.debug('Got direct download request for path "%s" with IP "%s"', path, request_ip)
s3_url_parsed = urllib.parse.urlparse(s3_presigned_url)
cf_url_parsed = s3_url_parsed._replace(netloc=self.cloudflare_domain)
expire_date = datetime.now() + timedelta(seconds=expires_in)
signed_url = self._cf_sign_url(cf_url_parsed, date_less_than=expire_date)
logger.debug(
'Returning CloudFlare URL for path "%s" with IP "%s": %s',
path,
request_ip,
signed_url,
)
return signed_url
def _cf_sign_url(self, cf_url_parsed, date_less_than):
expiry_ts = date_less_than.timestamp()
sign_data = "%s@%d" % (cf_url_parsed.path, expiry_ts)
signature = self.cloudflare_privatekey.sign(
sign_data.encode("utf8"), padding.PKCS1v15(), hashes.SHA256()
)
signature_b64 = base64.b64encode(signature)
return self._build_signed_url(cf_url_parsed, signature_b64, date_less_than)
def _build_signed_url(self, cf_url_parsed, signature, expiry_date):
query = cf_url_parsed.query
url_dict = dict(urllib.parse.parse_qsl(query))
params = {
"cf_sign": signature,
"cf_expiry": "%d" % expiry_date.timestamp(),
"region": self.region,
}
url_dict.update(params)
url_new_query = urllib.parse.urlencode(url_dict)
url_parse = cf_url_parsed._replace(query=url_new_query)
return urllib.parse.urlunparse(url_parse)
@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.
"""
if self._context.config_provider is None:
return None
with self._context.config_provider.get_volume_file(
cloudfront_privatekey_filename,
mode="rb",
) as key_file:
return serialization.load_pem_private_key(
key_file.read(), password=None, backend=default_backend()
)