aws / aws-sdk-js-v3

Modularized AWS SDK for JavaScript.
Apache License 2.0
3.06k stars 573 forks source link

Signed URLs for custom domains are invalid #5461

Closed ronnyroeller closed 2 months ago

ronnyroeller commented 11 months ago

Checkboxes for prior research

Describe the bug

I'm upgrading our application from aws-sdk v2 to v3, which all worked fine except generating signed URLs.

Our users have to download private files from S3. The user needs to access them via a custom domain - and the URL shouldn't contain the bucket. Ex: It should be https://www.example.com/file.png - not https://www.example.com/bucket/file.png or https://bucket.example.com/file.png.

To my understanding, the getSignedUrl method (@aws-sdk/s3-request-presigner) always adds the bucket name into the URL, i.e. I can't use it for my requirements. [Please correct me if I'm wrong on this].

I therefore tried to compose the request via HttpRequest and sign it. Yet, the signed URL returns a SignatureDoesNotMatch.

SDK version number

@aws-sdk/s3-request-presigner@3.441.0

Which JavaScript Runtime is this issue in?

Node.js

Details of the browser/Node.js/ReactNative version

v18.4.0

Reproduction Steps

import { S3 } from '@aws-sdk/client-s3';
import { HttpRequest } from '@aws-sdk/protocol-http';
import { S3RequestPresigner } from '@aws-sdk/s3-request-presigner';
import { formatUrl } from '@aws-sdk/util-format-url';

const s3 = new S3({
  endpoint: 'https://www.example.com',
  forcePathStyle: true,
});

const apiUrl = new URL(`https://www.example.com/${key}`);
const presigner = new S3RequestPresigner(s3.config);
const httpRequest = new HttpRequest({
  method: 'GET',
  hostname: apiUrl.hostname,
  path: apiUrl.pathname,
});

const signedRequest = await presigner.presign(httpRequest, {
  expiresIn: 900,
});
const signedUrl = formatUrl(signedRequest);

Observed Behavior

Opening the signed URL in a browser returns 403 error SignatureDoesNotMatch: The request signature we calculated does not match the signature you provided. Check your key and signing method.

Expected Behavior

File opens in browser

Possible Solution

No response

Additional Information/Context

The signing code is running within a Lambda function, which has the permission to s3:GetObject for the bucket (I'm confident that this is correct as it works for aws-sdk v2). To my understanding, the credentials will be automatically injected via the Lambda env variables.

The code to sign URLs does work if I don't use a custom domain, i.e. const s3 = new S3();

The signed URLs are served via CloudFront (i.e. browser -> CloudFront -> S3). Note: I don't restrict viewer access for CloudFront - only for S3 (i.e. they aren't public there).

RanVaknin commented 10 months ago

Hi @ronnyroeller ,

Thanks for opening this issue and for your patience. I believe this issue is related to yours.

Can you confirm if in v2 you were using the s3BucketEndpoint flag to achieve the desired functionality?

Thanks, Ran~

ronnyroeller commented 10 months ago

Can you confirm if in v2 you were using the s3BucketEndpoint flag to achieve the desired functionality?

Indeed, we used s3BucketEndpoint. We replaced s3BucketEndpoint with forcePathStyle but then faced that this actually doesn't work the same.

tudou99 commented 10 months ago

Private domain name uploading files

RanVaknin commented 2 months ago

https://github.com/aws/aws-sdk-js-v3/issues/4394 closes this issue

github-actions[bot] commented 2 months ago

This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs and link to relevant comments in this thread.