cli - Comenzar con la transmisión segura de AWS CloudFront con Python
install boto3 (2)
En Python, ¿cuál es la forma más fácil de generar una URL que expira para un archivo? Tengo el boto instalado, pero no veo cómo obtener un archivo de una distribución de transmisión.
Puede generar una URL firmada que expira para el recurso. La documentación de Boto3 tiene una buena solución de ejemplo para eso:
import datetime from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives.asymmetric import padding from botocore.signers import CloudFrontSigner def rsa_signer(message): with open(''path/to/key.pem'', ''rb'') as key_file: private_key = serialization.load_pem_private_key( key_file.read(), password=None, backend=default_backend() ) signer = private_key.signer(padding.PKCS1v15(), hashes.SHA1()) signer.update(message) return signer.finalize() key_id = ''AKIAIOSFODNN7EXAMPLE'' url = ''http://d2949o5mkkp72v.cloudfront.net/hello.txt'' expire_date = datetime.datetime(2017, 1, 1) cloudfront_signer = CloudFrontSigner(key_id, rsa_signer) # Create a signed url that will be valid until the specfic expiry date # provided using a canned policy. signed_url = cloudfront_signer.generate_presigned_url( url, date_less_than=expire_date) print(signed_url)
Creé un cubo S3, subí un video y creé una distribución de transmisión en CloudFront. Lo probé con un reproductor de HTML estático y funciona. Creé un par de llaves a través de la configuración de la cuenta. Tengo el archivo de clave privada en mi escritorio en este momento. Ahí es donde estoy.
Mi objetivo es llegar a un punto en el que mi sitio Django / Python cree URL seguras y las personas no puedan acceder a los videos a menos que procedan de una de mis páginas. El problema es que soy alérgico a la forma en que Amazon ha establecido las cosas y cada vez estoy más confundido.
Me doy cuenta de que esta no será la mejor pregunta en StackOverflow, pero estoy seguro de que no puedo ser el único tonto que no puede sacar conclusiones de cómo configurar una situación segura de CloudFront / S3. Realmente agradecería su ayuda y estoy dispuesto (una vez que hayan pasado dos días) a dar una recompensa de 500 puntos a la mejor respuesta.
Tengo varias preguntas que, una vez respondidas, deberían encajar en una explicación de cómo lograr lo que busco:
En la documentación (hay un ejemplo en el próximo punto) hay muchos XML mintiendo diciéndome que necesito
POST
cosas a varios lugares. ¿Hay una consola en línea para hacer esto? ¿O debo literalmente forzar esto a través de cURL (et al)?¿Cómo creo una identidad de acceso de origen para CloudFront y la enlace a mi distribución? He leído este documento pero, según el primer punto, no sé qué hacer con él. ¿Cómo encaja mi par de llaves en esto?
Una vez hecho esto, ¿cómo puedo limitar el segmento S3 para que solo permita que las personas descarguen cosas a través de esa identidad? Si se trata de otro trabajo XML, en lugar de hacer clic en la interfaz de usuario web, dígame dónde y cómo se supone que debo ingresar esto en mi cuenta.
En Python, ¿cuál es la forma más fácil de generar una URL que expira para un archivo? Tengo el
boto
instalado, pero no veo cómo obtener un archivo de una distribución de transmisión.¿Hay alguna aplicación o script que pueda tomar la dificultad de configurar este atuendo? Uso Ubuntu (Linux) pero tengo XP en una VM si solo es Windows. Ya he analizado CloudBerry S3 Explorer Pro, pero tiene más sentido que la interfaz de usuario en línea.
Tienes razón, se necesita mucho trabajo de API para configurar esto. ¡Espero que lo reciban en AWS Console pronto!
ACTUALIZACIÓN: He enviado este código a boto: a partir del boto v2.1 (lanzado el 2011-10-27), esto se vuelve mucho más fácil. Para boto <2.1, usa las instrucciones aquí. Para el boto 2.1 o superior, obtenga las instrucciones actualizadas en mi blog: http://www.secretmike.com/2011/10/aws-cloudfront-secure-streaming.html Una vez que el boto v2.1 se empaquete con más distros, lo haré actualiza la respuesta aquí.
Para lograr lo que desea, debe seguir los siguientes pasos que detallaré a continuación:
- Crea tu cubo s3 y sube algunos objetos (ya has hecho esto)
- Cree una "Identidad de acceso de origen" en la nube (básicamente una cuenta de AWS para permitir que el cloudfront acceda a su depósito de s3)
- Modifique las ACL en sus objetos para que solo su Identidad de Acceso a Orígenes de Cloudfront pueda leerlos (esto evita que las personas pasen por alto Cloudfront y vayan directamente a s3)
- Cree una distribución en la nube con URL básicas y otra que requiera URL firmadas
- Pruebe que puede descargar objetos de la distribución de la nube básica pero no desde s3 o la distribución de la nube firmada
- Crear un par de claves para firmar URL
- Genera algunas URL usando Python
- Pruebe que las URL firmadas funcionen
1 - Crear cubo y cargar el objeto
La forma más fácil de hacerlo es a través de AWS Console, pero para completar, le mostraré cómo usar boto. El código Boto se muestra aquí:
import boto
#credentials stored in environment AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY
s3 = boto.connect_s3()
#bucket name MUST follow dns guidelines
new_bucket_name = "stream.example.com"
bucket = s3.create_bucket(new_bucket_name)
object_name = "video.mp4"
key = bucket.new_key(object_name)
key.set_contents_from_filename(object_name)
2 - Crear una "Identidad de acceso de origen" en la nube
Por ahora, este paso solo puede realizarse usando la API. El código Boto está aquí:
import boto
#credentials stored in environment AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY
cf = boto.connect_cloudfront()
oai = cf.create_origin_access_identity(comment=''New identity for secure videos'')
#We need the following two values for later steps:
print("Origin Access Identity ID: %s" % oai.id)
print("Origin Access Identity S3CanonicalUserId: %s" % oai.s3_user_id)
3 - Modifica las ACL en tus objetos
Ahora que tenemos nuestra cuenta de usuario S3 especial (el S3CanonicalUserId que creamos anteriormente) necesitamos darle acceso a nuestros objetos s3. Podemos hacerlo fácilmente usando AWS Console abriendo la pestaña Permisos del objeto (¡no del contenedor!), Haga clic en el botón "Agregar más permisos" y pegue el muy largo S3CanonicalUserId que tenemos arriba en el campo "Beneficiario" de un nuevo. Asegúrese de otorgar los nuevos permisos de "Abrir / Descargar" derechos.
También puede hacer esto en código usando la siguiente secuencia de comandos del boto:
import boto
#credentials stored in environment AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY
s3 = boto.connect_s3()
bucket_name = "stream.example.com"
bucket = s3.get_bucket(bucket_name)
object_name = "video.mp4"
key = bucket.get_key(object_name)
#Now add read permission to our new s3 account
s3_canonical_user_id = "<your S3CanonicalUserID from above>"
key.add_user_grant("READ", s3_canonical_user_id)
4 - Crea una distribución en la nube
Tenga en cuenta que los orígenes personalizados y las distribuciones privadas no son totalmente compatibles con boto hasta la versión 2.0, que no se ha publicado formalmente en el momento de la redacción. El siguiente código extrae un código de la rama de boto 2.0 y lo piratea para que funcione, pero no es bonito. La rama 2.0 maneja esto de manera mucho más elegante: ¡definitivamente úsala si es posible!
import boto
from boto.cloudfront.distribution import DistributionConfig
from boto.cloudfront.exception import CloudFrontServerError
import re
def get_domain_from_xml(xml):
results = re.findall("<DomainName>([^<]+)</DomainName>", xml)
return results[0]
#custom class to hack this until boto v2.0 is released
class HackedStreamingDistributionConfig(DistributionConfig):
def __init__(self, connection=None, origin='''', enabled=False,
caller_reference='''', cnames=None, comment='''',
trusted_signers=None):
DistributionConfig.__init__(self, connection=connection,
origin=origin, enabled=enabled,
caller_reference=caller_reference,
cnames=cnames, comment=comment,
trusted_signers=trusted_signers)
#override the to_xml() function
def to_xml(self):
s = ''<?xml version="1.0" encoding="UTF-8"?>/n''
s += ''<StreamingDistributionConfig xmlns="http://cloudfront.amazonaws.com/doc/2010-07-15/">/n''
s += '' <S3Origin>/n''
s += '' <DNSName>%s</DNSName>/n'' % self.origin
if self.origin_access_identity:
val = self.origin_access_identity
s += '' <OriginAccessIdentity>origin-access-identity/cloudfront/%s</OriginAccessIdentity>/n'' % val
s += '' </S3Origin>/n''
s += '' <CallerReference>%s</CallerReference>/n'' % self.caller_reference
for cname in self.cnames:
s += '' <CNAME>%s</CNAME>/n'' % cname
if self.comment:
s += '' <Comment>%s</Comment>/n'' % self.comment
s += '' <Enabled>''
if self.enabled:
s += ''true''
else:
s += ''false''
s += ''</Enabled>/n''
if self.trusted_signers:
s += ''<TrustedSigners>/n''
for signer in self.trusted_signers:
if signer == ''Self'':
s += '' <Self/>/n''
else:
s += '' <AwsAccountNumber>%s</AwsAccountNumber>/n'' % signer
s += ''</TrustedSigners>/n''
if self.logging:
s += ''<Logging>/n''
s += '' <Bucket>%s</Bucket>/n'' % self.logging.bucket
s += '' <Prefix>%s</Prefix>/n'' % self.logging.prefix
s += ''</Logging>/n''
s += ''</StreamingDistributionConfig>/n''
return s
def create(self):
response = self.connection.make_request(''POST'',
''/%s/%s'' % ("2010-11-01", "streaming-distribution"),
{''Content-Type'' : ''text/xml''},
data=self.to_xml())
body = response.read()
if response.status == 201:
return body
else:
raise CloudFrontServerError(response.status, response.reason, body)
cf = boto.connect_cloudfront()
s3_dns_name = "stream.example.com.s3.amazonaws.com"
comment = "example streaming distribution"
oai = "<OAI ID from step 2 above like E23KRHS6GDUF5L>"
#Create a distribution that does NOT need signed URLS
hsd = HackedStreamingDistributionConfig(connection=cf, origin=s3_dns_name, comment=comment, enabled=True)
hsd.origin_access_identity = oai
basic_dist = hsd.create()
print("Distribution with basic URLs: %s" % get_domain_from_xml(basic_dist))
#Create a distribution that DOES need signed URLS
hsd = HackedStreamingDistributionConfig(connection=cf, origin=s3_dns_name, comment=comment, enabled=True)
hsd.origin_access_identity = oai
#Add some required signers (Self means your own account)
hsd.trusted_signers = [''Self'']
signed_dist = hsd.create()
print("Distribution with signed URLs: %s" % get_domain_from_xml(signed_dist))
5 - Prueba de que puedes descargar objetos desde la nube, pero no desde s3
Ahora debería poder verificar:
- stream.example.com.s3.amazonaws.com/video.mp4 - debería dar AccessDenied
- signed_distribution.cloudfront.net/video.mp4 - debería dar MissingKey (porque la URL no está firmada)
- basic_distribution.cloudfront.net/video.mp4 - debería funcionar bien
Las pruebas deberán ajustarse para que funcionen con tu reproductor de transmisión, pero la idea básica es que solo funcione la url básica de la nube.
6 - Crea un par de llaves para CloudFront
Creo que la única forma de hacerlo es a través del sitio 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.
7 - Genera algunas URL en Python
A partir de la versión 2.0 de boto, no parece haber soporte para generar URL firmadas de CloudFront. 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 que no sea de transmisión, debe usar la URL completa de 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(str(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}
#now base64 encode it (must be URL safe)
encoded_policy = aws_url_base64_encode(canned_policy)
#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 = "APKAIAZCZRKVIO4BQ" #from the AWS accounts page
priv_key_file = "cloudfront-pk.pem" #your private keypair file
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)
#Flash player doesn''t like query params so encode them
enc_url = encode_query_param(signed_url)
print(enc_url)
8 - Prueba las URL
Con suerte, ahora debería tener una URL funcional que se parece a esto:
video.mp4%3FExpires%3D1309979985%26Signature%3DMUNF7pw1689FhMeSN6JzQmWNVxcaIE9mk1x~KOudJky7anTuX0oAgL~1GW-ON6Zh5NFLBoocX3fUhmC9FusAHtJUzWyJVZLzYT9iLyoyfWMsm2ylCDBqpy5IynFbi8CUajd~CjYdxZBWpxTsPO3yIFNJI~R2AFpWx8qp3fs38Yw_%26Key-Pair-Id%3DAPKAIAZRKVIO4BQ
Pon esto en tu js y deberías tener algo que se parece a esto (del ejemplo de PHP en la documentación de CloudFront de Amazon):
var so_canned = new SWFObject(''http://location.domname.com/~jvngkhow/player.swf'',''mpl'',''640'',''360'',''9'');
so_canned.addParam(''allowfullscreen'',''true'');
so_canned.addParam(''allowscriptaccess'',''always'');
so_canned.addParam(''wmode'',''opaque'');
so_canned.addVariable(''file'',''video.mp4%3FExpires%3D1309979985%26Signature%3DMUNF7pw1689FhMeSN6JzQmWNVxcaIE9mk1x~KOudJky7anTuX0oAgL~1GW-ON6Zh5NFLBoocX3fUhmC9FusAHtJUzWyJVZLzYT9iLyoyfWMsm2ylCDBqpy5IynFbi8CUajd~CjYdxZBWpxTsPO3yIFNJI~R2AFpWx8qp3fs38Yw_%26Key-Pair-Id%3DAPKAIAZRKVIO4BQ'');
so_canned.addVariable(''streamer'',''rtmp://s3nzpoyjpct.cloudfront.net/cfx/st'');
so_canned.write(''canned'');
Resumen
Como puede ver, ¡no es muy fácil! Boto v2 ayudará mucho a configurar la distribución. ¡Descubriré si también es posible obtener algún código de generación de URL para mejorar esta gran biblioteca!