09 Dec 2014
This is a utility to PUT, GET and get data to make a signed request for Google Cloud Storage
#! /usr/bin/env python # -*- coding: utf-8 -*- # vim:fenc=utf-8 # # Copyright © 2014 dhilipsiva <dhilipsiva@gmail.com> # # Distributed under terms of the MIT license. """ Requirements: pycrypto requests """ import base64 import datetime import md5 import time import requests from Crypto.Hash import SHA256 from Crypto.PublicKey import RSA from Crypto.Signature import PKCS1_v1_5 # Set this to the path of your service account private key file, in DER format. # # PyCrypto requires using the DER key format, but GCS provides key files in # pkcs12 format. To convert between formats, you can use the provided commands # below. # # The default password for p12 file is `notasecret` # # Given a GCS key in pkcs12 format, convert it to PEM using this command: # openssl pkcs12 -in path/to/key.p12 -nodes -nocerts > path/to/key.pem # Given a GCS key in PEM format, convert it to DER format using this command: # openssl rsa -in privatekey.pem -inform PEM -out privatekey.der -outform DER GC_PRIVATE_KEY_PATH = "privatekey.der" GC_BUCKET_NAME = 'bucket' GC_STORAGE_ENDPOINT = '//storage.googleapis.com' GC_EMAIL_ADDRESS = '1234567890abcdefghijklmnopqr@developer.gserviceaccount.com' keytext = open(GC_PRIVATE_KEY_PATH, 'rb').read() key = RSA.importKey(keytext) # Sample Usage: # put_data('blah.txt', 'blah blah') # get_data('blah.txt') def _base64Sign(plaintext): """ Signs and returns a base64-encoded SHA256 digest. """ shahash = SHA256.new(plaintext) signer = PKCS1_v1_5.new(key) signature_bytes = signer.sign(shahash) return base64.b64encode(signature_bytes) def _makeSignatureString(verb, path, content_md5, content_type, expiration): """ Creates the signature string for signing according to GCS docs. """ signature_string = ( '{verb}\n' '{content_md5}\n' '{content_type}\n' '{expiration}\n' '{resource}' ) return signature_string.format( verb=verb, content_md5=content_md5, content_type=content_type, expiration=expiration, resource=path) def _makeUrl(verb, path, content_type='', content_md5=''): """ Forms and returns the full signed URL to access GCS. """ path = '/%s/%s' % (GC_BUCKET_NAME, path) base_url = '%s%s' % (GC_STORAGE_ENDPOINT, path) expiration = datetime.datetime.now() + datetime.timedelta(hours=1) expiration = int(time.mktime(expiration.timetuple())) signature_string = _makeSignatureString( verb, path, content_md5, content_type, expiration) signature_signed = _base64Sign(signature_string) query_params = { 'GoogleAccessId': GC_EMAIL_ADDRESS, 'Expires': str(expiration), 'Signature': signature_signed, } return base_url, query_params def get_data(file_key): """ Performs a GET request. Args: file_key: Key of the file you want to access as found in `<bucket-name>/<file_key>` Returns: An instance of requests.Response containing the HTTP response. """ base_url, query_params = _makeUrl('GET', file_key) return requests.get('https:' + base_url, params=query_params) def put_data(file_key, data, content_type='application/octet-stream'): """ Performs a PUT request. Args: file_key: Key of the file you want to access as found in `<bucket-name>/<file_key>` content_type: The content type to assign to the upload. data: The file data to upload to the new file. Returns: An instance of requests.Response containing the HTTP response. """ md5_digest = base64.b64encode(md5.new(data).digest()) base_url, query_params = _makeUrl( 'PUT', file_key, content_type, md5_digest) headers = { 'Content-Type': content_type, 'Content-MD5': md5_digest, } return requests.put( 'https:' + base_url, params=query_params, headers=headers, data=data) def get_put_params(file_key, content_type, md5_digest): """ Args: file_key: Key of the file you want to access as found in `<bucket-name>/<file_key>` content_type: The content type to assign to the upload. md5_digest: MD5 digest of file Returns: base_url, query_params and headers for signing the request """ base_url, query_params = _makeUrl( 'PUT', file_key, content_type, md5_digest) headers = { 'Content-Type': content_type, 'Content-MD5': md5_digest, } return base_url, query_params, headers