Search code examples
google-cloud-storage.net-6.0google-cloud-run

HttpRequestException (403 Forbidden) when creating a Google Cloud Storage SignedUrl (GET), only on Cloud Run


When running a docker container via Cloud Run, calling Sign(bucket, objectName, duration, HttpMethod.Get) on a Google.Cloud.Storage.V1.UrlSigner, it immediately throws an HttpRequestException, stemming from a 403 Forbidden response.

I suspect that I'm doing something incorrectly, but I have no idea what it is. The Service Account attached to the Cloud Run Revision has a role granting the storage.objects.get permission, and is capable of downloading the contents of the storage object.

Here's the code in question:

var urlSigner = UrlSigner.FromCredential(GoogleCredential.GetApplicationDefault());
var signedUrl = urlSigner.Sign("bucketName", "objectName", TimeSpan.FromMinutes(30), HttpMethod.Get);

If I run the project locally, (with GOOGLE_APPLICATION_CREDENTIALS pointing to a json service account key), I can create a signed url that works as expected. I can download the contents of the file both executing locally and on cloud run.

The service accounts are the same. I've verified this with the instance metadata at http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/email, which matches the "client_email" key in the credential file.

The only difference I've observed is that the GoogleCredential returned by GetApplicationDefault() wraps a ServiceAccountCredential locally, but a ComputeCredential when running on the cloud. This seems like a red herring, but I honestly don't know where else to look.


Versions that may be relevant:

  • Google.Cloud.Storage.V1 4.5.0
  • net6.0
  • ASP.NET Core 6.0

 

Some References:

Never set GOOGLE_APPLICATION_CREDENTIALS as an environment variable on a Cloud Run service. Always configure a user-managed service account instead

https://cloud.google.com/run/docs/securing/service-identity#per-service-identity

 

When you generate a signed URL, you specify a user or service account which must have sufficient permission to make the request that the signed URL will make.

https://cloud.google.com/storage/docs/access-control/signed-urls

 

Callstack for Exception:

System.Net.Http.HttpRequestException: Response status code does not indicate success: 403 (Forbidden).
  at System.Net.Http.HttpResponseMessage.EnsureSuccessStatusCode()
  at Google.Apis.Auth.OAuth2.Requests.RequestExtensions.PostJsonAsync[TResponse](Object request, HttpClient httpClient, String url, CancellationToken cancellationToken)
  at Google.Apis.Auth.OAuth2.ComputeCredential.SignBlobAsync(Byte[] blob, CancellationToken cancellationToken)
  at Google.Cloud.Storage.V1.UrlSigner.CredentialBlobSigner.CreateSignatureAsync(Byte[] data, BlobSignerParameters _, CancellationToken cancellationToken)
  at Google.Api.Gax.TaskExtensions.WaitWithUnwrappedExceptions(Task task)
  at Google.Api.Gax.TaskExtensions.ResultWithUnwrappedExceptions[T](Task`1 task)
  at Google.Cloud.Storage.V1.UrlSigner.CredentialBlobSigner.CreateSignature(Byte[] data, BlobSignerParameters signerParameters)
  at Google.Cloud.Storage.V1.UrlSigner.V4Signer.Sign(RequestTemplate requestTemplate, Options options, BlobSignerProvider blobSignerProvider, IClock clock)
  at Google.Cloud.Storage.V1.UrlSigner.Sign(RequestTemplate requestTemplate, Options options)
  at Google.Cloud.Storage.V1.UrlSigner.Sign(String bucket, String objectName, TimeSpan duration, HttpMethod httpMethod, Nullable`1 signingVersion)
  at <my code>

Solution

  • The service account also needs the iam.serviceAccounts.signBlob permission when running via Cloud Run.

    What the documentation for using the tools and client libraries doesn't mention is this prerequisite, found on the signing-urls-manually page:

    Note: If you're working in a Google Cloud environment such as Cloud Functions or App Engine, the IAM signBlob method is used to perform RSA signatures. To call this method, your service account must be assigned the iam.serviceAccounts.signBlob permission, which is granted in IAM roles such as Service Account Token Creator (roles/iam.serviceAccountTokenCreator).