python - Creating Signed Cookies for Amazon CloudFront -
amazon has introduced cloudfront signed cookie in addition signed url.
a similar quesition has been signed url. apparently there support signed url in cloudfront sdk
however cannot find support of feature in aws python sdk.
how can got create signed cookie?
i created boto feature request add this, in meantime got working django python app. here's simple code i've generified own. @ bottom sample django view method can see how set cookies web page containing cloudfront content.
import time boto.cloudfront import cloudfrontconnection boto.cloudfront.distribution import distribution config import settings import logging django.template.context import requestcontext django.shortcuts import render_to_response logger = logging.getlogger('boto') logger.setlevel(logging.critical) #disable debug logging that's enabled in aws 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 id of cloudfront dist cloudfront console ############################################ def generate_signed_cookies(resource,expire_minutes=5): """ @resource path s3 object inside bucket(or wildcard path,e.g. '/blah/*' or '*') @expire_minutes how many minutes before 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 cloudfront download distribution @cname boolean true use first domain cname, false use cloudfront domain name, defaults cname presumably matches writeable cookies ( .mydomain.com) """ self.download_dist = none self.domain = none try: download_dist = connection.get_distribution_info(download_dist_id) if cname , 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 resource (e.g. /mydir/somefile.txt); defaults wildcard if unset '*' @secure whether use https or http protocol cloudfront url - update match 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 cloudfront download distirbution signed cookies @resource path file, path, or wildcard pattern generate policy @expire_minutes number of minutes until expiration return tuple domain used within policy (so matches cookie domain), , dict of cloudfront cookies 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 /somedir files inside bucket # http_resource = self.get_http_resource_url("*") #blanket access files inside bucket #generate no-whitespace json policy, base64 encode & make url safe policy = distribution._canned_policy(http_resource,signedcookiedcloudfrontdistribution.get_expires(expire_minutes)) encoded_policy = distribution._url_base64_encode(policy) #assemble 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 use when sign urls 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 int causes malformed policy error , 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 .pem file sign policy return encoded signature use in cookie """ #sign policy - code borrowed distribution._create_signing_params() signature = distribution._sign_string(policy, private_key_file) #now base64 encode 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 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 domain control spans app & cloudfront cname #note - (e.g. if webapp www.mydomain.com , aws download distribution has cname cloud.mydomain.com, cant set cookies webapp # www.mydomain.com or localhost.mydomain.com or cloud.mydomain.com , have them work # -> instead set cookies .mydomain.com work across sub-domains, can verify in request headers cloudfront these cookies passed. # tip - if set_cookies page .mydomain.com suffix, don't see them set in chrome didn't set because of permissions - can't set diff subdomain or diff base domain # tip - if set_cookies , see them in chrome don't see them in request headers cloudfront, cookie domain narrow, need widen span subdomains base_domain = '.mydomain.com' # note: sanity check when testing can flag gotchas - have not tested using non-cname urls inside policy vs possible domains cookie if not domain.endswith(base_domain): logger.warn("this won't work - resource permissions use different domain cookies") 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)
notes setup:
- i had make 1 change download distribution set & working signed urls: had add cname base domain matching website.
i used chrome web developer tools:
- network: view request headers sent in cloudfront call , see 403 vs 200/status/response size
- console: see 403 errors until got working
- resources > cookies - verify [localhost or host].mydomain.com cookies show 3 cloudfront cookies populated, set domain=.mydomain.com , these values match values in request headers cloudfront (if missing, domain misconfigured)
my aws configuration
- s3 requires cloudfront origin
- cloudfront download distribution:
- distribution settings:
- cname defined: cloud.mydomain.com (new me!)
- default cloudfront certificate
- origins tab: 1 origin defined maps s3 bucket
- behaviors tab - default:
- all of these unchanged settings used signed urls
- http , https
- get,head
- forward headers: none
- use origin cache headers
- minimum ttl: 0
- forward cookies: none
- forward querystrings: no
- smooth streaming: no
- restrict viewer access: yes (no change since signed urls)
- trusted signers: self
- distribution settings:
trickiest parts once have cookie-generating code above:
- making sure cookie domain right
- making sure path/resource in policy matches request being made app
Comments
Post a Comment