Getting 403 "Anonymous caller does not have storage.objects.get access" when trying to upload to GCS with signed URLs
signedUploadUrl
creates a URL for the POST HTTP method (see the library source code at https://github.com/googleapis/google-cloud-php/blob/master/Storage/src/StorageObject.php). You are using that signed URL for a PUT request, so the request isn't permitted. The error message does not show this as the problem, but I think that's what it really is.
You can either look into how to upload a file via POST, or create a signed URL for PUT. I've done the latter in Python, but I don't see a way to do it with this library. I'm not a PHP programmer so I might be missing it.
Or you could create your own code to create a signed URL for PUT, starting with the library code as an example. Signed URLs are extremely tricky to get exactly right, and creating your own code will probably be frustrating. It was for me in Python.
tomphp
Updated on July 14, 2022Comments
-
tomphp almost 2 years
I'm trying to upload files directly from the browser to GCS using signed URLs. I'm generating a
v4
signed URL from an App Engine Standard PHP application and that seems to be working fine. The problem is when I try toPUT
to that URL I get a403
with the following XML response:<?xml version='1.0' encoding='UTF-8'?> <Error> <Code>AccessDenied</Code> <Message>Access denied.</Message> <Details>Anonymous caller does not have storage.objects.create access to <bucket-name>/some-object.txt.</Details> </Error>
My app engine service account has
Service Account Token Creator
, which enabled the URL to be created.I've enabled CORS on the bucket to accept
PUT
to*
, which allowed me to get to where I am now.I've switched from
v2
URLs tov4
as an issue on the Go SDK suggested that was a problem.I'm generating the signed URL using the PHP Google Cloud Library like so:
$storage = new StorageClient(); $bucket = $storage->bucket('<bucket-name>'); $object = $bucket->object('some-object.txt'); $url = $object->signedUploadUrl(new \DateTime('tomorrow'), ['version' => 'v4']);
I've tried adding the service account to the bucket's permissions and adding
Storage Object Admin
,Storage Object Creator
, etc. but nothing seems to get me past this403
(apart from opening it up toallUsers
).In this article is says
In addition, within Cloud Storage, you need to grant the following permissions to generate a Signed URL.
storage.buckets.get
storage.objects.create
storage.objects.delete
But I just can't work out which role they need to be added to.
At this point, I think there is one of two possibilities:
- The signed URL is not actually working because it should be authenticated as the service account and not anonymous. In this case, what could be causing this?
- Signed URLs authenticate as some type of anonymous role but that role does not have the permissions. In which case, how do I add the permissions for that role (
allUsers
is obviously wrong)?
SOLVED:
There was a number of things wrong with my implementation:
- As suggested by Brandon & Charles below,
signedUploadUrl
is not appropriate for a directPUT
. To get around this, I needed to usebeginSignedUploadSession
- As suggested by John below, I needed to have
Storage Object Creator
added on the service account user. This however, is already added on a GAE default service account as it isProject Editor
-
Service Account Token Creator
needs to be explicitly added to the service account asProject Editor
doesn't seem to cover it. - I was embedding the URL into Javascript with Twig uses
const url='{{ upload_url }}';
, however Twig automatically HTML encodes variables so that was breaking the URL, instead, I needed to use{{ upload_url|raw }}
. This broken format was the reason that the message includedAnonymous caller