Подтвердить что ты не робот

Создание подписанных URL-адресов для Amazon CloudFront

Краткая версия. Как сделать подписанные URL-адреса "по требованию" для имитации поведения Nginx X-Accel-Redirect (т.е. защиты загрузок) с помощью Amazon CloudFront/S3 с использованием Python.

У меня есть сервер Django и работает с интерфейсом Nginx. Меня забивают с запросами, а недавно пришлось установить его как приложение Tornado WSGI, чтобы предотвратить его сбой в режиме FastCGI.

Теперь у меня возникла проблема с увязкой моего сервера (т.е. большая часть его пропускной способности израсходована) из-за слишком большого количества запросов на создание мультимедиа, я изучал CDN, и я считаю, что Amazon CloudFront/S3 будет правильным решением для меня.

Я использую заголовок Nginx X-Accel-Redirect для защиты файлов от несанкционированной загрузки, но у меня нет этой возможности с CloudFront/S3, однако они предлагают подписанные URL-адреса. Я далеко не эксперт Python и определенно не знаю, как правильно создать подписанный URL, поэтому я надеялся, что у кого-то будет ссылка на то, как сделать эти URL-адреса "по требованию" или они захотят объяснить, как здесь, было бы весьма полезно.

Кроме того, это правильное решение, даже? Я не слишком знаком с CDN, есть ли CDN, который лучше подходит для этого?

4b9b3361

Ответ 1

Подписанные URL Amazon CloudFront работают иначе, чем подписанные URL-адреса Amazon S3. CloudFront использует подписи RSA на основе отдельной клавиатуры Keypoint для CloudFront, которую вы должны настроить на странице учетных записей учетной записи Amazon. Здесь некоторый код, чтобы фактически генерировать ограниченный по времени URL-адрес в Python, используя библиотеку M2Crypto:

Создать ключевую пару для CloudFront

Я думаю, что единственный способ сделать это - через веб-сайт Amazon. Перейдите на страницу "Учетная запись AWS" и нажмите ссылку "Учетные данные безопасности". Перейдите на вкладку "Ключевые пары" и нажмите "Создать новую пару ключей". Это создаст новую пару ключей для вас и автоматически загрузит файл закрытого ключа (pk-xxxxxxxxx.pem). Храните файл ключа в безопасном и закрытом состоянии. Также обратите внимание на "Идентификатор пары ключей" от амазонки, поскольку мы будем нуждаться в этом на следующем шаге.

Создать несколько URL-адресов в Python

Начиная с версии boto версии 2.0, похоже, нет никакой поддержки для создания подписанных URL CloudFront. Python не включает процедуры шифрования RSA в стандартной библиотеке, поэтому нам придется использовать дополнительную библиотеку. Я использовал M2Crypto в этом примере.

Для не потокового распространения вы должны использовать полный URL облачного облака в качестве ресурса, однако для потоковой передачи мы используем только имя объекта видеофайла. См. Приведенный ниже код для полного примера создания URL-адреса, который длится всего 5 минут.

Этот код основан на коде примера PHP, предоставленном Amazon в документации CloudFront.

from M2Crypto import EVP
import base64
import time

def aws_url_base64_encode(msg):
    msg_base64 = base64.b64encode(msg)
    msg_base64 = msg_base64.replace('+', '-')
    msg_base64 = msg_base64.replace('=', '_')
    msg_base64 = msg_base64.replace('/', '~')
    return msg_base64

def sign_string(message, priv_key_string):
    key = EVP.load_key_string(priv_key_string)
    key.reset_context(md='sha1')
    key.sign_init()
    key.sign_update(message)
    signature = key.sign_final()
    return signature

def create_url(url, encoded_signature, key_pair_id, expires):
    signed_url = "%(url)s?Expires=%(expires)s&Signature=%(encoded_signature)s&Key-Pair-Id=%(key_pair_id)s" % {
            'url':url,
            'expires':expires,
            'encoded_signature':encoded_signature,
            'key_pair_id':key_pair_id,
            }
    return signed_url

def get_canned_policy_url(url, priv_key_string, key_pair_id, expires):
    #we manually construct this policy string to ensure formatting matches signature
    canned_policy = '{"Statement":[{"Resource":"%(url)s","Condition":{"DateLessThan":{"AWS:EpochTime":%(expires)s}}}]}' % {'url':url, 'expires':expires}

    #sign the non-encoded policy
    signature = sign_string(canned_policy, priv_key_string)
    #now base64 encode the signature (URL safe as well)
    encoded_signature = aws_url_base64_encode(signature)

    #combine these into a full url
    signed_url = create_url(url, encoded_signature, key_pair_id, expires);

    return signed_url

def encode_query_param(resource):
    enc = resource
    enc = enc.replace('?', '%3F')
    enc = enc.replace('=', '%3D')
    enc = enc.replace('&', '%26')
    return enc


#Set parameters for URL
key_pair_id = "APKAIAZVIO4BQ" #from the AWS accounts CloudFront tab
priv_key_file = "cloudfront-pk.pem" #your private keypair file
# Use the FULL URL for non-streaming:
resource = "http://34254534.cloudfront.net/video.mp4"
#resource = 'video.mp4' #your resource (just object name for streaming videos)
expires = int(time.time()) + 300 #5 min

#Create the signed URL
priv_key_string = open(priv_key_file).read()
signed_url = get_canned_policy_url(resource, priv_key_string, key_pair_id, expires)

print(signed_url)

#Flash player doesn't like query params so encode them if you're using a streaming distribution
#enc_url = encode_query_param(signed_url)
#print(enc_url)

Убедитесь, что вы настроили свой дистрибутив с параметром TrustedSigners, установленным для учетной записи, содержащей вашу пару ключей (или "Self", если это ваша учетная запись)

См. Начало работы с безопасной потоковой трансляцией AWS CloudFront с Python для полностью обработанного примера по настройке этого для потоковой передачи с помощью Python

Ответ 2

Эта функция теперь уже поддерживается в Botocore, которая является базовой библиотекой Boto3, последний официальный AWS SDK для Python. (Следующий пример требует установки пакета rsa, но вы также можете использовать другой пакет RSA, просто определите свой собственный "нормализованный подписчик RSA".)

Использование выглядит следующим образом:

    from botocore.signers import CloudFrontSigner
    # First you create a cloudfront signer based on a normalized RSA signer::
    import rsa
    def rsa_signer(message):
        private_key = open('private_key.pem', 'r').read()
        return rsa.sign(
            message,
            rsa.PrivateKey.load_pkcs1(private_key.encode('utf8')),
            'SHA-1')  # CloudFront requires SHA-1 hash
    cf_signer = CloudFrontSigner(key_id, rsa_signer)

    # To sign with a canned policy::
    signed_url = cf_signer.generate_presigned_url(
        url, date_less_than=datetime(2015, 12, 1))

    # To sign with a custom policy::
    signed_url = cf_signer.generate_presigned_url(url, policy=my_policy)

Отказ от ответственности: я являюсь автором этого PR.

Ответ 3

Как уже отмечалось многими, первоначально принятый ответ не относится к Amazon CloudFront на самом деле, в Обслуживание личного содержимого через CloudFront требует использования выделенного Подписанные URL-адреса CloudFront - соответственно секретный ответ был правильным, но он тем временем устарел после того, как он сам взял время и Добавлена ​​поддержка для создания подписанных URL-адресов для CloudFront (спасибо большое за это!).

boto теперь поддерживает выделенный create_signed_url и прежняя двоичная зависимость M2Crypto недавно была заменена на чистая реализация Python RSA, см. Не используйте M2Crypto для подписания URL-адреса облачного интерфейса.

Как все чаще, можно найти один или несколько хороших примеров использования в соответствующих модульных тестах (см. test_signed_urls.py), например test_canned_policy (self) - см. setUp (self) для ссылочных переменных self.pk_id и self.pk_str (очевидно, вам понадобятся ваши собственные ключи):

def test_canned_policy(self):
    """
    Generate signed url from the Example Canned Policy in Amazon's
    documentation.
    """
    url = "http://d604721fxaaqy9.cloudfront.net/horizon.jpg?large=yes&license=yes"
    expire_time = 1258237200
    expected_url = "http://example.com/" # replaced for brevity
    signed_url = self.dist.create_signed_url(
        url, self.pk_id, expire_time, private_key_string=self.pk_str)
    # self.assertEqual(expected_url, signed_url)

Ответ 4

secretmike отвечает, но лучше использовать rsa вместо M2Crypto.

Я использовал boto, который использует rsa.

import boto
from boto.cloudfront import CloudFrontConnection
from boto.cloudfront.distribution import Distribution

expire_time = int(time.time() +3000)
conn = CloudFrontConnection('ACCESS_KEY_ID', 'SECRET_ACCESS_KEY')

##enter the id or domain name to select a distribution
distribution = Distribution(connection=conn, config=None, domain_name='', id='', last_modified_time=None, status='')
signed_url = distribution.create_signed_url(url='YOUR_URL', keypair_id='YOUR_KEYPAIR_ID_example-APKAIAZVIO4BQ',expire_time=expire_time,private_key_file="YOUR_PRIVATE_KEY_FILE_LOCATION")

Используйте boto documentation

Ответ 5

Это то, что я использую для создания политики, чтобы я мог предоставить доступ к нескольким файлам с одинаковой "сигнатурой":

import json 
import rsa
import time                                                                                                                                                                           

from base64 import b64encode 

url = "http://your_domain/*"                                                                                                                                                                      
expires = int(time.time() + 3600)

pem = """-----BEGIN RSA PRIVATE KEY-----  
...
-----END RSA PRIVATE KEY-----"""

key_pair_id = 'ABX....'

policy = {}                                                                                                                                                                           
policy['Statement'] = [{}]                                                                                                                                                            
policy['Statement'][0]['Resource'] = url                                                                                                                                              
policy['Statement'][0]['Condition'] = {}                                                                                                                                              
policy['Statement'][0]['Condition']['DateLessThan'] = {}                                                                                                                              
policy['Statement'][0]['Condition']['DateLessThan']['AWS:EpochTime'] = expires                                                                                                        

policy = json.dumps(policy)

private_key = rsa.PrivateKey.load_pkcs1(pem)                                                                                                                                          
signature = b64encode(rsa.sign(str(policy), private_key, 'SHA-1'))

print '?Policy=%s&Signature=%s&Key-Pair-Id=%s' % (b64encode(policy),                                                                                                                             
                                                  signature,                                                                                                                          
                                                  key_pair_id)

Я могу использовать его для всех файлов под http://your_domain/*, например:

 http://your_domain/image2.png?Policy...
 http://your_domain/image2.png?Policy...
 http://your_domain/file1.json?Policy...