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 to PUT
to that URL I get a 403
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 to v4
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 this 403
(apart from opening it up to allUsers
).
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:
allUsers
is obviously wrong)?SOLVED:
There was a number of things wrong with my implementation:
signedUploadUrl
is not appropriate for a direct PUT
. To get around this, I needed to use beginSignedUploadSession
Storage Object Creator
added on the service account user. This however, is already added on a GAE default service account as it is Project Editor
Service Account Token Creator
needs to be explicitly added to the service account as Project Editor
doesn't seem to cover it.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 included Anonymous caller
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.