user 007
user 007

Reputation: 911

Getting 403 forbidden when generating signed url with PUT request to upload an object to Google Cloud Storage

I'm trying to give a temporary access to a service account that has only read permission to upload an mp3 to a Google Cloud Storage bucket.

For that, I'm generating a signedurl but i can't get it to work.

What I'm doing to generate the URL and upload the file :

const config = {
    action: 'write',
    expires: '03-12-2019',
    contentType: 'audio/mpeg'
}

const storage = new Storage({
    projectId: projectId,
    keyFilename: './service-account-key.json'
})

let bucketSubDirectory = storage.bucket(bucketName).file('subdirectory/in/bucket/' + songName)

bucketSubDirectory.getSignedUrl(config, (err, url) => {
    if (err) {
        console.error(err)
        return
    }
    console.log(url)
    // The file is now available to be written to.
    let writeStream = request.put(url, {
       headers: {
        'Content-Type': 'audio/mpeg'
       }
    })
    writeStream.end('New data');

    writeStream.on('complete', function (resp) {
        console.log(resp.statusCode + " " + resp.statusMessage)

    })

})

When I execute the script I get a 403 forbidden response and when I try to access the generated URL from the browser I get the following :

<Error>
<Code>MalformedSecurityHeader</Code>
<Message>Your request has a malformed header.</Message><ParameterName>signature</ParameterName>
<Details>Signature was not base64 encoded</Details>
</Error>

Any idea how can I solve this problem?

Upvotes: 2

Views: 4427

Answers (1)

llompalles
llompalles

Reputation: 3166

According to the node.js client library documentation, the method getSignedUrl has a parameter config which as a parameter contentType:

contentType

Optional

string

If you provide this value, the client must provide this HTTP header set to the same value.

So you might need to modify your PUT request to include this header with the value contentType: audio/mpeg.

Also, if you enter the url in the browser you are not doing a PUT request but a GET one.

EDIT:

Also check that your service account which creates the signedurl has the right permissions granted. For the code your are running you need at least the roles/storage.objectAdmin role. To grant it:

gcloud projects add-iam-policy-binding yourProjectID --member yourServiceAccount --role "roles/storage.objectAdmin"

Once this is done, users will be able to write to the file with just a PUT request to the url.

const request = require('request');

let url = 'yourSignedURL'

let writeStream = request.put(url, {
       headers: {
        'Content-Type': 'audio/mpeg'
       }
    })
    writeStream.end('New data');

    writeStream.on('complete', function (resp) {
        console.log(resp.statusCode + " " + resp.statusMessage)

    })

Upvotes: 2

Related Questions