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:
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>
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 theiam.serviceAccounts.signBlob
permission, which is granted in IAM roles such as Service Account Token Creator (roles/iam.serviceAccountTokenCreator
).