Skip to content

Instantly share code, notes, and snippets.

@dhilipsiva
Created December 9, 2014 08:54
Show Gist options
  • Save dhilipsiva/3e51ccceebf4adbd535f to your computer and use it in GitHub Desktop.
Save dhilipsiva/3e51ccceebf4adbd535f to your computer and use it in GitHub Desktop.
A Google Cloud Storage utility - To upload a file, get a file and get a signed URL to post from browser.
#! /usr/bin/env python
# -*- coding: utf-8 -*-
# vim:fenc=utf-8
#
# Copyright © 2014 dhilipsiva <[email protected]>
#
# 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 = '[email protected]'
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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment