Skip to content

Commit 3f3f3a3

Browse files
committed
fix(s3-request-presigner): enable overriding hoistable headers
1 parent be5d9f0 commit 3f3f3a3

File tree

3 files changed

+75
-45
lines changed

3 files changed

+75
-45
lines changed

packages/s3-request-presigner/README.md

Lines changed: 23 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,6 @@ generate signed url for S3.
1010

1111
You can generated presigned url from S3 client and command. Here's the example:
1212

13-
JavaScript Example:
14-
15-
```javascript
16-
const { getSignedUrl } = require("@aws-sdk/s3-request-presigner");
17-
const { S3Client, GetObjectCommand } = require("@aws-sdk/client-s3");
18-
const client = new S3Client(clientParams);
19-
const command = new GetObjectCommand(getObjectParams);
20-
const url = await getSignedUrl(client, command, { expiresIn: 3600 });
21-
```
22-
23-
ES6 Example
24-
2513
```javascript
2614
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
2715
import { S3Client, GetObjectCommand } from "@aws-sdk/client-s3";
@@ -34,32 +22,11 @@ You can get signed URL for other S3 operations too, like `PutObjectCommand`.
3422
`expiresIn` config from the examples above is optional. If not set, it's default
3523
at `900`.
3624

37-
If your request contains server-side encryption(`SSE*`) configurations, because
38-
of S3 limitation, you need to send corresponding headers along with the
39-
presigned url. For more information, please go to [S3 SSE reference](https://docs.aws.amazon.com/AmazonS3/latest/dev/KMSUsingRESTAPI.html)
40-
4125
If you already have a request, you can pre-sign the request following the
4226
section bellow.
4327

4428
### Get Presigned URL from an Existing Request
4529

46-
JavaScript Example:
47-
48-
```javascript
49-
const { S3RequestPresigner } = require("@aws-sdk/s3-request-presigner");
50-
const { Sha256 } = require("@aws-crypto/sha256-browser");
51-
const { Hash } = require("@smithy/hash-node");
52-
const signer = new S3RequestPresigner({
53-
region: regionProvider,
54-
credentials: credentialsProvider,
55-
sha256: Hash.bind(null, "sha256"), // In Node.js
56-
//sha256: Sha256 // In browsers
57-
});
58-
const presigned = await signer.presign(request);
59-
```
60-
61-
ES6 Example:
62-
6330
```javascript
6431
import { S3RequestPresigner } from "@aws-sdk/s3-request-presigner";
6532
import { Sha256 } from "@aws-crypto/sha256-browser";
@@ -84,13 +51,6 @@ const signer = new S3RequestPresigner({
8451
});
8552
```
8653

87-
If your request contains server-side encryption(`x-amz-server-side-encryption*`)
88-
headers, because of S3 limitation, you need to send these headers along
89-
with the presigned url. That is to say, the url only from calling `formatUrl()`
90-
to `presigned` is not sufficient to make a request. You need to send the
91-
server-side encryption headers along with the url. These headers remain in the
92-
`presigned.headers`
93-
9454
### Get Presigned URL with headers that cannot be signed
9555

9656
By using the `getSignedUrl` with a `S3Client` you are able to sign your
@@ -140,4 +100,26 @@ const presigned = getSignedUrl(s3Client, command, {
140100
});
141101
```
142102

143-
For more information, please go to [S3 SSE reference](https://docs.aws.amazon.com/AmazonS3/latest/dev/KMSUsingRESTAPI.html)
103+
### PutObject with use of `hoistableHeaders`
104+
105+
`hoistableHeaders` overrides the default behavior of not hoisting
106+
any headers that begin with `x-amz-*`.
107+
108+
```js
109+
// example: Server Side Encryption headers
110+
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
111+
import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";
112+
113+
const params = {
114+
Key: "...",
115+
Bucket: "...",
116+
ServerSideEncryption: "aws:kms",
117+
SSEKMSKeyId: "arn:aws:kms:us-west-2:0000:key/abcd-1234-abcd",
118+
};
119+
const s3Client = new S3Client();
120+
const command = new PutObjectCommand(params);
121+
122+
const preSignedUrl = await getSignedUrl(s3Client, command, {
123+
hoistableHeaders: new Set(["x-amz-server-side-encryption", "x-amz-server-side-encryption-aws-kms-key-id"]),
124+
});
125+
```

packages/s3-request-presigner/src/presigner.spec.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,28 @@ describe("s3 presigner", () => {
108108
expect(signedHeaders).toContain("x-amz-server-side-encryption-customer-algorithm");
109109
});
110110

111+
it("should allow hoisting server-side-encryption headers to query when overridden", async () => {
112+
const signer = new S3RequestPresigner(s3ResolvedConfig);
113+
const signed = await signer.presign(
114+
{
115+
...minimalRequest,
116+
headers: {
117+
...minimalRequest.headers,
118+
"x-amz-server-side-encryption": "kms",
119+
"x-amz-server-side-encryption-customer-algorithm": "AES256",
120+
},
121+
},
122+
{
123+
hoistableHeaders: new Set(["x-amz-server-side-encryption", "x-amz-server-side-encryption-customer-algorithm"]),
124+
}
125+
);
126+
const signedHeadersHeader = signed.query?.["X-Amz-SignedHeaders"];
127+
const signedHeaders =
128+
typeof signedHeadersHeader === "string" ? signedHeadersHeader.split(";") : signedHeadersHeader;
129+
expect(signedHeaders).not.toContain("x-amz-server-side-encryption");
130+
expect(signedHeaders).not.toContain("x-amz-server-side-encryption-customer-algorithm");
131+
});
132+
111133
it("should inject host header with port if not supplied", async () => {
112134
const signer = new S3RequestPresigner(s3ResolvedConfig);
113135
const port = 12345;

packages/s3-request-presigner/src/presigner.ts

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,17 @@ export class S3RequestPresigner implements RequestPresigner {
2626

2727
public presign(
2828
requestToSign: IHttpRequest,
29-
{ unsignableHeaders = new Set(), unhoistableHeaders = new Set(), ...options }: RequestPresigningArguments = {}
29+
{
30+
unsignableHeaders = new Set(),
31+
hoistableHeaders = new Set(),
32+
unhoistableHeaders = new Set(),
33+
...options
34+
}: RequestPresigningArguments = {}
3035
): Promise<IHttpRequest> {
3136
this.prepareRequest(requestToSign, {
3237
unsignableHeaders,
3338
unhoistableHeaders,
39+
hoistableHeaders,
3440
});
3541
return this.signer.presign(requestToSign, {
3642
expiresIn: 900,
@@ -43,11 +49,17 @@ export class S3RequestPresigner implements RequestPresigner {
4349
public presignWithCredentials(
4450
requestToSign: IHttpRequest,
4551
credentials: AwsCredentialIdentity,
46-
{ unsignableHeaders = new Set(), unhoistableHeaders = new Set(), ...options }: RequestPresigningArguments = {}
52+
{
53+
unsignableHeaders = new Set(),
54+
hoistableHeaders = new Set(),
55+
unhoistableHeaders = new Set(),
56+
...options
57+
}: RequestPresigningArguments = {}
4758
): Promise<IHttpRequest> {
4859
this.prepareRequest(requestToSign, {
4960
unsignableHeaders,
5061
unhoistableHeaders,
62+
hoistableHeaders,
5163
});
5264
return this.signer.presignWithCredentials(requestToSign, credentials, {
5365
expiresIn: 900,
@@ -59,15 +71,29 @@ export class S3RequestPresigner implements RequestPresigner {
5971

6072
private prepareRequest(
6173
requestToSign: IHttpRequest,
62-
{ unsignableHeaders = new Set(), unhoistableHeaders = new Set() }: RequestPresigningArguments = {}
74+
{
75+
unsignableHeaders = new Set(),
76+
unhoistableHeaders = new Set(),
77+
hoistableHeaders = new Set(),
78+
}: RequestPresigningArguments = {}
6379
) {
6480
unsignableHeaders.add("content-type");
81+
6582
Object.keys(requestToSign.headers)
6683
.map((header) => header.toLowerCase())
6784
.filter((header) => header.startsWith("x-amz-server-side-encryption"))
6885
.forEach((header) => {
69-
unhoistableHeaders.add(header);
86+
if (!hoistableHeaders.has(header)) {
87+
/**
88+
* For smoother backwards compatibility with pre-GA PR
89+
* https://github.com/aws/aws-sdk-js-v3/issues/1576,
90+
* x-amz-sse headers are by default unhoisted,
91+
* but can be overridden.
92+
*/
93+
unhoistableHeaders.add(header);
94+
}
7095
});
96+
7197
requestToSign.headers[SHA256_HEADER] = UNSIGNED_PAYLOAD;
7298

7399
const currentHostHeader = requestToSign.headers.host;

0 commit comments

Comments
 (0)