googleapis / nodejs-storage

Node.js client for Google Cloud Storage: unified object storage for developers and enterprises, from live data serving to data analytics/ML to data archiving.
https://cloud.google.com/storage/
Apache License 2.0
900 stars 369 forks source link

Files uploaded with `predefinedAcl: 'publicRead'` and `public: true` are not publically accessible #1469

Closed callanbright closed 3 years ago

callanbright commented 3 years ago

Environment details

Steps to reproduce

Before uploading, I receive the pictures in a JSON object mapping filename to base64 data:

{
  "picture1": "base64....",
  "picture2": "base64...."
}

I am then using the nodejs client library to upload the data. The pattern I am following is:

const storageClient = new Storage({
  projectId: GOOGLE_PROJECT_ID,
  keyFilename: GOOGLE_APPLICATION_CREDENTIALS,
})

const iconsBucket = storageClient.bucket(ICONS_BUCKET!)

// (in the code function that uploads a single picture)

// iconImageData is base64 png
const { webName, iconImageData } = args

const filename = `${webName}.png`

const iconImageFile = iconImagesBucket.file(filename)

const imageBuffer = Buffer.from(iconImageData, 'base64')

return iconImageFile
  .save(imageBuffer, {
    contentType: 'image/png',
    validation: 'md5',
    public: true,
    metadata: {
      contentType: 'image/png',
    },
    predefinedAcl: 'publicRead',
  })
  .then(() => {
    childLogger.debug({
      msg: 'Uploaded icon image to bucket',
      itemId,
      filename,
    })

    batchReport.nIconsUploaded += 1
  })
  .catch(err => {
    childLogger.error({
      msg: 'Failed to upload icon image to bucket',
      itemId,
      filename,
      err,
    })

    batchReport.nIconsFailed += 1
  })

When I run the above code, the uploads appear to all succeed.

My bucket

My bucket is single-region, standard storage with fine-grained ACLs enabled. I had to enable fine-grained ACLs because of this issue with the client library when uploading data as base64: https://github.com/googleapis/nodejs-storage/issues/1230

I have granted Storage Object Viewer permission to allUsers in the bucket:

image

When looking at the picture in the bucket, its permissions say "Public Access": "Public to internet", however the picture does not load below:

image

The image is also not accessible by the public URL.

If I attempt to Edit Permissions for the picture in the GUI, I am told:

my-email@example.com does not have storage.objects.getIamPolicy access to the Google Cloud Storage object.

Is this a bug?

How can I make the picture uploaded by my code publically viewable?

How come setting public: true and predefinedAcl: 'publicRead' does not have the desired effect?

BenWhitehead commented 3 years ago

Hi @Callan93,

I haven't been able to reproduce the image loading failure you're running into here (full repro attempt below).

Can you verify that you recieve a 200 for your object using curl?

curl -sI https://storage.googleapis.com/pt-item-icons/3rd-age-amulet.png

Is it possible that the base64 image data is somehow corrupt or malformed?

My full attempted repro ```js const {Storage} = require('@google-cloud/storage'); // generated by running `curl -s https://avatars.githubusercontent.com/u/5160266 | base64 -w 0` const imageBase64 = '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' const options = { contentType: 'image/png', validation: 'md5', metadata: { contentType: 'image/png', }, }; const permissionCombinations = [ { public: true, }, { public: false, }, { predefinedAcl: 'publicRead', }, { public: true, predefinedAcl: 'publicRead', }, ] const storage = new Storage(); const bucket = storage.bucket('my-public-bucket'); async function save(options) { const fileName = `image_${options.public}_${options.predefinedAcl}.png`; const file = bucket.file(fileName); const buffer = Buffer.from(imageBase64, 'base64'); return file .save(buffer, options) .then(() => { console.log('Upload success: ' + fileName) }) .catch(err => { console.log('Upload failed ' + fileName, err) }) } Promise.all( permissionCombinations.map(o => Object.assign({}, options, o)) .map(opts => save(opts)) ) .then(() => { console.log('done') }); ``` Which when ran outputs: ``` # node src/bufferPublic.js Upload success: image_undefined_publicRead.png Upload success: image_false_undefined.png Upload success: image_true_publicRead.png Upload success: image_true_undefined.png done ``` And I verify public accessibility by running: ``` # curl -sI https://storage.googleapis.com/my-public-bucket/{image_undefined_publicRead,image_false_undefined,image_true_publicRead,image_true_undefined}.png | grep '^HTTP' HTTP/2 200 HTTP/2 403 HTTP/2 200 HTTP/2 200 ```
callanbright commented 3 years ago

Thanks @BenWhitehead

I can confirm that I receive 200:

> curl -sI https://storage.googleapis.com/pt-item-icons/3rd-age-amulet.png

HTTP/2 200 
x-guploader-uploadid: ABg5-Uwkln57gPwVAVZ7jKpW-qGrERLqkt2WnS99OrxOqg8FrL0ttAS4q428T8Zs0lrLLW8-X5LuJwIei0rf2fNcDJMlCzNTqA
expires: Thu, 20 May 2021 03:39:38 GMT
date: Thu, 20 May 2021 02:39:38 GMT
cache-control: public, max-age=3600
last-modified: Wed, 19 May 2021 07:24:59 GMT
etag: "127e8916f1a35fde0d8ccf823d1e0481"
x-goog-generation: 1621409099518342
x-goog-metageneration: 1
x-goog-stored-content-encoding: identity
x-goog-stored-content-length: 500
content-type: image/png
x-goog-hash: crc32c=PP3LVQ==
x-goog-hash: md5=En6JFvGjX94NjM+CPR4EgQ==
x-goog-storage-class: STANDARD
accept-ranges: bytes
content-length: 500
server: UploadServer
alt-svc: h3-29=":443"; ma=2592000,h3-T051=":443"; ma=2592000,h3-Q050=":443"; ma=2592000,h3-Q046=":443"; ma=2592000,h3-Q043=":443"; ma=2592000,quic=":443"; ma=2592000; v="46,43"

When I curl the contents I receive the base64 I expect:

> curl  https://storage.googleapis.com/pt-item-icons/3rd-age-amulet.png 

iVBORw0KGgoAAAANSUhEUgAAACQAAAAgCAYAAAB6kdqOAAABPElEQVR4Xu2Vv0vDQBTHe2PHDEIIFI5SpEMoodRBqEsL0kFKC6kRqaDoIji4uTjZRUHwT/7aa8iPe4mQq7m7DPnAW/Je7n24e8d1Oi0tLY2EQS20wnDiOEqhUSqTKe6CHPm6gPND0NVqIG6Ub1IWZUJ0pRooytCKDENC1WQE2oXk3aHZIo0TEhzzT2VUF1eZt6NQEdIuI6gqpH12EqoIFWX+rv03cjOapXnNMgIqZOWZyJM1FfG48BFOT6VvBmUEstD7xsfTfGBLJiE+nt3NCNsLjmgqmqs8J7XD8HUXHERWZ54kYkXm52GC8LyHy8DFetxNhWilIRiWYwcz34V7v5+diON6EkvRSkMwfIQcrwsP3sswlXqexUdHqw3B8Bn18X3bT+UsDnTCfpa2A7xd9Zogk2D1qpfTKBnd/ALyniY30GleuwAAAABJRU5ErkJggg==%  

When I put this data into a base64 image viewer, it shows the image I expect:

image

It appears the client library did perform the upload correctly, and the data that was uploaded was also correct.

With content-type being image/png and the data being correct I would expect the image to display when viewed in my browser, however this is not the case. Viewing https://storage.googleapis.com/pt-item-icons/3rd-age-amulet.png in my browser results in a tiny white square:

image

Looking at the network tab I can see a 304 response:

Request URL: https://storage.googleapis.com/pt-item-icons/3rd-age-amulet.png
Request Method: GET
Status Code: 304 
Remote Address: 142.250.204.16:443
Referrer Policy: strict-origin-when-cross-origin

Is there any additional config I should be adding to iconImageFile.save to make the asset viewable in the browser?

BenWhitehead commented 3 years ago

You shouldn't have to do anything else with your method call to get things to work.

I tried downloading and viewing the image and was met with this error in my image viewer image

It seems that some of the image metadata is missing (I'm seeing dimensions when I click into the details). It would seem that the original data that was written is not well formed and the browser is likely having a hard time figuring out how to render it.


Regarding the permission issues you ran into

my-email@example.com does not have storage.objects.getIamPolicy access to the Google Cloud Storage object.

I have clarified with a teammate, you need to ensure your user has one of the roles which contains storage.objects.getIamPolicy, a full list of roles can be found here https://cloud.google.com/storage/docs/access-control/iam-roles . Project Owner does not by default have storage.objects.getIamPolicy so one of those roles will need to be added.

BenWhitehead commented 3 years ago

I believe this has been addressed from the nodejs-storage perspective, feel free to re-open or create a new issues if this hasn't helped you resolve your issue.