Skip to content

Commit ee11e64

Browse files
author
Jon Wayne Parrott
committed
Merge pull request #230 from BrandonY/master
Add an example of CSK operations.
2 parents 1cd8358 + 87d5509 commit ee11e64

File tree

2 files changed

+187
-0
lines changed

2 files changed

+187
-0
lines changed

storage/api/customer_supplied_keys.py

+163
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
#!/usr/bin/env python
2+
3+
# Copyright 2016 Google Inc. All rights reserved.
4+
#
5+
# Licensed under the Apache License, Version 2.0 (the 'License');
6+
# you may not use this file except in compliance with the License.
7+
# You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an 'AS IS' BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
"""Command-line sample app demonstrating customer-supplied encryption keys.
17+
18+
This sample demonstrates uploading an object while supplying an encryption key,
19+
retrieving that object's contents, and finally rotating that key to a new
20+
value.
21+
22+
This sample is used on this page:
23+
24+
https://cloud.google.com/storage/docs/json_api/v1/json-api-python-samples
25+
26+
For more information, see the README.md under /storage.
27+
"""
28+
29+
import argparse
30+
import filecmp
31+
import tempfile
32+
33+
from googleapiclient import discovery
34+
from googleapiclient import http
35+
from oauth2client.client import GoogleCredentials
36+
37+
38+
# You can (and should) generate your own encryption key. Here's a good way to
39+
# accomplish this with Python:
40+
# python -c \
41+
# 'import base64; import os; print(base64.encodestring(os.urandom(32)))'
42+
# Although these keys are provided here for simplicity, please remember that it
43+
# is a bad idea to store your encryption keys in your source code.
44+
ENCRYPTION_KEY = '4RzDI0TeWa9M/nAvYH05qbCskPaSU/CFV5HeCxk0IUA='
45+
46+
# You can use openssl to quicly calculate the hash of any key.
47+
# Try running this:
48+
# openssl base64 -d <<< ENCRYPTION_KEY | openssl dgst -sha256 -binary \
49+
# | openssl base64
50+
KEY_HASH = 'aanjNC2nwso8e2FqcWILC3/Tt1YumvIwEj34kr6PRpI='
51+
52+
ANOTHER_ENCRYPTION_KEY = 'oevtavYZC+TfGtV86kJBKTeytXAm1s2r3xIqam+QPKM='
53+
ANOTHER_KEY_HASH = '/gd0N3k3MK0SEDxnUiaswl0FFv6+5PHpo+5KD5SBCeA='
54+
55+
56+
def create_service():
57+
"""Creates the service object for calling the Cloud Storage API."""
58+
# Get the application default credentials. When running locally, these are
59+
# available after running `gcloud init`. When running on compute
60+
# engine, these are available from the environment.
61+
credentials = GoogleCredentials.get_application_default()
62+
63+
# Construct the service object for interacting with the Cloud Storage API -
64+
# the 'storage' service, at version 'v1'.
65+
# You can browse other available api services and versions here:
66+
# https://developers.google.com/api-client-library/python/apis/
67+
return discovery.build('storage', 'v1', credentials=credentials)
68+
69+
70+
def upload_object(bucket, filename, encryption_key, key_hash):
71+
"""Uploads an object, specifying a custom encryption key."""
72+
service = create_service()
73+
74+
with open(filename, 'rb') as f:
75+
request = service.objects().insert(
76+
bucket=bucket, name=filename,
77+
# You can also just set media_body=filename, but for the sake of
78+
# demonstration, pass in the more generic file handle, which could
79+
# very well be a StringIO or similar.
80+
media_body=http.MediaIoBaseUpload(f, 'application/octet-stream'))
81+
request.headers['x-goog-encryption-algorithm'] = 'AES256'
82+
request.headers['x-goog-encryption-key'] = encryption_key
83+
request.headers['x-goog-encryption-key-sha256'] = key_hash
84+
85+
resp = request.execute()
86+
87+
return resp
88+
89+
90+
def download_object(bucket, obj, out_file, encryption_key, key_hash):
91+
"""Downloads an object protected by a custom encryption key."""
92+
service = create_service()
93+
94+
request = service.objects().get_media(bucket=bucket, object=obj)
95+
request.headers['x-goog-encryption-algorithm'] = 'AES256'
96+
request.headers['x-goog-encryption-key'] = encryption_key
97+
request.headers['x-goog-encryption-key-sha256'] = key_hash
98+
99+
# Unfortunately, http.MediaIoBaseDownload overwrites HTTP headers,
100+
# and so it cannot be used here. Instead, we shall download as a
101+
# single request.
102+
out_file.write(request.execute())
103+
104+
105+
def rotate_key(bucket, obj, current_encryption_key, current_key_hash,
106+
new_encryption_key, new_key_hash):
107+
"""Changes the encryption key used to store an existing object."""
108+
service = create_service()
109+
110+
request = service.objects().rewrite(
111+
sourceBucket=bucket, sourceObject=obj,
112+
destinationBucket=bucket, destinationObject=obj,
113+
body={})
114+
115+
# For very large objects, calls to rewrite may not complete on the first
116+
# call and may need to be resumed.
117+
while True:
118+
request.headers.update({
119+
'x-goog-copy-source-encryption-algorithm': 'AES256',
120+
'x-goog-copy-source-encryption-key': current_encryption_key,
121+
'x-goog-copy-source-encryption-key-sha256': current_key_hash,
122+
'x-goog-encryption-algorithm': 'AES256',
123+
'x-goog-encryption-key': new_encryption_key,
124+
'x-goog-encryption-key-sha256': new_key_hash})
125+
126+
rewrite_response = request.execute()
127+
128+
if rewrite_response['done']:
129+
break
130+
131+
print('Continuing rewrite call...')
132+
request = service.objects().rewrite(
133+
source_bucket=bucket, source_object=obj,
134+
destination_bucket=bucket, destination_object=obj,
135+
rewriteToken=rewrite_response['rewriteToken'])
136+
rewrite_response.execute()
137+
138+
139+
def main(bucket, filename):
140+
print('Uploading object gs://{}/{}'.format(bucket, filename))
141+
upload_object(bucket, filename, ENCRYPTION_KEY, KEY_HASH)
142+
print('Downloading it back')
143+
with tempfile.NamedTemporaryFile(mode='w+b') as tmpfile:
144+
download_object(bucket, filename, tmpfile, ENCRYPTION_KEY, KEY_HASH)
145+
tmpfile.seek(0)
146+
assert filecmp.cmp(filename, tmpfile.name), \
147+
'Downloaded file has different content from the original file.'
148+
print('Rotating its key')
149+
rotate_key(bucket, filename, ENCRYPTION_KEY, KEY_HASH,
150+
ANOTHER_ENCRYPTION_KEY, ANOTHER_KEY_HASH)
151+
print('Done')
152+
153+
154+
if __name__ == '__main__':
155+
parser = argparse.ArgumentParser(
156+
description=__doc__,
157+
formatter_class=argparse.RawDescriptionHelpFormatter)
158+
parser.add_argument('bucket', help='Your Cloud Storage bucket.')
159+
parser.add_argument('filename', help='A file to upload and download.')
160+
161+
args = parser.parse_args()
162+
163+
main(args.bucket, args.filename)
+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Copyright 2016, Google, Inc.
2+
# Licensed under the Apache License, Version 2.0 (the "License");
3+
# you may not use this file except in compliance with the License.
4+
# You may obtain a copy of the License at
5+
#
6+
# http://www.apache.org/licenses/LICENSE-2.0
7+
#
8+
# Unless required by applicable law or agreed to in writing, software
9+
# distributed under the License is distributed on an "AS IS" BASIS,
10+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
# See the License for the specific language governing permissions and
12+
# limitations under the License.
13+
14+
import re
15+
16+
from customer_supplied_keys import main
17+
18+
19+
def test_main(cloud_config, capsys):
20+
main(cloud_config.storage_bucket, __file__)
21+
out, err = capsys.readouterr()
22+
23+
assert not re.search(r'Downloaded file [!]=', out)
24+
assert re.search(r'Uploading.*Downloading.*Rotating.*Done', out, re.DOTALL)

0 commit comments

Comments
 (0)