'Testing a AWS S3 Presigned Url returns 403 Forbidden (Nodejs)

I have a Lambda Nodejs function behind an API Gateway which successfully returns a Presigned URL:

const AWS = require('aws-sdk');
const S3 = new AWS.S3({
  apiVersion: '2006-03-01',
  signatureVersion: 'v4'
});

function getSignedUrl(id, type) {
  const key = `uploads/${id}.${type}`;
  return S3.getSignedUrl('putObject', {
    Bucket: 'example-bucket-name',
    Key: key,
    Expires: 300
  });
}

The Presigned URL may look like this:

https://example-bucket-name.s3.eu-central-1.amazonaws.com/uploads/489eb7115d0c479eaf9c3b6a01eb1893.png?Content-Type=image%2Fpng&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=ASIARTVN4TPKUACY5POZ%2F20200616%2Feu-central-1%2Fs3%2Faws4_request&X-Amz-Date=20200616T104031Z&X-Amz-Expires=300&X-Amz-Security-Token=IQoJb3JpZ2luX2VjEEMaDGV1LWNlbnRyYWwtMSJHMEUCIQCDd%2B5hFjcBd%2FA6TEV7Se6L%2B6V8VtgCrMg0%2FbOkoGKy1wIgL20u20i%2B80rnBf49MfU1T3MQK2RQdoyQF6SwGQiYgeMq7gEIvP%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FARABGgwxMTA5NjAwOTAwNjkiDIcy2z8%2FpQIhMOOaZirCAUFhV6uGF%2Ff44lDl%2BaFxIt9D302gcuPaxlrgZWlRMHb%2FEdrKFJsWP%2FG7%2B6ovilKh9WmcBX1fzuVa%2BHQ6rv6OaCueMEnDOBEj%2FvJ1hrI%2FwMDF1RLVlqq7pTDp6h6hmUxPfbqXu1k8sjcFotVzXZTzR0dX6kmWl41uEvaglXjrGG3ApvviH%2BSFLdUdvK9PBgrgSlamGIhxdJN75xxBzQMELfdpPJ6QanhLEwIa%2FuMliHPliXC2fasMzFEheA3Xmik43McnMJ3DovcFOuABdae1G7uUXOSaQzGZ7IjPLLZnMFfow4SzosQHlMUurlqQATPbieC9W3McsMVwggwzZX6BcN9OJb%2B0Ag3x9pS5eLnLsEio%2FyAPZJfXzoGBH5AdZ6TAZtC5cgKy0TEebH%2F3bF4%2FiamoTQ6YcZ4f48NefoNFHcRPXl3VF%2FdINmuTSG1cNlh2svT9jAUfOgaeK7tnFAW79L38Nv7xnnFMYFpyxoUx8XVkffCXmq15dyG7rLIR0FHkJ7p4C8eEqbQzOj%2Fsj1ELFFAWPtq38ZgFnWF%2BYf6W4UrkHD9AGdUucD1qvAA%3D&X-Amz-Signature=e11f346296a979e586b8f81a9db2ef2ce58c9f7a13a4f3c31f9a0bb9997b8b81&X-Amz-SignedHeaders=host

However when testing said URL in the browser I get a SignatureDoesNotMatch. Which I guess seems fair since I want to only use this with a PUT from the application later.

Using curl or postman I however get 403 Forbidden:

curl -v -X PUT -T 489eb7115d0c479eaf9c3b6a01eb1893.png "https://example-bucket-name.s3.eu-central-1.amazonaws.com/uploads/489eb7115d0c479eaf9c3b6a01eb1893.png?Content-Type=image%2Fpng&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=ASIARTVN4TPKUACY5POZ%2F20200616%2Feu-central-1%2Fs3%2Faws4_request&X-Amz-Date=20200616T104031Z&X-Amz-Expires=300&X-Amz-Security-Token=IQoJb3JpZ2luX2VjEEMaDGV1LWNlbnRyYWwtMSJHMEUCIQCDd%2B5hFjcBd%2FA6TEV7Se6L%2B6V8VtgCrMg0%2FbOkoGKy1wIgL20u20i%2B80rnBf49MfU1T3MQK2RQdoyQF6SwGQiYgeMq7gEIvP%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FARABGgwxMTA5NjAwOTAwNjkiDIcy2z8%2FpQIhMOOaZirCAUFhV6uGF%2Ff44lDl%2BaFxIt9D302gcuPaxlrgZWlRMHb%2FEdrKFJsWP%2FG7%2B6ovilKh9WmcBX1fzuVa%2BHQ6rv6OaCueMEnDOBEj%2FvJ1hrI%2FwMDF1RLVlqq7pTDp6h6hmUxPfbqXu1k8sjcFotVzXZTzR0dX6kmWl41uEvaglXjrGG3ApvviH%2BSFLdUdvK9PBgrgSlamGIhxdJN75xxBzQMELfdpPJ6QanhLEwIa%2FuMliHPliXC2fasMzFEheA3Xmik43McnMJ3DovcFOuABdae1G7uUXOSaQzGZ7IjPLLZnMFfow4SzosQHlMUurlqQATPbieC9W3McsMVwggwzZX6BcN9OJb%2B0Ag3x9pS5eLnLsEio%2FyAPZJfXzoGBH5AdZ6TAZtC5cgKy0TEebH%2F3bF4%2FiamoTQ6YcZ4f48NefoNFHcRPXl3VF%2FdINmuTSG1cNlh2svT9jAUfOgaeK7tnFAW79L38Nv7xnnFMYFpyxoUx8XVkffCXmq15dyG7rLIR0FHkJ7p4C8eEqbQzOj%2Fsj1ELFFAWPtq38ZgFnWF%2BYf6W4UrkHD9AGdUucD1qvAA%3D&X-Amz-Signature=e11f346296a979e586b8f81a9db2ef2ce58c9f7a13a4f3c31f9a0bb9997b8b81&X-Amz-SignedHeaders=host"

The Lambda function has the following Permissions:

s3:ListBucket          Allow: arn:aws:s3:::example-bucket-name
s3:GetBucketLocation   Allow: arn:aws:s3:::example-bucket-name
s3:PutObject           Allow: arn:aws:s3:::example-bucket-name/uploads/*

The S3 Bucket has the following CORS Rules:

<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<CORSRule>
    <AllowedOrigin>*</AllowedOrigin>
    <AllowedMethod>HEAD</AllowedMethod>
    <AllowedMethod>GET</AllowedMethod>
    <AllowedMethod>PUT</AllowedMethod>
    <AllowedMethod>POST</AllowedMethod>
    <MaxAgeSeconds>3000</MaxAgeSeconds>
    <ExposeHeader>ETag</ExposeHeader>
    <AllowedHeader>*</AllowedHeader>
</CORSRule>
</CORSConfiguration>

Here is the Bucket ACL:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "AWSConfigBucketPermissionsCheck",
            "Effect": "Allow",
            "Principal": {
                "Service": "config.amazonaws.com"
            },
            "Action": "s3:GetBucketAcl",
            "Resource": "arn:aws:s3:::example-bucket-name"
        },
        {
            "Sid": "DenyUnEncryptedTraffic",
            "Effect": "Deny",
            "Principal": {
                "AWS": "*"
            },
            "Action": "*",
            "Resource": "arn:aws:s3:::example-bucket-name/*",
            "Condition": {
                "Bool": {
                    "aws:SecureTransport": "false"
                }
            }
        }
    ]
}

Could it be that the above DenyUnEncryptedTraffic Rule is causing the 403? I tested it by removing said rule but I still get a 403.

Block all public access is On!

The bucket owner has full access to the bucket!

I have been wasting too much time on this already and really need some help!



Solution 1:[1]

Ok now I feel stupid:

Just tested said Presigned PUT URL with Postman and somehow it works:

enter image description here

Make sure you enter the Presigned URL, Select PUT and then select binary and add a file.

I will leave this up in case someone finds this useful.

Solution 2:[2]

Be careful when you create the sign url, if the code that generates the sign url does not have the correct permission on IAM, you won't have any error when generating the pre-signed url, but you will have errors when using the URL.

Solution 3:[3]

It can happen because your request's headers don't match the headers that you used when generating the pre-signed URL. Maybe the HTTP library that you are using adds default headers in case you did not mention one, such as Content-Type. I know that Axios does that.

Solution 4:[4]

The reason why AWS returns the SignatureDoesNotMatch error (403) is usually that the secret key is incorrect

Solution 5:[5]

I had the same error before (403). I solved it doing the following:

  1. The role attached to your lambda has s3:PutObject and s3:GetObject permissions.
  2. Check if the s3 key is valid (No blank spaces or non ascii characters).

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 NJones
Solution 2 jocelyn
Solution 3 Shahar Yakov
Solution 4 raul7
Solution 5 Yerson Roa