web-dev-qa-db-ja.com

Pythonを使用した安全なAWS CloudFrontストリーミングの開始

S3バケットを作成し、ビデオをアップロードし、CloudFrontでストリーミング配信を作成しました。静的なHTMLプレーヤーでテストすると動作します。アカウント設定でキーペアを作成しました。現在、秘密鍵ファイルをデスクトップに置いています。それは私がいるところです。

私の目的は、私のDjango/Pythonサイトが安全なURLを作成し、私のページのいずれかからアクセスしない限り、人々がビデオにアクセスできないようにすることです。問題は、Amazonがレイアウトした方法にアレルギーがあり、ますます混乱していることです。

これはStackOverflowでの最良の質問ではないことを理解していますが、安全なCloudFront/S3状況をセットアップする方法から頭または尾を作ることができない唯一の馬鹿ではないことは確かです。私は本当にあなたの助けに感謝し、(2日が経過したら)最高の答えに500ポイントの賞金を喜んで与えます。

いくつかの質問がありますが、回答すると、私が求めていることを達成する方法の1つの説明に当てはまるはずです。

  • ドキュメント(次のポイントに例があります)には、さまざまな場所にPOSTする必要があると言っているXMLがたくさんあります。これを行うためのオンラインコンソールはありますか?それとも文字通りcURL(et al)でこれを強制する必要がありますか?

  • CloudFrontのOrigin Access Identityを作成してディストリビューションにバインドするにはどうすればよいですか?私は このドキュメント を読みましたが、最初のポイントでは、それをどうするかわかりません。キーペアはこれにどのように適合しますか?

  • それが終わったら、S3バケットを制限して、そのIDを介したダウンロードのみを許可する方法を教えてください。これがWeb UIをクリックするのではなく、別のXMLジョブである場合は、これをアカウントに追加する場所と方法を教えてください。

  • Pythonでは、ファイルの有効期限が切れるURLを生成する最も簡単な方法は何ですか。 botoをインストールしましたが、ストリーミング配信からファイルを取得する方法がわかりません。

  • このガーブを設定するのが難しいアプリケーションやスクリプトはありますか?私はUbuntu(Linux)を使用していますが、XP in a VM if it it is only the only only)です。CloudBerryS3 Explorer Proはすでに見ましたが、オンラインUIと同じくらい理にかなっています。

40
Oli

そうです、これを設定するには多くのAPI作業が必要です。 AWSコンソールですぐに表示されることを願っています!

更新:このコードをbotoに送信しました-boto v2.1(2011-10-27リリース)以降、これははるかに簡単です。 boto <2.1の場合は、こちらの手順を使用してください。 boto 2.1以降については、私のブログで最新の説明を入手してください: http://www.secretmike.com/2011/10/aws-cloudfront-secure-streaming.html boto v2.1が取得されるより多くのディストリビューションによってパッケージ化されています。ここで回答を更新します。

あなたが望むものを達成するために、あなたは私が以下に詳述する以下のステップを実行する必要があります:

  1. S3バケットを作成し、いくつかのオブジェクトをアップロードします(これはすでに行っています)。
  2. Cloudfrontの「Origin Access Identity」を作成します(基本的には、Cloudfrontがs3バケットにアクセスできるようにするAWSアカウント)
  3. オブジェクトのACLを変更して、Cloudfront Origin Access Identityのみがそれらを読み取ることができるようにします(これにより、人々がCloudfrontをバイパスしてs3に直接アクセスするのを防ぎます)。
  4. 基本的なURLと署名付きURLを必要とするCloudfrontディストリビューションを作成する
  5. 基本的なcloudfrontディストリビューションからオブジェクトをダウンロードできるが、s3または署名されたcloudfrontディストリビューションからはダウンロードできないことをテストする
  6. URLに署名するためのキーペアを作成する
  7. Pythonを使用していくつかのURLを生成する
  8. 署名付きURLが機能することをテストする

1-バケットを作成し、オブジェクトをアップロードします

これを行う最も簡単な方法はAWSコンソールを使用することですが、完全を期すためにbotoの使用方法を示します。 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の「元のアクセスID」を作成します

現時点では、このステップはAPIを使用してのみ実行できます。 Botoコードはこちらです:

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 Consoleを使用して、オブジェクトの(バケットではなく)[権限]タブを開き、[権限を追加]ボタンをクリックして、上で取得した非常に長いS3CanonicalUserIdを新しい[Grantee]フィールドに貼り付けます。新しい権限に「開く/ダウンロード」権限を付与していることを確認してください。

次の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-cloudfrontディストリビューションを作成します

カスタムオリジンとプライベートディストリビューションは、執筆時点で正式にリリースされていないバージョン2.0まではbotoで完全にはサポートされていません。以下のコードは、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-オブジェクトをcloudfrontからダウンロードできるが、s3からはダウンロードできないことをテストします

これで、次のことを確認できるはずです。

  • stream.example.com.s3.amazonaws.com/video.mp4-AccessDeniedを与える必要があります
  • signed_distribution.cloudfront.net/video.mp4-MissingKeyを提供する必要があります(URLが署名されていないため)
  • basic_distribution.cloudfront.net/video.mp4-正常に動作するはずです

ストリームプレーヤーで機能するようにテストを調整する必要がありますが、基本的な考え方は、基本的なcloudfront urlのみが機能することです。

6-CloudFrontのキーペアを作成します

これを行う唯一の方法は、AmazonのWebサイトを経由することだと思います。 AWSの「アカウント」ページに移動し、「セキュリティ認証情報」リンクをクリックします。 [キーペア]タブをクリックし、[新しいキーペアを作成]をクリックします。これにより、新しい鍵ペアが生成され、秘密鍵ファイル(pk-xxxxxxxxx.pem)が自動的にダウンロードされます。キーファイルを安全かつプライベートに保管します。また、次のステップで必要になるため、Amazonからの「キーペアID」を書き留めておきます。

7-PythonでいくつかのURLを生成します

Botoバージョン2.0の時点では、署名されたCloudFront URLの生成はサポートされていないようです。 Pythonには、標準ライブラリにRSA暗号化ルーチンが含まれていないため、追加のライブラリを使用する必要があります。この例ではM2Cryptoを使用しました。

非ストリーミング配信の場合は、リソースとして完全なクラウドフロントURLを使用する必要がありますが、ストリーミングの場合は、ビデオファイルのオブジェクト名のみを使用します。 5分間しか続かないURLを生成する完全な例については、以下のコードを参照してください。

このコードは、CloudFrontドキュメントでAmazonによって提供されているPHPサンプルコードに大まかに基づいています。

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の例から):

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生成コードをそこに含めることができるかどうかを確認します!

53
secretmike

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)
3
kmonsoor