开始使用Python进行安全AWS CloudFrontstream式传输
我创build了一个S3存储桶,上传了一个video,在CloudFront中创build了一个stream式发布。 用一个静态的HTML播放器testing它,它工作。 我通过帐户设置创build了一个密钥对。 我现在在桌面上有私钥文件。 那是我的地方。
我的目标是达到Django / Python站点创build安全URL的地步,除非他们来自我的一个页面,否则人们无法访问video。 问题是我对亚马逊已经布置的东西过敏,我越来越困惑。
我意识到这不会是StackOverflow上最好的问题,但是我确信我不能成为这里唯一无法摆脱如何设置安全的CloudFront / S3情况的笨蛋。 我真的很感激你的帮助,并愿意(一旦两天过去了)给予500分的赏金最好的答案。
我有几个问题,一旦回答,应该适合一个解释如何完成我所追求的:
-
在文档中(下面有一个例子),有大量的XML在告诉我需要将东西发布到不同的地方。 有没有一个在线控制台这样做? 或者我真的不得不通过cURL(et al)强制这个?
-
如何为CloudFront创build一个Origin Access身份并将其绑定到我的发行版? 我读过这个文件,但是,第一点,不知道该怎么办。 我的钥匙匹配如何?
-
一旦完成了,我如何限制S3存储桶只允许人们通过这个身份下载东西? 如果这是另一个XML jobby,而不是点击Web UI,请告诉我应该在哪里以及如何将这些内容存入我的帐户。
-
在Python中,为文件生成到期URL的最简单方法是什么? 我有
boto
安装,但我不知道如何从stream分配文件。 -
是否有任何应用程序或脚本,可以采取困难的设置这件衣服? 我使用的是Ubuntu(Linux),但是如果是Windows,我只能在虚拟机上安装XP。 我已经看过CloudBerry S3 Explorer Pro,但它和在线用户界面差不多。
你是对的,需要很多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被更多发行版打包,我会在这里更新答案。
要完成你想要的,你需要执行下面的步骤,我将在下面详细说明:
- 创build你的s3桶并上传一些对象(你已经这样做了)
- 创build一个Cloudfront“Origin Access Identity”(基本上是一个AWS账户,允许cloudfront访问您的s3存储桶)
- 修改对象上的ACL,以便只允许您的Cloudfront Origin访问标识读取它们(这样可以避免人们绕过Cloudfront并直接进入s3)
- 使用基本URL创build一个cloudfront发行版,并且需要签名的URL
- testing您可以从基本的cloudfront发行版下载对象,但不能从s3或签名的cloudfront发行版下载对象
- 创build一个用于签名URL的密钥对
- 使用Python生成一些URL
- testing签名的URL是否有效
1 – 创build桶和上传对象
最简单的方法是通过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 – 创build一个Cloudfront“原始访问标识”
目前,这一步只能使用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用户帐户(我们上面创build的S3CanonicalUserId),我们需要给它访问我们的s3对象。 我们可以通过打开对象的(不是存储桶的)权限选项卡,点击“添加更多权限”button,并将我们上面得到的很长的S3CanonicalUserId粘贴到新的“支持者”字段,轻松地使用AWS控制台来实现。 确保你给新的权限“打开/下载”权限。
您也可以使用以下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 – 创build一个云端分布
请注意,自定义起源和私人发行版直到版本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 – testing你可以从cloudfront而不是从s3下载对象
您现在应该可以validation:
- stream.example.com.s3.amazonaws.com/video.mp4 – 应该给AccessDenied
- signed_distribution.cloudfront.net/video.mp4 – 应该给MissingKey(因为URL没有签名)
- basic_distribution.cloudfront.net/video.mp4 – 应该可以正常工作
testing将不得不进行调整,以适应您的stream媒体播放器,但基本的想法是,只有基本的云端url应该工作。
6 – 为CloudFront创build一个密钥对
我认为唯一的方法就是通过亚马逊的网站。 进入您的AWS“帐户”页面并点击“安全证书”链接。 点击“Key Pairs”标签,然后点击“Create a New Key Pair”。 这将为您生成一个新的密钥对,并自动下载一个私钥文件(pk-xxxxxxxxx.pem)。 保持密钥文件安全和私密。 同时记下来自亚马逊的“Key Pair ID”,因为我们将在下一步中使用它。
7 – 在Python中生成一些URL
从boto 2.0版开始,似乎没有任何支持生成签署的CloudFront URL。 Python不包括标准库中的RSAencryption例程,所以我们将不得不使用额外的库。 在这个例子中我使用了M2Crypto。
对于非stream媒体分发,您必须使用完整的云端URL作为资源,但对于stream媒体,我们只使用video文件的对象名称。 请参阅下面的代码以获取生成仅持续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中,你应该看起来像这样(从Amazon的CloudFront文档中的PHP示例):
var so_canned = new SWFObject('~jvngkhow/player.html','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');
概要
正如你所看到的,不是很容易! 博托V2将有助于build立分配很多。 我会发现是否有可能在那里得到一些URL生成代码以及改善这个伟大的图书馆!
在Python中,为文件生成到期URL的最简单方法是什么? 我有boto安装,但我不知道如何从stream分配文件。
您可以为资源生成到期的signed-URL。 Boto3文档有一个很好的示例解决scheme :
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)