Skip to content

Instantly share code, notes, and snippets.

@ruiwen
Last active December 19, 2015 14:59
Show Gist options
  • Save ruiwen/5973509 to your computer and use it in GitHub Desktop.
Save ruiwen/5973509 to your computer and use it in GitHub Desktop.
Self Signed S3 REST API Using POST
import boto
import os
import json
import base64
import hashlib
import requests
import datetime
import hmac
import pytz
AWS_ACCESS_KEY = "xxx"
AWS_SECRET_KEY = "xxx"
ROOMMEDIA_BUCKET = "roommedia-dev"
sts_conn = boto.connect_sts(AWS_ACCESS_KEY, AWS_SECRET_KEY)
s3_policy ='''{
"Statement": [
{
"Sid": "AllowOnlyPutToBucket",
"Action": ["s3:PutObject"],
"Effect": "Allow",
"Resource": "arn:aws:s3:::rb-%(bucket_name)s/%(user_id)s/*"
},
{
"Sid": "AllowOnlyDeleteToBucket",
"Action": ["s3:DeleteObject"],
"Effect": "Allow",
"Resource": "arn:aws:s3:::rb-%(bucket_name)s/%(user_id)s/*"
}
]
}'''
data = {
"bucket_name": ROOMMEDIA_BUCKET,
"user_id": "abc123"
}
ft = sts_conn.get_federation_token(name=data['user_id'], policy=s3_policy % data, duration=3600)
creds = ft.credentials
print "Credentials obtained"
print "Clients should implement the process below this line"
print "#" * 30
print "\n\n"
# Assuming we want to PUT a file of name 'img_thumb.jpg'
filename = "img_thumb.jpg"
f = open(filename, "rb")
# Construct an upload_name, of <uniquehash>_<filename>
uniquehash = hashlib.new("sha1", "%s%s" % (datetime.datetime.utcnow().isoformat(), data['user_id'])).hexdigest()
upload_name = "%s/%s_%s" % (data['user_id'], uniquehash, filename)
print "Upload name: %s" % upload_name
# Certain assumptions
content_type = "image/jpeg"
content_md5 = base64.b64encode(hashlib.new("md5", f.read()).digest()) # Binary MD5 digest, not hexdigest
f.seek(0)
content_length = os.fstat(f.fileno()).st_size
# Headers
cache_ttl = 60 * 60 * 24 * 30 * 6
headers = {
'Host': 'rb-%s.s3.amazonaws.com' % data['bucket_name'],
'Date': (datetime.datetime.utcnow()).strftime("%a, %d %b %Y %H:%M:%S +0000"),
'Content-Length': content_length, # Get file size
'Expect': '100-continue', # Expects a 100 response from server before sending the body. Saves bandwidth
'Origin': "http://dev.homie.co",
'Cache-Control': 'max-age=%(cache_ttl)s; s-maxage=%(cache_ttl)s, public' % {"cache_ttl": cache_ttl}
}
# This generated policy is only valid for 25 minutes
expiration = datetime.datetime.utcnow() + datetime.timedelta(minutes=25)
# S3's REST API does not like the trailing +00:00 that a timezone aware datetime object generates
#expiration = expiration.replace(tzinfo=pytz.UTC)
expiration = expiration.replace(microsecond=0)
# Each clause in 'condition' needs to correspond with the same field
# in the POST data, except for 'AWSAccessKeyId', 'policy' and 'signature'
upload_policy = base64.b64encode(json.dumps({
"expiration": expiration.isoformat() + "Z", # S3's REST API does not like the trailing +00:00 that a timezone aware datetime object generates
"conditions": [
["content-length-range", 0, 1048576],
{"bucket": "rb-%s" % data['bucket_name']},
# Ensure that this upload can only be used once, ie. no double click == double upload
["starts-with", "$key", "%s/%s" % (data['user_id'], uniquehash)],
["starts-with", "$Cache-Control", ""],
["starts-with", "$Content-MD5", ""],
["starts-with", "$Content-Type", ""],
{'x-amz-meta-uploadedby': data['user_id']},
{'x-amz-meta-transaction': "txn01"},
{'x-amz-security-token': creds.session_token},
{'x-amz-meta-agent': "Test/0.0.1/Code"},
{'x-amz-server-side-encryption': 'AES256'}
]
}))
# Signature = Base64( HMAC-SHA1( YourSecretAccessKeyID, UTF-8-Encoding-Of( StringToSign ) ) );
signature = base64.b64encode(hmac.new(creds.secret_key.encode("utf-8"), upload_policy.encode("utf-8"), hashlib.sha1).digest())
form = {
'AWSAccessKeyId': creds.access_key,
'policy': upload_policy,
'signature': signature,
'bucket': 'rb-%s' % data['bucket_name'],
'key': upload_name,
'Content-Type': content_type,
'Content-MD5': content_md5,
'Cache-Control': 'max-age=%(cache_ttl)s; s-maxage=%(cache_ttl)s, public' % {"cache_ttl": cache_ttl},
'x-amz-meta-uploadedby': data['user_id'],
'x-amz-meta-transaction': "txn01",
'x-amz-security-token': creds.session_token,
'x-amz-meta-agent': "Test/0.0.1/Code",
'x-amz-server-side-encryption': 'AES256',
}
print form
# No Authorization header, as compared to a PUT REST API request
# Simulate Cross Origin Request
print "Sending OPTIONS"
options_headers = {}
options_headers.update(headers)
options_headers['Access-Control-Request-Method'] = 'POST'
options_headers['Access-Control-Request-Headers'] = 'accept, cache-control, origin, x-requested-with, content-type'
r = requests.options("https://rb-%s.s3.amazonaws.com/" % data['bucket_name'], headers=options_headers)
print r.headers
print r.status_code
print r.text
if int(r.status_code) != 200:
exit(0)
print "Making the POST"
f.seek(0)
r = requests.post("https://rb-%s.s3.amazonaws.com/" % data['bucket_name'], data=form, files={"file": f}, headers=headers)
print r.headers
print r.status_code
print r.text
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment