diff --git a/docs/backends/gcloud.rst b/docs/backends/gcloud.rst index 962298eb..0c18f27e 100644 --- a/docs/backends/gcloud.rst +++ b/docs/backends/gcloud.rst @@ -61,6 +61,7 @@ For development use cases, or other instances outside Google infrastructure: Alternatively, you can use the setting ``credentials`` or ``GS_CREDENTIALS`` as described below. +It is also now possible to use workload identity by providing the service account via ``GS_SA_SIGNING_EMAIL``. Settings ~~~~~~~~ @@ -219,3 +220,12 @@ Settings It supports `timedelta`, `datetime`, or `integer` seconds since epoch time. Note: The maximum value for this option is 7 days (604800 seconds) in version `v4` (See this `Github issue `_) + +``sa_email`` or ``GS_SA_SIGNING_EMAIL`` + + default: ``''`` + + This is the signing email if it is not fetched from the credentials. Or if you wish to sign the signed urls with a different service_account. + + As above please note that, Default Google Compute Engine (GCE) Service accounts are + `unable to sign urls `_. diff --git a/pyproject.toml b/pyproject.toml index 66dded8d..a01a6c96 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -54,7 +54,7 @@ dropbox = [ "dropbox>=7.2.1; python_version<'3.12'", ] google = [ - "google-cloud-storage>=1.27", + "google-cloud-storage>=2.14", ] libcloud = [ "apache-libcloud", diff --git a/storages/backends/gcloud.py b/storages/backends/gcloud.py index ace69244..46a40069 100644 --- a/storages/backends/gcloud.py +++ b/storages/backends/gcloud.py @@ -20,6 +20,8 @@ from storages.utils import to_bytes try: + from google import auth + from google.auth.transport import requests from google.cloud.exceptions import NotFound from google.cloud.storage import Blob from google.cloud.storage import Client @@ -142,12 +144,18 @@ def get_default_settings(self): # roll over. "max_memory_size": setting("GS_MAX_MEMORY_SIZE", 0), "blob_chunk_size": setting("GS_BLOB_CHUNK_SIZE"), + "sa_email": setting("GS_SA_SIGNING_EMAIL") } @property def client(self): if self._client is None: - self._client = Client(project=self.project_id, credentials=self.credentials) + project_id, credentials = self.project_id, self.credentials + if project_id is None and credentials is None: + credentials, project_id = auth.default(scopes=['https://www.googleapis.com/auth/cloud-platform']) + if not hasattr(credentials, "service_account_email"): + credentials.service_account_email = self.sa_email + self._client = Client(project=project_id, credentials=credentials) return self._client @property @@ -319,17 +327,14 @@ def url(self, name, parameters=None): quoted_name=_quote(name, safe=b"/~"), ) else: - default_params = { - "bucket_bound_hostname": self.custom_endpoint, + params = { + "service_account_email": self.credentials.service_account_email, + "access_token": self.credentials.token, + "credentials": self.credentials, "expiration": self.expiration, - "version": "v4", } - params = parameters or {} - - for key, value in default_params.items(): - if value and key not in params: - params[key] = value - + if self.custom_endpoint: + params["api_access_endpoint"] = self.custom_endpoint return blob.generate_signed_url(**params) def get_available_name(self, name, max_length=None):