"Access key does not exist" when generating pre-signed S3 URL from Lambda function

Viewed 231

I'm trying to generate a presigned URL from within a Lambda function, to get an existing S3 object .

(The Lambda function runs an ExpressJS app, and the code to generate the URL is called on one of its routes.)

I'm getting an error "The AWS Access Key Id you provided does not exist in our records." when I visit the generated URL, though, and Google isn't helping me:

<Error>
<Code>InvalidAccessKeyId</Code>
<Message>The AWS Access Key Id you provided does not exist in our records.</Message>
<AWSAccessKeyId>AKIAJ4LNLEBHJ5LTJZ5A</AWSAccessKeyId>
<RequestId>DKQ55DK3XJBYGKQ6</RequestId>
<HostId>IempRjLRk8iK66ncWcNdiTV0FW1WpGuNv1Eg4Fcq0mqqWUATujYxmXqEMAFHAPyNyQQ5tRxto2U=</HostId>
</Error>

The Lambda function is defined via AWS SAM and given bucket access via the predefined S3CrudPolicy template:

ExpressLambdaFunction:
    Type: AWS::Serverless::Function
    Properties:
      FunctionName: ExpressJSApp
      Description: Main website request handler
      CodeUri: ../lambda.zip
      Handler: lambda.handler
      [SNIP]
      Policies:
        - S3CrudPolicy:
            BucketName: my-bucket-name

The URL is generated via the AWS SDK:

const router = require('express').Router();
const AWS = require('aws-sdk');

router.get('/', (req, res) => {

    const s3 = new AWS.S3({
        region: 'eu-west-1',
        signatureVersion: 'v4'
    });
    const params = {
        'Bucket': 'my-bucket-name',
        'Key': 'my-file-name'
    };
    s3.getSignedUrl('getObject', params, (error, url) => {
        res.send(`<p><a href="${url}">${url}</a></p>`)
    });
});

What's going wrong? Do I need to pass credentials explicitly when calling getSignedUrl() from within a Lambda function? Doesn't the function's execute role supply those? Am I barking up the wrong tree?

2 Answers

tldr; Go sure, to have the correct order of signature_v4 headers/formdata, in your request.

I had the same exact issue.

I am not sure if this is the solution for everyone who is encountering the problem, but I learned the following:

The error message, and other misleading error messages can occur, if you don't use the correct order of security headers. In my case I was using the endpoint to create a presigned url, for posting a file, to upload it. In this case, you need to go sure, that you are having the correct order of security relevant data in your form-data. For signatureVersion 's3v3' it is:

  • key
  • x-amz-algorithm
  • x-amz-credential
  • x-amz-date
  • policy
  • x-amz-security-token
  • x-amz-signature

In the special case of a POST-Request to a presigned url, to upload a file, it's important to have your file, AFTER the security data.

After that, the request works as expected.

I can't say for certain but I'm guessing this may have something to do with you using the old SDK. Here it is w/ v3 of the SDK. You may need to massage it a little more.

const { getSignedUrl } = require("@aws-sdk/s3-request-presigner");
const { S3Client, GetObjectCommand } = require("@aws-sdk/client-s3");
// ...
const client = new S3Client({ region: 'eu-west-1' });
const params = {
    'Bucket': 'my-bucket-name',
    'Key': 'my-file-name'
};
const command = new GetObjectCommand(params);
getSignedUrl(client, command(error, url) => {
    res.send(`<p><a href="${url}">${url}</a></p>`)
});
Related