Skip to content

Commit 735c648

Browse files
authored
feat: enable clockSkew correction by default (#459)
1 parent 4920e03 commit 735c648

File tree

3 files changed

+151
-6
lines changed

3 files changed

+151
-6
lines changed

packages/middleware-signing/src/configurations.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,14 @@ export interface AwsAuthInputConfig {
1818
signer?: RequestSigner;
1919

2020
/**
21-
* Whether to escape request path when signing the request
21+
* Whether to escape request path when signing the request.
2222
*/
2323
signingEscapePath?: boolean;
24+
25+
/**
26+
* An offset value in milliseconds to apply to all signing times.
27+
*/
28+
systemClockOffset?: number;
2429
}
2530
interface PreviouslyResolved {
2631
credentialDefaultProvider: (input: any) => Provider<Credentials>;
@@ -32,6 +37,7 @@ export interface AwsAuthResolvedConfig {
3237
credentials: Provider<Credentials>;
3338
signer: RequestSigner;
3439
signingEscapePath: boolean;
40+
systemClockOffset: number;
3541
}
3642
export function resolveAwsAuthConfig<T>(
3743
input: T & AwsAuthInputConfig & PreviouslyResolved
@@ -40,8 +46,10 @@ export function resolveAwsAuthConfig<T>(
4046
input.credentials || input.credentialDefaultProvider(input as any);
4147
const normalizedCreds = normalizeProvider(credentials);
4248
const signingEscapePath = input.signingEscapePath || false;
49+
const systemClockOffset = input.systemClockOffset || 0;
4350
return {
4451
...input,
52+
systemClockOffset,
4553
signingEscapePath,
4654
credentials: normalizedCreds,
4755
signer: new SignatureV4({

packages/middleware-signing/src/middleware.spec.ts

Lines changed: 120 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,24 @@ import { HttpRequest } from "@aws-sdk/protocol-http";
44

55
describe("SigningHandler", () => {
66
const noOpSigner: RequestSigner = {
7-
sign: (request: HttpRequest) =>
7+
sign: (request: HttpRequest, options: { signingDate: Date }) =>
88
Promise.resolve({
99
...request,
1010
headers: {
1111
...request.headers,
12-
signed: "true"
12+
signed: "true",
13+
signingDateTime: options.signingDate.getTime()
1314
}
1415
})
1516
} as any;
16-
const noOpNext = jest.fn();
17+
const noOpNext = jest.fn().mockReturnValue({ response: "" });
1718

1819
beforeEach(() => {
1920
(noOpNext as any).mockClear();
2021
});
2122

2223
it("should sign the request and pass it to the next handler", async () => {
24+
expect.assertions(2);
2325
const signingHandler = awsAuthMiddleware({ signer: noOpSigner } as any)(
2426
noOpNext,
2527
{} as any
@@ -35,4 +37,119 @@ describe("SigningHandler", () => {
3537
expect(calls.length).toBe(1);
3638
expect(calls[0][0].request.headers.signed).toBe("true");
3739
});
40+
41+
it("should add systemClockOffset while signing the request", async () => {
42+
expect.assertions(3);
43+
const systemClockOffset = 1000000;
44+
const now = Date.now();
45+
const signingHandler = awsAuthMiddleware({
46+
signer: noOpSigner,
47+
systemClockOffset
48+
} as any)(noOpNext, {} as any);
49+
await signingHandler({
50+
input: {},
51+
request: new HttpRequest({
52+
headers: {}
53+
})
54+
});
55+
56+
const { calls } = (noOpNext as any).mock;
57+
expect(calls.length).toBe(1);
58+
expect(calls[0][0].request.headers.signed).toBe("true");
59+
// Using greater than to ensure there are no timing issues
60+
expect(calls[0][0].request.headers.signingDateTime).toBeGreaterThan(
61+
now + systemClockOffset - 1
62+
);
63+
});
64+
65+
describe("update systemClockOffset if there is clockSkew", () => {
66+
// Set up clockSkew as abs(newSystemClockOffset - systemClockOffset) > 300000
67+
const clockSkewPresent = [
68+
{ current: 100000, new: 500000 },
69+
{ current: -100000, new: 250000 },
70+
{ current: 200000, new: -150000 },
71+
{ current: -100000, new: -450000 }
72+
];
73+
74+
clockSkewPresent.forEach(systemClockOffsetVal => {
75+
it(`current systemClockOffset: ${systemClockOffsetVal.current}, new systemClockOffset: ${systemClockOffsetVal.new}`, async () => {
76+
expect.assertions(3);
77+
const systemClockOffset = systemClockOffsetVal.current;
78+
const newSystemClockOffset = systemClockOffsetVal.new;
79+
const options = {
80+
signer: noOpSigner,
81+
systemClockOffset
82+
};
83+
const signingHandler = awsAuthMiddleware(options as any)(
84+
noOpNext,
85+
{} as any
86+
);
87+
noOpNext.mockReturnValue({
88+
response: {
89+
headers: {
90+
date: new Date(Date.now() + newSystemClockOffset).toString()
91+
}
92+
}
93+
});
94+
95+
await signingHandler({
96+
input: {},
97+
request: new HttpRequest({
98+
headers: {}
99+
})
100+
});
101+
102+
const { calls } = (noOpNext as any).mock;
103+
expect(calls.length).toBe(1);
104+
expect(calls[0][0].request.headers.signed).toBe("true");
105+
expect(options.systemClockOffset).not.toBe(systemClockOffset);
106+
});
107+
});
108+
});
109+
110+
describe("do not update systemClockOffset if there is no clockSkew", () => {
111+
// Do not set up clockSkew as abs(newSystemClockOffset - systemClockOffset) < 300000
112+
const clockSkewNotPresent = [
113+
{ current: 100000, new: 250000 },
114+
{ current: -100000, new: 50000 },
115+
{ current: 50000, new: -150000 },
116+
{ current: -100000, new: -150000 }
117+
];
118+
119+
clockSkewNotPresent.forEach(systemClockOffsetVal => {
120+
it(`current systemClockOffset: ${systemClockOffsetVal.current}, new systemClockOffset: ${systemClockOffsetVal.new}`, async () => {
121+
expect.assertions(3);
122+
const systemClockOffset = systemClockOffsetVal.current;
123+
const newSystemClockOffset = systemClockOffsetVal.new;
124+
const options = {
125+
signer: noOpSigner,
126+
systemClockOffset
127+
};
128+
129+
const signingHandler = awsAuthMiddleware(options as any)(
130+
noOpNext,
131+
{} as any
132+
);
133+
noOpNext.mockReturnValue({
134+
response: {
135+
headers: {
136+
date: new Date(Date.now() + newSystemClockOffset).toString()
137+
}
138+
}
139+
});
140+
141+
await signingHandler({
142+
input: {},
143+
request: new HttpRequest({
144+
headers: {}
145+
})
146+
});
147+
148+
const { calls } = (noOpNext as any).mock;
149+
expect(calls.length).toBe(1);
150+
expect(calls[0][0].request.headers.signed).toBe("true");
151+
expect(options.systemClockOffset).toBe(systemClockOffset);
152+
});
153+
});
154+
});
38155
});

packages/middleware-signing/src/middleware.ts

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,13 @@ import {
1010
import { AwsAuthResolvedConfig } from "./configurations";
1111
import { HttpRequest } from "@aws-sdk/protocol-http";
1212

13+
const isClockSkewed = (newServerTime: number, systemClockOffset: number) =>
14+
Math.abs(getSkewCorrectedDate(systemClockOffset).getTime() - newServerTime) >=
15+
300000;
16+
17+
const getSkewCorrectedDate = (systemClockOffset: number) =>
18+
new Date(Date.now() + systemClockOffset);
19+
1320
export function awsAuthMiddleware<Input extends object, Output extends object>(
1421
options: AwsAuthResolvedConfig
1522
): FinalizeRequestMiddleware<Input, Output> {
@@ -20,10 +27,23 @@ export function awsAuthMiddleware<Input extends object, Output extends object>(
2027
args: FinalizeHandlerArguments<Input>
2128
): Promise<FinalizeHandlerOutput<Output>> {
2229
if (!HttpRequest.isInstance(args.request)) return next(args);
23-
return next({
30+
const output = await next({
2431
...args,
25-
request: await options.signer.sign(args.request)
32+
request: await options.signer.sign(args.request, {
33+
signingDate: new Date(Date.now() + options.systemClockOffset)
34+
})
2635
});
36+
37+
const { headers } = output.response as any;
38+
const dateHeader = headers && (headers.date || headers.Date);
39+
if (dateHeader) {
40+
const serverTime = Date.parse(dateHeader);
41+
if (isClockSkewed(serverTime, options.systemClockOffset)) {
42+
options.systemClockOffset = serverTime - Date.now();
43+
}
44+
}
45+
46+
return output;
2747
};
2848
}
2949

0 commit comments

Comments
 (0)