Начало работы с безопасной потоковой передачей AWS CloudFront с помощью Python - PullRequest
39 голосов
/ 01 июля 2011

Я создал корзину S3, загрузил видео, создал потоковый дистрибутив в CloudFront.Протестировал его с помощью статического HTML-плеера, и он работает.Я создал пару ключей через настройки учетной записи.В данный момент у меня на рабочем столе находится файл с закрытым ключом.Вот где я.

Моя цель - добраться до точки, где мой сайт Django / Python создает безопасные URL-адреса, и люди не могут получить доступ к видео, если они не пришли с одной из моих страниц.Проблема в том, что у меня аллергия на то, как Amazon выкладывает вещи, и я все больше и больше путаюсь.

Я понимаю, что это не самый лучший вопрос по StackOverflow, но яуверен, что я не могу быть единственным дураком, который не может придумать, как настроить безопасную ситуацию CloudFront / S3.Я был бы очень признателен за вашу помощь и готов (по прошествии двух дней) дать 500pt вознаграждение за лучший ответ.

У меня есть несколько вопросов, на которые после ответа следует вписать одно объяснение того, как выполнить то, чтоЯ за:

  • В документации (есть пример в следующем пункте) много XML лежит, говоря мне, что мне нужно POST вещи в разных местах.Есть ли онлайн-консоль для этого?Или я буквально должен форсировать это через cURL (и др.)?

  • Как мне создать Origin Access Identity для CloudFront и связать его с моим дистрибутивом?Я прочитал этот документ , но, во-первых, не знаю, что с ним делать.Как моя пара ключей вписывается в это?

  • Как только это будет сделано, как я могу ограничить корзину S3, чтобы позволить людям загружать вещи только через эту личность?Если это другое задание по XML, а не щелкать по веб-интерфейсу, скажите, где и как я должен получить это в свой аккаунт.

  • В Python, какой самый простой способсоздания устаревшего URL для файла.У меня установлено boto, но я не вижу, как получить файл из потокового дистрибутива.

  • Существуют ли какие-либо приложения или сценарии, которые могут затруднить настройку этой одежды?вверх?Я использую Ubuntu (Linux), но у меня есть XP на виртуальной машине, если она только для Windows.Я уже смотрел на CloudBerry S3 Explorer Pro - но он имеет такой же смысл, как и онлайн-интерфейс.

Ответы [ 2 ]

53 голосов
/ 06 июля 2011

Вы правы, для настройки требуется много API.Я надеюсь, что они скоро получат его в консоли AWS!

ОБНОВЛЕНИЕ: я отправил этот код в boto - начиная с boto v2.1 (выпущен 2011-10-27), это становится намного проще.Для boto <2.1 используйте инструкции здесь.Для boto 2.1 или выше, получите обновленные инструкции в моем блоге: <a href="http://www.secretmike.com/2011/10/aws-cloudfront-secure-streaming.html" rel="noreferrer">http://www.secretmike.com/2011/10/aws-cloudfront-secure-streaming.html После того, как boto v2.1 будет упакован большим количеством дистрибутивов, я обновлю ответ здесь.

Для чеговы хотите выполнить следующие шаги, которые я подробно опишу ниже:

  1. Создайте корзину s3 и загрузите некоторые объекты (вы уже сделали это)
  2. Создать Cloudfront "«Origin Access Identity» (в основном это учетная запись AWS, позволяющая облачному фронту получить доступ к вашей корзине s3)
  3. Измените ACL на ваших объектах так, чтобы только ваша Cloudfront Origin Access Identity могла их читать (это мешает людям обходить Cloudfrontи перейдем непосредственно к s3)
  4. Создание дистрибутива облачного фронта с базовыми URL-адресами, в котором требуются подписанные URL-адреса
  5. Проверка того, что вы можете загружать объекты из базового дистрибутива облачного фронта, но не из s3 или подписанного дистрибутива облачного фронта
  6. Создание пары ключей для подписи URL-адресов
  7. Создание некоторых URL-адресов с помощью Python
  8. Проверка работоспособности подписанных URL-адресов

1 - Создать Bucket и загрузить объект

Самый простой способ сделать эточерез консоль AWS, но для полноты картины покажу, как использовать boto.Бото-код показан здесь:

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 - создайте Cloudfront «Origin Access Identity»

На данный момент этот шаг можно выполнить только с помощью API.Бото-код здесь:

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 - Изменить ACL на ваших объектах

Теперь, когда у нас есть специальная учетная запись пользователя S3 (созданный нами S3CanonicalUserIdвыше) нам нужно дать ему доступ к нашим объектам s3.Это можно легко сделать с помощью консоли AWS, открыв вкладку разрешений объекта (а не корзины!), Нажав кнопку «Добавить дополнительные разрешения», и вставив очень длинный S3CanonicalUserId, который мы получили выше, в поле «Получатель» нового объекта.Убедитесь, что вы предоставили новому разрешению «Открыть / Загрузить» права.

Вы также можете сделать это в коде, используя следующий скрипт 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 - Создание облачного фронтадистрибутив

Обратите внимание, что пользовательские источники и частные дистрибутивы не полностью поддерживаются в boto до версии 2.0, которая официально не была выпущена на момент написания.Приведенный ниже код извлекает некоторый код из ветки boto 2.0 и собирает его вместе, чтобы запустить, но это не красиво.Ветвь 2.0 справляется с этим гораздо более элегантно - определенно используйте это, если возможно!

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 - Проверьте, что вы можете загружать объекты из облачного фронта, но не из s3

Теперь вы должны быть в состоянии проверить:

  • stream.example.com.s3.amazonaws.com / video.mp4 - должен предоставить AccessDenied
  • signature_distribution.cloudfront.net / video.mp4 - должен дать MissingKey (потому что URL не подписан)
  • basic_distribution.cloudfront.net / video.mp4 - должен нормально работать

Тесты должны быть настроены для работыс вашим потоковым плеером, но основная идея заключается в том, что должен работать только базовый URL-адрес облачного фронта.

6 - Создать пару ключей для CloudFront

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

7 - сгенерируйте несколько 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(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 - опробуйте URL-адреса

Надеюсь, теперь у вас должен быть рабочий URL-адрес, который выглядит примерно так:

video.mp4%3FExpires%3D1309979985%26Signature%3DMUNF7pw1689FhMeSN6JzQmWNVxcaIE9mk1x~KOudJky7anTuX0oAgL~1GW-ON6Zh5NFLBoocX3fUhmC9FusAHtJUzWyJVZLzYT9iLyoyfWMsm2ylCDBqpy5IynFbi8CUajd~CjYdxZBWpxTsPO3yIFNJI~R2AFpWx8qp3fs38Yw_%26Key-Pair-Id%3DAPKAIAZRKVIO4BQ

Вставьте это в свой js, и выдолжно иметь что-то похожее на это (из примера PHP в документации Amazon CloudFront):

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');

Сводка

Как видите, не оченьлегко!Boto v2 очень поможет в настройке дистрибутива.Я выясню, возможно ли добавить туда код генерации URL и улучшить эту великолепную библиотеку!

3 голосов
/ 15 марта 2016

В Python, какой самый простой способ создания устаревшего URL для файла.У меня установлено boto, но я не вижу, как получить файл из потокового дистрибутива.

Вы можете создать истекающий подписанный URL для ресурса.В документации Boto3 есть хороший пример решения для этого:

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)
...