Amazon CloudFront 私有分发访问被拒绝
我正在尝试设置CloudFront来分发私有内容,但每次访问生成的链接时都出现“访问被拒绝”的错误。为了说明,我已经创建了CloudFront分发,并将其设置为私有,同时创建了一个Origin Access ID,并给它授予了对所有相关文件的读取权限。
我写了一个简单的Python脚本,用来生成这些链接,参考了亚马逊网页上关于签名链接的示例,下面是我写的代码:
import os, time
def GetCloudFrontURL(file, expires=86400):
resource = "http://mydistribution.cloudfront.net/" + file
exptime = int(time.time()) + expires
epochtime = str(exptime)
policy = '{"Statement":[{"Resource":"' + resource + '","Condition":{"DateLessThan":{"AWS:EpochTime":' + epochtime + '}}}]}'
pk = "MY-PK-GOES-HERE"
signature = os.popen("echo '" + policy + "' | openssl sha1 -sign /path/to/file/pk-" + pk + ".pem | openssl base64 | tr '+=/' '-_~'").read()
signature = signature.replace('\n','')
url = resource + "&Expires=" + epochtime + "&Signature=" + signature + "&Key-Pair-Id=" + pk
return url
有没有人能看出我做的事情有什么明显的问题吗?我已经验证过,当我用私钥对摘要进行签名时,可以用公钥进行验证(前提是我在经过base64编码和转换步骤之前进行验证)。
谢谢。
3 个回答
这里教你怎么生成带签名的链接,而不需要使用os.popen去调用openssl。我们会用到一个很不错的Python库,叫做M2Crypto。
这段代码大致是参考了亚马逊在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}
#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 = "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参数设置为持有你密钥对的账户(如果是你自己的账户,就设置为“Self”)。
想要了解如何用Python设置流媒体,可以查看这个链接,里面有一个完整的示例。
根据这个,我做了一些小调整,终于让它正常工作了。
另外,可以看看boto的set_all_permissions函数,它可以自动为你设置S3的访问控制列表(ACL)。
from OpenSSL.crypto import *
import base64
import time
from django.conf import settings
ALT_CHARS = '-~'
def get_cloudfront_url(file, expires=86400):
resource = "https://" + settings.AWS_CLOUDFRONT_URL + "/" + file
exptime = int(time.time()) + expires
epochtime = str(exptime)
policy = '{"Statement":[{"Resource":"' + resource + '","Condition":{"DateLessThan":{"AWS:EpochTime":' + epochtime + '}}}]}'
f = open(settings.AWS_PRIVATE_KEY, 'r')
private_key = load_privatekey(FILETYPE_PEM, f.read())
f.close()
signature = base64.b64encode(sign(private_key, policy, 'sha1'), ALT_CHARS)
signature = signature.replace('=', '_')
url = resource + "?Expires=" + epochtime + "&Signature=" + signature + "&Key-Pair-Id=" + settings.AWS_CLOUDFRONT_KEY_PAIR_ID
return url
运行你的方法时,我在第一个关键词“Expires”前面看到了一个&符号。
>>> GetCloudFrontURL('test123')
http://mydistribution.cloudfront.net/test123&Expires=1297954193&Signature=&Key-Pair-Id=MY-PK-GOES-HERE
不知道这是否能解决你所有的问题,但我怀疑你需要在网址中加个问号,这样参数才能正确解析。试试这样的写法:
url = resource + "?Expires=" + epochtime + "&Signature=" + signature + "&Key-Pair-Id=" + pk
另外,urllib.urlencode这个方法可以把参数的字典转换成网址。你可以查看这个链接了解更多:http://docs.python.org/library/urllib.html#urllib.urlencode