Просмотр исходного кода

Use updated signing method for gcp

Nuwan Goonasekera 4 лет назад
Родитель
Сommit
f63e3a1253

+ 91 - 0
cloudbridge/providers/gcp/helpers.py

@@ -1,5 +1,12 @@
+import binascii
+import collections
+import datetime
+import hashlib
 import re
 
+import six
+from six.moves.urllib.parse import quote
+
 from googleapiclient.errors import HttpError
 
 import tenacity
@@ -186,3 +193,87 @@ def change_label(resource, key, value, res_att, request):
             response, zone=getattr(resource, 'zone_name', None))
     finally:
         resource.refresh()
+
+
+# https://cloud.google.com/storage/docs/access-control/signing-urls-manually#python-sample
+def generate_signed_url(credentials, bucket_name, object_name,
+                        subresource=None, expiration=604800, http_method='GET',
+                        query_parameters=None, headers=None):
+
+    if expiration > 604800:
+        # max allowed expiration time is 7 days
+        expiration = 604800
+
+    escaped_object_name = quote(six.ensure_binary(object_name), safe=b'/~')
+    canonical_uri = '/{}'.format(escaped_object_name)
+
+    datetime_now = datetime.datetime.utcnow()
+    request_timestamp = datetime_now.strftime('%Y%m%dT%H%M%SZ')
+    datestamp = datetime_now.strftime('%Y%m%d')
+
+    client_email = credentials.service_account_email
+    credential_scope = '{}/auto/storage/goog4_request'.format(datestamp)
+    credential = '{}/{}'.format(client_email, credential_scope)
+
+    if headers is None:
+        headers = dict()
+    host = '{}.storage.googleapis.com'.format(bucket_name)
+    headers['host'] = host
+
+    canonical_headers = ''
+    ordered_headers = collections.OrderedDict(sorted(headers.items()))
+    for k, v in ordered_headers.items():
+        lower_k = str(k).lower()
+        strip_v = str(v).lower()
+        canonical_headers += '{}:{}\n'.format(lower_k, strip_v)
+
+    signed_headers = ''
+    for k, _ in ordered_headers.items():
+        lower_k = str(k).lower()
+        signed_headers += '{};'.format(lower_k)
+    signed_headers = signed_headers[:-1]  # remove trailing ';'
+
+    if query_parameters is None:
+        query_parameters = dict()
+    query_parameters['X-Goog-Algorithm'] = 'GOOG4-RSA-SHA256'
+    query_parameters['X-Goog-Credential'] = credential
+    query_parameters['X-Goog-Date'] = request_timestamp
+    query_parameters['X-Goog-Expires'] = expiration
+    query_parameters['X-Goog-SignedHeaders'] = signed_headers
+    if subresource:
+        query_parameters[subresource] = ''
+
+    canonical_query_string = ''
+    ordered_query_parameters = collections.OrderedDict(
+        sorted(query_parameters.items()))
+    for k, v in ordered_query_parameters.items():
+        encoded_k = quote(str(k), safe='')
+        encoded_v = quote(str(v), safe='')
+        canonical_query_string += '{}={}&'.format(encoded_k, encoded_v)
+    canonical_query_string = canonical_query_string[:-1]  # remove trailing '&'
+
+    canonical_request = '\n'.join([http_method,
+                                   canonical_uri,
+                                   canonical_query_string,
+                                   canonical_headers,
+                                   signed_headers,
+                                   'UNSIGNED-PAYLOAD'])
+
+    canonical_request_hash = hashlib.sha256(
+        canonical_request.encode()).hexdigest()
+
+    string_to_sign = '\n'.join(['GOOG4-RSA-SHA256',
+                                request_timestamp,
+                                credential_scope,
+                                canonical_request_hash])
+
+    # signer.sign() signs using RSA-SHA256 with PKCS1v15 padding
+    signature = binascii.hexlify(
+        credentials.signer.sign(string_to_sign)
+    ).decode()
+
+    scheme_and_host = '{}://{}'.format('https', host)
+    signed_url = '{}{}?{}&x-goog-signature={}'.format(
+        scheme_and_host, canonical_uri, canonical_query_string, signature)
+
+    return signed_url

+ 0 - 3
cloudbridge/providers/gcp/provider.py

@@ -334,9 +334,6 @@ class GCPCloudProvider(BaseCloudProvider):
                 self.credentials_obj, _ = google.auth.default()
         return self.credentials_obj
 
-    def sign_blob(self, string_to_sign):
-        return self._credentials.sign_blob(string_to_sign)[1]
-
     @property
     def client_id(self):
         return self._credentials.service_account_email

+ 4 - 15
cloudbridge/providers/gcp/resources.py

@@ -1,15 +1,12 @@
 """
 DataTypes used by this provider
 """
-import base64
-import calendar
 import hashlib
 import inspect
 import io
 import logging
 import math
 import re
-import time
 import uuid
 from collections import namedtuple
 
@@ -1982,18 +1979,10 @@ class GCPBucketObject(BaseBucketObject):
         """
         Generates a signed URL accessible to everyone.
         """
-        expiration = calendar.timegm(time.gmtime()) + expires_in
-        signed_signature = self._provider.sign_blob(
-            'GET\n\n\n%d\n/%s/%s' %
-            (expiration, self._obj['bucket'], self.name))
-        encoded_signature = base64.b64encode(signed_signature).decode("utf-8")
-        url_encoded_signature = (encoded_signature.replace('+', '%2B')
-                                                  .replace('/', '%2F'))
-        return ('https://storage.googleapis.com/%s/%s?GoogleAccessId=%s'
-                '&Expires=%d&Signature=%s' % (self._obj['bucket'], self.name,
-                                              self._provider.client_id,
-                                              expiration,
-                                              url_encoded_signature))
+        # pylint:disable=protected-access
+        return helpers.generate_signed_url(
+            self._provider._credentials, self._obj['bucket'], self.name,
+            expiration=expires_in)
 
     def refresh(self):
         # pylint:disable=protected-access