Vous avez raison, il faut beaucoup de travail sur l'API pour mettre cela en place. J'espère qu'elle sera bientôt disponible dans la console AWS !
MISE À JOUR : J'ai soumis ce code à boto - à partir de boto v2.1 (publié le 2011-10-27), cela devient beaucoup plus facile. Pour boto < 2.1, utilisez les instructions ici. Pour boto 2.1 ou plus, utilisez les instructions mises à jour sur mon blog : http://www.secretmike.com/2011/10/aws-cloudfront-secure-streaming.html Une fois que boto v2.1 sera empaqueté par plus de distros, je mettrai à jour la réponse ici.
Pour réaliser ce que vous voulez, vous devez suivre les étapes suivantes que je vais détailler ci-dessous :
- Créez votre seau s3 et téléchargez des objets (vous l'avez déjà fait).
- Créez une "identité d'accès d'origine" Cloudfront (en fait, un compte AWS pour permettre à Cloudfront d'accéder à votre seau s3).
- Modifiez les ACL sur vos objets afin que seule votre identité d'accès d'origine Cloudfront soit autorisée à les lire (cela empêche les gens de contourner Cloudfront et d'aller directement sur s3).
- Créez une distribution cloudfront avec des URLs basiques et une autre qui requiert des URLs signées.
- Testez que vous pouvez télécharger des objets à partir de la distribution cloudfront de base mais pas à partir de s3 ou de la distribution cloudfront signée.
- Créer une paire de clés pour signer les URLs
- Générer des URL à l'aide de Python
- Testez que les URLs signés fonctionnent
1 - Créer un seau et télécharger un objet
La façon la plus simple de le faire est de passer par la console AWS, mais pour être complet, je vais vous montrer comment utiliser boto. Le code de boto est montré ici :
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 - Créer une "identité d'accès d'origine" Cloudfront
Pour l'instant, cette étape ne peut être réalisée qu'en utilisant l'API. Le code Boto est ici :
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 - Modifier les ACLs sur vos objets
Maintenant que nous avons notre compte utilisateur S3 spécial (le S3CanonicalUserId que nous avons créé ci-dessus), nous devons lui donner accès à nos objets s3. Nous pouvons le faire facilement à l'aide de la console AWS en ouvrant l'onglet Permissions de l'objet (pas du seau !), en cliquant sur le bouton "Add more permissions", et en collant le très long S3CanonicalUserId que nous avons obtenu ci-dessus dans le champ "Grantee" d'une nouvelle permission. Assurez-vous que vous donnez à la nouvelle permission les droits "Open/Download".
Vous pouvez également le faire en code en utilisant le boto suivant script :
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 - Créer une distribution cloudfront
Notez que les origines personnalisées et les distributions privées ne sont pas entièrement prises en charge par boto avant la version 2.0 qui n'a pas été officiellement publiée au moment de l'écriture. Le code ci-dessous extrait du code de la branche 2.0 de boto et le modifie pour le faire fonctionner, mais ce n'est pas joli. La branche 2.0 gère cela de manière beaucoup plus élégante - utilisez-la si possible !
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 - Testez que vous pouvez télécharger des objets depuis cloudfront mais pas depuis s3
Vous devriez maintenant être en mesure de vérifier :
- stream.example.com.s3.amazonaws.com/video.mp4 - devrait donner AccessDenied
- signed_distribution.cloudfront.net/video.mp4 - devrait donner MissingKey (parce que l'URL n'est pas signé)
- basic_distribution.cloudfront.net/video.mp4 - devrait fonctionner correctement
Les tests devront être adaptés pour fonctionner avec votre lecteur de flux, mais l'idée de base est que seule l'url de base de Cloudfront devrait fonctionner.
6 - Créer une paire de clés pour CloudFront
Je pense que le seul moyen de le faire est de passer par le site Web d'Amazon. Allez sur la page "Compte" de votre AWS et cliquez sur le lien "Security Credentials". Cliquez sur l'onglet "Key Pairs" puis sur "Create a New Key Pair". Cela va générer une nouvelle paire de clés pour vous et télécharger automatiquement un fichier de clé privée (pk-xxxxxxxxx.pem). Conservez le fichier de clé en lieu sûr et privé. Notez également l'"ID de la paire de clés" d'Amazon, car nous en aurons besoin à l'étape suivante.
7 - Générer quelques URLs en Python
À partir de la version 2.0 de boto, il ne semble pas y avoir de support pour générer des URL signées de CloudFront. Python n'inclut pas de routines de chiffrement RSA dans la bibliothèque standard, nous devrons donc utiliser une bibliothèque supplémentaire. J'ai utilisé M2Crypto dans cet exemple.
Pour une distribution non-streaming, vous devez utiliser l'URL complète de cloudfront comme ressource, cependant pour le streaming nous utilisons seulement le nom d'objet du fichier vidéo. Voir le code ci-dessous pour un exemple complet de génération d'une URL qui ne dure que 5 minutes.
Ce code est librement basé sur le code d'exemple PHP fourni par Amazon dans la documentation 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 - Essayez les URLs
Avec un peu de chance, vous devriez maintenant avoir une URL fonctionnelle qui ressemble à quelque chose comme ceci :
video.mp4%3FExpires%3D1309979985%26Signature%3DMUNF7pw1689FhMeSN6JzQmWNVxcaIE9mk1x~KOudJky7anTuX0oAgL~1GW-ON6Zh5NFLBoocX3fUhmC9FusAHtJUzWyJVZLzYT9iLyoyfWMsm2ylCDBqpy5IynFbi8CUajd~CjYdxZBWpxTsPO3yIFNJI~R2AFpWx8qp3fs38Yw_%26Key-Pair-Id%3DAPKAIAZRKVIO4BQ
Insérez ceci dans votre js et vous devriez avoir quelque chose qui ressemble à ceci (tiré de l'exemple PHP dans la documentation CloudFront d'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');
Résumé
Comme vous pouvez le voir, pas très facile ! boto v2 aidera beaucoup à mettre en place la distribution. Je vais voir s'il est possible d'y inclure du code de génération d'URL pour améliorer cette grande bibliothèque !