【问题标题】:Creating Signed Cookies for Amazon CloudFront为 Amazon CloudFront 创建签名 Cookie
【发布时间】:2015-06-05 15:53:30
【问题描述】:

亚马逊最近除了签名网址外,还推出了Cloudfront signed cookie

一个类似的问题是关于signed url。显然支持signed url in the cloudfront SDK

但是我在 aws python SDK 中找不到此功能的支持。

我怎样才能开始创建一个签名的 cookie?

【问题讨论】:

  • 你试过我的解决方案了吗?

标签: python amazon-web-services amazon-cloudfront


【解决方案1】:

我创建了一个 boto 功能请求来添加它,但同时我让它与我的 django python 应用程序一起工作。这是我自己生成的简单代码。底部是一个示例 django 视图方法,因此您可以看到我如何为包含 Cloudfront 内容的网页设置 cookie。

import time
from boto.cloudfront import CloudFrontConnection
from boto.cloudfront.distribution import Distribution
from config import settings
import logging
from django.template.context import RequestContext
from django.shortcuts import render_to_response
logger = logging.getLogger('boto')
logger.setLevel(logging.CRITICAL) #disable DEBUG logging that's enabled in AWS by default (outside of django)

AWS_ACCESS_KEY="AKABCDE1235ABCDEF22A"#SAMPLE
AWS_SECRET_KEY="a1wd2sD1A/GS8qggkXK1u8kHlh+BiLp0C3nBJ2wW" #SAMPLE
key_pair_id="APKABCDEF123ABCDEFAG" #SAMPLE
DOWNLOAD_DIST_ID = "E1ABCDEF3ABCDE" #SAMPLE replace with the ID of your Cloudfront dist from Cloudfront console

############################################
def generate_signed_cookies(resource,expire_minutes=5):
    """
    @resource   path to s3 object inside bucket(or a wildcard path,e.g. '/blah/*' or  '*')
    @expire_minutes     how many minutes before we expire these access credentials (within cookie)
    return tuple of domain used in resource URL & dict of name=>value cookies
    """
    if not resource:
        resource = 'images/*'
    dist_id = DOWNLOAD_DIST_ID
    conn = CloudFrontConnection(AWS_ACCESS_KEY, AWS_SECRET_KEY)
    dist = SignedCookiedCloudfrontDistribution(conn,dist_id)
    return dist.create_signed_cookies(resource,expire_minutes=expire_minutes)

############################################
class SignedCookiedCloudfrontDistribution():

    def __init__(self,connection,download_dist_id,cname=True):
        """
        @download_dist_id   id of your Cloudfront download distribution
        @cname          boolean True to use first domain cname, False to use 
                        cloudfront domain name, defaults to cname
                        which presumably matches your writeable cookies ( .mydomain.com)
        """
        self.download_dist = None
        self.domain = None
        try:
            download_dist = connection.get_distribution_info(download_dist_id)
            if cname and download_dist.config.cnames:
                self.domain = download_dist.config.cnames[0] #use first cname if defined
            else:
                self.domain = download_dist.domain_name
            self.download_dist = download_dist
        except Exception, ex:
            logging.error(ex)

    def get_http_resource_url(self,resource=None,secure=False):
        """
        @resource   optional path and/or filename to the resource 
                   (e.g. /mydir/somefile.txt);
                    defaults to wildcard if unset '*'
        @secure     whether to use https or http protocol for Cloudfront URL - update  
                    to match your distribution settings 
        return constructed URL
        """
        if not resource:
            resource = '*'
        protocol = "http" if not secure else "https"
        http_resource = '%s://%s/%s' % (protocol,self.domain,resource)
        return http_resource

    def create_signed_cookies(self,resource,expire_minutes=3):
        """
        generate the Cloudfront download distirbution signed cookies
        @resource   path to the file, path, or wildcard pattern to generate policy for
        @expire_minutes  number of minutes until expiration
        return      tuple with domain used within policy (so it matches 
                    cookie domain), and dict of cloudfront cookies you
                    should set in request header
        """
        http_resource = self.get_http_resource_url(resource,secure=False)    #per-file access #NOTE secure should match security settings of cloudfront distribution
    #    http_resource = self.get_http_resource_url("somedir/*")  #blanket access to all /somedir files inside my bucket
    #    http_resource = self.get_http_resource_url("*")          #blanket access to all files inside my bucket

        #generate no-whitespace json policy, then base64 encode & make url safe
        policy = Distribution._canned_policy(http_resource,SignedCookiedCloudfrontDistribution.get_expires(expire_minutes))
        encoded_policy = Distribution._url_base64_encode(policy)

        #assemble the 3 Cloudfront cookies
        signature = SignedCookiedCloudfrontDistribution.generate_signature(policy,private_key_file=settings.AMAZON_PRIV_KEY_FILE)
        cookies = {
            "CloudFront-Policy" :encoded_policy,
            "CloudFront-Signature" :signature,
            "CloudFront-Key-Pair-Id" :key_pair_id #e.g, APKA..... -> same value you use when you sign URLs with boto distribution.create_signed_url() function
        }
        return self.domain,cookies

    @staticmethod
    def get_expires(minutes):
        unixTime = time.time() + (minutes * 60)
        expires = int(unixTime)  #if not converted to int causes Malformed Policy error and has 2 decimals in value
        return expires

    @staticmethod
    def generate_signature(policy,private_key_file=None):
        """
        @policy     no-whitespace json str (NOT encoded yet)
        @private_key_file   your .pem file with which to sign the policy
        return encoded signature for use in cookie
        """
        #sign the policy - code borrowed from Distribution._create_signing_params()
        signature = Distribution._sign_string(policy, private_key_file)
        #now base64 encode the signature & make URL safe
        encoded_signature = Distribution._url_base64_encode(signature)
        return encoded_signature

############################################
def sample_django_view_method(request,template="mytemplate.html"):
    expireLen = 30 #30 minutes
    s3resource = "somepath_in_my_bucket/afile.mp4"
    context = {} #variables I'm passing to my html template
    response = render_to_response(template, context, context_instance=RequestContext(request))
    domain,cookies = generate_signed_cookies(s3resource,expire_minutes=expireLen)
    #TROUBLESHOOTING COOKIES:
    #NOTE - Cookie Domain must be a domain you control that spans your app & your Cloudfront CNAME
    #NOTE - (e.g. if my webapp is www.mydomain.com and my AWS Download Distribution has cname cloud.mydomain.com, cant set cookies from webapp to 
            # www.mydomain.com or localhost.mydomain.com or cloud.mydomain.com and have them work 
        # -> instead set cookies to .mydomain.com to work across sub-domains, you can then verify in request headers to CloudFront that these cookies get passed.
        # TIP - if you set_cookies from a page with a .mydomain.com suffix, but don't see them get set in Chrome they didn't get set because of permissions - can't set to a diff subdomain or diff base domain
        # TIP - if you set_cookies and see them in Chrome but don't see them in request headers to Cloudfront, cookie domain is likely too narrow, need to widen to span subdomains
    base_domain = '.mydomain.com'
    # NOTE: Sanity check when testing so you can flag any gotchas - I have not fully tested using non-cname urls inside policy vs all possible domains for cookie itself   
    if not domain.endswith(base_domain):
        logger.warn("This likely won't work - your resource permissions use a different domain than your cookies")
    for name,value in cookies.items():
        response.set_cookie(name,value=value,httponly=True,domain=base_domain)
    return response

############################################

if __name__ == '__main__':
    domain,cookies = generate_signed_cookies('images/*',expire_minutes=30)

关于我的设置的注意事项:

  • 我只需对已设置并适用于签名 URL 的下载分发进行一项更改:我必须添加一个 cname,其基域与我的网站匹配。
  • 我使用了 Chrome Web Developer 工具:

    • 网络:查看在云端调用中发送的请求标头并查看 403 与 200/状态/响应大小
    • 控制台:在一切正常之前查看 403 错误
    • Resources > Cookies - 验证 [localhost 或您的主机].mydomain.com cookie 显示填充的 3 个 Cloudfront cookie,它们设置为 Domain=.mydomain.com 并且这些值与请求标头中的值匹配Cloudfront(如果缺少,可能是域配置错误)

我的 AWS 配置

  • S3 需要 Cloudfront 源
  • Cloudfront 下载分发:
    • 分发设置:
      • cname 定义:cloud.mydomain.com(对我来说是新的!)
      • 默认 Cloudfront 证书
    • “源”选项卡:定义的 1 个源映射到我的 S3 存储桶
    • 行为选项卡 - 默认:
      • 所有这些都与我已经用于签名 URL 的设置保持不变
      • HTTP 和 HTTPS
      • 获取,头部
      • 转发标头:无
      • 使用原始缓存标头
      • 最小 TTL:0
      • 转发 Cookie:无
      • 转发查询字符串:否
      • 流畅的流式传输:否
      • 限制查看者访问:是(自签名 URL 后没有更改)
      • 可信签名者:自我

一旦你有了上面的 cookie 生成代码,最棘手的部分:

  • 确保 cookie 域正确
  • 确保您的政策中的路径/资源与您的应用发出的请求相匹配

【讨论】:

  • AWS 文档中也有一条说明,流式 URL 不支持签名的 cookie,因此排除在上面。
  • 感谢您的详细解答。我仍在尝试代码。我收到很多消息,例如“WARNING 2015-04-22 06:38:19,060 urlfetch_stub.py:504] 从 URLFetch 请求中删除了禁止的标头:['Content-Length', 'Host'] ERROR 2015-04-22 06:38:32,057 testmain.py:110] 连接服务器时出错:无法获取 URL:cloudfront.amazonaws.com/2010-11-01/distribution/<MY-CF-ID> 错误:[Errno 61] 连接被拒绝”
  • 您是否拥有自己控制 DNS 的 .com 域(不是 cloudfront/aws 的一部分)?这就是我需要使用的东西才能让它工作。我使用 Route53 添加了一个指向 cloudfront 的新 cname。
  • 是的,我在我的 dns 服务提供商中定义了一个 CNAME。运行时错误更多地与在谷歌应用引擎中使用 boto 相关
猜你喜欢
  • 2011-02-04
  • 2017-04-02
  • 2011-02-07
  • 1970-01-01
  • 2019-04-24
  • 2012-01-04
  • 1970-01-01
  • 1970-01-01
  • 2016-07-20
相关资源
最近更新 更多