2

This problem has been driving me nuts for two days now.

The objective: Upload an image directly from the browser to S3 via a pre-signed URL supplied by the getSignedUrl function in the AWS Javascript SDK.

I haven't had any problems generating URLs with getSignedUrl. The following code...

const params = {
  Key:         key,
  Bucket:      process.env.S3_BUCKET,
  ContentType: "image/jpeg"
};

S3.getSignedUrl("putObject", params, callback);

...yields something like:

https://s3.amazonaws.com/foobar-bucket/someImage.jpeg?AWSAccessKeyId=ACCESSKEY123&Content-Type=image%2Fjpeg&Expires=1543357053&Signature=3fgjyj7gpJiQvbIGhqWXSY40JUU%3D&x-amz-acl=private&x-amz-security-token=FQoGZXIvYXdzEDYaDPzeqKMbfgetCcZBaCL0AWftL%2BIT%2BP3tqTDVtNU1G8eC9sjl9unhwknrYvnEcrztfR9%2FO9AGD6VDiDDKfTQ9SmQpfXmiyTKDwAcevTwxeRnj6hGwnHgvzFVBzoslrB8MxrxjUpiI7NQW3oRMunbLskZ4LgvQYs8Rh%2FDjat4H%2F%2BvfPxDSQUSa41%2BFKcoySUHGh2xqfBFGCkHlIqVgk1KELDHmTaNckkvc9B4cgEXmAd3u1f1KC9mbobYcLLRPIzMj9bLJH%2BIlINylzubao1pCQ7m%2BWdX5xAZDhTSNwQfo4ywSWV7kUpbq2dgEriOiKAReEjmFQtuGqYBi3t2dhrasptOlXFXUozdz23wU%3D

But uploading an image via PUT request to the provided URL always returns a 403 SignatureDoesNotMatch error from S3.

What DOES work:

  • Calling getSignedUrl() from a local instance of AWS Lambda (via serverless-offline).

What DOESN'T work:

  • Setting the query string variables as headers (Content-Type, x-amz-*, etc.)
  • Removing all headers
  • Changing the ACL when getting the URL (private, public-read-write, no ACL, etc.)
  • Changing the region of aws-sdk in Node
  • Trying POST instead of PUT (it's worth a shot)

Any help on this issue would be greatly appreciated. I'm about to throw my computer out the window and jump out after it in frustration if this continues to be a problem, as it simply does NOT want to work!

pkoeppen
  • 131
  • 1
  • 7

2 Answers2

11

I figured it out. The Lambda function invoking getSignedUrl() did not have the correct IAM role permissions to access the S3 bucket in question. In serverless.yml...

iamRoleStatements:
    - Effect: Allow
      Action:
        - s3:*
      Resource: "arn:aws:s3:::foobar-bucket/*"

I wouldn't actually use a wildcard here, but you get the picture. The fact that getSignedUrl() still succeeds and returns a URL even when the URL is doomed to fail because of missing permissions is extremely misleading.

I hope this answer helps some confused soul in the future.

pkoeppen
  • 131
  • 1
  • 7
  • Thank you so much. I found I had to do the permission for _both_ my IAM role and my bucket permissions though, not just the iam role – Cobertos Sep 30 '19 at 06:15
  • Thanks for this. I had it working on a previous test lambda but when i switched over didnt give it write permissions. Thank you! – xPaillant Jul 29 '20 at 08:22
0

It worked for me doing it in the old school way: (axios kept giving 403 Forbidden)

  const xhr = new XMLHttpRequest();
  xhr.open("PUT", signedRequest);
  xhr.onreadystatechange = () => {

    if (xhr.readyState === 4) {
      if (xhr.status === 200) {
        //Put your logic here..
         //When it get's here you can access the image using the url you got when signed.
      }    
    }
  };
  xhr.send(file);

Notice this needs to run from the client, so you will need to configure the Cross Origin Police in your Bucket.

enter image description here

Roberto Rodriguez
  • 3,179
  • 32
  • 31