4

The response of PUT request with signed URL doesn't contain header Access-Control-Allow-Origin.

import os
from datetime import timedelta

import requests
from google.cloud import storage

os.environ['GOOGLE_APPLICATION_CREDENTIALS'] = <path to google credentials>
client = storage.Client()
bucket = client.get_bucket('my_bucket')
policies = [
    {
        'origin': ['*'],
        'method': ['PUT'],
    }
]
bucket.cors = policies
bucket.update()
blob = bucket.blob('new_file')
url = blob.generate_signed_url(timedelta(days=30), method='PUT')
response = requests.put(url, data='some data')

for header in response.headers.keys():
    print(header)

Output:

X-GUploader-UploadID
ETag
x-goog-generation
x-goog-metageneration
x-goog-hash
x-goog-stored-content-length
x-goog-stored-content-encoding
Vary
Content-Length
Date
Server
Content-Type
Alt-Svc

As you can see there is no CORS-headers. So, can I conclude that GCS doesn't support CORS properly/fully?

Symon
  • 1,626
  • 1
  • 23
  • 31

2 Answers2

2

Cross Origin Resource Sharing (CORS) allows interactions between resources from different origins. By default, in Google Cloud Storage it is prohibited/disabled in order to prevent malicious behavior.

You can enable it either using Cloud Libraries, Rest API or Cloud SDK, keeping in mind following rules:

  1. Authenticate using user/service account with the permissions for Cloud Storage type: FULL_CONTROL.

  2. Using XML API to get proper CORS headers, use one of the two URLs:

- storage.googleapis.com/[BUCKET_NAME]
- [BUCKET_NAME].storage.googleapis.com

Origin storage.cloud.google.com/[BUCKET_NAME] will not respond with CORS header.

  1. Request need proper ORIGIN header to match bucket policy ORIGIN configuration as stated in the point 3 of the CORS troubleshooting documentation, in case of your code:
headers = {
    'ORIGIN': '*'
}
response = requests.put(url, data='some data', headers=headers)

for header in response.headers.keys():
    print(header)

gives following output:

X-GUploader-UploadID
ETag
x-goog-generation
x-goog-metageneration
x-goog-hash
x-goog-stored-content-length
x-goog-stored-content-encoding
Access-Control-Allow-Origin
Access-Control-Expose-Headers
Content-Length
Date
Server
Content-Type
Pawel Czuczwara
  • 1,442
  • 9
  • 20
  • 1
    This works only for GET requests. But doesn't work for PUT. Look at the code I provided. I do the same things you suggested except the method is PUT. – Symon Apr 18 '19 at 13:27
  • BTW DELETE which you used in bucket policy in your example doesn't work as well. – Symon Apr 18 '19 at 13:50
  • [Documentation states](https://cloud.google.com/storage/docs/cross-origin#how_cors_works), that request is pref lighted in any of the other method than: GET, HEAD or POST. That includes: POST and Delete. Did you try to check it? – Pawel Czuczwara Apr 18 '19 at 15:14
  • Yes. Response on preflight request doesn't contain CORS headers. In my case it's PUT (DELETE checked as well). – Symon Apr 18 '19 at 15:26
  • Maybe this article will help: https://blog.francium.tech/google-cloud-storage-and-cors-37236fbb99ba – Pawel Czuczwara Apr 18 '19 at 15:36
  • The article describes the same way of setting CORS policy I used. I guess it's either a bug or lack of documentation. Is there a way to report a bug to Google dev team? – Symon Apr 18 '19 at 15:46
  • I will try to reproduce it next week not later than Tuesday. If the preflight (as described in the example provided) does not get correct headers and it will not work for me either, I will file a public issue for you. – Pawel Czuczwara Apr 18 '19 at 18:17
  • BTW The same problem reported half a year ago: https://stackoverflow.com/questions/53290008/preflight-for-google-cloud-storage-signed-url-not-returning-cors-response-header – Symon Apr 19 '19 at 18:31
  • 1
    I did reproduce your issue, and updated answer accordingly. It works for me. – Pawel Czuczwara Apr 22 '19 at 11:13
2

I had this issue. For me the problem was I was using POST instead of PUT. Furthermore, I had to set the Content-Type of the upload to match the content type used to generate the form. The default Content-Type in the demo is "application/octet-stream", so I had to change it to be whatever was the content type of the upload. When doing the XMLHttpRequest, I just had to send the file directly instead of using FormData.

This was how I got the signed url.

    const options = {
        version: 'v4',
        action: 'write',
        expires: Date.now() + 15 * 60 * 1000, // 15 minutes
        contentType: 'application/octet-stream',
    };

    // Get a v4 signed URL for uploading file
    const [url] = await storage
        .bucket("lsa-storage")
        .file(upload.id)
        .getSignedUrl(options as any);
user3413723
  • 11,147
  • 6
  • 55
  • 64