2010-04-04 28 views
22

Versione corta: come creare URL "su richiesta" per simulare il comportamento di X-Accel-Redirect di Nginx (ovvero protezione dei download) con Amazon CloudFront/S3 utilizzando Python.Creazione di URL firmati per Amazon CloudFront

Ho un server Django installato e funzionante con un front-end Nginx. Mi è stato richiesto un sacco di richieste e recentemente ho dovuto installarlo come un'applicazione WSGI Tornado per impedirne l'arresto in modalità FastCGI.

Ora sto avendo un problema con il mio server impantanarsi (cioè la maggior parte della sua larghezza di banda è esaurita) a causa delle troppe richieste per i media che sono state fatte ad esso, ho esaminato i CDN e credo Amazon CloudFront/S3 sarebbe la soluzione giusta per me.

Ho utilizzato l'intestazione X-Accel-Redirect di Nginx per proteggere i file dal download non autorizzato, ma non ho questa capacità con CloudFront/S3, tuttavia offrono URL firmati. Non sono un esperto di Python e sicuramente non so come creare correttamente un URL firmato, quindi speravo che qualcuno avesse un link su come rendere questi URL "on-demand" o sarebbe disposto a spiegare come qui, sarebbe molto apprezzato.

Inoltre, è questa la soluzione corretta, anche? Non ho molta familiarità con i CDN, c'è una CDN che sarebbe più adatta a questo?

+0

In ogni caso, passa a uWSGI e il tuo FastCGI non si arresterà più. –

risposta

30

Amazon CloudFront Signed URLs funzionano in modo diverso rispetto agli URL firmati Amazon S3. CloudFront utilizza le firme RSA basate su una coppia di chiavi CloudFront separata che devi configurare nella pagina delle credenziali dell'account Amazon. Ecco un po 'di codice per generare in realtà un URL a tempo limitato in Python utilizzando la libreria M2Crypto:

Creare una coppia di chiavi per CloudFront

Credo che l'unico modo per farlo è attraverso il sito web di Amazon. Vai nella pagina "Account" di AWS e fai clic sul link "Credenziali di sicurezza". Fare clic sulla scheda "Coppie di chiavi", quindi fare clic su "Crea una nuova coppia di chiavi". Questo genererà una nuova coppia di chiavi per te e scaricherà automaticamente un file di chiave privata (pk-xxxxxxxxx.pem). Mantieni il file chiave sicuro e privato. Annota anche il "Key Pair ID" di Amazon, come avremo bisogno nel prossimo passaggio.

generare alcuni URL in Python

Come di boto versione 2.0 non sembra essere alcun supporto per la generazione di URL CloudFront firmati. Python non include le routine di crittografia RSA nella libreria standard, quindi dovremo utilizzare una libreria aggiuntiva. Ho usato M2Crypto in questo esempio.

Per una distribuzione non in streaming, è necessario utilizzare l'URL del cloudfront completo come risorsa, tuttavia per lo streaming viene utilizzato solo il nome dell'oggetto del file video. Vedere il codice qui sotto per un esempio completo di generazione di un URL che dura solo per 5 minuti.

Questo codice si basa liberamente sul codice di esempio PHP fornito da Amazon nella documentazione di 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) 

Assicurarsi di configurare la distribuzione con un parametro TrustedSigners impostato al conto di deposito propria coppia di chiavi (o "Auto" se è il proprio account)

Vedi Getting started with secure AWS CloudFront streaming with Python per una completamente lavorato ad esempio su come impostare questo in su per lo streaming con Python

+0

all'interno di 'get_canned_policy_url' si imposta la politica codificata su una variabile' encoded_policy', ma non la si usa mai. Dovrebbe essere lì? – MattoTodd

+2

Ciao MattoTodd, hai ragione, non è necessario essere lì. Inoltre, boto v2.1 ora supporta questo in modo nativo. Vedi http://www.secretmike.com/2011/10/aws-cloudfront-secure-streaming.html per sapere come funziona con il nuovo codice boto. Una volta che la versione di boto è stata fuori per un po 'aggiornerò queste risposte. – secretmike

+1

Esiste un esempio utilizzando il metodo create_signed_url e [Boto 2.5.2?] (Http://boto.cloudhackers.com/en/latest/ref/cloudfront.html) – ipegasus

14

Come molti hanno già commentato, il initially accepted answer non si applica ai Amazon CloudFront infatti, nella misura in cui Serving Private Content through CloudFront richiede l'uso di dedicato CloudFront Signed URLs - di conseguenza secretmike's answer è stato corretto, ma è nel frattempo obsoleto dopo che lui stesso ha preso il tempo e Added support for generating signed URLs for CloudFront (grazie mille per questo!).

boto ora supporta un metodo dedicato create_signed_url e l'ex dipendenza binario M2Crypto è stato recentemente sostituito con un pure-Python RSA implementation pure, vedi Don't use M2Crypto for cloudfront URL signing.

Come sempre più comune, si possono trovare uno o più buoni esempi di utilizzo nei test di unità correlate (cfr test_signed_urls.py), per esempio test_canned_policy(self) - vedi setUp(self) per le variabili di riferimento self.pk_id e self.pk_str (ovviamente avrete bisogno le chiavi) : risposta

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

di secretmike funziona, ma è meglio usare rsa invece di M2Crypto.

Ho utilizzato boto che utilizza 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") 

Utilizzare la boto documentation

+0

Sembra non esserci alcuna politica esportata in signed_url. In ogni caso non ho potuto farlo funzionare. – user2105469

0

Questo è quello che uso per creare una politica in modo che io possa dare accesso a più file con lo stesso "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) 

posso usarlo per tutti i file sotto http://your_domain/* ad esempio:

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

Questa funzione è ora already supported in Botocore, che è la libreria sottostante di Boto3, the latest official AWS SDK for Python. (L'esempio seguente richiede l'installazione del pacchetto rsa, ma è possibile utilizzare altro pacchetto RSA troppo, basta definire il proprio "firmatario normalizzato RSA".)

L'utilizzo è simile al seguente:

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) 

di responsabilità : Sono l'autore di quel PR.

Problemi correlati