Skip to content

Commit a2635af

Browse files
authored
feat: support per-request region and service override in signer (#1444)
* feat: support per-request region and service override in signer * feat(signature-v4): address review feedbacks
1 parent e693c98 commit a2635af

File tree

5 files changed

+206
-59
lines changed

5 files changed

+206
-59
lines changed

Diff for: packages/middleware-signing/src/middleware.spec.ts

+25-3
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,17 @@ import { awsAuthMiddleware } from "./middleware";
55

66
describe("SigningHandler", () => {
77
const noOpSigner: RequestSigner = {
8-
sign: (request: HttpRequest, options: { signingDate: Date }) =>
8+
sign: jest.fn().mockImplementation((request: HttpRequest, options: { signingDate: Date }) =>
99
Promise.resolve({
1010
...request,
1111
headers: {
1212
...request.headers,
1313
signed: "true",
1414
signingDateTime: options.signingDate.getTime(),
1515
},
16-
}),
17-
} as any;
16+
})
17+
) as any,
18+
};
1819
const noOpNext = jest.fn().mockReturnValue({ response: "" });
1920

2021
beforeEach(() => {
@@ -36,6 +37,27 @@ describe("SigningHandler", () => {
3637
expect(calls[0][0].request.headers.signed).toBe("true");
3738
});
3839

40+
it("should call the signer with the region and service overrides from context", async () => {
41+
(noOpSigner.sign as jest.Mock).mockClear();
42+
const handlerContext = {
43+
signing_region: "us-foo-1",
44+
signing_service: "BAR",
45+
};
46+
const signingHandler = awsAuthMiddleware({ signer: noOpSigner } as any)(noOpNext, handlerContext as any);
47+
await signingHandler({
48+
input: {},
49+
request: new HttpRequest({
50+
headers: {},
51+
}),
52+
});
53+
54+
expect(noOpSigner.sign).toBeCalled();
55+
expect((noOpSigner.sign as jest.Mock).mock.calls[0][1]).toMatchObject({
56+
signingRegion: handlerContext.signing_region,
57+
signingService: handlerContext.signing_service,
58+
});
59+
});
60+
3961
it("should add systemClockOffset while signing the request", async () => {
4062
expect.assertions(3);
4163
const systemClockOffset = 1000000;

Diff for: packages/middleware-signing/src/middleware.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
FinalizeHandlerArguments,
55
FinalizeHandlerOutput,
66
FinalizeRequestMiddleware,
7+
HandlerExecutionContext,
78
Pluggable,
89
RelativeMiddlewareOptions,
910
} from "@aws-sdk/types";
@@ -18,14 +19,16 @@ const getSkewCorrectedDate = (systemClockOffset: number) => new Date(Date.now()
1819
export function awsAuthMiddleware<Input extends object, Output extends object>(
1920
options: AwsAuthResolvedConfig
2021
): FinalizeRequestMiddleware<Input, Output> {
21-
return (next: FinalizeHandler<Input, Output>): FinalizeHandler<Input, Output> =>
22+
return (next: FinalizeHandler<Input, Output>, context: HandlerExecutionContext): FinalizeHandler<Input, Output> =>
2223
async function (args: FinalizeHandlerArguments<Input>): Promise<FinalizeHandlerOutput<Output>> {
2324
if (!HttpRequest.isInstance(args.request)) return next(args);
2425
const signer = typeof options.signer === "function" ? await options.signer() : options.signer;
2526
const output = await next({
2627
...args,
2728
request: await signer.sign(args.request, {
2829
signingDate: new Date(Date.now() + options.systemClockOffset),
30+
signingRegion: context["signing_region"],
31+
signingService: context["signing_service"],
2932
}),
3033
});
3134

Diff for: packages/signature-v4/src/SignatureV4.spec.ts

+100-18
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,17 @@ import {
2020
import { SignatureV4 } from "./SignatureV4";
2121
import { iso8601 } from "./utilDate";
2222

23-
const signer = new SignatureV4({
23+
const signerInit = {
2424
service: "foo",
2525
region: "us-bar-1",
2626
sha256: Sha256,
2727
credentials: {
2828
accessKeyId: "foo",
2929
secretAccessKey: "bar",
3030
},
31-
});
31+
};
32+
33+
const signer = new SignatureV4(signerInit);
3234

3335
const minimalRequest = new HttpRequest({
3436
method: "POST",
@@ -64,6 +66,27 @@ describe("SignatureV4", () => {
6466
});
6567
});
6668

69+
it("should support overriding region and service in the signer instance", async () => {
70+
const signer = new SignatureV4({
71+
...signerInit,
72+
service: "qux",
73+
region: "us-foo-1",
74+
});
75+
const { query } = await signer.presign(minimalRequest, {
76+
...presigningOptions,
77+
signingService: signerInit.service,
78+
signingRegion: signerInit.region,
79+
});
80+
expect(query).toEqual({
81+
[ALGORITHM_QUERY_PARAM]: ALGORITHM_IDENTIFIER,
82+
[CREDENTIAL_QUERY_PARAM]: "foo/20000101/us-bar-1/foo/aws4_request",
83+
[AMZ_DATE_QUERY_PARAM]: "20000101T000000Z",
84+
[EXPIRES_QUERY_PARAM]: presigningOptions.expiresIn.toString(),
85+
[SIGNED_HEADERS_QUERY_PARAM]: HOST_HEADER,
86+
[SIGNATURE_QUERY_PARAM]: "46f0091f3e84cbd4552a184f43830a4f8b42fd18ceaefcdc2c225be1efd9e00e",
87+
});
88+
});
89+
6790
it("should default expires to 3600 seconds if not explicitly passed", async () => {
6891
const { query } = await signer.presign(minimalRequest);
6992
expect(query).toMatchObject({
@@ -313,6 +336,22 @@ describe("SignatureV4", () => {
313336
);
314337
});
315338

339+
it("should support overriding region and service in the signer instance", async () => {
340+
const signer = new SignatureV4({
341+
...signerInit,
342+
service: "qux",
343+
region: "us-foo-1",
344+
});
345+
const { headers } = await signer.sign(minimalRequest, {
346+
signingDate: new Date("2000-01-01T00:00:00.000Z"),
347+
signingService: signerInit.service,
348+
signingRegion: signerInit.region,
349+
});
350+
expect(headers[AUTH_HEADER]).toBe(
351+
"AWS4-HMAC-SHA256 Credential=foo/20000101/us-bar-1/foo/aws4_request, SignedHeaders=host;x-amz-content-sha256;x-amz-date, Signature=1e3b24fcfd7655c0c245d99ba7b6b5ca6174eab903ebfbda09ce457af062ad30"
352+
);
353+
});
354+
316355
it("should sign requests without host header", async () => {
317356
const request = minimalRequest.clone();
318357
delete request.headers[HOST_HEADER];
@@ -556,37 +595,78 @@ describe("SignatureV4", () => {
556595
});
557596

558597
describe("#sign (string)", () => {
598+
const signerInit = {
599+
service: "s3",
600+
region: "us-east-1",
601+
credentials: {
602+
accessKeyId: "AKIAIOSFODNN7EXAMPLE",
603+
secretAccessKey: "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
604+
},
605+
sha256: Sha256,
606+
};
607+
559608
it("should produce signatures matching known outputs", async () => {
560609
// Example copied from https://github.com/aws/aws-sdk-php/blob/3.42.0/tests/S3/PostObjectV4Test.php#L37
561-
const signer = new SignatureV4({
562-
service: "s3",
563-
region: "us-east-1",
564-
credentials: {
565-
accessKeyId: "AKIAIOSFODNN7EXAMPLE",
566-
secretAccessKey: "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
567-
},
568-
sha256: Sha256,
569-
});
610+
const signer = new SignatureV4(signerInit);
570611
const signingDate = new Date("2015-12-29T00:00:00Z");
571612
const stringToSign =
572613
"eyJleHBpcmF0aW9uIjoiMjAxNS0xMi0yOVQwMTowMDowMFoiLCJjb25kaXRpb25zIjpbeyJidWNrZXQiOiJzaWd2NGV4YW1wbGVidWNrZXQifSxbInN0YXJ0cy13aXRoIiwiJGtleSIsInVzZXJcL3VzZXIxXC8iXSx7ImFjbCI6InB1YmxpYy1yZWFkIn0seyJzdWNjZXNzX2FjdGlvbl9yZWRpcmVjdCI6Imh0dHA6XC9cL3NpZ3Y0ZXhhbXBsZWJ1Y2tldC5zMy5hbWF6b25hd3MuY29tXC9zdWNjZXNzZnVsX3VwbG9hZC5odG1sIn0sWyJzdGFydHMtd2l0aCIsIiRDb250ZW50LVR5cGUiLCJpbWFnZVwvIl0seyJ4LWFtei1tZXRhLXV1aWQiOiIxNDM2NTEyMzY1MTI3NCJ9LHsieC1hbXotc2VydmVyLXNpZGUtZW5jcnlwdGlvbiI6IkFFUzI1NiJ9LFsic3RhcnRzLXdpdGgiLCIkeC1hbXotbWV0YS10YWciLCIiXSx7IlgtQW16LURhdGUiOiIyMDE1MTIyOVQwMDAwWiJ9LHsiWC1BbXotQ3JlZGVudGlhbCI6IkFLSUFJT1NGT0ROTjdFWEFNUExFXC8yMDE1MTIyOVwvdXMtZWFzdC0xXC9zM1wvYXdzNF9yZXF1ZXN0In0seyJYLUFtei1BbGdvcml0aG0iOiJBV1M0LUhNQUMtU0hBMjU2In1dfQ==";
573614
expect(await signer.sign(stringToSign, { signingDate })).toBe(
574615
"683963a1575bb197c642490ac60f3f08cda08233cd3a163ad31b554e9327a3ff"
575616
);
576617
});
618+
619+
it("should support overriding region and service in the signer instance", async () => {
620+
const signer = new SignatureV4({
621+
...signerInit,
622+
service: "qux",
623+
region: "us-foo-1",
624+
});
625+
const signingDate = new Date("2015-12-29T00:00:00Z");
626+
const stringToSign =
627+
"eyJleHBpcmF0aW9uIjoiMjAxNS0xMi0yOVQwMTowMDowMFoiLCJjb25kaXRpb25zIjpbeyJidWNrZXQiOiJzaWd2NGV4YW1wbGVidWNrZXQifSxbInN0YXJ0cy13aXRoIiwiJGtleSIsInVzZXJcL3VzZXIxXC8iXSx7ImFjbCI6InB1YmxpYy1yZWFkIn0seyJzdWNjZXNzX2FjdGlvbl9yZWRpcmVjdCI6Imh0dHA6XC9cL3NpZ3Y0ZXhhbXBsZWJ1Y2tldC5zMy5hbWF6b25hd3MuY29tXC9zdWNjZXNzZnVsX3VwbG9hZC5odG1sIn0sWyJzdGFydHMtd2l0aCIsIiRDb250ZW50LVR5cGUiLCJpbWFnZVwvIl0seyJ4LWFtei1tZXRhLXV1aWQiOiIxNDM2NTEyMzY1MTI3NCJ9LHsieC1hbXotc2VydmVyLXNpZGUtZW5jcnlwdGlvbiI6IkFFUzI1NiJ9LFsic3RhcnRzLXdpdGgiLCIkeC1hbXotbWV0YS10YWciLCIiXSx7IlgtQW16LURhdGUiOiIyMDE1MTIyOVQwMDAwWiJ9LHsiWC1BbXotQ3JlZGVudGlhbCI6IkFLSUFJT1NGT0ROTjdFWEFNUExFXC8yMDE1MTIyOVwvdXMtZWFzdC0xXC9zM1wvYXdzNF9yZXF1ZXN0In0seyJYLUFtei1BbGdvcml0aG0iOiJBV1M0LUhNQUMtU0hBMjU2In1dfQ==";
628+
expect(
629+
await signer.sign(stringToSign, {
630+
signingDate,
631+
signingRegion: signerInit.region,
632+
signingService: signerInit.service,
633+
})
634+
).toBe("683963a1575bb197c642490ac60f3f08cda08233cd3a163ad31b554e9327a3ff");
635+
});
577636
});
578637

579638
describe("#sign (event)", () => {
580639
//adopt to Ruby SDK: https://github.com/aws/aws-sdk-ruby/blob/3c47c05aa77bdbb7b803a3ff932b3a89c32276ac/gems/aws-sigv4/spec/signer_spec.rb#L274
640+
const signerInit = {
641+
service: "SERVICE",
642+
region: "REGION",
643+
credentials: {
644+
accessKeyId: "akid",
645+
secretAccessKey: "secret",
646+
},
647+
sha256: Sha256,
648+
};
649+
581650
it("support event signing", async () => {
582-
const signer = new SignatureV4({
583-
service: "SERVICE",
584-
region: "REGION",
585-
credentials: {
586-
accessKeyId: "akid",
587-
secretAccessKey: "secret",
651+
const signer = new SignatureV4(signerInit);
652+
const eventSignature = await signer.sign(
653+
{
654+
headers: Uint8Array.from([5, 58, 100, 97, 116, 101, 8, 0, 0, 1, 103, 247, 125, 87, 112]),
655+
payload: "foo" as any,
588656
},
589-
sha256: Sha256,
657+
{
658+
signingDate: new Date(1369353600000),
659+
priorSignature: "",
660+
}
661+
);
662+
expect(eventSignature).toEqual("204bb5e2713e95354680e9522986d3ac0304aeafd33397f39e6540ca51ffe226");
663+
});
664+
665+
it("should support overriding region and service in the signer instance", async () => {
666+
const signer = new SignatureV4({
667+
...signerInit,
668+
service: "qux",
669+
// region: "us-foo-1",
590670
});
591671
const eventSignature = await signer.sign(
592672
{
@@ -596,6 +676,8 @@ describe("SignatureV4", () => {
596676
{
597677
signingDate: new Date(1369353600000),
598678
priorSignature: "",
679+
// signingRegion: signerInit.region,
680+
signingService: signerInit.service,
599681
}
600682
);
601683
expect(eventSignature).toEqual("204bb5e2713e95354680e9522986d3ac0304aeafd33397f39e6540ca51ffe226");

0 commit comments

Comments
 (0)