2010-04-04 19 views
22

Versión corta: ¿Cómo puedo hacer que las URL firmadas "a petición" imiten el comportamiento X-Accel-Redirect de Nginx (es decir, proteger las descargas) con Amazon CloudFront/S3 usando Python.Creación de URL firmados para Amazon CloudFront

Tengo un servidor Django en funcionamiento con un front-end Nginx. Me han estado solicitando y recientemente tuve que instalarlo como una aplicación WSGI Tornado para evitar que se bloquee en el modo FastCGI.

Ahora tengo un problema con mi servidor empantanado (es decir, se está agotando la mayor parte de su ancho de banda) debido a demasiadas solicitudes de medios, he estado investigando los CDN y creo Amazon CloudFront/S3 sería la solución adecuada para mí.

He estado usando el encabezado X-Accel-Redirect de Nginx para proteger los archivos de descargas no autorizadas, pero no tengo esa capacidad con CloudFront/S3, sin embargo, ofrecen URL firmadas. No soy un experto en Python y, definitivamente, no sé cómo crear una URL firmada correctamente, así que esperaba que alguien tuviese un enlace sobre cómo hacer estas URL "a petición" o si estaría dispuesto a explicar cómo hacerlo. aquí, sería muy apreciado.

Además, ¿es esta la solución adecuada, incluso? No estoy muy familiarizado con los CDN, ¿hay un CDN que sea más adecuado para esto?

+0

Por cierto, cambie a uWSGI y su FastCGI no se bloqueará más. –

Respuesta

30

Amazon CloudFront Signed URLs funcionan de manera diferente a las URL firmadas por Amazon S3. CloudFront utiliza firmas RSA basadas en un par de claves de CloudFront separado que debe configurar en su página de credenciales de cuenta de Amazon. Aquí hay algo de código para generar realmente una URL de duración limitada en Python usando la biblioteca de M2Crypto:

Crear un par de claves para CloudFront

Creo que la única manera de hacer esto es a través de la página web de Amazon. Vaya a la página "Cuenta" de AWS y haga clic en el enlace "Credenciales de seguridad". Haga clic en la pestaña "Pares clave" y luego haga clic en "Crear un nuevo par de claves". Esto generará un nuevo par de claves para usted y descargará automáticamente un archivo de clave privada (pk-xxxxxxxxx.pem). Mantenga el archivo de clave seguro y privado. También anote el "ID del par de claves" de Amazon, ya que lo necesitaremos en el siguiente paso.

generar algo de direcciones URL en Python

partir de la versión 2.0 boto no parece ser ningún apoyo para la generación de direcciones URL CloudFront firmados. Python no incluye las rutinas de encriptación RSA en la biblioteca estándar, por lo que tendremos que usar una biblioteca adicional. He usado M2Crypto en este ejemplo.

Para una distribución sin transmisión, debe usar la URL completa en la nube como recurso, sin embargo, para la transmisión, solo usamos el nombre del objeto del archivo de video. Consulte el siguiente código para obtener un ejemplo completo de generación de una URL que solo dura 5 minutos.

Este código se basa libremente en el código de ejemplo de PHP proporcionado por Amazon en la documentación de 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) 

Asegúrese de que configura su distribución con un parámetro TrustedSigners ajustado a la cuenta de haberes de su par de claves (o "Auto" si es su propia cuenta)

Ver Getting started with secure AWS CloudFront streaming with Python para un trabajado completamente ejemplo sobre cómo configurar esto para el streaming con Python

+0

dentro de 'get_canned_policy_url' estableces la política codificada en una variable' encoded_policy', pero nunca la uses. Se supone que debe estar ahí? – MattoTodd

+2

Hola MattoTodd, tienes razón, eso no necesita estar allí. Además, boto v2.1 ahora es compatible de forma nativa. Consulte http://www.secretmike.com/2011/10/aws-cloudfront-secure-streaming.html para saber cómo funciona con el nuevo código de boto. Una vez que la versión boto ha estado fuera por un tiempo, actualizaré estas respuestas. – secretmike

+1

¿Hay algún ejemplo que use el método create_signed_url y [Boto 2.5.2?] (Http://boto.cloudhackers.com/en/latest/ref/cloudfront.html) – ipegasus

14

Como muchos ya han comentado, el initially accepted answer no se aplica a Amazon CloudFront de hecho, en la medida en Serving Private Content through CloudFront requiere el uso de dedicada CloudFront Signed URLs - secretmike's answer consecuencia ha sido correcta, pero está desactualizado después de que él mismo se tomó el tiempo y Added support for generating signed URLs for CloudFront (¡muchas gracias por esto!).

boto ahora es compatible con un método específico create_signed_url y la antigua dependencia binaria M2Crypto ha sido recientemente sustituido con un pure-Python RSA implementation así, ver Don't use M2Crypto for cloudfront URL signing.

Como cada vez más común, se puede encontrar uno o más ejemplos de uso dentro de las pruebas unitarias relacionadas (ver test_signed_urls.py), por ejemplo test_canned_policy(self) - ver setUp(self) para las variables referenciados self.pk_id y self.pk_str (obviamente, que necesita sus propias claves) : respuesta

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

de secretmike funciona, pero es mejor usar rsa en lugar de M2Crypto.

He usado boto que usa 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") 

Usar la boto documentation

+0

No parece haber ninguna política exportada en la signed_url. En cualquier caso, no podría hacer que esto funcione. – user2105469

0

Esto es lo que utilizo para crear una política para que pueda dar acceso a varios archivos con el mismo "firma":

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) 

lo puedo usar para todos los archivos en http://your_domain/* por ejemplo:

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

Esta característica ahora es already supported in Botocore, que es la biblioteca subyacente de Boto3, the latest official AWS SDK for Python. (El siguiente ejemplo requiere la instalación del paquete de RSA, pero se puede utilizar otro paquete RSA también, al definir su propio "firmante RSA normalizada".)

El uso es el siguiente:

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) 

exención de responsabilidad : Soy el autor de ese PR.

Cuestiones relacionadas