Skip to content

Commit

Permalink
fix(s3-request-presigner): enable overriding hoistable headers
Browse files Browse the repository at this point in the history
  • Loading branch information
kuhe committed Sep 30, 2024
1 parent dddd164 commit c80a162
Show file tree
Hide file tree
Showing 3 changed files with 75 additions and 45 deletions.
64 changes: 23 additions & 41 deletions packages/s3-request-presigner/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,6 @@ generate signed url for S3.

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

JavaScript Example:

```javascript
const { getSignedUrl } = require("@aws-sdk/s3-request-presigner");
const { S3Client, GetObjectCommand } = require("@aws-sdk/client-s3");
const client = new S3Client(clientParams);
const command = new GetObjectCommand(getObjectParams);
const url = await getSignedUrl(client, command, { expiresIn: 3600 });
```

ES6 Example

```javascript
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
import { S3Client, GetObjectCommand } from "@aws-sdk/client-s3";
Expand All @@ -34,32 +22,11 @@ You can get signed URL for other S3 operations too, like `PutObjectCommand`.
`expiresIn` config from the examples above is optional. If not set, it's default
at `900`.

If your request contains server-side encryption(`SSE*`) configurations, because
of S3 limitation, you need to send corresponding headers along with the
presigned url. For more information, please go to [S3 SSE reference](https://docs.aws.amazon.com/AmazonS3/latest/dev/KMSUsingRESTAPI.html)

If you already have a request, you can pre-sign the request following the
section bellow.

### Get Presigned URL from an Existing Request

JavaScript Example:

```javascript
const { S3RequestPresigner } = require("@aws-sdk/s3-request-presigner");
const { Sha256 } = require("@aws-crypto/sha256-browser");
const { Hash } = require("@smithy/hash-node");
const signer = new S3RequestPresigner({
region: regionProvider,
credentials: credentialsProvider,
sha256: Hash.bind(null, "sha256"), // In Node.js
//sha256: Sha256 // In browsers
});
const presigned = await signer.presign(request);
```

ES6 Example:

```javascript
import { S3RequestPresigner } from "@aws-sdk/s3-request-presigner";
import { Sha256 } from "@aws-crypto/sha256-browser";
Expand All @@ -84,13 +51,6 @@ const signer = new S3RequestPresigner({
});
```

If your request contains server-side encryption(`x-amz-server-side-encryption*`)
headers, because of S3 limitation, you need to send these headers along
with the presigned url. That is to say, the url only from calling `formatUrl()`
to `presigned` is not sufficient to make a request. You need to send the
server-side encryption headers along with the url. These headers remain in the
`presigned.headers`

### Get Presigned URL with headers that cannot be signed

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

For more information, please go to [S3 SSE reference](https://docs.aws.amazon.com/AmazonS3/latest/dev/KMSUsingRESTAPI.html)
### PutObject with use of `hoistableHeaders`

`hoistableHeaders` overrides the default behavior of not hoisting
any headers that begin with `x-amz-*`.

```js
// example: Server Side Encryption headers
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";

const params = {
Key: "...",
Bucket: "...",
ServerSideEncryption: "aws:kms",
SSEKMSKeyId: "arn:aws:kms:us-west-2:0000:key/abcd-1234-abcd",
};
const s3Client = new S3Client();
const command = new PutObjectCommand(params);

const preSignedUrl = await getSignedUrl(s3Client, command, {
hoistableHeaders: new Set(["x-amz-server-side-encryption", "x-amz-server-side-encryption-aws-kms-key-id"]),
});
```
22 changes: 22 additions & 0 deletions packages/s3-request-presigner/src/presigner.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,28 @@ describe("s3 presigner", () => {
expect(signedHeaders).toContain("x-amz-server-side-encryption-customer-algorithm");
});

it("should allow hoisting server-side-encryption headers to query when overridden", async () => {
const signer = new S3RequestPresigner(s3ResolvedConfig);
const signed = await signer.presign(
{
...minimalRequest,
headers: {
...minimalRequest.headers,
"x-amz-server-side-encryption": "kms",
"x-amz-server-side-encryption-customer-algorithm": "AES256",
},
},
{
hoistableHeaders: new Set(["x-amz-server-side-encryption", "x-amz-server-side-encryption-customer-algorithm"]),
}
);
const signedHeadersHeader = signed.query?.["X-Amz-SignedHeaders"];
const signedHeaders =
typeof signedHeadersHeader === "string" ? signedHeadersHeader.split(";") : signedHeadersHeader;
expect(signedHeaders).not.toContain("x-amz-server-side-encryption");
expect(signedHeaders).not.toContain("x-amz-server-side-encryption-customer-algorithm");
});

it("should inject host header with port if not supplied", async () => {
const signer = new S3RequestPresigner(s3ResolvedConfig);
const port = 12345;
Expand Down
34 changes: 30 additions & 4 deletions packages/s3-request-presigner/src/presigner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,17 @@ export class S3RequestPresigner implements RequestPresigner {

public presign(
requestToSign: IHttpRequest,
{ unsignableHeaders = new Set(), unhoistableHeaders = new Set(), ...options }: RequestPresigningArguments = {}
{
unsignableHeaders = new Set(),
hoistableHeaders = new Set(),
unhoistableHeaders = new Set(),
...options
}: RequestPresigningArguments = {}
): Promise<IHttpRequest> {
this.prepareRequest(requestToSign, {
unsignableHeaders,
unhoistableHeaders,
hoistableHeaders,
});
return this.signer.presign(requestToSign, {
expiresIn: 900,
Expand All @@ -43,11 +49,17 @@ export class S3RequestPresigner implements RequestPresigner {
public presignWithCredentials(
requestToSign: IHttpRequest,
credentials: AwsCredentialIdentity,
{ unsignableHeaders = new Set(), unhoistableHeaders = new Set(), ...options }: RequestPresigningArguments = {}
{
unsignableHeaders = new Set(),
hoistableHeaders = new Set(),
unhoistableHeaders = new Set(),
...options
}: RequestPresigningArguments = {}
): Promise<IHttpRequest> {
this.prepareRequest(requestToSign, {
unsignableHeaders,
unhoistableHeaders,
hoistableHeaders,
});
return this.signer.presignWithCredentials(requestToSign, credentials, {
expiresIn: 900,
Expand All @@ -59,15 +71,29 @@ export class S3RequestPresigner implements RequestPresigner {

private prepareRequest(
requestToSign: IHttpRequest,
{ unsignableHeaders = new Set(), unhoistableHeaders = new Set() }: RequestPresigningArguments = {}
{
unsignableHeaders = new Set(),
unhoistableHeaders = new Set(),
hoistableHeaders = new Set(),
}: RequestPresigningArguments = {}
) {
unsignableHeaders.add("content-type");

Object.keys(requestToSign.headers)
.map((header) => header.toLowerCase())
.filter((header) => header.startsWith("x-amz-server-side-encryption"))
.forEach((header) => {
unhoistableHeaders.add(header);
if (!hoistableHeaders.has(header)) {
/**
* For smoother backwards compatibility with pre-GA PR
* https://github.com/aws/aws-sdk-js-v3/issues/1576,
* x-amz-sse headers are by default unhoisted,
* but can be overridden.
*/
unhoistableHeaders.add(header);
}
});

requestToSign.headers[SHA256_HEADER] = UNSIGNED_PAYLOAD;

const currentHostHeader = requestToSign.headers.host;
Expand Down

0 comments on commit c80a162

Please sign in to comment.