8000 Add customer-supplied encryption to storage. · googleapis/google-cloud-python@f713c3e · GitHub
[go: up one dir, main page]

Skip to content

Commit f713c3e

Browse files
committed
Add customer-supplied encryption to storage.
1 parent b27907b commit f713c3e

File tree

2 files changed

+246
-11
lines changed

2 files changed

+246
-11
lines changed

gcloud/storage/blob.py

Lines changed: 96 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@
1414

1515
"""Create / interact with Google Cloud Storage blobs."""
1616

17+
import base64
1718
import copy
19+
import hashlib
1820
from io import BytesIO
1921
import json
2022
import mimetypes
@@ -109,6 +111,23 @@ def path_helper(bucket_path, blob_name):
109111
"""
110112
return bucket_path + '/o/' + quote(blob_name, safe='')
111113

114< 10000 /code>+
@staticmethod
115+
def _get_customer_encryption_headers(key):
116+
"""Builds customer encyrption key headers
117+
118+
:type key: str
119+
:param key: 32 byte key to build request key and hash.
120+
"""
121+
headers = {}
122+
key_hash = base64.encodestring(hashlib.sha256(key.encode('utf-8'))
123+
.digest()).rstrip()
124+
encoded_key = base64.encodestring(bytes(key.encode('utf-8'))).rstrip()
125+
headers['X-Goog-Encryption-Algorithm'] = 'AES256'
126+
headers['X-Goog-Encryption-Key'] = encoded_key.decode('utf-8')
127+
headers['X-Goog-Encryption-Key-Sha256'] = key_hash.decode('utf-8')
128+
129+
return headers
130+
112131
@property
113132
def acl(self):
114133
"""Create our ACL on demand."""
@@ -276,17 +295,34 @@ def delete(self, client=None):
276295
"""
277296
return self.bucket.delete_blob(self.name, client=client)
278297

279-
def download_to_file(self, file_obj, client=None):
298+
def download_to_file(self, file_obj, key=None, client=None):
280299
"""Download the contents of this blob into a file-like object.
281300
282301
.. note::
283302
284303
If the server-set property, :attr:`media_link`, is not yet
285304
initialized, makes an additional API request to load it.
286305
306+
Downloading a file that has been `customer-supplied
307+
<https://cloud.google.com/storage/docs/encryption#customer-supplied>`_
308+
encryption::
309+
310+
>>> from gcloud import storage
311+
>>> from gcloud.storage import Blob
312+
313+
>>> sc = storage.Client(project='my-project')
314+
>>> bucket = sc.get_bucket('my-bucket')
315+
>>> key = 'aa426195405adee2c8081bb9e7e74b19'
316+
>>> blob = Blob('secure-data', bucket)
317+
>>> with open('/tmp/my-secure-file& 10000 #39;, 'w') as file_obj:
318+
>>> blob.download_to_file(file_obj, key=key)
319+
287320
:type file_obj: file
288321
:param file_obj: A file handle to which to write the blob's data.
289322
323+
:type key: str
324+
:param key: Optional 32 byte key for customer-supplied encryption.
325+
290326
:type client: :class:`gcloud.storage.client.Client` or ``NoneType``
291327
:param client: Optional. The client to use. If not passed, falls back
292328
to the ``client`` stored on the blob's bucket.
@@ -305,7 +341,11 @@ def download_to_file(self, file_obj, client=None):
305341
if self.chunk_size is not None:
306342
download.chunksize = self.chunk_size
307343

308-
request = Request(download_url, 'GET')
344+
headers = {}
345+
if key:
346+
headers.update(self._get_customer_encryption_headers(key))
347+
348+
request = Request(download_url, 'GET', headers)
309349

310350
# Use the private ``_connection`` rather than the public
311351
# ``.connection``, since the public connection may be a batch. A
@@ -315,27 +355,46 @@ def download_to_file(self, file_obj, client=None):
315355
# it has all three (http, API_BASE_URL and build_api_url).
316356
download.initialize_download(request, client._connection.http)
317357

318-
def download_to_filename(self, filename, client=None):
358+
def download_to_filename(self, filename, key=None, client=None):
319359
"""Download the contents of this blob into a named file.
320360
321361
:type filename: string
322362
:param filename: A filename to be passed to ``open``.
323363
364+
:type key: str
365+
:param key: Optional 32 byte key for customer-supplied encryption.
366+
324367
:type client: :class:`gcloud.storage.client.Client` or ``NoneType``
325368
:param client: Optional. The client to use. If not passed, falls back
326369
to the ``client`` stored on the blob's bucket.
327370
328371
:raises: :class:`gcloud.exceptions.NotFound`
329372
"""
330373
with open(filename, 'wb') as file_obj:
331-
self.download_to_file(file_obj, client=client)
374+
self.download_to_file(file_obj, key=key, client=client)
332375

333376
mtime = time.mktime(self.updated.timetuple())
334377
os.utime(file_obj.name, (mtime, mtime))
335378

336-
def download_as_string(self, client=None):
379+
def download_as_string(self, key=None, client=None):
337380
"""Download the contents of this blob as a string.
338381
382+
Downloading a blob that has been `customer-supplied
383+
<https://cloud.google.com/storage/docs/encryption#customer-supplied>`_
384+
encryption::
385+
386+
>>> from gcloud import storage
387+
>>> from gcloud.storage import Blob
388+
389+
>>> sc = storage.Client(project='my-project')
390+
>>> bucket = sc.get_bucket('my-bucket')
391+
>>> key = 'aa426195405adee2c8081bb9e7e74b19'
392+
>>> blob = Blob('secure-data', bucket)
393+
>>> data = blob.download_as_string(file_obj, key=key)
394+
395+
:type key: str
396+
:param key: Optional 32 byte key for customer-supplied encryption.
397+
339398
:type client: :class:`gcloud.storage.client.Client` or ``NoneType``
340399
:param client: Optional. The client to use. If not passed, falls back
341400
to the ``client`` stored on the blob's bucket.
@@ -345,7 +404,7 @@ def download_as_string(self, client=None):
345404
:raises: :class:`gcloud.exceptions.NotFound`
346405
"""
347406
string_buffer = BytesIO()
348-
self.download_to_file(string_buffer, client=client)
407+
self.download_to_file(string_buffer, key=key, client=client)
349408
return string_buffer.getvalue()
350409

351410
@staticmethod
@@ -358,7 +417,8 @@ def _check_response_error(request, http_response):
358417
raise make_exception(faux_response, http_response.content,
359418
error_info=request.url)
360419

361-
def upload_from_file(self, file_obj, rewind=False, size=None,
420+
# pylint: disable=too-many-arguments,too-many-locals
421+
def upload_from_file(self, file_obj, rewind=False, size=None, key=None,
362422
content_type=None, num_retries=6, client=None):
363423
"""Upload the contents of this blob from a file-like object.
364424
@@ -391,6 +451,9 @@ def upload_from_file(self, file_obj, rewind=False, size=None,
391451
:func:`os.fstat`. (If the file handle is not from the
392452
filesystem this won't be possible.)
393453
454+
:type key: str
455+
:param key: Optional 32 byte key for customer-supplied encryption.
456+
394457
:type content_type: string or ``NoneType``
395458
:param content_type: Optional type of content being uploaded.
396459
@@ -434,6 +497,9 @@ def upload_from_file(self, file_obj, rewind=False, size=None,
434497
'User-Agent': connection.USER_AGENT,
435498
}
436499

500+
if key:
501+
headers.update(self._get_customer_encryption_headers(key))
502+
437503
upload = Upload(file_obj, content_type, total_bytes,
438504
auto_transfer=False)
439505

@@ -474,7 +540,7 @@ def upload_from_file(self, file_obj, rewind=False, size=None,
474540
response_content = response_content.decode('utf-8')
475541
self._set_properties(json.loads(response_content))
476542

477-
def upload_from_filename(self, filename, content_type=None,
543+
def upload_from_filename(self, filename, content_type=None, key=None,
478544
client=None):
479545
"""Upload this blob's contents from the content of a named file.
480546
@@ -500,6 +566,9 @@ def upload_from_filename(self, filename, content_type=None,
500566
:type content_type: string or ``NoneType``
501567
:param content_type: Optional type of content being uploaded.
502568
569+
:type key: str
570+
:param key: Optional 32 byte key for customer-supplied encryption.
571+
503572
:type client: :class:`gcloud.storage.client.Client` or ``NoneType``
504573
:param client: Optional. The client to use. If not passed, falls back
505574
to the ``client`` stored on the blob's bucket.
@@ -509,10 +578,10 @@ def upload_from_filename(self, filename, content_type=None,
509578
content_type, _ = mimetypes.guess_type(filename)
510579

511580
with 10000 open(filename, 'rb') as file_obj:
512-
self.upload_from_file(file_obj, content_type=content_type,
581+
self.upload_from_file(file_obj, content_type=content_type, key=key,
513582
client=client)
514583

515-
def upload_from_string(self, data, content_type='text/plain',
584+
def upload_from_string(self, data, content_type='text/plain', key=None,
516585
client=None):
517586
"""Upload contents of this blob from the provided string.
518587
@@ -527,6 +596,19 @@ def upload_from_string(self, data, content_type='text/plain',
527596
`lifecycle <https://cloud.google.com/storage/docs/lifecycle>`_
528597
API documents for details.
529598
599+
Uploading a string that with `customer-supplied
600+
<https://cloud.google.com/storage/docs/encryption#customer-supplied>`_
601+
encryption::
602+
603+
>>> from gcloud import storage
604+
>>> from gcloud.storage import Blob
605+
606+
>>> sc = storage.Client(project='my-project')
607+
>>> bucket = sc.get_bucket('my-bucket')
608+
>>> key = 'aa426195405adee2c8081bb9e7e74b19'
609+
>>> blob = Blob('secure-data', bucket)
610+
>>> b.upload_from_string('my secure string', key=key)
611+
530612
:type data: bytes or text
531613
:param data: The data to store in this blob. If the value is
532614
text, it will be encoded as UTF-8.
@@ -535,6 +617,9 @@ def upload_from_string(self, data, content_type='text/plain',
535617
:param content_type: Optional type of content being uploaded. Defaults
536618
to ``'text/plain'``.
537619
620+
:type key: str
621+
:param key: Optional 32 byte key for customer-supplied encryption.
622+
538623
:type client: :class:`gcloud.storage.client.Client` or ``NoneType``
539624
:param client: Optional. The client to use. If not passed, falls back
540625
to the ``client`` stored on the blob's bucket.
@@ -545,7 +630,7 @@ def upload_from_string(self, data, content_type='text/plain',
545630
string_buffer.write(data)
546631
self.upload_from_file(file_obj=string_buffer, rewind=True,
547632
size=len(data), content_type=content_type,
548-
client=client)
633+
key=key, client=client)
549634

550635
def make_public(self, client=None):
551636
"""Make this blob public giving all users read access.

0 commit comments

Comments
 (0)
0