amazons3-使用Python开始使用安全的AWS CloudFront流媒体



我已经创建了一个S3 bucket,上传了一个视频,在CloudFront中创建了流媒体分发。用一个静态HTML播放器测试了它,它工作正常。我已经通过帐户设置创建了一个密钥对。现在我的桌面上有一个私钥文件。那就是我所在的地方。

我的目标是让我的Django/Python网站创建安全的URL,除非视频来自我的某个页面,否则人们无法访问这些视频。问题是我对亚马逊的布局方式过敏,我只是越来越困惑。

我意识到这不会是StackOverflow上最好的问题,但我确信我不会是这里唯一一个对如何设置安全的CloudFront/S3情况一无所知的傻瓜。我真的很感谢你的帮助,并愿意(两天后)悬赏500英镑给最好的答案。

我有几个问题,一旦得到回答,就应该成为如何完成我追求的目标的一个解释:

  • 在文档中(下一点有一个例子),有很多XML告诉我需要将D_1的东西POST到各个地方。有在线控制台吗?或者我真的必须通过cURL(等)强制执行吗?

  • 如何为CloudFront创建Origin Access Identity并将其绑定到我的分发版?我读过这份文件,但根据第一点,我不知道该怎么办。我的键盘是如何适应的?

  • 一旦完成,我如何将S3存储桶限制为只允许人们通过该身份下载东西?如果这是另一个XML作业,而不是点击web UI,请告诉我应该在哪里以及如何将其输入我的帐户。

  • 在Python中,为文件生成过期URL的最简单方法是什么。我安装了boto,但我不知道如何从流分发中获取文件。

  • 有没有任何应用程序或脚本可以克服设置这种混乱的困难?我使用Ubuntu(Linux),但如果只是Windows,我在虚拟机中有XP。我已经看过CloudBerry S3 Explorer Pro,但它和在线UI一样有意义。

你说得对,要设置这个需要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 bucket并上传一些对象(您已经完成了)
  2. 创建一个Cloudfront"Origin Access Identity"(基本上是一个AWS帐户,允许Cloudfront访问您的s3 bucket)
  3. 修改对象上的ACL,以便只允许您的Cloudfront Origin Access Identity读取它们(这可以防止人们绕过Cloudfront直接进入s3)
  4. 创建一个包含基本URL和需要签名URL的cloudfront分发
  5. 测试您是否可以从基本的cloudfront发行版下载对象,但不能从s3或签名的cloudfront发行版下载
  6. 创建用于对URL进行签名的密钥对
  7. 使用Python生成一些URL
  8. 测试已签名的URL是否有效

1-创建Bucket并上传对象

最简单的方法是通过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"Origin Access Identity">

目前,只能使用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控制台轻松做到这一点,方法是打开对象的(而不是bucket的!)权限选项卡,单击"添加更多权限"按钮,并将我们上面得到的很长的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-创建云锋分布

请注意,直到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创建一个密钥对

我认为唯一的方法是通过亚马逊的网站。进入您的AWS"帐户"页面,点击"安全凭据"链接。单击"密钥对"选项卡,然后单击"创建新密钥对"。这将为您生成一个新的密钥对,并自动下载一个私钥文件(pk-xxxxxxxxxx.pem)。确保密钥文件的安全和私有。还要记下亚马逊的"密钥对ID",因为我们在下一步需要它。

7-在Python中生成一些URL

从boto 2.0版本开始,似乎不支持生成签名的CloudFront URL。Python在标准库中不包括RSA加密例程,因此我们必须使用一个额外的库。我在这个例子中使用了M2Crypto。

对于非流媒体分发,您必须使用完整的cloudfront URL作为资源,但是对于流媒体,我们只使用视频文件的对象名。有关生成仅持续5分钟的URL的完整示例,请参阅下面的代码。

此代码松散地基于Amazon在CloudFront文档中提供的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中,你应该会有这样的东西(来自亚马逊CloudFront文档中的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');

摘要

正如你所看到的,不是很容易!botov2将在很大程度上帮助设置分发。我会发现是否有可能在那里获得一些URL生成代码,以改进这个伟大的库!

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

最新更新