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 15 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 6 commits
Commits
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
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import { HttpRequest } from "@smithy/protocol-http";

jest.mock("@smithy/signature-v4");

jest.mock("@aws-sdk/signature-v4-crt");
jest.mock("@smithy/signature-v4a");

import { CrtSignerV4 } from "@aws-sdk/signature-v4-crt";
import { SignatureV4 } from "@smithy/signature-v4";
import { SignatureV4a } from "@smithy/signature-v4a";

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

describe("SignatureV4MultiRegion", () => {
Expand All @@ -28,6 +30,7 @@ describe("SignatureV4MultiRegion", () => {

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

Expand Down Expand Up @@ -59,33 +62,41 @@ describe("SignatureV4MultiRegion", () => {
expect(CrtSignerV4.mock.instances[0].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: "*" });
//@ts-ignore
expect(SignatureV4a.mock.instances[0].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: "*" });
//@ts-ignore
expect(SignatureV4a.mock.instances[0].sign).toBeCalledTimes(1);
});

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;
const signer = new SignatureV4MultiRegion(params);
await signer.sign(minimalRequest, { signingRegion: "*" });
expect(SignatureV4a).toHaveBeenCalledTimes(1);
});

it("should throw if sign with SigV4a and signature-v4-crt is not installed", async () => {
it("should throw if neither CrtSignerV4 nor SignatureV4a is available", async () => {
signatureV4CrtContainer.CrtSignerV4 = null;
signatureV4aContainer.SignatureV4a = 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` +
"For more information please go to " +
"https://github.com/aws/aws-sdk-js-v3#functionality-requiring-aws-common-runtime-crt"
"Neither CRT nor JS SigV4a implementation is available. " +
"Please load either @aws-sdk/signature-v4-crt or @smithy/signature-v4a."
siddsriv marked this conversation as resolved.
Show resolved Hide resolved
);
});
});
70 changes: 44 additions & 26 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,52 @@ 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;
let JsSigV4aSigner: OptionalSigV4aSigner | null = null;

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 (signatureV4CrtContainer.CrtSignerV4) {
try {
CrtSignerV4 = signatureV4CrtContainer.CrtSignerV4;
if (typeof CrtSignerV4 !== "function") throw new Error();
siddsriv marked this conversation as resolved.
Show resolved Hide resolved
} 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;
}

this.sigv4aSigner = new CrtSignerV4({
...this.signerOptions,
signingAlgorithm: 1,
});
} else if (signatureV4aContainer.SignatureV4a) {
try {
JsSigV4aSigner = signatureV4aContainer.SignatureV4a;
if (typeof JsSigV4aSigner !== "function") throw new Error();
} catch (e) {
e.message =
`${e.message}\n` +
`Please check whether you have installed the "@smithy/signature-v4a" package explicitly. \n` +
`You must also register the package by calling [require("@smithy/signature-v4a");] ` +
`or an ESM equivalent such as [import "@smithy/signature-v4a";]. \n`;
throw e;
siddsriv marked this conversation as resolved.
Show resolved Hide resolved
}

this.sigv4aSigner = new CrtSignerV4({
...this.signerOptions,
signingAlgorithm: 1,
});
this.sigv4aSigner = new JsSigV4aSigner({
...this.signerOptions,
});
} else {
throw new Error(
"Neither CRT nor JS SigV4a implementation is available. " +
"Please load either @aws-sdk/signature-v4-crt or @smithy/signature-v4a."
);
}
}
return this.sigv4aSigner;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import type { AwsCredentialIdentity } from "@aws-sdk/types";
import type { HttpRequest, RequestPresigner, RequestSigner, RequestSigningArguments } from "@smithy/types";

/**
* @public
*/
export type OptionalSigV4aSigner = {
/**
* This constructor is not typed so as not to require a type import
* from the signature-v4a package.
*
* The true type is SignatureV4a from @smithy/signature-v4a.
*/
new (options: any): RequestPresigner &
RequestSigner & {
signWithCredentials(
requestToSign: HttpRequest,
credentials: AwsCredentialIdentity,
options: RequestSigningArguments
): Promise<HttpRequest>;
};
};

/**
* @public
*
* \@smithy/signature-v4a will install the constructor in this
* container if it is installed.
*
* This avoids a runtime-require being interpreted statically by bundlers.
*/
export const signatureV4aContainer: {
SignatureV4a: null | OptionalSigV4aSigner;
} = {
SignatureV4a: null,
};
Loading