Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(signature-v4-multi-region): add support for sigv4a package #6267

Draft
wants to merge 20 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
f052f03
feat(signature-v4-multi-region): add support for sigv4a package
siddsriv Jul 10, 2024
81ddf6c
chore(signature-v4-multi-region): package.json update for adding sigv…
siddsriv Jul 10, 2024
9cd37b2
fix(signature-v4-multi-region): container based loading for CRT and J…
siddsriv Jul 10, 2024
c4f2e54
chore(signature-v4-multi-region): de-async getSigV4a owing to containers
siddsriv Jul 10, 2024
b4f86d9
chore(signature-v4-multi-region): cleanups and docstrings
siddsriv Jul 11, 2024
2a954d4
test(signature-v4-multi-region): add unit tests
siddsriv Jul 11, 2024
b3ce48a
chore(README): readme notes for using sigv4a package
siddsriv Aug 15, 2024
655ddba
fix(signature-v4-multi-region): add check for node and error messages
siddsriv Aug 15, 2024
fcb8b20
test(signature-v4-multi-region): add unit tests
siddsriv Aug 20, 2024
959eafc
test(signature-v4-multi-region): add e2e test for cloudfront-keyvalue…
siddsriv Aug 20, 2024
a9b7f8f
test(signature-v4-multi-region): add e2e test for eventbridge
siddsriv Aug 20, 2024
19b2750
test(signature-v4-multi-region): e2e test runner configs
siddsriv Aug 20, 2024
7bd6a61
test(signature-v4-multi-region): events signingName fix
siddsriv Aug 20, 2024
3dfab74
test(signature-v4-multi-region): add s3 e2e test
siddsriv Aug 21, 2024
aa227cc
test(signature-v4-multi-region): initial browser test
siddsriv Sep 4, 2024
42121bd
test(signature-v4-multi-region): move testing to vitest
siddsriv Apr 1, 2025
b3148bc
test(signature-v4-multi-region): vitest version
siddsriv Apr 1, 2025
b5b0646
chore(signature-v4-multi-region): resolve deps
siddsriv Apr 1, 2025
6a26a14
Merge branch 'main' into sid/feat/sigv4a
siddsriv Apr 1, 2025
6227cf1
test(signature-v4-multi-region): update test command
siddsriv Apr 1, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -698,6 +698,48 @@ import "@aws-sdk/signature-v4-crt";
Only the import statement is needed. The implementation then registers itself with `@aws-sdk/signature-v4-multi-region`
and becomes available for its use. You do not need to use any imported objects directly.

Note that you can also use the native JavaScript implementation of SigV4a that does not depend on AWS CRT and can be used in browsers (unlike the CRT version). The instructions for using that package are below.

### Using JavaScript (non-CRT) implementation of SigV4a

AWS SDK for JavaScript v3 now supports a native JavaScript version of SigV4a that can be used in browsers and Node, and does not depend on [AWS Common Runtime (CRT)](https://docs.aws.amazon.com/sdkref/latest/guide/common-runtime.html). This is an alternative to the AWS CRT version, so you don't need to have both packages together.

If neither the CRT components nor the JavaScript SigV4a implementation are installed, you will receive an error like:

```console
Neither CRT nor JS SigV4a implementation is available.
Please load either @aws-sdk/signature-v4-crt or @smithy/signature-v4a.
```

indicating that at least one of the required dependencies is missing to use the associated functionality. To install the JavaScript SigV4a dependency, follow the provided instructions.

#### Installing the JavaScript SigV4a Dependency

You can install the JavaScript SigV4a dependency with different commands depending on the package management tool you are using.
If you are using NPM:

```console
npm install @smithy/signature-v4a
```

If you are using Yarn:

```console
yarn add @smithy/signature-v4a
```

Additionally, load the signature-v4a package by importing it.

```js
require("@smithy/signature-v4a");
// or ESM
import "@smithy/signature-v4a";
```

Only the import statement is needed. The implementation then registers itself with `@aws-sdk/signature-v4-multi-region`
and becomes available for its use. You do not need to use any imported objects directly.
Note that if both the CRT-based implementation and the JavaScript implementation are available, the SDK will prefer to use the CRT-based implementation for better performance. If you specifically want to use the JavaScript implementation, ensure that the CRT package is not installed or imported.

#### Related issues

1. [S3 Multi-Region Access Point(MRAP) is not available unless with additional dependency](https://github.com/aws/aws-sdk-js-v3/issues/2822)
5 changes: 5 additions & 0 deletions packages/signature-v4-multi-region/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
"build:types:downlevel": "downlevel-dts dist-types dist-types/ts3.4",
"clean": "rimraf ./dist-* && rimraf *.tsbuildinfo",
"test": "yarn g:vitest run",
"test:e2e": "vitest run --config vitest.config.e2e.js",
"test:browser": "vitest run --config vitest.config.browser.js",
"test:watch": "yarn g:vitest watch"
},
"main": "./dist-cjs/index.js",
Expand All @@ -23,6 +25,9 @@
"dependencies": {
"@aws-sdk/middleware-sdk-s3": "*",
"@aws-sdk/types": "*",
"@aws-sdk/signature-v4-crt": "*",
"@aws-sdk/client-s3": "*",
"@smithy/signature-v4a": "^3.0.0",
"@smithy/protocol-http": "^5.1.0",
"@smithy/signature-v4": "^5.0.2",
"@smithy/types": "^4.2.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,16 @@ import { beforeEach, describe, expect, test as it, vi } from "vitest";
vi.mock("@smithy/signature-v4");
vi.mock("@aws-sdk/middleware-sdk-s3");
vi.mock("@aws-sdk/signature-v4-crt");
vi.mock("@smithy/signature-v4a");

import { SignatureV4S3Express } from "@aws-sdk/middleware-sdk-s3";
import { CrtSignerV4 } from "@aws-sdk/signature-v4-crt";
import { SignatureV4 } from "@smithy/signature-v4";
import { SignatureV4a } from "@smithy/signature-v4a";
import { Checksum } from "@smithy/types";

import { signatureV4CrtContainer } from "./signature-v4-crt-container";
import { signatureV4aContainer } from "./signature-v4a-container";
import { SignatureV4MultiRegion, SignatureV4MultiRegionInit } from "./SignatureV4MultiRegion";

describe("SignatureV4MultiRegion", () => {
Expand Down Expand Up @@ -44,65 +48,100 @@ describe("SignatureV4MultiRegion", () => {

beforeEach(() => {
signatureV4CrtContainer.CrtSignerV4 = CrtSignerV4 as any;
signatureV4aContainer.SignatureV4a = SignatureV4a as any;
vi.clearAllMocks();
});

it("should sign with SigV4 signer", async () => {
const signer = new SignatureV4MultiRegion(params);
await signer.sign(minimalRequest);

//@ts-ignore
expect(SignatureV4S3Express.mock.instances[0].sign).toBeCalledTimes(1);
expect(SignatureV4.prototype.sign).toBeCalledTimes(1);
});

it("should presign with SigV4 signer", async () => {
const signer = new SignatureV4MultiRegion(params);
await signer.presign(minimalRequest);
//@ts-ignore
expect(SignatureV4S3Express.mock.instances[0].presign).toBeCalledTimes(1);
expect(SignatureV4.prototype.presign).toBeCalledTimes(1);
});

it("should sign with SigV4a signer if mult_region option is set", async () => {
const signer = new SignatureV4MultiRegion(params);
await signer.presign(minimalRequest, { signingRegion: "*" });
//@ts-ignore
expect(CrtSignerV4.mock.instances[0].presign).toBeCalledTimes(1);
expect(CrtSignerV4.prototype.presign).toBeCalledTimes(1);
});

it("should presign with SigV4 signer", async () => {
const signer = new SignatureV4MultiRegion(params);
await signer.presign(minimalRequest, { signingRegion: "*" });
//@ts-ignore
expect(CrtSignerV4.mock.instances[0].presign).toBeCalledTimes(1);
expect(CrtSignerV4.prototype.presign).toBeCalledTimes(1);
});

it("should throw if sign with SigV4a in unsupported runtime", async () => {
expect.assertions(1);
const signer = new SignatureV4MultiRegion({ ...params, runtime: "browser" });
await expect(async () => await signer.sign(minimalRequest, { signingRegion: "*" })).rejects.toThrow(
"This request requires signing with SigV4Asymmetric algorithm. It's only available in Node.js"
);
it("should presign with SigV4a signer if signingRegion is '*'", async () => {
const signer = new SignatureV4MultiRegion(params);
await signer.presign(minimalRequest, { signingRegion: "*" });
expect(SignatureV4a.prototype.presign).toBeCalledTimes(1);
});

it("should throw if preSign with SigV4a in unsupported runtime", async () => {
expect.assertions(1);
const signer = new SignatureV4MultiRegion({ ...params, runtime: "browser" });
await expect(signer.presign(minimalRequest, { signingRegion: "*" })).rejects.toThrow(
"This request requires signing with SigV4Asymmetric algorithm. It's only available in Node.js"
);
it("should sign with SigV4a signer if signingRegion is '*'", async () => {
const signer = new SignatureV4MultiRegion(params);
await signer.sign(minimalRequest, { signingRegion: "*" });
expect(SignatureV4a.prototype.sign).toBeCalledTimes(1);
});

it("should throw if sign with SigV4a and signature-v4-crt is not installed", async () => {
it("should use CrtSignerV4 if available", async () => {
const signer = new SignatureV4MultiRegion(params);
await signer.sign(minimalRequest, { signingRegion: "*" });
expect(CrtSignerV4).toHaveBeenCalledTimes(1);
});

it("should use SignatureV4a if CrtSignerV4 is not available", async () => {
signatureV4CrtContainer.CrtSignerV4 = null;
expect.assertions(1);
const signer = new SignatureV4MultiRegion({ ...params });
await expect(async () => await signer.sign(minimalRequest, { signingRegion: "*" })).rejects.toThrow(
"\n" +
`Please check whether you have installed the "@aws-sdk/signature-v4-crt" package explicitly. \n` +
`You must also register the package by calling [require("@aws-sdk/signature-v4-crt");] ` +
`or an ESM equivalent such as [import "@aws-sdk/signature-v4-crt";]. \n` +
const signer = new SignatureV4MultiRegion(params);
await signer.sign(minimalRequest, { signingRegion: "*" });
expect(SignatureV4a).toHaveBeenCalledTimes(1);
});

it("should throw an error for presignWithCredentials with star region", async () => {
const signer = new SignatureV4MultiRegion(params);
const testCredentials = {
accessKeyId: "test-access-key",
secretAccessKey: "test-secret-key",
};
await expect(
signer.presignWithCredentials(minimalRequest, testCredentials, { signingRegion: "*" })
).rejects.toThrow("Method presignWithCredentials is not supported for [signingRegion=*].");
});

it("should throw an error if neither CrtSignerV4 nor JsSigV4aSigner is available in node runtime", async () => {
signatureV4CrtContainer.CrtSignerV4 = null;
signatureV4aContainer.SignatureV4a = null;
const signer = new SignatureV4MultiRegion(params);
await expect(signer.sign(minimalRequest, { signingRegion: "*" })).rejects.toThrow(
"Neither CRT nor JS SigV4a implementation is available. " +
"Please load either @aws-sdk/signature-v4-crt or @smithy/signature-v4a. " +
"For more information please go to " +
"https://github.com/aws/aws-sdk-js-v3#functionality-requiring-aws-common-runtime-crt"
);
});

it("should throw an error if JsSigV4aSigner is not available in non-node runtime", async () => {
const nonNodeParams = { ...params, runtime: "browser" };
signatureV4aContainer.SignatureV4a = null;
const signer = new SignatureV4MultiRegion(nonNodeParams);
await expect(signer.sign(minimalRequest, { signingRegion: "*" })).rejects.toThrow(
"JS SigV4a implementation is not available or not a valid constructor. " +
"Please check whether you have installed the @smithy/signature-v4a package explicitly. " +
"You must also register the package by calling [require('@smithy/signature-v4a');] " +
"or an ESM equivalent such as [import '@smithy/signature-v4a';]. " +
"For more information please go to " +
"https://github.com/aws/aws-sdk-js-v3#using-javascript-non-crt-implementation-of-sigv4a"
);
});

it("should use JsSigV4aSigner in non-node runtime", async () => {
const nonNodeParams = { ...params, runtime: "browser" };
const signer = new SignatureV4MultiRegion(nonNodeParams);
await signer.sign(minimalRequest, { signingRegion: "*" });
expect(SignatureV4a).toHaveBeenCalledTimes(1);
});
});
74 changes: 47 additions & 27 deletions packages/signature-v4-multi-region/src/SignatureV4MultiRegion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
} from "@smithy/types";

import { OptionalCrtSignerV4, signatureV4CrtContainer } from "./signature-v4-crt-container";
import { OptionalSigV4aSigner, signatureV4aContainer } from "./signature-v4a-container";

/**
* @internal
Expand All @@ -24,11 +25,10 @@ export type SignatureV4MultiRegionInit = SignatureV4Init &
* dynamically, the signer wraps native module SigV4a signer and JS SigV4 signer. It signs the request with SigV4a
* algorithm if the request needs to be signed with `*` region. Otherwise, it signs the request with normal SigV4
* signer.
* Note that SigV4a signer is only supported in Node.js now because it depends on a native dependency.
* @internal
*/
export class SignatureV4MultiRegion implements RequestPresigner, RequestSigner {
private sigv4aSigner?: InstanceType<OptionalCrtSignerV4>;
private sigv4aSigner?: InstanceType<OptionalCrtSignerV4> | InstanceType<OptionalSigV4aSigner>;
private readonly sigv4Signer: SignatureV4S3Express;
private readonly signerOptions: SignatureV4MultiRegionInit;

Expand All @@ -39,8 +39,6 @@ export class SignatureV4MultiRegion implements RequestPresigner, RequestSigner {

public async sign(requestToSign: HttpRequest, options: RequestSigningArguments = {}): Promise<HttpRequest> {
if (options.signingRegion === "*") {
if (this.signerOptions.runtime !== "node")
throw new Error("This request requires signing with SigV4Asymmetric algorithm. It's only available in Node.js");
return this.getSigv4aSigner().sign(requestToSign, options);
}
return this.sigv4Signer.sign(requestToSign, options);
Expand All @@ -55,17 +53,13 @@ export class SignatureV4MultiRegion implements RequestPresigner, RequestSigner {
options: RequestSigningArguments = {}
): Promise<HttpRequest> {
if (options.signingRegion === "*") {
if (this.signerOptions.runtime !== "node")
throw new Error("This request requires signing with SigV4Asymmetric algorithm. It's only available in Node.js");
return this.getSigv4aSigner().signWithCredentials(requestToSign, credentials, options);
}
return this.sigv4Signer.signWithCredentials(requestToSign, credentials, options);
}

public async presign(originalRequest: HttpRequest, options: RequestPresigningArguments = {}): Promise<HttpRequest> {
if (options.signingRegion === "*") {
if (this.signerOptions.runtime !== "node")
throw new Error("This request requires signing with SigV4Asymmetric algorithm. It's only available in Node.js");
return this.getSigv4aSigner().presign(originalRequest, options);
}
return this.sigv4Signer.presign(originalRequest, options);
Expand All @@ -82,28 +76,54 @@ export class SignatureV4MultiRegion implements RequestPresigner, RequestSigner {
return this.sigv4Signer.presignWithCredentials(originalRequest, credentials, options);
}

private getSigv4aSigner(): InstanceType<OptionalCrtSignerV4> {
private getSigv4aSigner(): InstanceType<OptionalCrtSignerV4> | InstanceType<OptionalSigV4aSigner> {
if (!this.sigv4aSigner) {
let CrtSignerV4: OptionalCrtSignerV4 | null = null;
const CrtSignerV4 = signatureV4CrtContainer.CrtSignerV4;
const JsSigV4aSigner = signatureV4aContainer.SignatureV4a;

try {
CrtSignerV4 = signatureV4CrtContainer.CrtSignerV4;
if (typeof CrtSignerV4 !== "function") throw new Error();
} catch (e) {
e.message =
`${e.message}\n` +
`Please check whether you have installed the "@aws-sdk/signature-v4-crt" package explicitly. \n` +
`You must also register the package by calling [require("@aws-sdk/signature-v4-crt");] ` +
`or an ESM equivalent such as [import "@aws-sdk/signature-v4-crt";]. \n` +
"For more information please go to " +
"https://github.com/aws/aws-sdk-js-v3#functionality-requiring-aws-common-runtime-crt";
throw e;
}
if (this.signerOptions.runtime === "node") {
if (!CrtSignerV4 && !JsSigV4aSigner) {
throw new Error(
"Neither CRT nor JS SigV4a implementation is available. " +
"Please load either @aws-sdk/signature-v4-crt or @smithy/signature-v4a. " +
"For more information please go to " +
"https://github.com/aws/aws-sdk-js-v3#functionality-requiring-aws-common-runtime-crt"
);
}

if (CrtSignerV4 && typeof CrtSignerV4 === "function") {
this.sigv4aSigner = new CrtSignerV4({
...this.signerOptions,
signingAlgorithm: 1,
});
} else if (JsSigV4aSigner && typeof JsSigV4aSigner === "function") {
this.sigv4aSigner = new JsSigV4aSigner({
...this.signerOptions,
});
} else {
throw new Error(
"Available SigV4a implementation is not a valid constructor. " +
"Please ensure you've properly imported @aws-sdk/signature-v4-crt or @smithy/signature-v4a." +
"For more information please go to " +
"https://github.com/aws/aws-sdk-js-v3#functionality-requiring-aws-common-runtime-crt"
);
}
} else {
if (!JsSigV4aSigner || typeof JsSigV4aSigner !== "function") {
throw new Error(
"JS SigV4a implementation is not available or not a valid constructor. " +
"Please check whether you have installed the @smithy/signature-v4a package explicitly. " +
"You must also register the package by calling [require('@smithy/signature-v4a');] " +
"or an ESM equivalent such as [import '@smithy/signature-v4a';]. " +
"For more information please go to " +
"https://github.com/aws/aws-sdk-js-v3#using-javascript-non-crt-implementation-of-sigv4a"
);
}

this.sigv4aSigner = new CrtSignerV4({
...this.signerOptions,
signingAlgorithm: 1,
});
this.sigv4aSigner = new JsSigV4aSigner({
...this.signerOptions,
});
}
}
return this.sigv4aSigner;
}
Expand Down
Loading
Loading