Pierwsze kroki z secure AWS CloudFront streaming with Python

Stworzyłem wiadro S3, przesłałem wideo, stworzyłem dystrybucję strumieniową w CloudFront. Przetestowałem go za pomocą statycznego odtwarzacza HTML i działa. W ustawieniach konta utworzyłem keypair. Mam w tej chwili plik klucza prywatnego na pulpicie. Tam właśnie jestem.

Moim celem jest dotarcie do punktu, w którym Moja strona Django/Python tworzy bezpieczne adresy URL, a ludzie nie mogą uzyskać dostępu do filmów, chyba że pochodzą z jednej z moich stron. Problem w tym, że mam alergię na sposób Amazonka już wszystko wyjaśniła i jestem coraz bardziej zdezorientowana.

Zdaję sobie sprawę, że to nie będzie najlepsze pytanie na temat StackOverflow, ale jestem pewien, że nie mogę być jedynym głupcem tutaj, który nie może się zorientować, jak skonfigurować bezpieczną sytuację CloudFront / S3. Byłbym naprawdę wdzięczny za pomoc i jestem gotów (po upływie dwóch dni) dać nagrodę 500pt za najlepszą odpowiedź.

Mam kilka pytań, które po udzieleniu odpowiedzi powinny pasować do jednego wyjaśnienia jak osiągnąć to, czego szukam:

  • W dokumentacji (jest przykład w następnym punkcie) jest wiele XML leżących wokół mówi mi, że muszę POST rzeczy do różnych miejsc. Czy jest do tego konsola online? A może dosłownie muszę to wymusić poprzez cURL(et al)?

  • Jak utworzyć tożsamość Origin Access dla CloudFront i powiązać ją z moją dystrybucją? Przeczytałem ten dokument ale w pierwszym punkcie Nie wiem co zrobić z nim. Jak do tego pasuje moje urządzenie keypair?

  • Kiedy to zrobisz, jak ograniczyć wiadro S3, aby umożliwić ludziom pobieranie rzeczy tylko za pośrednictwem tej tożsamości? Jeśli jest to kolejny XML jobby zamiast klikać w web UI, proszę mi powiedzieć, gdzie i jak mam to dostać na moje konto.

  • W Pythonie, jaki jest najprostszy sposób generowania wygasającego adresu URL dla pliku. Mam zainstalowany boto ale nie widzę jak pobrać plik ze streamingu Dystrybucja.

  • Czy są jakieś aplikacje lub skrypty, które mogą podjąć trudność ustawienia tego stroju? Używam Ubuntu (Linux), ale mam XP w VM, jeśli to tylko Windows. Już patrzyłem na CloudBerry S3 Explorer Pro-ale to ma tyle sensu, co interfejs online.

Author: Oli, 2011-07-01

2 answers

Masz rację, potrzeba dużo pracy z API, aby to skonfigurować. Mam nadzieję, że wkrótce trafią do konsoli AWS!

UPDATE: złożyłem ten kod do boto - od boto v2. 1 (wydany 2011-10-27) robi się to znacznie łatwiejsze. W przypadku boto http://www.secretmike.com/2011/10/aws-cloudfront-secure-streaming.html gdy boto v2. 1 zostanie spakowane przez kolejne DISTRO zaktualizuję odpowiedź proszę.

Aby osiągnąć to, co chcesz, musisz wykonać następujące kroki, które wyszczególnię poniżej:

    [18]} Utwórz wiadro s3 i prześlij kilka obiektów (już to zrobiłeś) CloudFront to usługa, która umożliwia dostęp do platformy S3 w chmurze za pomocą aplikacji Origin Access Identity (w zasadzie konta AWS).]}
  1. zmodyfikuj ACL na obiektach, aby tylko tożsamość CloudFront Origin Access mogła je odczytywać (zapobiega to omijaniu Cloudfront i przejście bezpośrednio na s3)
  2. Tworzenie dystrybucji cloudfront z podstawowymi adresami URL i taką, która wymaga podpisanych adresów URL]}
  3. Test, że można pobierać obiekty z podstawowej dystrybucji cloudfront, ale nie z s3 lub podpisanej dystrybucji cloudfront
  4. tworzenie pary kluczy do podpisywania adresów URL
  5. generowanie niektórych adresów URL za pomocą Pythona
  6. Test działania podpisanych adresów URL

1 - Utwórz Bucket i prześlij obiekt

The easiest można to zrobić przez konsolę AWS, ale dla kompletności pokażę jak używać boto. Kod Boto jest pokazany tutaj:

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 - Tworzenie Cloudfront "Origin Access Identity"

Na razie ten krok można wykonać tylko przy użyciu API. Kod Boto jest tutaj:

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 - Modyfikuj ACL na swoich obiektach

Teraz, gdy mamy nasze specjalne konto użytkownika S3 (s3canonicaluserid, które stworzyliśmy powyżej), musimy dać mu dostęp do naszych obiektów S3. Możemy zrób to łatwo za pomocą konsoli AWS, otwierając obiekt (nie wiadro!) Zakładka uprawnienia, kliknij przycisk" Dodaj więcej uprawnień "i wklejając bardzo długi S3CanonicalUserId, który otrzymaliśmy powyżej, do pola" Beneficjent " nowego. Upewnij się, że dajesz nowe uprawnienia "Otwórz / Pobierz" prawa.

Można to również zrobić w kodzie za pomocą następującego skryptu 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 - Tworzenie dystrybucji cloudfront

Zauważ, że własne pochodzenie i prywatne dystrybucje nie są w pełni obsługiwane w boto aż do wersji 2.0, która nie została oficjalnie wydana w momencie pisania. Poniższy kod wyciąga trochę kodu z gałęzi boto 2.0 i hakuje go, aby go uruchomić, ale nie jest ładny. Gałąź 2.0 radzi sobie o wiele bardziej elegancko - zdecydowanie użyj tego, jeśli to możliwe!

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 - Test, że można pobierać obiekty z cloudfront, ale nie z s3

Powinieneś teraz być w stanie verify:

  • stream.example.com.s3.amazonaws.com/video.mp4 -powinno dać dostęp
  • signed_distribution.cloudfront.net/video.mp4 - powinno dać MissingKey (ponieważ adres URL nie jest podpisany)
  • basic_distribution.cloudfront.net/video.mp4 -powinno działać dobrze

Testy będą musiały zostać dostosowane do pracy z odtwarzaczem stream, ale podstawowa idea jest taka, że powinien działać tylko podstawowy adres URL cloudfront.

6 - Tworzenie klawiatury dla CloudFront

Myślę, że jedynym sposobem, aby to zrobić, jest strona internetowa Amazon. Przejdź do strony "konto" AWS i kliknij link "poświadczenia bezpieczeństwa". Kliknij kartę "pary kluczy", a następnie kliknij "Utwórz nową parę kluczy". Spowoduje to wygenerowanie nowej pary kluczy i automatyczne pobranie pliku klucza prywatnego (PK-xxxxxxxxx.pem). Zachowaj plik klucza bezpieczny i prywatny. Zanotuj również "identyfikator pary kluczy" od amazon, ponieważ będziemy go potrzebować w następnym kroku.

7 - Wygeneruj trochę Adresy URL w Pythonie

Od wersji boto 2.0 nie ma wsparcia dla generowania podpisanych adresów URL CloudFront. Python nie zawiera procedur szyfrowania RSA w bibliotece standardowej, więc będziemy musieli użyć dodatkowej biblioteki. Użyłem M2Crypto w tym przykładzie.

Dla dystrybucji nie strumieniowej, musisz użyć pełnego adresu URL cloudfront jako zasobu, jednak do przesyłania strumieniowego używamy tylko nazwy obiektu pliku wideo. Zobacz poniższy kod, aby uzyskać pełny przykład generowanie adresu URL, który trwa tylko 5 minut.

Ten kod jest luźno oparty na przykładowym kodzie PHP dostarczonym przez Amazon w dokumentacji 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 - Wypróbuj adresy URL

Mam nadzieję, że teraz powinieneś mieć działający adres URL, który wygląda mniej więcej tak:

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

Włóż to do swojego js i powinieneś mieć coś, co wygląda tak (z przykładu PHP w CloudFront Amazona dokumentacja):

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

Podsumowanie

Jak widzisz, nie jest to łatwe! boto v2 bardzo pomoże w konfiguracji dystrybucji. Dowiem się, czy możliwe jest uzyskanie kodu generującego URL, aby ulepszyć tę wspaniałą bibliotekę!

 53
Author: secretmike,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-12-08 01:47:07

W Pythonie, jaki jest najprostszy sposób generowania wygasającego adresu URL dla pliku. Mam zainstalowany boto, ale nie widzę, jak pobrać plik z dystrybucji strumieniowej.

Możesz wygenerować wygasający podpisany adres URL dla zasobu. Dokumentacja Boto3 ma ładne przykładowe rozwiązanie do tego:

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)
 3
Author: kmonsoor,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-03-15 12:11:22