talkiq / gcloud-aio

(Asyncio OR Threadsafe) Google Cloud Client Library for Python
https://talkiq.github.io/gcloud-aio
274 stars 90 forks source link

GCS get_signed_url() permission denied #590

Open RoccoFortuna opened 1 year ago

RoccoFortuna commented 1 year ago

I'm trying to get signed urls for files in my bucket and it all works fine with the official library: https://cloud.google.com/storage/docs/samples/storage-generate-signed-url-v4#storage_generate_signed_url_v4-python

When I use gcloud.aio.storage's .get_signed_url() it throws a 403:

raise aiohttp.ClientResponseError(resp.request_info, resp.history,
aiohttp.client_exceptions.ClientResponseError: 403, message='Forbidden: {\n  "error": {\n    "code": 403,\n    "message": "Permission \'iam.serviceAccounts.implicitDelegation\' denied on resource (or it may not exist).",\n    "status": "PERMISSION_DENIED",\n    "details": [\n      {\n        "@type": "type.googleapis.com/google.rpc.ErrorInfo",\n        "reason": "IAM_PERMISSION_DENIED",\n        "domain": "iam.googleapis.com",\n        "metadata": {\n          "permission": "iam.serviceAccounts.implicitDelegation"\n        }\n      }\n    ]\n  }\n}\n', url=URL('https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/ppp-955@pppprint-378113.iam.gserviceaccount.com:signBlob')

Although the credentials used are the same and obviously the resource exists. Am I doing something wrong or is this function broken?

RoccoFortuna commented 1 year ago

My minimal reproducible snippet:

bn = "my_bucket_name"

async def generate_download_signed_url_v4(
    blob_name: str, expiration: int = 600
):
    """Generates a v4 signed URL for downloading a blob.

    blob_name (str): the name of the file in the gcs bucket,
    expiration (int): the number of seconds before the link expires
    """
    async with gcloud.aio.storage.Storage() as client:
        bucket = client.get_bucket(bucket_name=bn)
        blob = await bucket.get_blob(blob_name=blob_name)

        return await blob.get_signed_url(
            expiration=expiration,  # 600s = 10 minutes
        )

if __name__ == "__main__":
    import os

    os.environ[
        "GOOGLE_APPLICATION_CREDENTIALS"
    ] = "./path_to_credentials.json"
    # check credentials are read fine
    with open(os.environ["GOOGLE_APPLICATION_CREDENTIALS"]) as f:
        print(f.read())

    asyncio.run(generate_download_signed_url_v4(blob_name="1.jpg"))