Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Google Cloud Storage: CORS settings doesn't work for signed URLs

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?

like image 297
Symon Avatar asked Aug 31 '25 04:08

Symon


2 Answers

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
like image 93
Pawel Czuczwara Avatar answered Sep 02 '25 17:09

Pawel Czuczwara


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);
like image 35
user3413723 Avatar answered Sep 02 '25 19:09

user3413723