简短版本:如何“按需”制作签名网址,以使用Python模仿Nginx的X-Accel-Redirect行为(即保护下载)与Amazon CloudFront / S3。
我有一台Django服务器并运行Nginx前端。我一直受到了对它的请求的打击,最近不得不将其安装为Tornado WSGI应用程序,以防止它在FastCGI模式下崩溃。
现在我遇到了一个问题,我的服务器陷入困境(因为它的大部分带宽正在用完),因为有太多的媒体请求,我一直在研究CDN,我相信亚马逊CloudFront / S3对我来说是合适的解决方案。
我一直在使用Nginx的X-Accel-Redirect标头来保护文件免受未经授权的下载,但我没有使用CloudFront / S3的功能 - 但是它们确实提供了签名的URL。到目前为止,我不是Python专家,并且肯定不知道如何正确创建签名URL,所以我希望有人可以获得如何“按需”制作这些URL的链接,或者愿意解释如何非常感谢。
另外,这是正确的解决方案吗?我不太熟悉CDN,是否有更适合这种情况的CDN?
答案 0 :(得分:31)
Amazon CloudFront Signed URLs的工作方式与Amazon S3签名的网址不同。 CloudFront使用基于单独CloudFront密钥对的RSA签名,您必须在Amazon帐户凭据页面中设置该密钥对。以下是使用M2Crypto库在Python中实际生成限时URL的一些代码:
为CloudFront创建密钥对
我认为这样做的唯一方法是通过亚马逊的网站。进入您的AWS“帐户”页面,然后单击“安全凭据”链接。单击“密钥对”选项卡,然后单击“创建新密钥对”。这将为您生成一个新的密钥对,并自动下载私钥文件(pk-xxxxxxxxx.pem)。保持密钥文件安全和私密。另请注意亚马逊的“密钥对ID”,因为我们将在下一步中使用它。
在Python中生成一些网址
从boto版本2.0开始,似乎没有任何支持来生成签名的CloudFront URL。 Python在标准库中不包含RSA加密例程,因此我们必须使用其他库。我在这个例子中使用过M2Crypto。
对于非流式分发,您必须使用完整的云端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(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}
#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 = "APKAIAZVIO4BQ" #from the AWS accounts CloudFront tab
priv_key_file = "cloudfront-pk.pem" #your private keypair file
# Use the FULL URL for non-streaming:
resource = "http://34254534.cloudfront.net/video.mp4"
#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)
print(signed_url)
#Flash player doesn't like query params so encode them if you're using a streaming distribution
#enc_url = encode_query_param(signed_url)
#print(enc_url)
确保使用设置为持有密钥对的帐户的TrustedSigners参数设置您的分配(如果是您自己的帐户,则设置为“自我”)
请参阅Getting started with secure AWS CloudFront streaming with Python以获取有关使用Python进行流式传输的完整工作示例
答案 1 :(得分:21)
此功能现在是already supported in Botocore,它是Boto3, the latest official AWS SDK for Python的基础库。 (以下示例需要安装rsa软件包,但您也可以使用其他RSA软件包,只需定义自己的"规范化RSA签名者"。)
用法如下:
from botocore.signers import CloudFrontSigner
# First you create a cloudfront signer based on a normalized RSA signer::
import rsa
def rsa_signer(message):
private_key = open('private_key.pem', 'r').read()
return rsa.sign(
message,
rsa.PrivateKey.load_pkcs1(private_key.encode('utf8')),
'SHA-1') # CloudFront requires SHA-1 hash
cf_signer = CloudFrontSigner(key_id, rsa_signer)
# To sign with a canned policy::
signed_url = cf_signer.generate_presigned_url(
url, date_less_than=datetime(2015, 12, 1))
# To sign with a custom policy::
signed_url = cf_signer.generate_presigned_url(url, policy=my_policy)
免责声明:我是该公关的作者。
答案 2 :(得分:14)
正如许多人已经评论过的那样,initially accepted answer实际上并不适用于Amazon CloudFront,因为Serving Private Content through CloudFront要求使用专用CloudFront Signed URLs - 因此secretmike's answer一直是正确的,但在他自己花时间和Added support for generating signed URLs for CloudFront之后它同时过时了(非常感谢!)。
boto现在支持专用的create_signed_url方法,之前的二进制依赖关系M2Crypto最近也替换为pure-Python RSA implementation,请参阅Don't use M2Crypto for cloudfront URL signing。
越来越常见,人们可以在相关的单元测试中找到一个或多个良好的用法示例(请参阅test_signed_urls.py),例如test_canned_policy(self) - 请参阅setUp(self)了解引用的变量{{ 1}}和self.pk_id
(显然你需要自己的密钥):
self.pk_str
答案 3 :(得分:0)
secretmike的答案有效,但最好使用rsa
代替M2Crypto
。
我使用了使用rsa
的{{3}}。
import boto
from boto.cloudfront import CloudFrontConnection
from boto.cloudfront.distribution import Distribution
expire_time = int(time.time() +3000)
conn = CloudFrontConnection('ACCESS_KEY_ID', 'SECRET_ACCESS_KEY')
##enter the id or domain name to select a distribution
distribution = Distribution(connection=conn, config=None, domain_name='', id='', last_modified_time=None, status='')
signed_url = distribution.create_signed_url(url='YOUR_URL', keypair_id='YOUR_KEYPAIR_ID_example-APKAIAZVIO4BQ',expire_time=expire_time,private_key_file="YOUR_PRIVATE_KEY_FILE_LOCATION")
使用boto
答案 4 :(得分:0)
这是我用于创建策略的内容,以便我可以访问具有相同“签名”的多个文件:
import json
import rsa
import time
from base64 import b64encode
url = "http://your_domain/*"
expires = int(time.time() + 3600)
pem = """-----BEGIN RSA PRIVATE KEY-----
...
-----END RSA PRIVATE KEY-----"""
key_pair_id = 'ABX....'
policy = {}
policy['Statement'] = [{}]
policy['Statement'][0]['Resource'] = url
policy['Statement'][0]['Condition'] = {}
policy['Statement'][0]['Condition']['DateLessThan'] = {}
policy['Statement'][0]['Condition']['DateLessThan']['AWS:EpochTime'] = expires
policy = json.dumps(policy)
private_key = rsa.PrivateKey.load_pkcs1(pem)
signature = b64encode(rsa.sign(str(policy), private_key, 'SHA-1'))
print '?Policy=%s&Signature=%s&Key-Pair-Id=%s' % (b64encode(policy),
signature,
key_pair_id)
我可以将它用于http://your_domain/*
下的所有文件,例如:
http://your_domain/image2.png?Policy...
http://your_domain/image2.png?Policy...
http://your_domain/file1.json?Policy...
答案 5 :(得分:0)
我发现简单的解决方案不需要更改s3.generate_url
方式,
只需选择您的Cloudfront配置:Yes, Update bucket policy
。
之后的更改:
https://xxxx.s3.amazonaws.com/hello.png&Signature=sss&Expires=1585008320&AWSAccessKeyId=kkk
到
https://yyy.cloudfront.net/hello.png&Signature=sss&Expires=1585008320&AWSAccessKeyId=kkk
yyy.cloudfront.net
是您的CloudFront域
请参阅:https://aws.amazon.com/blogs/developer/accessing-private-content-in-amazon-cloudfront/