aws-sdk-js: getSignedUrl not always returning a valid signed-url

Confirm by changing [ ] to [x] below to ensure that it’s a bug:

Describe the bug Sometimes when calling s3.getSignedUrl(method, params) we receive an invalid signed URL from the SDK and no error is thrown. EG:https://s3.us-west-2.amazonaws.com/ This happens on both getObject and putObject methods, and retrying the call produces the expected results.

Is the issue in the browser/Node.js? Node.js

If on Node.js, are you running this on AWS Lambda? No Details of the browser/Node.js version Node v10.21.0

SDK version number v2.694.0

To Reproduce (observed behavior) Excerpt from our code

const AWS = require('aws-sdk');
const path = require('path');
const log = require('../logger');
const config = require('config');
const bucket = config.s3.uploads.bucket;
const region = config.s3.uploads.region;
const s3 = new AWS.S3({
    apiVersion: '2006-03-01',
    signatureVersion: 'v4',
    region
});

async function getSignedUrl(method, params) {
    try {
        const preSignedUrl = s3.getSignedUrl(method, params);
        if (!preSignedUrl.includes("X-Amz-Signature")) {
            log.error(`received a non valid ${method} presigned url : ${preSignedUrl}`);
            throw new Error ("Illegal presigned url returned!");
        }
        return preSignedUrl;
    } catch (err)  {
        log.error(err, `Error retrieving ${method} pre-signed url`);
        throw err;
    }
}

Params being:

            const uploadlinkparams = {
                Key: prefix,
                Bucket: bucket,
                Expires: 60 * 60, // one hour
                Tagging: "" // This header needs to exist otherwise we can't add tags
            };

            const downloadlinkparams = {
                Key: prefix,
                Bucket: bucket,
                Expires: downloadttl ? downloadttl * 60 : 60 * 60 * 24 * 7 // 7 days if not specified
            };

Expected behavior We expect to receive a valid pre-signed url, or an error to be thrown by the SDK

Screenshots N/A

Additional context This code runs on EC2 instances. I cannot reproduce locally outside of AWS. It is hard to reproduce in AWS.

We see the following calls made before the failure: PUT //169.254.169.254/latest/api/token [200] GET //169.254.169.254/latest/meta-data/iam/security-credentials/ [200] GET //169.254.169.254/latest/meta-data/iam/security-credentials/<censored>InstanceProfile [200]

With the failure message being: received a non valid putObject presigned url : https://s3.us-west-2.amazonaws.com/

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Reactions: 5
  • Comments: 19 (5 by maintainers)

Most upvoted comments

Commenting to keep this open

As of today, it seems like a call to this function (promise or not) returns the bare legacy https://s3.amazonaws.com/ while only https://s3.[region].amazonaws.com/ is allowed.

Having the same problem here, tried changing to await getSignedUrlPromise without luck

Yep switched to getSignedUrlPromise seems good so far.

I think there should be some documentation at-least about this failure-mode.

I ran into this issue. I solved it by adding signatureVersion to the AWS SDK config (see below). Without it getSignedUrlPromise will return a url without the signature field query string parameter. This causes the URL not to work.

import AWS from 'aws-sdk';

AWS.config.update({
  signatureVersion: 'v4'
});

const s3 = new AWS.S3();

when we get the image or video file from the aws getSignedUrl why the getting files are showing in the source page folder while doing the inspect element? How to avoid or clear the image files. Please anyone can explain immediately.

@Mnkras That can be regarded as a feature request but I think it has already been raised but since there is a workaround for that the priority for that is really low.

Also it will be beneficial for your app to use getSignedUrlPromise

https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#getSignedUrlPromise-property

It doesn’t solve your problem but I thought it can save you some lines of code.

As of today, it seems like a call to this function (promise or not) returns the bare legacy https://s3.amazonaws.com/ while only https://s3.[region].amazonaws.com/ is allowed.

@sshadmand I had the same issue, but I resolved it by adding an explicitly signature version like this:

  private getAwsClient() {
    const options: AWS.S3.ClientConfiguration = {
      region: this.region,
      logger: process.stdout,
      s3ForcePathStyle: true,
      signatureVersion: 'v4',
    };
    return new AWS.S3(options);
  }
  
  public generateUrlForBucketAndKey(
    bucketName: string,
    keyName: string,
    expiresInSeconds = 300,
  ): Promise<string> {
    return this.getAwsClient().getSignedUrlPromise('getObject', {
      Bucket: bucketName,
      Key: keyName,
      Expires: expiresInSeconds,
    });
  }