Создание подписанных URL для Amazon CloudFront - PullRequest
28 голосов
/ 04 апреля 2010

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

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

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

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

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

Ответы [ 5 ]

31 голосов
/ 08 июля 2011

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

Создание пары ключей для CloudFront

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

Создание некоторых URL в Python

Начиная с версии 2.0 boto, похоже, что поддержка создания подписанных 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

21 голосов
/ 17 декабря 2015

Эта функция теперь уже поддерживается в 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)

Отказ от ответственности: я автор этого пиара.

14 голосов
/ 22 апреля 2013

Как уже отмечали многие, первоначально принятый ответ не относится к Amazon CloudFront на самом деле, поскольку Обслуживание частного контента через CloudFront требует использования выделенных подписанных URL-адресов CloudFront - соответственно ответ secretmike был верным, но он тем временем устарел после того, как он сам нашел время, и Добавлена ​​поддержка создания подписанных 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)
0 голосов
/ 14 апреля 2014

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

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...
0 голосов
/ 17 июня 2013
Ответ

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

...